Generating API Documentation

You can use Doctor to generate Sphinx documentation for your API. It will introspect the list of routes for your Flask app, and will use the values from your schema to generate a list of parameters for those routes.

For example, to generate API documentation for the example Flask app, you would add doctor.docs.flask to the extensions list in Sphinx’s conf.py file:

extensions = [
    'sphinx.ext.autodoc',
    'sphinx.ext.coverage',
    'sphinx.ext.viewcode',
    'doctor.docs.flask',
]

You’ll also need to import and instantiate AutoFlaskHarness in conf.py:

from doctor.docs.flask import AutoFlaskHarness
autoflask_harness = AutoFlaskHarness(
    routes_filename='examples/flask/app.py',
    url_prefix='http://127.0.0.1:8080')

This harness class provides setup and teardown handlers that are used to load your Flask application. The documentation directives use the harness to introspect and make mock requests against your app. If you have custom setup and teardown steps that you would like to take (such as loading fixtures into a database), you can subclass it and customize it. Take a look at BaseHarness for a list of the hooks that are available.

If you are adding extra logic to the harness and subclassing AutoFlaskHarness, make note of the signature of setup_app(). The sphinx_app parameter is not the Flask application. To access the Flask application object, use self.app. e.g.

from doctor.docs.flask import AutoFlaskHarness
from myapp import db
class MyCustomHarness(AutoFlaskHarness):
    def setup_app(self, sphinx_app):
        super(MyCustomHarness, self).setup_app(sphinx_app)
        with self.app.app_context():
            db.init_app(self.app) # initialize sqlalchemy db extension

Then, add an autoflask directive to one of your rst files:

API Documentation
-----------------

.. autoflask::

When you run Sphinx, it will render documentation like this:

API Status

Retrieve

GET /
Request Headers:
 

Example Request:

curl http://127.0.0.1:8080/ -X GET -H 'Authorization: testtoken'

Example Response:

"Notes API v1.0.0"

Notes (v1)

Create

POST /note/

Create a new note.

Request JSON Object:
 
  • body (str) – Required. Note body
  • done (bool) – Marks if a note is done or not.
Request Headers:
 
Response JSON Object:
 
  • body (str) – Note body
  • done (bool) – Marks if a note is done or not.
  • note_id (int) – Note ID

Example Request:

curl http://127.0.0.1:8080/note/ -X POST -H 'Authorization: testtoken' \
  -H 'Content-Type: application/json' -d \
  '{
    "body": "body",
    "done": false
   }'

Example Response:

{"done": false, "body": "body", "note_id": 2}

Delete

DELETE /note/(int: note_id)/

Delete an existing note.

Query Parameters:
 
  • note_id (int) – Required. Note ID
Request Headers:
 

Example Request:

curl http://127.0.0.1:8080/note/1/ -X DELETE -H 'Authorization: testtoken'

Example Response:


Retrieve

GET /note/(int: note_id)/

Get a note by ID.

Query Parameters:
 
  • note_id (int) – Required. Note ID
  • note_type (str) – Required. The type of note Must be one of: [‘quick’, ‘detailed’].
Request Headers:
 
Response JSON Object:
 
  • body (str) – Note body
  • done (bool) – Marks if a note is done or not.
  • note_id (int) – Note ID

Example Request:

curl 'http://127.0.0.1:8080/note/1/?note_type=quick' -X GET \
  -H 'Authorization: testtoken'

Example Response:

{"done": true, "body": "Example body", "note_id": 1}

Retrieve List

GET /note/

Get a list of notes.

Request Headers:
 
  • Authorization – The auth token for the authenticated user.
  • X-GeoIp-Country – An ISO 3166-1 alpha-2 country code.
Response JSON Array of Objects:
 
  • body (str) – Note body
  • done (bool) – Marks if a note is done or not.
  • note_id (int) – Note ID

Example Request:

curl http://127.0.0.1:8080/note/ -X GET -H 'Authorization: testtoken' \
  -H 'X-GeoIp-Country: US'

