/usr/share/pyshared/maasserver/apidoc.py is in python-django-maas 1.2+bzr1373+dfsg-0ubuntu1~12.04.6.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 | # Copyright 2012 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Utilities to help document/describe the public facing API."""
from __future__ import (
absolute_import,
print_function,
unicode_literals,
)
__metaclass__ = type
__all__ = [
"describe_handler",
"describe_resource",
"find_api_resources",
"generate_api_docs",
]
from inspect import getdoc
from itertools import izip_longest
from django.core.urlresolvers import (
get_resolver,
RegexURLPattern,
RegexURLResolver,
)
from piston.authentication import NoAuthentication
from piston.doc import generate_doc
from piston.handler import BaseHandler
from piston.resource import Resource
def accumulate_api_resources(resolver, accumulator):
"""Accumulate handlers from the given resolver.
Handlers are of type :class:`HandlerMetaClass`, and must define a
`resource_uri` method.
:rtype: Generator, yielding handlers.
"""
p_has_resource_uri = lambda resource: (
getattr(resource.handler, "resource_uri", None) is not None)
for pattern in resolver.url_patterns:
if isinstance(pattern, RegexURLResolver):
accumulate_api_resources(pattern, accumulator)
elif isinstance(pattern, RegexURLPattern):
if isinstance(pattern.callback, Resource):
resource = pattern.callback
if p_has_resource_uri(resource):
accumulator.add(resource)
else:
raise AssertionError(
"Not a recognised pattern or resolver: %r" % (pattern,))
def find_api_resources(urlconf=None):
"""Find the API resources defined in `urlconf`.
:rtype: :class:`set` of :class:`Resource` instances.
"""
resolver, accumulator = get_resolver(urlconf), set()
accumulate_api_resources(resolver, accumulator)
return accumulator
def generate_api_docs(resources):
"""Generate ReST documentation objects for the ReST API.
Yields Piston Documentation objects describing the given resources.
This also ensures that handlers define 'resource_uri' methods. This is
easily forgotten and essential in order to generate proper documentation.
:return: Generates :class:`piston.doc.HandlerDocumentation` instances.
"""
sentinel = object()
for resource in resources:
handler = type(resource.handler)
if getattr(handler, "resource_uri", sentinel) is sentinel:
raise AssertionError(
"Missing resource_uri in %s" % handler.__name__)
yield generate_doc(handler)
def merge(*iterables):
"""Merge iterables.
The iterables are iterated in lock-step. For the values at each iteration,
the first defined one is yielded, the rest are discarded.
This is useful for unpacking variable length results with defaults:
>>> a, b, c = merge("AB", "123")
>>> print(a, b, c)
A B 3
"""
undefined = object()
for values in izip_longest(*iterables, fillvalue=undefined):
yield next(value for value in values if value is not undefined)
def describe_actions(handler):
"""Describe the actions that `handler` supports.
For each action, which could be a CRUD operation or a custom (piggybacked)
operation, a dict with the following entries is generated:
method: string, HTTP method.
name: string, a human-friendly name for the action.
doc: string, documentation about the action.
op: string or None, the op parameter to pass in requests for
non-CRUD/ReSTful requests.
restful: Indicates if this is a CRUD/ReSTful action.
"""
from maasserver.api import OperationsResource
getname = OperationsResource.crudmap.get
for signature, function in handler.exports.items():
http_method, operation = signature
name = getname(http_method) if operation is None else operation
yield dict(
method=http_method, name=name, doc=getdoc(function),
op=operation, restful=(operation is None))
def describe_handler(handler):
"""Return a serialisable description of a handler.
:type handler: :class:`OperationsHandler` or
:class:`AnonymousOperationsHandler` instance or subclass.
"""
# Want the class, not an instance.
if isinstance(handler, BaseHandler):
handler = type(handler)
path = generate_doc(handler).resource_uri_template
path = "" if path is None else path
resource_uri = getattr(handler, "resource_uri", lambda: ())
view_name, uri_params, uri_kw = merge(resource_uri(), (None, (), {}))
assert uri_kw == {}, (
"Resource URI specifications with keyword parameters are not yet "
"supported: handler=%r; view_name=%r" % (handler, view_name))
return {
"actions": list(describe_actions(handler)),
"doc": getdoc(handler),
"name": handler.__name__,
"params": uri_params,
"path": path,
}
def describe_resource(resource):
"""Return a serialisable description of a resource.
:type resource: :class:`OperationsResource` instance.
"""
authenticate = not any(
isinstance(auth, NoAuthentication)
for auth in resource.authentication)
if authenticate:
if resource.anonymous is None:
anon = None
auth = describe_handler(resource.handler)
else:
anon = describe_handler(resource.anonymous)
auth = describe_handler(resource.handler)
else:
anon = describe_handler(resource.handler)
auth = None
name = anon["name"] if auth is None else auth["name"]
return {"anon": anon, "auth": auth, "name": name}
|