Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also compare across forks.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also compare across forks.
base fork: chromakode/reddit
base: d810479e0e
...
head fork: chromakode/reddit
compare: bb6fd2b991
  • 5 commits
  • 5 files changed
  • 0 commit comments
  • 2 contributors
Commits on Mar 16, 2012
@dpifke dpifke Do API documentation formatting in template instead.
Changes the value of the api_methods nested dictionary to be a
dictionary instead of a string.  At a minimum, it will contain
an item named __doc__ with the docstring.  Any OAuth scopes,
instead of being prepended to the docstring, will be present as
an item named oauth2_scopes.  Additional introspected data (such
as required request arguments) can be added later.
e6b9b07
Commits on Mar 17, 2012
Max Goodman Cleanup. dd6f37b
Commits on Mar 18, 2012
Max Goodman Annotate API docs with sections. a584484
Max Goodman Add rudimentary parameter documentation. 4ce5a46
Max Goodman Stylin' bb6fd2b
View
108 r2/r2/controllers/api.py
@@ -77,12 +77,21 @@ def reject_vote(thing):
(voteword, c.user.name, request.ip, thing.__class__.__name__,
thing._id36, request.referer), "info")
+def api_doc(section=None):
+ def add_metadata(api_function):
+ api_function._api_doc = {
+ "section": section
+ }
+ return api_function
+ return add_metadata
+
class ApiminimalController(MinimalController):
"""
Put API calls in here which don't rely on the user being logged in
"""
@validatedForm()
+ @api_doc(section="misc")
def POST_new_captcha(self, form, jquery, *a, **kw):
iden = get_iden()
jquery("body").captcha(iden)
@@ -109,6 +118,7 @@ def ajax_login_redirect(self, form, jquery, dest):
@validate(link1 = VUrl(['url']),
link2 = VByName('id'),
count = VLimit('limit'))
+ @api_doc(section="links & comments")
def GET_info(self, link1, link2, count):
"""
Gets a listing of links which have the provided url.
@@ -124,6 +134,7 @@ def GET_info(self, link1, link2, count):
@json_validate()
+ @api_doc(section="account")
def GET_me(self, responder):
if c.user_is_loggedin:
return Wrapped(c.user).render()
@@ -164,6 +175,7 @@ def POST_feedback(self, form, jquery, name, email, reason, message):
to = VMessageRecipient('to'),
subject = VRequired('subject', errors.NO_SUBJECT),
body = VMarkdown(['text', 'message']))
+ @api_doc(section="messages")
def POST_compose(self, form, jquery, to, subject, body, ip):
"""
handles message composition under /message/compose.
@@ -195,6 +207,7 @@ def POST_compose(self, form, jquery, to, subject, body, ip):
then = VOneOf('then', ('tb', 'comments'),
default='comments'),
extension = VLength("extension", 20))
+ @api_doc(section="links & comments")
def POST_submit(self, form, jquery, url, selftext, kind, title,
save, sr, ip, then, extension):
from r2.models.admintools import is_banned_domain
@@ -382,10 +395,12 @@ def _login(self, responder, user, rem = None):
responder._send_data(cookie = user.make_cookie())
@cross_domain(allow_credentials=True)
+ @api_doc(section="account")
def POST_login(self, *args, **kwargs):
return self._handle_login(*args, **kwargs)
@cross_domain(allow_credentials=True)
+ @api_doc(section="account")
def POST_register(self, *args, **kwargs):
return self._handle_register(*args, **kwargs)
@@ -439,6 +454,7 @@ def _handle_register(self, form, responder, name, email,
@noresponse(VUser(),
VModhash(),
container = VByName('id'))
+ @api_doc(section="moderation")
def POST_leavemoderator(self, container):
"""
Handles self-removal as moderator from a subreddit as rendered
@@ -453,6 +469,7 @@ def POST_leavemoderator(self, container):
@noresponse(VUser(),
VModhash(),
container = VByName('id'))
+ @api_doc(section="moderation")
def POST_leavecontributor(self, container):
"""
same comment as for POST_leave_moderator.
@@ -468,6 +485,7 @@ def POST_leavecontributor(self, container):
container = VByName('container'),
type = VOneOf('type', ('friend', 'enemy', 'moderator',
'contributor', 'banned')))
+ @api_doc(section="users")
def POST_unfriend(self, nuser, iuser, container, type):
"""
Handles removal of a friend (a user-user relation) or removal
@@ -516,6 +534,7 @@ def POST_unfriend(self, nuser, iuser, container, type):
type = VOneOf('type', ('friend', 'moderator',
'contributor', 'banned')),
note = VLength('note', 300))
+ @api_doc(section="users")
def POST_friend(self, form, jquery, ip, friend,
container, type, note):
"""
@@ -606,6 +625,7 @@ def POST_friendnote(self, form, jquery, friend, note):
VModhash(),
password = VPassword(['curpass', 'curpass']),
dest = VDestination())
+ @api_doc(section="account")
def POST_clear_sessions(self, form, jquery, password, dest):
"""Clear all session cookies and update the current one."""
# password is required to proceed
@@ -626,6 +646,7 @@ def POST_clear_sessions(self, form, jquery, password, dest):
email = ValidEmails("email", num = 1),
password = VPassword(['newpass', 'verpass']),
verify = VBoolean("verify"))
+ @api_doc(section="account")
def POST_update(self, form, jquery, email, password, verify):
"""
handles /prefs/update for updating email address and password.
@@ -677,6 +698,7 @@ def POST_update(self, form, jquery, email, password, verify):
username = VRequired("user", errors.NOT_USER),
user = VThrottledLogin(["user", "passwd"]),
confirm = VBoolean("confirm"))
+ @api_doc(section="account")
def POST_delete_user(self, form, jquery, delete_message, username, user, confirm):
"""
/prefs/delete. Check the username/password and confirmation.
@@ -745,6 +767,7 @@ def POST_del(self, thing):
VModhash(),
VSrCanAlter('id'),
thing = VByName('id'))
+ @api_doc(section="links & comments")
def POST_marknsfw(self, thing):
thing.over_18 = True
thing._commit()
@@ -760,6 +783,7 @@ def POST_marknsfw(self, thing):
VModhash(),
VSrCanAlter('id'),
thing = VByName('id'))
+ @api_doc(section="links & comments")
def POST_unmarknsfw(self, thing):
thing.over_18 = False
thing._commit()
@@ -773,6 +797,7 @@ def POST_unmarknsfw(self, thing):
@noresponse(VUser(), VModhash(),
thing = VByName('id'))
+ @api_doc(section="links & comments")
def POST_report(self, thing):
'''for reporting...'''
if not thing or thing._deleted:
@@ -799,6 +824,7 @@ def POST_report(self, thing):
@noresponse(VUser(), VModhash(),
thing=VByName('id'))
+ @api_doc(section="messages")
def POST_block(self, thing):
'''for blocking via inbox'''
if not thing:
@@ -840,6 +866,7 @@ def POST_indict(self, thing):
VModhash(),
item = VByNameIfAuthor('thing_id'),
text = VSelfText('text'))
+ @api_doc(section="links & comments")
def POST_editusertext(self, form, jquery, item, text):
if (not form.has_errors("text",
errors.NO_TEXT, errors.TOO_LONG) and
@@ -880,6 +907,7 @@ def POST_editusertext(self, form, jquery, item, text):
ip = ValidIP(),
parent = VSubmitParent(['thing_id', 'parent']),
comment = VMarkdown(['text', 'comment']))
+ @api_doc(section="links & comments")
def POST_comment(self, commentform, jquery, parent, comment, ip):
should_ratelimit = True
#check the parent type here cause we need that for the
@@ -1065,6 +1093,7 @@ def POST_juryvote(self, dir, thing, ip):
ip = ValidIP(),
dir = VInt('dir', min=-1, max=1),
thing = VByName('id'))
+ @api_doc(section="links & comments")
def POST_vote(self, dir, thing, ip, vote_type):
ip = request.ip
user = c.user
@@ -1098,6 +1127,7 @@ def POST_vote(self, dir, thing, ip, vote_type):
# nop is safe: handled after auth checks below
stylesheet_contents = nop('stylesheet_contents'),
op = VOneOf('op',['save','preview']))
+ @api_doc(section="subreddits")
def POST_subreddit_stylesheet(self, form, jquery,
stylesheet_contents = '', op='save'):
if not c.site.can_change_stylesheet(c.user):
@@ -1171,6 +1201,7 @@ def POST_subreddit_stylesheet(self, form, jquery,
@validatedForm(VSrModerator(),
VModhash(),
name = VCssName('img_name'))
+ @api_doc(section="subreddits")
def POST_delete_sr_img(self, form, jquery, name):
"""
Called called upon requested delete on /about/stylesheet.
@@ -1188,6 +1219,7 @@ def POST_delete_sr_img(self, form, jquery, name):
@validatedForm(VSrModerator(),
VModhash(),
sponsor = VInt("sponsor", min = 0, max = 1))
+ @api_doc(section="subreddits")
def POST_delete_sr_header(self, form, jquery, sponsor):
"""
Called when the user request that the header on a sr be reset.
@@ -1235,6 +1267,7 @@ def GET_upload_sr_img(self, *a, **kw):
form_id = VLength('formid', max_length = 100),
header = VInt('header', max=1, min=0),
sponsor = VSubredditSponsorship('sponsor'))
+ @api_doc(section="subreddits")
def POST_upload_sr_img(self, file, header, sponsor, name, form_id, img_type):
"""
Called on /about/stylesheet when an image needs to be replaced
@@ -1323,6 +1356,7 @@ def POST_upload_sr_img(self, file, header, sponsor, name, form_id, img_type):
sponsor_url = VLength('sponsorship-url', max_length = 500),
css_on_cname = VBoolean("css_on_cname"),
)
+ @api_doc(section="subreddits")
def POST_site_admin(self, form, jquery, name, ip, sr,
sponsor_text, sponsor_url, sponsor_name, **kw):
# the status button is outside the form -- have to reset by hand
@@ -1441,6 +1475,7 @@ def POST_searchfeedback(self, q, sort, t, approval):
why = VSrCanBan('id'),
thing = VByName('id'),
spam = VBoolean('spam', default=True))
+ @api_doc(section="moderation")
def POST_remove(self, why, thing, spam):
# Don't remove a promoted link
@@ -1479,6 +1514,7 @@ def POST_remove(self, why, thing, spam):
@noresponse(VUser(), VModhash(),
why = VSrCanBan('id'),
thing = VByName('id'))
+ @api_doc(section="moderation")
def POST_approve(self, why, thing):
if not thing: return
if thing._deleted: return
@@ -1502,6 +1538,7 @@ def POST_approve(self, why, thing):
VCanDistinguish(('id', 'how')),
thing = VByName('id'),
how = VOneOf('how', ('yes','no','admin','special')))
+ @api_doc(section="moderation")
def POST_distinguish(self, form, jquery, thing, how):
if not thing:return
@@ -1532,6 +1569,7 @@ def POST_distinguish(self, form, jquery, thing, how):
@noresponse(VUser(),
VModhash(),
thing = VByName('id'))
+ @api_doc(section="links & comments")
def POST_save(self, thing):
if not thing: return
r = thing._save(c.user)
@@ -1541,6 +1579,7 @@ def POST_save(self, thing):
@noresponse(VUser(),
VModhash(),
thing = VByName('id'))
+ @api_doc(section="links & comments")
def POST_unsave(self, thing):
if not thing: return
r = thing._unsave(c.user)
@@ -1619,18 +1658,21 @@ def unread_handler(self, things, unread):
@noresponse(VUser(),
VModhash(),
things = VByName('id', multiple=True, limit=25))
+ @api_doc(section="messages")
def POST_unread_message(self, things):
self.unread_handler(things, True)
@noresponse(VUser(),
VModhash(),
things = VByName('id', multiple=True, limit=25))
+ @api_doc(section="messages")
def POST_read_message(self, things):
self.unread_handler(things, False)
@noresponse(VUser(),
VModhash(),
thing = VByName('id'))
+ @api_doc(section="links & comments")
def POST_hide(self, thing):
if not thing: return
r = thing._hide(c.user)
@@ -1640,6 +1682,7 @@ def POST_hide(self, thing):
@noresponse(VUser(),
VModhash(),
thing = VByName('id'))
+ @api_doc(section="links & comments")
def POST_unhide(self, thing):
if not thing: return
r = thing._unhide(c.user)
@@ -1674,6 +1717,7 @@ def POST_moremessages(self, form, jquery, parent):
children = VCommentIDs('children'),
pv_hex = VPrintable('pv_hex', 40),
mc_id = nop('id'))
+ @api_doc(section="links & comments")
def POST_morechildren(self, form, jquery, link, sort, children,
pv_hex, mc_id):
user = c.user if c.user_is_loggedin else None
@@ -1879,6 +1923,7 @@ def POST_frame(self):
VModhash(),
action = VOneOf('action', ('sub', 'unsub')),
sr = VSubscribeSR('sr', 'sr_name'))
+ @api_doc(section="subreddits")
def POST_subscribe(self, action, sr):
# only users who can make edits are allowed to subscribe.
# Anyone can leave.
@@ -2055,6 +2100,7 @@ def POST_editaward(self, form, jquery, award, colliding_award, codename,
prefer_existing=True),
text = VFlairText("text"),
css_class = VFlairCss("css_class"))
+ @api_doc(section="flair")
def POST_flair(self, form, jquery, user, text, css_class):
# Check validation.
if form.has_errors('name', errors.USER_DOESNT_EXIST, errors.NO_USER):
@@ -2103,6 +2149,7 @@ def POST_flair(self, form, jquery, user, text, css_class):
VModhash(),
user = VExistingUname("name", allow_deleted=True,
prefer_existing=True))
+ @api_doc(section="flair")
def POST_deleteflair(self, form, jquery, user):
# Check validation.
if form.has_errors('name', errors.USER_DOESNT_EXIST, errors.NO_USER):
@@ -2123,6 +2170,7 @@ def POST_deleteflair(self, form, jquery, user):
@validate(VFlairManager(),
VModhash(),
flair_csv = nop('flair_csv'))
+ @api_doc(section="flair")
def POST_flaircsv(self, flair_csv):
limit = 100 # max of 100 flair settings per call
results = FlairCsv()
@@ -2190,6 +2238,7 @@ def POST_flaircsv(self, flair_csv):
@validatedForm(VUser(),
VModhash(),
flair_enabled = VBoolean("flair_enabled"))
+ @api_doc(section="flair")
def POST_setflairenabled(self, form, jquery, flair_enabled):
setattr(c.user, 'flair_%s_enabled' % c.site._id, flair_enabled)
c.user._commit()
@@ -2201,6 +2250,7 @@ def POST_setflairenabled(self, form, jquery, flair_enabled):
flair_enabled = VBoolean("flair_enabled"),
flair_position = VOneOf("flair_position", ("left", "right")),
flair_self_assign_enabled = VBoolean("flair_self_assign_enabled"))
+ @api_doc(section="flair")
def POST_flairconfig(self, form, jquery, flair_enabled, flair_position,
flair_self_assign_enabled):
if c.site.flair_enabled != flair_enabled:
@@ -2222,6 +2272,7 @@ def POST_flairconfig(self, form, jquery, flair_enabled, flair_position,
@validate(VFlairManager(),
user = VOptionalExistingUname('name', allow_deleted=True,
prefer_existing=True))
+ @api_doc(section="flair")
def GET_flairlist(self, num, after, reverse, count, user):
flair = FlairList(num, after, reverse, '', user)
return BoringPage(_("API"), content = flair).render()
@@ -2232,6 +2283,7 @@ def GET_flairlist(self, num, after, reverse, count, user):
text = VFlairText('text'),
css_class = VFlairCss('css_class'),
text_editable = VBoolean('text_editable'))
+ @api_doc(section="flair")
def POST_flairtemplate(self, form, jquery, flair_template, text,
css_class, text_editable):
if text is None:
@@ -2286,6 +2338,7 @@ def POST_flairtemplate(self, form, jquery, flair_template, text,
@validatedForm(VFlairManager(),
VModhash(),
flair_template = VFlairTemplateByID('flair_template_id'))
+ @api_doc(section="flair")
def POST_deleteflairtemplate(self, form, jquery, flair_template):
idx = FlairTemplateBySubredditIndex.by_sr(c.site._id)
if idx.delete_by_id(flair_template._id):
@@ -2294,6 +2347,7 @@ def POST_deleteflairtemplate(self, form, jquery, flair_template):
details='flair_delete_template')
@validatedForm(VFlairManager(), VModhash())
+ @api_doc(section="flair")
def POST_clearflairtemplates(self, form, jquery):
FlairTemplateBySubredditIndex.clear(c.site._id)
jquery.refresh()
@@ -2313,6 +2367,7 @@ def POST_flairselector(self, user):
user = VOptionalExistingUname('name'),
flair_template = VFlairTemplateByID('flair_template_id'),
text = VFlairText("text"))
+ @api_doc(section="flair")
def POST_selectflair(self, form, jquery, user, flair_template, text):
if not flair_template:
# TODO: serve error to client
@@ -2578,48 +2633,61 @@ def POST_deleteapp(self, client):
class ApihelpController(RedditController):
@staticmethod
def docs_from_controller(controller, url_prefix='/api'):
- api_methods = defaultdict(dict)
+ """
+ Examines a controller for documentation. A dictionary of URLs is
+ returned. For each URL, a dictionary of HTTP methods (GET, POST, etc.)
+ is contained. For each URL/method pair, a dictionary containing the
+ following items is available:
+
+ - `__doc__`: Markdown-formatted docstring.
+ - `oauth2_scopes`: List of OAuth2 scopes
+ - *more to come...*
+ """
+
+ api_docs = defaultdict(lambda: defaultdict(dict))
for name, func in controller.__dict__.iteritems():
- i = name.find('_')
- if i > 0:
- method = name[:i]
- action = name[i+1:]
- else:
+ method, sep, action = name.partition('_')
+ if not action:
continue
- if func.__doc__ and method in ('GET', 'POST'):
- docs = re.sub(r'\n +', '\n', func.__doc__).strip()
+ if getattr(func, '_api_doc', None) and method in ('GET', 'POST'):
+ docs = {}
+ if func.__doc__:
+ docs['doc'] = re.sub(r'\n +', '\n', func.__doc__).strip()
+ docs.update(func._api_doc)
+
if hasattr(func, 'oauth2_perms'):
scopes = func.oauth2_perms.get('allowed_scopes')
if scopes:
- docs = '*OAuth2 scope(s): %s*\n\n%s' % (
- ', '.join([
- ('[%s](#oauth2_scope_%s)' % (scope, scope))
- for scope in scopes
- ]),
- docs,
- )
- api_methods['/'.join((url_prefix, action))][method] = docs
+ docs['oauth2_scopes'] = scopes
+
+ # TODO: in the future, it would be cool to introspect the
+ # validators in order to generate a list of request
+ # parameters. Some decorators also give a hint as to response
+ # type (JSON, etc.) which could be included as well.
+
+ uri = '/'.join((url_prefix, action))
+ api_docs[docs['section']][uri][method] = docs
- return api_methods
+ return api_docs
def GET_help(self):
from r2.controllers.apiv1 import APIv1Controller
from r2.controllers.oauth2 import OAuth2FrontendController, OAuth2AccessController, scope_info
- api_methods = defaultdict(dict)
+ api_docs = defaultdict(dict)
for controller, url_prefix in ((ApiController, '/api'),
(ApiminimalController, '/api'),
(OAuth2FrontendController, '/api/v1'),
(OAuth2AccessController, '/api/v1'),
(APIv1Controller, '/api/v1')):
for url, methods in self.docs_from_controller(controller, url_prefix).iteritems():
- api_methods[url].update(methods)
+ api_docs[url].update(methods)
return BoringPage(
_('api documentation'),
content=ApiHelp(
- api_methods=api_methods,
+ api_docs=api_docs,
oauth2_scopes=scope_info,
),
css_class="api-help",
View
65 r2/r2/controllers/validator/validator.py
@@ -44,6 +44,7 @@
from curses.ascii import isprint
import re, inspect
import pycountry
+from itertools import chain
def visible_promo(article):
is_promo = getattr(article, "promoted", None) is not None
@@ -86,6 +87,12 @@ def set_error(self, error, msg_params = {}, field = False):
c.errors.add(error, msg_params = msg_params, field = field)
+ def param_docs(self):
+ param_info = {}
+ for param in filter(None, tup(self.param)):
+ param_info[param] = None
+ return param_info
+
def __call__(self, url):
a = []
if self.param:
@@ -136,6 +143,18 @@ def _make_validated_kw(fn, simple_vals, param_vals, env):
kw[var] = validator(env)
return kw
+def set_api_docs(fn, newfn, simple_vals, param_vals):
+ newfn.__name__ = fn.__name__
+ newfn.__doc__ = fn.__doc__
+ if hasattr(fn, '_api_doc'):
+ newfn._api_doc = fn._api_doc
+ param_info = {}
+ for validator in chain(simple_vals, param_vals.itervalues()):
+ param_info.update(validator.param_docs())
+ newfn._api_doc['parameters'] = param_info
+ return newfn
+
+
def validate(*simple_vals, **param_vals):
def val(fn):
def newfn(self, *a, **env):
@@ -147,8 +166,7 @@ def newfn(self, *a, **env):
except VerifiedUserRequiredException:
return self.intermediate_redirect('/verify')
- newfn.__name__ = fn.__name__
- newfn.__doc__ = fn.__doc__
+ set_api_docs(fn, newfn, simple_vals, param_vals)
return newfn
return val
@@ -194,8 +212,7 @@ def newfn(self, *a, **env):
responder.send_failure(errors.VERIFIED_USER_REQUIRED)
return self.api_wrapper(responder.make_response())
- newfn.__name__ = fn.__name__
- newfn.__doc__ = fn.__doc__
+ set_api_docs(fn, newfn, simple_vals, param_vals)
return newfn
return val
return _api_validate
@@ -593,6 +610,11 @@ def run(self, items):
return self.set_error(self._error)
+ def param_docs(self):
+ return {
+ self.param: _('the id of an existing thing')
+ }
+
class VByNameIfAuthor(VByName):
def run(self, fullname):
thing = VByName.run(self, fullname)
@@ -629,6 +651,11 @@ def __init__(self, param=None, fatal=True, *a, **kw):
def run(self, uh):
pass
+ def param_docs(self):
+ return {
+ self.param: _('a modhash')
+ }
+
class VVotehash(Validator):
def run(self, vh, thing_name):
return True
@@ -809,6 +836,11 @@ def run(self, fullname, fullname2):
#else
abort(403, "forbidden")
+ def param_docs(self):
+ return {
+ self.param[0]: _('id of parent thing')
+ }
+
class VSubmitSR(Validator):
def __init__(self, srname_param, linktype_param=None, promotion=False):
self.require_linktype = False
@@ -990,6 +1022,16 @@ def run(self, url, sr = None, resubmit=False):
return url
return self.error(errors.BAD_URL)
+ def param_docs(self):
+ params = {}
+ try:
+ params[self.param[0]] = _('a valid URL')
+ params[self.param[1]] = _('a subreddit')
+ params[self.param[2]] = _('boolean value')
+ except IndexError:
+ pass
+ return params
+
class VOptionalExistingUname(VRequired):
def __init__(self, item, allow_deleted=False, prefer_existing=False,
*a, **kw):
@@ -1030,6 +1072,11 @@ def run(self, name):
self.error()
return user
+ def param_docs(self):
+ return {
+ self.param: _('the name of an existing user')
+ }
+
class VExistingUnameNotSelf(VExistingUname):
def run(self, name):
user = super(VExistingUnameNotSelf, self).run(name)
@@ -1080,6 +1127,11 @@ def run(self, val):
return False
return bool(val)
+ def param_docs(self):
+ return {
+ self.param: _('boolean value')
+ }
+
class VNumber(Validator):
def __init__(self, param, min=None, max=None, coerce = True,
error = errors.BAD_NUMBER, *a, **kw):
@@ -1354,6 +1406,11 @@ def run(self, val):
else:
return val
+ def param_docs(self):
+ return {
+ self.param: _('one of (%s)') % ', '.join(self.options)
+ }
+
class VImageType(Validator):
def run(self, img_type):
if not img_type in ('png', 'jpg'):
View
4 r2/r2/lib/pages/pages.py
@@ -3800,7 +3800,7 @@ def __init__(self):
super(UserIPHistory, self).__init__()
class ApiHelp(Templated):
- def __init__(self, api_methods, oauth2_scopes, *a, **kw):
- self.api_methods = api_methods
+ def __init__(self, api_docs, oauth2_scopes, *a, **kw):
+ self.api_docs = api_docs
self.oauth2_scopes = oauth2_scopes
super(ApiHelp, self).__init__(*a, **kw)
View
86 r2/r2/public/static/css/reddit.css
@@ -5142,66 +5142,94 @@ tr.gold-accent + tr > td {
}
.api-help {
- font-size: 1.15em;
- margin-left: 0;
- margin-right: 0;
+ margin: 0;
+ font-size: 1.25em;
}
-.api-help .section {
- padding: 10px 30px;
+.api-help .toc {
+ float: left;
+ margin-left: 0;
+ background: #eff7ff;
+ border: 1px solid #cee3f8;
+ border-left: none;
+ padding: 15px 2em 0 2em;
+ width: 18em;
}
.api-help h1 {
- font-size: 1.60em;
+ margin-top: 0;
+ font-size: 1.50em;
+ font-weight: bold;
}
.api-help .contents {
- background: #eff7ff;
- border: 2px solid #cee3f8;
- border-left: none;
- border-right: none;
+ padding: 0 20px;
+ margin-left: 24em;
+ margin-top: 20px;
+ max-width: 50em;
}
-.api-help .contents h1 {
- margin-top: .5em;
+.api-help .contents .section {
+ margin-bottom: 2em;
}
-.api-help .contents strong {
- font-size: 1.25em;
+.api-help .toc strong {
+ color: #333;
font-weight: bold;
}
-.api-help .contents a:hover {
+.api-help .toc a:hover {
text-decoration: underline;
}
-.api-help .contents > ul > li {
- margin-bottom: 1em;
+.api-help .toc ul {
+ margin-top: .5em;
+ margin-bottom: 1.5em;
}
-.api-help .contents li li {
- display: inline-block;
- width: 20em;
+.api-help .endpoint, .api-help .oauth2-scope {
+ margin-bottom: 1.5em;
}
-.api-help .endpoint, .api-help .oauth2-scope {
+.api-help .methods h2 {
+ color: black;
+ font-size: 1.45em;
+ text-align: middle;
+ margin-top: 1.5em;
margin-bottom: 1em;
+ border-bottom: 1px solid #aaa;
+}
+
+.api-help .endpoint .info {
+ padding-left: 1em;
+ border-left: 1px solid #ddd;
}
-.api-help .endpoint h2, .api-help .oauth2-scope h2 {
+.api-help .endpoint h3, .api-help .oauth2-scope h2 {
+ color: #369;
margin-bottom: .5em;
}
-.api-help .method, .api-help .oauth2-scope p {
- border-left: 2px solid #ccc;
- padding-left: 1em;
- margin-bottom: 1em;
+.api-help .endpoint .method {
+ font-weight: normal;
+ color: gray;
}
-.api-help .method h3 {
+.api-help .endpoint h4 {
color: #555;
}
-.api-help .method .md p {
- margin-top: 0;
+.api-help .parameters {
+ background: #f0f0f0;
+ border-collapse: separate;
+ border-radius: 3px;
+ padding: 5px 10px;
+ max-width: 45em;
+}
+
+.api-help .parameters .name {
+ display: inline-block;
+ padding-right: 2em;
+ font-family: 'Courier New', monospace;
+ width: 20em;
}
View
94 r2/r2/templates/apihelp.html
@@ -2,29 +2,37 @@
from r2.lib.filters import safemarkdown
%>
-<div class="section introduction">
-<p>This is the automatically-generated documentation for the Reddit API.</p>
-<p>It's gathered from the docstrings in the code.</p>
-</div>
+<%def name="api_method_id(uri, method)">${method}_${uri.split('/')[-1]}</%def>
-<div class="section contents">
-<h1>Contents</h1>
+<%
+ api = thing.api_docs
+ scopes = thing.oauth2_scopes
+%>
+<div class="toc">
<ul>
<li>
<strong>API methods</strong>
<ul>
- %for uri in sorted(thing.api_methods.keys()):
- <li>
- <a href="#api_method_${uri.split('/')[-1]}">${uri}</a>
- &nbsp; <span class="gray">(${', '.join(sorted(thing.api_methods[uri].keys()))})</span>
- </li>
- %endfor
+ %for section in sorted(api):
+ <li>
+ <strong>${section}</strong>
+ <ul>
+ %for uri in sorted(api[section]):
+ <% methods = sorted(api[section][uri].keys()) %>
+ <li>
+ <a href="#${api_method_id(uri, methods[0])}">${uri}</a>
+ &nbsp; <span class="gray">(${', '.join(methods)})</span>
+ </li>
+ %endfor
+ </ul>
+ </li>
+ %endfor
</ul>
<li>
- <strong>OAuth scopes</strong>
+ <strong>OAuth2 scopes</strong>
<ul>
- %for scope in sorted(thing.oauth2_scopes.keys()):
+ %for scope in sorted(scopes):
<li><a href="#oauth2_scope_${scope}">${scope}</a></li>
%endfor
</ul>
@@ -32,30 +40,48 @@
</ul>
</div>
-<div class="section methods">
-<h1>API methods</h1>
+<div class="contents">
+ <div class="section introduction">
+ <p>This is the automatically-generated documentation for the Reddit API.</p>
+ <p>It's gathered from the docstrings in the code.</p>
+ </div>
-%for uri in sorted(thing.api_methods.keys()):
- <div class="endpoint">
- <a name="api_method_${uri.split('/')[-1]}"></a>
- <h2>${uri}</h2>
- %for method in sorted(thing.api_methods[uri].keys()):
- <div class="method">
- <h3>${method}</h3>
- ${unsafe(safemarkdown(thing.api_methods[uri][method]))}
- </div>
+ <div class="section methods">
+ %for section in sorted(api):
+ <h2>${section}</h2>
+ %for uri in sorted(api[section]):
+ %for method in sorted(api[section][uri]):
+ <div class="endpoint" id="${api_method_id(uri, method)}">
+ <% docs = api[section][uri][method] %>
+ <h3><span class="method">${method}</span>&nbsp;${uri}</h3>
+ <div class="info">
+ ${unsafe(safemarkdown(docs.get('doc')))}
+ <% params = docs.get('parameters') %>
+ %if params:
+ <ul class="parameters">
+ %for param in sorted(params):
+ <li>
+ <span class="name">${param}</span>
+ <span class="desc">${params[param]}</span>
+ </li>
+ %endfor
+ </ul>
+ %endif
+ </div>
+ </div>
+ %endfor
+ %endfor
%endfor
</div>
-%endfor
-</div>
-<div class="section scopes">
-<h1>OAuth2 scopes</h1>
+ <div class="section scopes">
+ <h1>OAuth2 scopes</h1>
-%for scope in sorted(thing.oauth2_scopes.keys()):
- <div class="oauth2-scope">
- <h2>${scope} (${thing.oauth2_scopes[scope]['name']})</h2>
- <p class="oauth2-scope-description">${thing.oauth2_scopes[scope]['description']}</p>
+ %for scope in sorted(scopes.keys()):
+ <div class="oauth2-scope">
+ <h2>${scope} (${scopes[scope]['name']})</h2>
+ <p class="oauth2-scope-description">${scopes[scope]['description']}</p>
+ </div>
+ %endfor
</div>
-%endfor
</div>

No commit comments for this range

Something went wrong with that request. Please try again.