Example Response:

[{"done": true, "body": "Example body", "note_id": 1}]

Update

PUT /note/(int: note_id)/

Update an existing note.

Request JSON Object:
 
  • note_id (int) – Required. Note ID
  • body (str) – Note body
  • done (bool) – Marks if a note is done or not.
Request Headers:
 
Response JSON Object:
 
  • body (str) – Note body
  • done (bool) – Marks if a note is done or not.
  • note_id (int) – Note ID

Example Request:

curl http://127.0.0.1:8080/note/1/ -X PUT -H 'Authorization: testtoken' \
  -H 'Content-Type: application/json' -d \
  '{
    "body": "body",
    "done": false,
    "note_id": 1
   }'

Example Response:

{"done": false, "body": "body", "note_id": 1}

Customizing API Endpoint Headings

You can specify a short title when creating the routes which will show up as a sub link below the group heading. To do this, pass a value to title kwarg when defining your http methods for a route. If a title is not provided, one will be generated based on the http method. The automatic title will be one of Retrieve, Delete, Create, or Update.

from doctor.routing import delete, get, put, post, Route

routes = (
    Route('/', methods=(
        get(status, title='Show API Version'),)),
    Route('/note/', methods=(
        get(get_notes, title='Get Notes'),
        post(create_note, title='Create Note'))
    ),
    Route('/note/<int:note_id>/', methods=(
        delete(delete_note, title='Delete Note'),
        get(get_note, title='Get Note'),
        put(update_note, title='Update Note'))
    ),
)

Overriding Example Values For Specific Endpoints

By default doctor will use the example value you specified on your custom type or if one wasn’t given, the default example for the subclass of your type. Sometimes you need to set a very specific value for a parameter in a request when generating documentation. doctor supports this behavior by using define_example_values(). This method allows you to override parameters on a per request basis. To do this subclass the AutoFlaskHarness and override the setup_app() method. Then you can define example values for a particular route and method.

from doctor.docs.flask import AutoFlaskHarness

class MyHarness(AutoFlaskHarness):
    def setup_app(self, sphinx_app):
        super(MyHarness, self).setup_app(sphinx_app)
        self.define_example_values('GET', '^/foo/bar/?$', {'foobar': 1})

The above code sample will change the parameters sent when sending a GET request to /foo/bar when generating documentation for that route. You can call this method for as many routes as you need to provide custom parameters.

Remember if you create your own harness you’ll need to update the harness class that you instantiate in conf.py.

Note

For a flask api the 2nd parameter passed to define_example_values() is the route pattern as a string. e.g. /foo/bar/.

Documenting and Sending Headers on Requests

If you need to pass header values for a request you can define them in two ways.

The first method will add the header to all requests when generating documentation. An example where this may be useful is an Authorization header. To add this simply define a headers dict on your harness. If you would like to provide a definition in the documentation for the header, also define a header_definitions dict where the header key matches the header you wish to document.

from doctor.docs.flask import AutoFlaskHarness

class MyHarness(AutoFlaskHarness):
    headers = {'Authorization': 'testtoken'}
    header_definitions = {
        'Authorization': 'The auth token for the authenticated user.'}

    def setup_app(self, sphinx_app):
        super(MyHarness, self).setup_app(sphinx_app)

If you need to define a header for a specific route and method you can set those up in your harness using define_header_values().

from doctor.docs.flask import AutoFlaskHarness

class MyHarness(AutoFlaskHarness):
    headers = {'Authorization': 'testtoken'}
    header_definitions = {
        'Authorization': 'The auth token for the authenticated user.',
        'X-GeoIp-Country': 'An ISO 3166-1 alpha-2 country code.'}

    def setup_app(self, sphinx_app):
        super(MyHarness, self).setup_app(sphinx_app)
        self.define_header_values('GET', '/foo/bar/', {'X-GeoIp-Country': 'US'})

