Files
reddit/r2/r2/controllers/api_docs.py
2013-03-19 20:03:40 -07:00

207 lines
7.6 KiB
Python

# The contents of this file are subject to the Common Public Attribution
# License Version 1.0. (the "License"); you may not use this file except in
# compliance with the License. You may obtain a copy of the License at
# http://code.reddit.com/LICENSE. The License is based on the Mozilla Public
# License Version 1.1, but Sections 14 and 15 have been added to cover use of
# software over a computer network and provide for limited attribution for the
# Original Developer. In addition, Exhibit A has been modified to be consistent
# with Exhibit B.
#
# Software distributed under the License is distributed on an "AS IS" basis,
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
# the specific language governing rights and limitations under the License.
#
# The Original Code is reddit.
#
# The Original Developer is the Initial Developer. The Initial Developer of
# the Original Code is reddit Inc.
#
# All portions of the code written by reddit are Copyright (c) 2006-2013 reddit
# Inc. All Rights Reserved.
###############################################################################
import re
from collections import defaultdict
from itertools import chain
import inspect
from os.path import abspath, relpath
from pylons import g
from pylons.i18n import _
from reddit_base import RedditController
from r2.lib.utils import Storage
from r2.lib.pages import BoringPage, ApiHelp
from r2.lib.validator import validate, VOneOf
# API sections displayed in the documentation page.
# Each section can have a title and a markdown-formatted description.
section_info = {
'account': {
'title': _('account'),
},
'apps': {
'title': _('apps'),
},
'flair': {
'title': _('flair'),
},
'links_and_comments': {
'title': _('links & comments'),
},
'messages': {
'title': _('private messages'),
},
'moderation': {
'title': _('moderation'),
},
'misc': {
'title': _('misc'),
},
'listings': {
'title': _('listings'),
},
'search': {
'title': _('search'),
},
'subreddits': {
'title': _('subreddits'),
},
'users': {
'title': _('users'),
},
'wiki': {
'title': _('wiki'),
}
}
api_section = Storage((k, k) for k in section_info)
def api_doc(section, **kwargs):
"""
Add documentation annotations to the decorated function.
See ApidocsController.docs_from_controller for a list of annotation fields.
"""
def add_metadata(api_function):
doc = api_function._api_doc = getattr(api_function, '_api_doc', {})
if 'extends' in kwargs:
kwargs['extends'] = kwargs['extends']._api_doc
doc.update(kwargs)
doc['section'] = section
doc['lineno'] = api_function.func_code.co_firstlineno
file_path = abspath(api_function.func_code.co_filename)
root_dir = g.paths['root']
if file_path.startswith(root_dir):
doc['relfilepath'] = relpath(file_path, root_dir)
return api_function
return add_metadata
class ApidocsController(RedditController):
@staticmethod
def docs_from_controller(controller, url_prefix='/api', oauth_only=False):
"""
Examines a controller for documentation. A dictionary index of
sections containing dictionaries 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.
- `uri`: Manually-specified URI to list the API method as
- `uri_variants`: Alternate URIs to access the API method from
- `extensions`: URI extensions the API method supports
- `parameters`: Dictionary of possible parameter names and descriptions.
- `extends`: API method from which to inherit documentation
"""
api_docs = defaultdict(lambda: defaultdict(dict))
for name, func in controller.__dict__.iteritems():
method, sep, action = name.partition('_')
if not action:
continue
api_doc = getattr(func, '_api_doc', None)
if api_doc and 'section' in api_doc and method in ('GET', 'POST'):
docs = {}
docs['doc'] = inspect.getdoc(func)
if 'extends' in api_doc:
docs.update(api_doc['extends'])
# parameters are handled separately.
docs['parameters'] = {}
docs.update(api_doc)
uri = docs.get('uri') or '/'.join((url_prefix, action))
if 'extensions' in docs:
# if only one extension was specified, add it to the URI.
if len(docs['extensions']) == 1:
uri += '.' + docs['extensions'][0]
del docs['extensions']
docs['uri'] = uri
oauth_perms = getattr(func, 'oauth2_perms', {})
docs['oauth_scopes'] = oauth_perms.get('allowed_scopes', [])
# add every variant to the index -- the templates will filter
# out variants in the long-form documentation
if oauth_only:
if not docs['oauth_scopes']:
continue
for scope in docs['oauth_scopes']:
for variant in chain([uri],
docs.get('uri_variants', [])):
api_docs[scope][variant][method] = docs
else:
for variant in chain([uri], docs.get('uri_variants', [])):
api_docs[docs['section']][variant][method] = docs
return api_docs
@validate(
mode=VOneOf('mode', options=('methods', 'oauth'), default='methods'))
def GET_docs(self, mode):
# controllers to gather docs from.
from r2.controllers.api import ApiController, ApiminimalController
from r2.controllers.apiv1 import APIv1Controller
from r2.controllers.front import FrontController
from r2.controllers.wiki import WikiApiController
from r2.controllers import listingcontroller
api_controllers = [
(APIv1Controller, '/api/v1'),
(ApiController, '/api'),
(ApiminimalController, '/api'),
(WikiApiController, '/api/wiki'),
(FrontController, '')
]
for name, value in vars(listingcontroller).iteritems():
if name.endswith('Controller'):
api_controllers.append((value, ''))
# merge documentation info together.
api_docs = defaultdict(dict)
oauth_index = defaultdict(set)
for controller, url_prefix in api_controllers:
controller_docs = self.docs_from_controller(controller, url_prefix,
mode == 'oauth')
for section, contents in controller_docs.iteritems():
api_docs[section].update(contents)
for variant, method_dict in contents.iteritems():
for method, docs in method_dict.iteritems():
for scope in docs['oauth_scopes']:
oauth_index[scope].add((section, variant, method))
return BoringPage(
_('api documentation'),
content=ApiHelp(
api_docs=api_docs,
oauth_index=oauth_index,
mode=mode,
),
css_class="api-help",
show_sidebar=False,
show_infobar=False
).render()