The above harness will send the Authorization header on all requests and will additionally send the X-GeoIp-Country header on a GET request to /foo/bar/.

Module Documentation

This module can be used to generate Sphinx documentation for an API.

class doctor.docs.base.BaseDirective(name, arguments, options, content, lineno, content_offset, block_text, state, state_machine)[source]

Bases: docutils.parsers.rst.Directive

Base class for doctor Sphinx directives.

You probably want to use AutoFlaskDirective instead of this class.

_prepare_env()[source]

Setup the document’s environment, if necessary.

_render_rst()[source]

Render lines of reStructuredText for items yielded by iter_annotations().

directive_name = None

Name to use for this directive.

This is the identifier used within the Sphinx documentation to trigger this directive. This value should be set by subclasses. For example, in AutoFlaskDirective, this is set to “autoflask”.

classmethod get_outdated_docs(app, env, added, changed, removed)[source]

Handler for Sphinx’s env-get-outdated event.

This handler gives a Sphinx extension a chance to indicate that some set of documents are out of date and need to be re-rendered. The implementation here is stupid, for now, and always says that anything that uses the directive needs to be re-rendered.

We should make it smarter, at some point, and have it figure out which modules are used by the associated handlers, and whether they have actually been updated since the last time the given document was rendered.

harness = None

Harness for the Flask app this directive is documenting. This is responsible for setting up and tearing down the mock app. It is defined in Sphinx’s conf.py file, and set on the directive in run_setup(). It should be an instance of BaseHarness.

has_content = True

Indicates to Sphinx that this directive will yield content.

classmethod purge_docs(app, env, docname)[source]

Handler for Sphinx’s env-purge-doc event.

This event is emitted when all traces of a source file should be cleaned from the environment (that is, if the source file is removed, or before it is freshly read). This is for extensions that keep their own caches in attributes of the environment.

For example, there is a cache of all modules on the environment. When a source file has been changed, the cache’s entries for the file are cleared, since the module declarations could have been removed from the file.

run()[source]

Called by Sphinx to generate documentation for this directive.

classmethod setup(app)[source]

Called by Sphinx to setup an extension.

class doctor.docs.base.BaseHarness(url_prefix)[source]

Bases: object

Base class for doctor directive harnesses. A harness is defined in Sphinx’s conf.py, and the directive invokes the various methods at the appropriate times, so the app can bootstrap a mock version of itself.

_get_annotation_heading(handler, route, heading=None)[source]

Returns the heading text for an annotation.

Attempts to get the name of the heading from the handler attribute schematic_title first.

If schematic_title it is not present, it attempts to generate the title from the class path. This path: advertiser_api.handlers.foo_bar.FooListHandler would translate to ‘Foo Bar’

If the file name with the resource is generically named handlers.py or it doesn’t have a full path then we attempt to get the resource name from the class name. So FooListHandler and FooHandler would translate to ‘Foo’. If the handler class name starts with ‘Internal’, then that will be appended to the heading. So InternalFooListHandler would translate to ‘Foo (Internal)’

Parameters:
  • handler (mixed) – The handler class. Will be a flask resource class
  • route (str) – The route to the handler.
Returns:

The text for the heading as a string.

_get_example_values(route, annotation)[source]

Gets example values for all properties in the annotation’s schema.

Parameters:
  • route (werkzeug.routing.Rule for a flask api.) – The route to get example values for.
  • annotation (doctor.resource.ResourceAnnotation) – Schema annotation for the method to be requested.
Retruns:

A dict containing property names as keys and example values as values.

Return type:

Dict[str, Any]

_get_headers(route, annotation)[source]

Gets headers for the provided route.

Parameters:
  • route (werkzeug.routing.Rule for a flask api.) – The route to get example values for.
  • annotation (doctor.resource.ResourceAnnotation) – Schema annotation for the method to be requested.
Retruns:

A dict containing headers.

Return type:

Dict[~KT, ~VT]

define_example_values(http_method, route, values, update=False)[source]

Define example values for a given request.

By default, example values are determined from the example properties in the schema. But if you want to change the example used in the documentation for a specific route, and this method lets you do that.

Parameters:
  • http_method (str) – An HTTP method, like “get”.
  • route (str) – The route to match.
  • values (dict) – A dictionary of parameters for the example request.
  • update (bool) – If True, the values will be merged into the default example values for the request. If False, the values will replace the default example values.
define_header_values(http_method, route, values, update=False)[source]

Define header values for a given request.

By default, header values are determined from the class attribute headers. But if you want to change the headers used in the documentation for a specific route, this method lets you do that.

Parameters:
  • http_method (str) – An HTTP method, like “get”.
  • route (str) – The route to match.
  • values (dict) – A dictionary of headers for the example request.
  • update (bool) – If True, the values will be merged into the default headers for the request. If False, the values will replace the default headers.
defined_header_values = None

Stores headers for particular methods and routes.

header_definitions = None

Stores definitions for header keys for documentation.

headers = None

Stores global headers to use on all requests

iter_annotations()[source]

Yield a tuple for each schema annotated handler to document.

This must be implemented by subclasses. See AutoFlaskHarness for an example implementation.

request(route, handler, annotation)[source]

Make a request against the app.

This must be implemented by subclasses. See AutoFlaskHarness for an example implementation.

setup_app(sphinx_app)[source]

Called once before building documentation.

Parameters:sphinx_app – Sphinx application object.
setup_request(sphinx_directive, route, handler, annotation)[source]

Called before each request to the mock app.

Parameters:
  • sphinx_directive (BaseDirective) – The directive that is making the mock request.
  • route – Path for the route. For Flask, this will be a Route object.
  • handler – Flask resource for the route.
  • annotation (ResourceAnnotation) – Annotation for the request.
teardown_app(sphinx_app)[source]

Called once after building documentation.

Parameters:sphinx_app – Sphinx application object.
teardown_request(sphinx_directive, route, handler, annotation)[source]

Called after each request to the mock app.

Parameters:
  • sphinx_directive (BaseDirective) – The directive that is making the mock request.
  • route – Path for the route. For Flask, this will be a Route object.
  • handler – Flask resource for the route.
  • annotation (ResourceAnnotation) – Annotation for the request.
doctor.docs.base.CAMEL_CASE_RE = re.compile('[A-Z][^A-Z]*')

Used to transform a class name into it’s various words, splitting on uppercase characters. So MyClassName becomes [‘My’, ‘Class’, ‘Name’]

class doctor.docs.base.DirectiveState[source]

Bases: object

This is used to hold Sphinx serialized state for our directives.

doctor.docs.base.HTTP_METHODS = ('get', 'post', 'put', 'patch', 'delete')

These are the HTTP methods that will be documented on handlers.

Note that HEAD and OPTIONS aren’t included here.

doctor.docs.base.TYPE_MAP = {'array': 'list', 'boolean': 'bool', 'integer': 'int', 'number': 'float', 'object': 'dict', 'string': 'str'}

Used to map the JSON schema types to more Pythonic types for consistency.

doctor.docs.base.URL_PARAMS_RE = re.compile('\\(([a-zA-Z_]+)\\:')

Used to get all url parameter names.

doctor.docs.base.get_example_curl_lines(method, url, params, headers)[source]

Render a cURL command for the given request.

Parameters:
  • method (str) – HTTP request method (e.g. “GET”).
  • url (str) – HTTP request URL.
  • params (dict) – JSON body, for POST and PUT requests.
  • headers (dict) – A dict of HTTP headers.
Return type:

List[str]

Returns:

list

doctor.docs.base.get_example_lines(headers, method, url, params, response)[source]

Render a reStructuredText example for the given request and response.

Parameters:
  • headers (dict) – A dict of HTTP headers.
  • method (str) – HTTP request method (e.g. “GET”).
  • url (str) – HTTP request URL.
  • params (dict) – Form parameters, for POST and PUT requests.
  • response (str) – Text response body.
Return type:

List[str]

Returns:

list

doctor.docs.base.get_json_lines(annotation, field, route, request=False)[source]

Generate documentation lines for the given annotation.

This only documents schemas of type “object”, or type “list” where each “item” is an object. Other types are ignored (but a warning is logged).

Parameters:
  • annotation (doctor.resource.ResourceAnnotation) – Annotation object for the associated handler method.
  • field (str) – Sphinx field type to use (e.g. ‘<json’).
  • route (str) – The route the annotation is attached to.
  • request (bool) – Whether the resource annotation is for the request or not.
Return type:

List[~T]

Returns:

list of strings, one for each line.

doctor.docs.base.get_json_object_lines(annotation, properties, field, url_params, request=False, object_property=False)[source]

Generate documentation for the given object annotation.

Parameters:
  • annotation (doctor.resource.ResourceAnnotation) – Annotation object for the associated handler method.
  • field (str) – Sphinx field type to use (e.g. ‘<json’).
  • url_params (list) – A list of url parameter strings.
  • request (bool) – Whether the schema is for the request or not.
  • object_property (bool) – If True it indicates this is a property of an object that we are documenting. This is only set to True when called recursively when encountering a property that is an object in order to document the properties of it.
Return type:

List[str]

Returns:

list of strings, one for each line.

doctor.docs.base.get_name(value)[source]

Return a best guess at the qualified name for a class or function.

Parameters:value (class or function) – A class or function object.
Returns str:
Return type:str
doctor.docs.base.normalize_route(route)[source]

Strip some of the ugly regexp characters from the given pattern.

>>> normalize_route('^/user/<user_id:int>/?$')
u'/user/(user_id:int)/'
Return type:str
doctor.docs.base.prefix_lines(lines, prefix)[source]

Add the prefix to each of the lines.

>>> prefix_lines(['foo', 'bar'], '  ')
['  foo', '  bar']
>>> prefix_lines('foo\nbar', '  ')
['  foo', '  bar']
Parameters:
  • or str lines (list) – A string or a list of strings. If a string is passed, the string is split using splitlines().
  • prefix (str) – Prefix to add to the lines. Usually an indent.
Returns:

list

This module provides Sphinx directives to generate documentation for Flask resources which have been annotated with schema information. It is broken into a separate module so that Flask applications using doctor for validation don’t need to include Sphinx in their runtime dependencies.

class doctor.docs.flask.AutoFlaskDirective(name, arguments, options, content, lineno, content_offset, block_text, state, state_machine)[source]

Bases: doctor.docs.base.BaseDirective

Sphinx directive to document schema annotated Flask resources.

class doctor.docs.flask.AutoFlaskHarness(app_module_filename, url_prefix)[source]

Bases: doctor.docs.base.BaseHarness

iter_annotations()[source]

Yield a tuple for each Flask handler containing annotated methods.

Each tuple contains a heading, routing rule, the view class associated with the rule, and the annotations for the methods in that class.

request(rule, view_class, annotation)[source]

Make a request against the app.

This attempts to use the schema to replace any url params in the path pattern. If there are any unused parameters in the schema, after substituting the ones in the path, they will be sent as query string parameters or form parameters. The substituted values are taken from the “example” value in the schema.

Returns a dict with the following keys:

  • url – Example URL, with url_prefix added to the path pattern, and the example values substituted in for URL params.
  • method – HTTP request method (e.g. “GET”).
  • params – A dictionary of query string or form parameters.
  • response – The text response to the request.
Parameters:
  • route – Werkzeug Route object.
  • view_class – View class for the annotated method.
  • annotation (doctor.resource.ResourceAnnotation) – Annotation for the method to be requested.
Returns:

dict

setup_app(sphinx_app)[source]

Called once before building documentation.

Parameters:sphinx_app – Sphinx application object.
doctor.docs.flask.setup(app)[source]

This setup function is called by Sphinx.