Add dcumentation (#12)

* added sphinx documentation
* filled in most missing docstrings
* updated README and setup.py, added CONTRIBUTING
* added docs build target
openapi3
Cristi Vîjdea 2017-12-12 11:14:33 +01:00 committed by GitHub
parent bfced82ae4
commit 53ac55a24b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 1821 additions and 447 deletions

27
.editorconfig 100644
View File

@ -0,0 +1,27 @@
root = true
[*]
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
charset = utf-8
[*.py]
indent_style = space
indent_size = 4
[*.rst]
indent_style = space
indent_size = 3
[{package.json,package-lock.json}]
indent_style = space
indent_size = 2
[*.{yml,yaml}]
indent_style = space
indent_size = 2
[Makefile]
indent_style = tab

View File

@ -27,6 +27,13 @@
</value> </value>
</option> </option>
</inspection_tool> </inspection_tool>
<inspection_tool class="PyPep8Inspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<option name="ignoredErrors">
<list>
<option value="E402" />
</list>
</option>
</inspection_tool>
<inspection_tool class="PyPep8NamingInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false"> <inspection_tool class="PyPep8NamingInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false">
<option name="ignoredErrors"> <option name="ignoredErrors">
<list> <list>
@ -42,6 +49,7 @@
<option value="format" /> <option value="format" />
<option value="type" /> <option value="type" />
<option value="filter" /> <option value="filter" />
<option value="copyright" />
</list> </list>
</option> </option>
</inspection_tool> </inspection_tool>

View File

@ -13,6 +13,8 @@ env:
matrix: matrix:
fast_finish: true fast_finish: true
include: include:
- python: '3.5'
env: TOXENV=docs
- python: '2.7' - python: '2.7'
env: TOXENV=flake8 env: TOXENV=flake8
- python: '3.6' - python: '3.6'
@ -21,7 +23,6 @@ matrix:
allow_failures: allow_failures:
- env: TOXENV=flake8 - env: TOXENV=flake8
- env: DRF=master - env: DRF=master
- python: '2.7'
- python: '3.7-dev' - python: '3.7-dev'
install: install:

84
CONTRIBUTING.rst 100644
View File

@ -0,0 +1,84 @@
.. |br| raw:: html
<br />
############
Contributing
############
Contributions are always welcome and appreciated! Here are some ways you can contribut.
******
Issues
******
You can and should open an issue for any of the following reasons:
* you found a bug; steps for reproducing, or a pull request with a failing test case will be greatly appreciated
* you wanted to do something but did not find a way to do it after reading the documentation
* you believe the current way of doing something is more complicated or less elegant than it can be
* a related feature that you want is missing from the package
Please always check for existing issues before opening a new issue.
*************
Pull requests
*************
You want to contribute some code? Great! Here are a few steps to get you started:
#. Fork the repository on GitHub
#. Clone your fork and create a branch for the code you want to add
#. Create a new virtualenv and install the package in development mode
.. code:: console
$ virtualenv venv
$ source venv/bin/activate
(venv) $ pip install -e .[validation,test]
(venv) $ pip install -r requirements/dev.txt
#. Make your changes and check them against the test project
.. code:: console
(venv) $ cd testproj
(venv) $ python manage.py runserver
(venv) $ curl localhost:8000/swagger.yaml
#. Update the tests if necessary
You can find them in the ``tests`` directory.
If your change modifies the expected schema output, you should download the new generated ``swagger.yaml``, diff it
against the old reference output in ``tests/reference.yaml``, and replace it after checking that no unexpected
changes appeared.
#. Run tests. The project is setup to use tox and pytest for testing
.. code:: console
# run tests in the current environment, faster than tox
(venv) $ pytest --cov
# (optional) run tests for other python versions in separate environments
(venv) $ tox
#. Update documentation
If the change modifies behaviour or adds new features, you should update the documentation and ``README.rst``
accordingly. Documentation is written in reStructuredText and built using Sphinx. You can find the sources in the
``docs`` directory.
To build and check the docs, run
.. code:: console
(venv) $ tox -e docs
#. Push your branch and submit a pull request to the master branch on GitHub
Incomplete/Work In Progress pull requrests are encouraged, because they allow you to get feedback and help more
easily.
#. Your code must pass all the required travis jobs before it is merged. As of now, this includes running on
Python 2.7, 3.4, 3.5 and 3.6, and building the docs succesfully.

View File

@ -1,7 +1,16 @@
BSD 3-Clause License .. |br| raw:: html
Copyright (c) 2017, Cristi Vîjdea <br />
All rights reserved.
#######
License
#######
********************
BSD 3-Clause License
********************
Copyright (c) 2017, Cristian V. <cristi@cvjd.me> |br|\ All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met: modification, are permitted provided that the following conditions are met:

View File

@ -1,5 +1,5 @@
include README.md include README.rst
include LICENSE include LICENSE.rst
recursive-include requirements * recursive-include requirements *
recursive-include src/drf_swagger/static * recursive-include src/drf_swagger/static *
recursive-include src/drf_swagger/templates * recursive-include src/drf_swagger/templates *

287
README.md
View File

@ -1,287 +0,0 @@
# drf-swagger - Yet another Swagger generator for Django Rest Framework
_**WARNING**: this project is under rapid development; the APIs described here might change at any time without notice_
## Background
`OpenAPI 2.0`, 'formerly known as' `Swagger`, is a format designed to encode information about a Web API into an
easily parsable schema that can then be used for rendering documentation, generating code, etc.
More details are available on [swagger.io](https://swagger.io/) and on the
[OpenAPI 2.0 specification page](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md).
From here on, the terms "OpenAPI" and "Swagger" are used interchangeably.
#### Swagger in Django Rest Framework
Since Django Rest 3.7, there is now [built in support](http://www.django-rest-framework.org/api-guide/schemas/) for
automatic OpenAPI (Swagger) 2.0 schema generation. However, this generation is based on the
[coreapi](http://www.coreapi.org/) standard, which for the moment is vastly inferior to OpenAPI in both support and
features. In particular, the OpenAPI codec/compatibility layer provided has a few major problems:
* there is no support for documenting response schemas and status codes
* nested schemas do not work properly
* does not handle more complex fields such as `FileField`, `ChoiceField`, ...
In short this makes the generated schema unusable for code generation, and mediocre at best for documentation.
#### Third-party libraries
There are currently two decent Swagger schema generators that I could find for django-rest-framework:
* [django-rest-swagger](https://github.com/marcgibbons/django-rest-swagger)
* [drf-openapi](https://github.com/limdauto/drf_openapi)
Out of the two, `django-rest-swagger` is just a wrapper around DRF 3.7 schema generation with an added UI, and thus has
the same problems. `drf-openapi` is a bit more involved and implements some custom handling for response schemas, but
ultimately still falls short in code generation because the responses are plain `object`s.
Both projects are also relatively dead and stagnant.
## Table of contents
<!-- toc -->
- [Design](#design)
* [Aim](#aim)
* [Implementation progress](#implementation-progress)
+ [Features](#features)
- [Usage](#usage)
* [1. Quickstart](#1-quickstart)
* [2. Configuration](#2-configuration)
- [a. `get_schema_view` parameters](#a-get_schema_view-parameters)
- [b. `SchemaView` instantiators](#b-schemaview-instantiators)
- [c. `SWAGGER_SETTINGS` and `REDOC_SETTINGS`](#c-swagger_settings-and-redoc_settings)
* [3. More customization](#3-more-customization)
* [4. Caching](#4-caching)
* [5. Validation](#5-validation)
+ [`swagger-ui` validation badge](#swagger-ui-validation-badge)
- [Online](#online)
- [Offline](#offline)
+ [using `swagger-cli`](#using-swagger-cli)
+ [manually, on `editor.swagger.io`](#manually-on-editorswaggerio)
- [Planned feature support](#planned-feature-support)
<!-- tocstop -->
## Design
### Aim
This project aims for full compatibility with Swagger/OpenAPI 2.0 code generation tools. More precisely, this means:
* support documentation of response schemas for multiple possible status codes
* support unbounded schema nesting
* generate real OpenAPI Schema definitions instead of inline models
* allow easy and pluggable manual overrides for all schema components
### Implementation progress
For the first release, most of `django-rest-swagger`'s functionality is implemented. A lot of inspiration and code was
drawn from the existing implementations mentioned above, so thanks are due to their respective authors.
#### Features
* schema generation is a wrapper around coreapi & drf
* bundled latest version of [swagger-ui](https://github.com/swagger-api/swagger-ui) and
[redoc](https://github.com/Rebilly/ReDoc)
* supports dumping of schema in JSON and YAML
* schema view is cacheable out of the box
* generated Swagger schema can be automatically validated by [flex](https://github.com/pipermerriam/flex) or
[swagger-spec-validator](https://github.com/Yelp/swagger_spec_validator)
![Swagger UI](/screenshots/snippets-swagger-ui.png?raw=true "Snippets API in Swagger UI")
![ReDoc](/screenshots/snippets-redoc.png?raw=true "Snippets API in ReDoc")
## Usage
### 1. Quickstart
In `settings.py`:
```python
INSTALLED_APPS = [
...
'drf_swagger',
...
]
```
In `urls.py`:
```python
...
from drf_swagger.views import get_schema_view
from drf_swagger import openapi
...
schema_view = get_schema_view(
openapi.Info(
title="Snippets API",
default_version='v1',
description="Test description",
terms_of_service="https://www.google.com/policies/terms/",
contact=openapi.Contact(email="contact@snippets.local"),
license=openapi.License(name="BSD License"),
),
validators=['flex', 'ssv'],
public=False,
permission_classes=(permissions.AllowAny,),
)
urlpatterns = [
url(r'^swagger(?P<format>.json|.yaml)$', schema_view.without_ui(cache_timeout=None), name='schema-json'),
url(r'^swagger/$', schema_view.with_ui('swagger', cache_timeout=None), name='schema-swagger-ui'),
url(r'^redoc/$', schema_view.with_ui('redoc', cache_timeout=None), name='schema-redoc'),
...
]
```
You've just created:
* A JSON view of your schema at `/swagger.json`
* A YAML view of your schema at `/swagger.yaml`
* A swagger-ui view of your schema at `/swagger/`
* A ReDoc view of your schema at `/redoc/`
### 2. Configuration
##### a. `get_schema_view` parameters
* `info` - Required. Swagger API Info object
* `url` - API base url; if left blank will be deduced from the location the view is served at
* `patterns` - passed to SchemaGenerator
* `urlconf` - passed to SchemaGenerator
* `public` - if False, includes only endpoints the current user has access to
* `validators` - a list of validator names to apply on the generated schema; allowed values are `flex`, `ssv`
* `authentication_classes` - authentication classes for the schema view itself
* `permission_classes` - permission classes for the schema view itself
##### b. `SchemaView` instantiators
* `SchemaView.with_ui(renderer, ...)` - get a view instance using the specified UI renderer; one of `swagger`, `redoc`
* `SchemaView.without_ui(...)` - get a view instance with no UI renderer; same as `as_cached_view` with no kwargs
* `SchemaView.as_cached_view(...)` - same as `as_view`, but with optional caching
* you can, of course, call `as_view` as usual
All of the first 3 methods take two optional arguments, `cache_timeout` and `cache_kwargs`; if present, these are
passed on to Django's `cached_page` decorator in order to enable caching on the resulting viewl.
See [4. Caching](#4-caching).
##### c. `SWAGGER_SETTINGS` and `REDOC_SETTINGS`
Additionally, you can include some more settings in your `settings.py` file.
The possible settings and their default values are as follows:
```python
SWAGGER_SETTINGS = {
'USE_SESSION_AUTH': True, # add Django Login and Django Logout buttons, CSRF token to swagger UI page
'LOGIN_URL': getattr(django.conf.settings, 'LOGIN_URL', None), # URL for the login button
'LOGOUT_URL': getattr(django.conf.settings, 'LOGOUT_URL', None), # URL for the logout button
# Swagger security definitions to include in the schema;
# see https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#security-definitions-object
'SECURITY_DEFINITIONS': {
'basic': {
'type': 'basic'
}
},
# url to an external Swagger validation service; defaults to 'http://online.swagger.io/validator/'
# set to None to disable the schema validation badge in the UI
'VALIDATOR_URL': '',
# swagger-ui configuration settings, see https://github.com/swagger-api/swagger-ui#parameters of the same name
'OPERATIONS_SORTER': None,
'TAGS_SORTER': None,
'DOC_EXPANSION': 'list',
'DEEP_LINKING': False,
'SHOW_EXTENSIONS': True,
'DEFAULT_MODEL_RENDERING': 'model',
'DEFAULT_MODEL_DEPTH': 2,
}
```
```python
REDOC_SETTINGS = {
# ReDoc UI configuration settings, see https://github.com/Rebilly/ReDoc#redoc-tag-attributes
'LAZY_RENDERING': True,
'HIDE_HOSTNAME': False,
'EXPAND_RESPONSES': 'all',
'PATH_IN_MIDDLE': False,
}
```
### 3. More customization
Should you have need of more fine-grained customization over the schema view and generation, you are on your own to
figure out where you need to subclass and plug your functionality. Here are a few high-level hints:
* `OpenAPISchemaGenerator` enumerates all the API endpoints registered in Django Rest Framework, inspects their view
classes and generates an appropriate `Swagger` object describing the API structure
* `SchemaView` gets a `drf_swagger.openapi.Swagger` schema object from a generator and renders it into an HTTP response
* you can subclass `SchemaView` by extending the return value of `get_schema_view`, e.g.:
```python
SchemaView = get_schema_view(info, ...)
class CustomSchemaView(SchemaView):
generator_class = CustomSchemaGenerator
renderer_classes = (CustomRenderer1, CustomRenderer2,)
```
* `drf_swagger.renderers` take a `Swagger` object and transform it into an OpenAPI 2.0 specification document
using `OpenAPICodecJson`, `OpenAPICodecYaml`, or into a web interface using an OpenAPI renderer library.
* `drf_swagger.codecs` take a `Swagger` object and encode it in an exportable format (json or yaml by default).
### 4. Caching
Since the schema does not usually change during the lifetime of the django process, there is out of the box support
for caching the schema view in-memory, with some sane defaults:
* caching is enabled by the [`cache_page`](https://docs.djangoproject.com/en/1.11/topics/cache/#the-per-view-cache)
decorator, using the default Django cache backend but this can be changed using the `cache_kwargs` argument
* HTTP caching of the response is blocked to avoid confusing situations caused by being served stale schemas
* the cached schema varies on the `Cookie` and `Authorization` HTTP headers to enable filtering of visible endpoints
according to the authentication credentials of each user; note that this means that every user accessing the schema
will have a separate schema cached in memory.
### 5. Validation
Given the numerous methods to manually customzie the generated schema, it makes sense to validate the result to ensure
it still conforms to OpenAPI 2.0. To this end, validation is provided at the generation point using python swagger
libraries, and can be activated by passing `validators=['flex', 'ssv']` to get_schema_view; if the generated schema
is not valid, a `SwaggerValidationError` is raised by the handling codec and nothing is returned.
**Warning:** This internal validation is quite slow and can be a DOS vector if left activated on a publicly available view.
Caching can mitigate the speed impact of validation on restricted views.
The provided validation will catch syntactic errors, but more subtle violations of the spec might slip by them. To
ensure compatibility with code generation tools, it is recommended to also employ one or more of the following methods:
- #### `swagger-ui` validation badge
##### Online
If your schema is publicly accessible, `swagger-ui` will automatically validate it against the official swagger
online validator and display the result in the bottom-right validation badge.
##### Offline
If your schema is not accessible from the internet, you can run a local copy of
[swagger-validator](https://hub.docker.com/r/swaggerapi/swagger-validator/) and set the `VALIDATOR_URL` accordingly:
```python
SWAGGER_SETTINGS = {
...
'VALIDATOR_URL': 'http://localhost:8189',
...
}
```
```bash
$ docker run --name swagger-validator -d -p 8189:8080 --add-host test.local:10.0.75.1 swaggerapi/swagger-validator
84dabd52ba967c32ae6b660934fa6a429ca6bc9e594d56e822a858b57039c8a2
$ curl http://localhost:8189/debug?url=http://test.local:8002/swagger/?format=openapi
{}
```
- #### using `swagger-cli`
[https://www.npmjs.com/package/swagger-cli](https://www.npmjs.com/package/swagger-cli)
```bash
$ npm install -g swagger-cli
[...]
$ swagger-cli validate http://test.local:8002/swagger.yaml
http://test.local:8002/swagger.yaml is valid
```
- #### manually, on `editor.swagger.io`
Importing the generated spec into [https://editor.swagger.io/](https://editor.swagger.io/) will automatically
trigger validation on it.
## Planned feature support
* **OpenAPI 3.0** - if I get 2.0 working like I want, and it's not too hard to adapt to 3.0

343
README.rst 100644
View File

@ -0,0 +1,343 @@
.. role:: python(code)
:language: python
###########
drf-swagger
###########
|travis| |nbsp| |codecov|
Generate **real** Swagger/OpenAPI 2.0 specifications from a Django Rest Framework API.
Compatible with
- **Django Rest Framework**: 3.7
- **Django**: 1.11, 2.0
- **Python**: 2.7, 3.4, 3.5, 3.6
**Source**: https://github.com/axnsan12/drf-swagger/ |br|
**Documentation**: https://drf-swagger.readthedocs.io/en/latest/
********
Features
********
- full support for nested Serializers and Schemas
- response schemas and descriptions
- model definitions compatible with codegen tools
- customization hooks at all points in the spec generation process
- JSON and YAML format for spec
- bundles latest version of
`swagger-ui <https://github.com/swagger-api/swagger-ui>`__ and
`redoc <https://github.com/Rebilly/ReDoc>`__ for viewing the generated documentation
- schema view is cacheable out of the box
- generated Swagger schema can be automatically validated by
`swagger-spec-validator <https://github.com/Yelp/swagger_spec_validator>`__ or
`flex <https://github.com/pipermerriam/flex>`__
.. figure:: https://raw.githubusercontent.com/axnsan12/drf-swagger/docs/screenshots/redoc-nested-response.png
:width: 100%
:figwidth: image
:alt: redoc screenshot
**Fully nested request and response schemas.**
.. figure:: https://raw.githubusercontent.com/axnsan12/drf-swagger/docs/screenshots/swagger-ui-list.png
:width: 100%
:figwidth: image
:alt: swagger-ui screenshot
**Choose between redoc and swagger-ui.**
.. figure:: https://raw.githubusercontent.com/axnsan12/drf-swagger/docs/screenshots/swagger-ui-models.png
:width: 100%
:figwidth: image
:alt: model definitions screenshot
**Real Model definitions.**
*****************
Table of contents
*****************
.. contents::
:depth: 4
*****
Usage
*****
.. _readme-quickstart:
1. Quickstart
=============
.. code:: console
pip install drf-swagger[validation]
In ``settings.py``:
.. code:: python
INSTALLED_APPS = [
...
'drf_swagger',
...
]
In ``urls.py``:
.. code:: python
...
from drf_swagger.views import get_schema_view
from drf_swagger import openapi
...
schema_view = get_schema_view(
openapi.Info(
title="Snippets API",
default_version='v1',
description="Test description",
terms_of_service="https://www.google.com/policies/terms/",
contact=openapi.Contact(email="contact@snippets.local"),
license=openapi.License(name="BSD License"),
),
validators=['ssv', 'flex'],
public=True,
permission_classes=(permissions.AllowAny,),
)
urlpatterns = [
url(r'^swagger(?P<format>.json|.yaml)$', schema_view.without_ui(cache_timeout=None), name='schema-json'),
url(r'^swagger/$', schema_view.with_ui('swagger', cache_timeout=None), name='schema-swagger-ui'),
url(r'^redoc/$', schema_view.with_ui('redoc', cache_timeout=None), name='schema-redoc'),
...
]
This exposes 4 cached, validated and publicly available endpoints:
* A JSON view of your API specification at ``/swagger.json``
* A YAML view of your API specification at ``/swagger.yaml``
* A swagger-ui view of your API specification at ``/swagger/``
* A ReDoc view of your API specification at ``/redoc/``
2. Configuration
================
a. ``get_schema_view`` parameters
---------------------------------
- ``info`` - Required. Swagger API Info object
- ``url`` - API base url; if left blank will be deduced from the location the view is served at
- ``patterns`` - passed to SchemaGenerator
- ``urlconf`` - passed to SchemaGenerator
- ``public`` - if False, includes only endpoints the current user has access to
- ``validators`` - a list of validator names to apply on the generated schema; allowed values are ``flex``, ``ssv``
- ``authentication_classes`` - authentication classes for the schema view itself
- ``permission_classes`` - permission classes for the schema view itself
b. ``SchemaView`` options
-------------------------------
- :python:`SchemaView.with_ui(renderer, cache_timeout, cache_kwargs)` - get a view instance using the
specified UI renderer; one of ``swagger``, ``redoc``
- :python:`SchemaView.without_ui(cache_timeout, cache_kwargs)` - get a view instance with no UI renderer;
same as ``as_cached_view`` with no kwargs
- :python:`SchemaView.as_cached_view(cache_timeout, cache_kwargs, **initkwargs)` - same as ``as_view``,
but with optional caching
- you can, of course, call :python:`as_view` as usual
All of the first 3 methods take two optional arguments,
``cache_timeout`` and ``cache_kwargs``; if present, these are passed on
to Djangos :python:`cached_page` decorator in order to enable caching on the
resulting view. See `3. Caching`_.
c. ``SWAGGER_SETTINGS`` and ``REDOC_SETTINGS``
----------------------------------------------
Additionally, you can include some more settings in your ``settings.py`` file.
The possible settings and their default values are as follows:
.. code:: python
SWAGGER_SETTINGS = {
'USE_SESSION_AUTH': True, # add Django Login and Django Logout buttons, CSRF token to swagger UI page
'LOGIN_URL': getattr(django.conf.settings, 'LOGIN_URL', None), # URL for the login button
'LOGOUT_URL': getattr(django.conf.settings, 'LOGOUT_URL', None), # URL for the logout button
# Swagger security definitions to include in the schema;
# see https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#security-definitions-object
'SECURITY_DEFINITIONS': {
'basic': {
'type': 'basic'
}
},
# url to an external Swagger validation service; defaults to 'http://online.swagger.io/validator/'
# set to None to disable the schema validation badge in the UI
'VALIDATOR_URL': '',
# swagger-ui configuration settings, see https://github.com/swagger-api/swagger-ui/blob/112bca906553a937ac67adc2e500bdeed96d067b/docs/usage/configuration.md#parameters
'OPERATIONS_SORTER': None,
'TAGS_SORTER': None,
'DOC_EXPANSION': 'list',
'DEEP_LINKING': False,
'SHOW_EXTENSIONS': True,
'DEFAULT_MODEL_RENDERING': 'model',
'DEFAULT_MODEL_DEPTH': 2,
}
.. code:: python
REDOC_SETTINGS = {
# ReDoc UI configuration settings, see https://github.com/Rebilly/ReDoc#redoc-tag-attributes
'LAZY_RENDERING': True,
'HIDE_HOSTNAME': False,
'EXPAND_RESPONSES': 'all',
'PATH_IN_MIDDLE': False,
}
3. Caching
==========
Since the schema does not usually change during the lifetime of the django process, there is out of the box support for
caching the schema view in-memory, with some sane defaults:
* caching is enabled by the `cache_page <https://docs.djangoproject.com/en/1.11/topics/cache/#the-per-view-cache>`__
decorator, using the default Django cache backend, can be changed using the ``cache_kwargs`` argument
* HTTP caching of the response is blocked to avoid confusing situations caused by being shown stale schemas
* if `public` is set to ``False`` on the SchemaView, the cached schema varies on the ``Cookie`` and ``Authorization``
HTTP headers to enable filtering of visible endpoints according to the authentication credentials of each user; note
that this means that every user accessing the schema will have a separate schema cached in memory.
4. Validation
=============
Given the numerous methods to manually customzie the generated schema, it makes sense to validate the result to ensure
it still conforms to OpenAPI 2.0. To this end, validation is provided at the generation point using python swagger
libraries, and can be activated by passing :python:`validators=['ssv', 'flex']` to ``get_schema_view``; if the generated
schema is not valid, a :python:`SwaggerValidationError` is raised by the handling codec.
**Warning:** This internal validation can slow down your server. |br|
Caching can mitigate the speed impact of validation.
The provided validation will catch syntactic errors, but more subtle violations of the spec might slip by them. To
ensure compatibility with code generation tools, it is recommended to also employ one or more of the following methods:
``swagger-ui`` validation badge
-------------------------------
Online
^^^^^^
If your schema is publicly accessible, `swagger-ui` will automatically validate it against the official swagger
online validator and display the result in the bottom-right validation badge.
Offline
^^^^^^^
If your schema is not accessible from the internet, you can run a local copy of
`swagger-validator <https://hub.docker.com/r/swaggerapi/swagger-validator/>`_ and set the `VALIDATOR_URL` accordingly:
.. code:: python
SWAGGER_SETTINGS = {
...
'VALIDATOR_URL': 'http://localhost:8189',
...
}
.. code:: console
$ docker run --name swagger-validator -d -p 8189:8080 --add-host test.local:10.0.75.1 swaggerapi/swagger-validator
84dabd52ba967c32ae6b660934fa6a429ca6bc9e594d56e822a858b57039c8a2
$ curl http://localhost:8189/debug?url=http://test.local:8002/swagger/?format=openapi
{}
Using ``swagger-cli``
---------------------
https://www.npmjs.com/package/swagger-cli
.. code:: console
$ npm install -g swagger-cli
[...]
$ swagger-cli validate http://test.local:8002/swagger.yaml
http://test.local:8002/swagger.yaml is valid
Manually on `editor.swagger.io <https://editor.swagger.io/>`__
--------------------------------------------------------------
Importing the generated spec into https://editor.swagger.io/ will automatically trigger validation on it.
This method is currently the only way to get both syntactic and semantic validation on your specification.
The other validators only provide JSON schema-level validation, but miss things like duplicate operation names,
improper content types, etc
**********
Background
**********
``OpenAPI 2.0``/``Swagger`` is a format designed to encode information about a Web API into an easily parsable schema
that can then be used for rendering documentation, generating code, etc.
More details are available on `swagger.io <https://swagger.io/>`__ and on the `OpenAPI 2.0 specification
page <https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md>`__.
From here on, the terms “OpenAPI” and “Swagger” are used interchangeably.
Swagger in Django Rest Framework
================================
Since Django Rest 3.7, there is now `built in support <http://www.django-rest-framework.org/api-guide/schemas/>`__ for
automatic OpenAPI 2.0 schema generation. However, this generation is based on the `coreapi <http://www.coreapi.org/>`__
standard, which for the moment is vastly inferior to OpenAPI in both features and tooling support. In particular,
the OpenAPI codec/compatibility layer provided has a few major problems:
* there is no support for documenting response schemas and status codes
* nested schemas do not work properly
* does not handle more complex fields such as ``FileField``, ``ChoiceField``, …
In short this makes the generated schema unusable for code generation, and mediocre at best for documentation.
Other libraries
===============
There are currently two decent Swagger schema generators that I could
find for django-rest-framework:
* `django-rest-swagger <https://github.com/marcgibbons/django-rest-swagger>`__
* `drf-openapi <https://github.com/limdauto/drf_openapi>`__
Out of the two, ``django-rest-swagger`` is just a wrapper around DRF 3.7 schema generation with an added UI, and
thus presents the same problems. ``drf-openapi`` is a bit more involved and implements some custom handling for response
schemas, but ultimately still falls short in code
generation because the responses are plain ``object``\ s.
Both projects are also currently unmantained.
Documentation, advanced usage
=============================
https://drf-swagger.readthedocs.io/en/latest/
.. |travis| image:: https://img.shields.io/travis/axnsan12/drf-swagger/master.svg
:target: https://travis-ci.org/axnsan12/drf-swagger
:alt: Travis CI
.. |codecov| image:: https://img.shields.io/codecov/c/github/axnsan12/drf-swagger/master.svg
:target: https://codecov.io/gh/axnsan12/drf-swagger
:alt: Codecov
.. |nbsp| unicode:: 0xA0
:trim:
.. |br| raw:: html
<br />

20
docs/Makefile 100644
View File

@ -0,0 +1,20 @@
# Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
SPHINXPROJ = drf-swagger
SOURCEDIR = .
BUILDDIR = _build
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
.PHONY: help Makefile
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

1
docs/_static/.gitignore vendored 100644
View File

@ -0,0 +1 @@
# force directory to show up in git

1
docs/_templates/.gitignore vendored 100644
View File

@ -0,0 +1 @@
# force directory to show up in git

204
docs/conf.py 100644
View File

@ -0,0 +1,204 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# drf-swagger documentation build configuration file, created by
# sphinx-quickstart on Sun Dec 10 15:20:34 2017.
import os
import sys
import sphinx_rtd_theme
# -- General configuration ------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#
# needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode']
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string:
#
# source_suffix = ['.rst', '.md']
source_suffix = '.rst'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = 'drf-swagger'
copyright = '2017, Cristi V.'
author = 'Cristi V.'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = '1.0'
# The full version, including alpha/beta/rc tags.
release = '1.0.0'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = None
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This patterns also effect to html_static_path and html_extra_path
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
modindex_common_prefix = ['drf_swagger.']
# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = False
# -- Options for HTML output ----------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
# html_theme = 'default'
html_theme = "sphinx_rtd_theme"
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#
# html_theme_options = {}
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
# Custom sidebar templates, must be a dictionary that maps document names
# to template names.
#
# This is required for the alabaster theme
# refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars
html_sidebars = {
'**': [
'relations.html', # needs 'show_related': True theme option to display
'searchbox.html',
]
}
# -- Options for HTMLHelp output ------------------------------------------
# Output file base name for HTML help builder.
htmlhelp_basename = 'drf-swaggerdoc'
# -- Options for LaTeX output ---------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#
# 'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#
# 'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#
# 'preamble': '',
# Latex figure (float) alignment
#
# 'figure_align': 'htbp',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
(master_doc, 'drf-swagger.tex', 'drf-swagger Documentation',
'Cristi V.', 'manual'),
]
# -- Options for manual page output ---------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
(master_doc, 'drf-swagger', 'drf-swagger Documentation',
[author], 1)
]
# -- Options for Texinfo output -------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
(master_doc, 'drf-swagger', 'drf-swagger Documentation',
author, 'drf-swagger', 'One line description of project.',
'Miscellaneous'),
]
autodoc_default_flags = ['private-members']
autodoc_member_order = 'bysource'
autoclass_content = 'both'
autodoc_mock_imports = []
nitpick_ignore = [
('py:class', 'object'),
('py:class', 'Exception'),
('py:class', 'collections.OrderedDict'),
('py:class', 'ruamel.yaml.dumper.SafeDumper'),
('py:class', 'rest_framework.renderers.BaseRenderer'),
('py:class', 'rest_framework.views.APIView'),
('py:class', 'OpenAPICodecYaml'),
('py:class', 'OpenAPICodecJson'),
('py:class', 'OpenAPISchemaGenerator'),
('py:obj', 'bool'),
('py:obj', 'dict'),
('py:obj', 'list'),
('py:obj', 'str'),
('py:obj', 'int'),
('py:obj', 'bytes'),
('py:obj', 'tuple'),
('py:obj', 'callable'),
('py:obj', 'type'),
('py:obj', 'OrderedDict'),
('py:obj', 'coreapi.Field'),
('py:obj', 'BaseFilterBackend'),
('py:obj', 'BasePagination'),
('py:obj', 'rest_framework.request.Request'),
('py:obj', 'rest_framework.serializers.Field'),
('py:obj', 'serializers.Field'),
('py:obj', 'serializers.BaseSerializer'),
('py:obj', 'Serializer'),
('py:obj', 'APIView'),
]
sys.path.insert(0, os.path.abspath('../testproj'))
os.putenv('DJANGO_SETTINGS_MODULE', 'testproj.settings')
from django.conf import settings
settings.configure()
import drf_swagger.views
# instantiate a SchemaView in the views module to make it available to autodoc
drf_swagger.views.SchemaView = drf_swagger.views.get_schema_view(None)

View File

@ -0,0 +1 @@
.. include:: ../CONTRIBUTING.rst

View File

@ -0,0 +1,148 @@
.. |br| raw:: html
<br />
########################
Custom schema generation
########################
If the default spec generation does not quite match what you were hoping to achieve, ``drf-swagger`` provides some
custom behavior hooks by default.
*********************
Swagger spec overview
*********************
This library generates OpenAPI 2.0 documents. The authoritative specification for this document's structure will always
be the official documentation over at `swagger.io <https://swagger.io/>`__ and the `OpenAPI 2.0 specification
page <https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md>`__.
Beause the above specifications are a bit heavy and convoluted, here is a general overview of how the specification
is structured, starting from the root ``Swagger`` object.
* :class:`.Swagger` object
+ ``info``, ``schemes``, ``securityDefinitions`` and other informative attributes
+ ``paths``: :class:`.Paths` object
A list of all the paths in the API in the form of a mapping
- ``{path}``: :class:`.PathItem` - each :class:`.PathItem` has multiple operations keyed by method
* ``{http_method}``: :class:`.Operation`
Each operation is thus uniquely identified by its ``(path, http_method)`` combination,
e.g. ``GET /articles/``, ``POST /articles/``, etc.
* ``parameters``: [:class:`.Parameter`] - and a list of path parameters
+ ``definitions``: named Models
A list of all the named models in the API in the form of a mapping
- ``{ModelName}``: :class:`.Schema`
* :class:`.Operation` contains the following information about each operation:
+ ``parameters``: [:class:`.Parameter`]
A list of all the *query*, *header* and *form* parameters accepted by the operation.
- there can also be **at most one** body parameter whose structure is represented by a
:class:`.Schema` or a reference to one (:class:`.SchemaRef`)
+ ``responses``: :class:`.Responses`
A list of all the possible responses the operation is expected to return. Each response can optionally have a
:class:`.Schema` which describes the structure of its body.
- ``{status_code}``: :class:`.Response` - mapping of status code to response definition
+ ``operationId`` - should be unique across all operations
+ ``tags`` - used to group operations in the listing
It is interesting to note that the main difference between ``Parameter`` and ``Schema`` is that Schemas can nest other
Schemas, while Parameters are "primitives" and cannot contain other Parameters. The only exception are ``body``
Parameters, which can contain a Schema.
**************************************
The ``@swagger_auto_schema`` decorator
**************************************
You can use the :func:`@swagger_auto_schema <.swagger_auto_schema>` decorator on view functions to override
some properties of the generated :class:`.Operation`. For example, in a ``ViewSet``,
.. code:: python
@swagger_auto_schema(operation_description="partial_update description override", responses={404: 'slug not found'})
def partial_update(self, request, *args, **kwargs):
"""partial_update method docstring"""
...
will override the description of the ``PATCH /article/{id}/`` operation, and document a 404 response with no body and
the given description.
Where you can use the :func:`@swagger_auto_schema <.swagger_auto_schema>` decorator depends on the type of your view:
* for function based ``@api_view``\ s, because the same view can handle multiple methods, and thus represent multiple
operations, you have to add the decorator multiple times if you want to override different operations:
.. code:: python
test_param = openapi.Parameter('test', openapi.IN_QUERY, description="test manual param", type=openapi.TYPE_BOOLEAN)
user_response = openapi.Response('response description', UserSerializer)
@swagger_auto_schema(method='get', manual_parameters=[test_param], responses={200: user_response})
@swagger_auto_schema(methods=['put', 'post'], request_body=UserSerializer)
@api_view(['GET', 'PUT', 'POST'])
def user_detail(request, pk):
...
* for class based ``APIView``, ``GenericAPIView`` and non-``ViewSet`` derivatives, you have to decorate the respective
method of each operation:
.. code:: python
class UserList(APIView):
@swagger_auto_schema(responses={200: UserSerializer(many=True)})
def get(self, request):
...
@swagger_auto_schema(operation_description="description")
def post(self, request):
...
* for ``ViewSet``, ``GenericViewSet``, ``ModelViewSet``, because each viewset corresponds to multiple **paths**, you have
to decorate the *action methods*, i.e. ``list``, ``create``, ``retrieve``, etc. |br|
Additionally, ``@list_route``\ s or ``@detail_route``\ s defined on the viewset, like function based api views, can
respond to multiple HTTP methods and thus have multiple operations that must be decorated separately:
.. code:: python
class ArticleViewSet(viewsets.ModelViewSet):
@swagger_auto_schema(operation_description='GET /articles/today/')
@list_route(methods=['get'])
def today(self, request):
...
@swagger_auto_schema(method='get', operation_description="GET /articles/{id}/image/")
@swagger_auto_schema(method='post', operation_description="POST /articles/{id}/image/")
@detail_route(methods=['get', 'post'], parser_classes=(MultiPartParser,))
def image(self, request, id=None):
...
@swagger_auto_schema(operation_description="PUT /articles/{id}/")
def update(self, request, *args, **kwargs):
...
@swagger_auto_schema(operation_description="PATCH /articles/{id}/")
def partial_update(self, request, *args, **kwargs):
...
*************************
Subclassing and extending
*************************
For more advanced control you can subclass :class:`.SwaggerAutoSchema` - see the documentation page for a list of
methods you can override.
You can put your custom subclass to use by setting it on a view method using the
:func:`@swagger_auto_schema <.swagger_auto_schema>` decorator described above.
If you need to control things at a higher level than :class:`.Operation` objects (e.g. overall document structure,
vendor extensions in metadata) you can also subclass :class:`.OpenAPISchemaGenerator` - again, see the documentation
page for a list of its methods.
This custom generator can be put to use by setting it as the :attr:`.generator_class` of a :class:`.SchemaView` using
:func:`.get_schema_view`.

View File

@ -0,0 +1,7 @@
######################
Customizing the web UI
######################
There is currently no pluggable way of customizing the web UI apart from the settings available in
:ref:`swagger-ui-settings` and :ref:`redoc-ui-settings`. If you really need to, you can override one of the
``drf-swagger/swagger-ui.html`` or ``drf-swagger/redoc.html`` templates that are used for rendering.

View File

@ -0,0 +1,87 @@
drf\_swagger package
====================
drf\_swagger\.app\_settings
----------------------------------
.. automodule:: drf_swagger.app_settings
:members:
:undoc-members:
:show-inheritance:
drf\_swagger\.codecs
---------------------------
.. automodule:: drf_swagger.codecs
:members:
:undoc-members:
:show-inheritance:
:exclude-members: SaneYamlDumper
drf\_swagger\.errors
---------------------------
.. automodule:: drf_swagger.errors
:members:
:undoc-members:
:show-inheritance:
drf\_swagger\.generators
-------------------------------
.. automodule:: drf_swagger.generators
:members:
:undoc-members:
:show-inheritance:
drf\_swagger\.inspectors
-------------------------------
.. automodule:: drf_swagger.inspectors
:members:
:undoc-members:
:show-inheritance:
drf\_swagger\.middleware
-------------------------------
.. automodule:: drf_swagger.middleware
:members:
:undoc-members:
:show-inheritance:
drf\_swagger\.openapi
----------------------------
.. automodule:: drf_swagger.openapi
:members:
:undoc-members:
:show-inheritance:
drf\_swagger\.renderers
------------------------------
.. automodule:: drf_swagger.renderers
:members:
:undoc-members:
:show-inheritance:
drf\_swagger\.utils
--------------------------
.. automodule:: drf_swagger.utils
:members:
:undoc-members:
:show-inheritance:
drf\_swagger\.views
--------------------------
.. automodule:: drf_swagger.views
:members:
:undoc-members:
:show-inheritance:
.. |br| raw:: html
<br />

31
docs/index.rst 100644
View File

@ -0,0 +1,31 @@
.. drf-swagger documentation master file, created by
sphinx-quickstart on Sun Dec 10 15:20:34 2017.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
drf-swagger
===========
.. toctree::
:maxdepth: 2
:caption: Table of contents:
readme.rst
rendering.rst
custom_spec.rst
custom_ui.rst
settings.rst
contributing.rst
license.rst
Source code documentation
=========================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`
.. toctree::
:maxdepth: 2
drf_swagger.rst

1
docs/license.rst 100644
View File

@ -0,0 +1 @@
.. include:: ../LICENSE.rst

36
docs/make.bat 100644
View File

@ -0,0 +1,36 @@
@ECHO OFF
pushd %~dp0
REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set SOURCEDIR=.
set BUILDDIR=_build
set SPHINXPROJ=drf-swagger
if "%1" == "" goto help
%SPHINXBUILD% >NUL 2>NUL
if errorlevel 9009 (
echo.
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
echo.installed, then set the SPHINXBUILD environment variable to point
echo.to the full path of the 'sphinx-build' executable. Alternatively you
echo.may add the Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.http://sphinx-doc.org/
exit /b 1
)
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
goto end
:help
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
:end
popd

1
docs/readme.rst 100644
View File

@ -0,0 +1 @@
.. include:: ../README.rst

35
docs/rendering.rst 100644
View File

@ -0,0 +1,35 @@
##################
Serving the schema
##################
************************************************
``get_schema_view`` and the ``SchemaView`` class
************************************************
The :func:`.get_schema_view` function and the :class:`.SchemaView` class it returns (click links for documentation)
are intended to cover the majority of use cases one might want to configure. The class returned by
:func:`.get_schema_view` can be used to obtain view instances via :meth:`.SchemaView.with_ui`,
:meth:`.SchemaView.without_ui` and :meth:`.SchemaView.as_cached_view` - see :ref:`readme-quickstart`
in the README for a usage example.
You can also subclass :class:`.SchemaView` by extending the return value of :func:`.get_schema_view`, e.g.:
.. code:: python
SchemaView = get_schema_view(info, ...)
class CustomSchemaView(SchemaView):
generator_class = CustomSchemaGenerator
renderer_classes = (CustomRenderer1, CustomRenderer2,)
********************
Renderers and codecs
********************
If you need to modify how your Swagger spec is presented in views, you might want to override one of the renderers in
:mod:`.renderers` or one of the codecs in :mod:`.codecs`. The codec is the last stage where the Swagger object
arrives before being transformed into bytes, while the renderer is the stage responsible for tying toghether the
codec and the view.
You can use your custom renderer classes as kwargs to :meth:`.SchemaView.as_cached_view` or by subclassing
:class:`.SchemaView`.

201
docs/settings.rst 100644
View File

@ -0,0 +1,201 @@
.. role:: python(code)
:language: python
.. |br| raw:: html
<br />
########
Settings
########
Settings are configurable in ``settings.py`` by defining ``SWAGGER_SETTINGS`` or ``REDOC_SETTINGS``.
Example:
**settings.py**
.. code:: python
SWAGGER_SETTINGS = {
'SECURITY_DEFINITIONS': {
'basic': {
'type': 'basic'
}
},
...
}
REDOC_SETTINGS = {
'LAZY_RENDERING': True,
...
}
The possible settings and their default values are as follows:
********************
``SWAGGER_SETTINGS``
********************
Authorization
=============
USE_SESSION_AUTH
----------------
Enable/disable Django login as an authentication/authorization mechanism. If True, a login/logout button will be
displayed in Swagger UI.
**Default**: :python:`True`
LOGIN_URL
---------
URL for the Django Login action when using `USE_SESSION_AUTH`_.
**Default**: :python:`django.conf.settings.LOGIN_URL`
LOGOUT_URL
----------
URL for the Django Logout action when using `USE_SESSION_AUTH`_.
**Default**: :python:`django.conf.settings.LOGOUT_URL`
SECURITY_DEFINITIONS
--------------------
Swagger security definitions to be included in the specification. |br|
See https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#security-definitions-object.
**Default**:
.. code:: python
'basic': {
'type': 'basic'
}
.. _swagger-ui-settings:
Swagger UI settings
===================
Swagger UI configuration settings. |br|
See https://github.com/swagger-api/swagger-ui/blob/112bca906553a937ac67adc2e500bdeed96d067b/docs/usage/configuration.md#parameters.
VALIDATOR_URL
-------------
URL pointing to a swagger-validator instance; used for the validation badge shown in swagger-ui. Can be modified to
point to a local install of `swagger-validator <https://hub.docker.com/r/swaggerapi/swagger-validator/>`_ or
set to ``None`` to remove the badge.
**Default**: :python:`'http://online.swagger.io/validator/'` |br|
*Maps to parameter*: ``validatorUrl``
OPERATIONS_SORTER
-----------------
Sorting order for the operation list of each tag.
* :python:`None`: show in the order returned by the server
* :python:`alpha`: sort alphabetically by path
* :python:`method`: sort by HTTP method
**Default**: :python:`None` |br|
*Maps to parameter*: ``operationsSorter``
TAGS_SORTER
-----------
Sorting order for tagged operation groups.
* :python:`None`: Swagger UI default ordering
* :python:`alpha`: sort alphabetically
**Default**: :python:`None` |br|
*Maps to parameter*: ``tagsSorter``
DOC_EXPANSION
-------------
Controls the default expansion setting for the operations and tags.
* :python:`None`: everything is collapsed
* :python:`list`: only tags are expanded
* :python:`full`: all operations are expanded
**Default**: :python:`'list'` |br|
*Maps to parameter*: ``docExpansion``
DEEP_LINKING
------------
Automatically update the fragment part of the URL with permalinks to the currently selected operation.
**Default**: :python:`False` |br|
*Maps to parameter*: ``deepLinking``
SHOW_EXTENSIONS
---------------
Show vendor extension (``x-..``) fields.
**Default**: :python:`True` |br|
*Maps to parameter*: ``showExtensions``
DEFAULT_MODEL_RENDERING
-----------------------
Controls whether operations show the model structure or the example value by default.
* :python:`model`: show the model fields by default
* :python:`example`: show the example value by default
**Default**: :python:`'model'` |br|
*Maps to parameter*: ``defaultModelRendering``
DEFAULT_MODEL_DEPTH
-------------------
Controls how many levels are expaned by default when showing nested models.
**Default**: :python:`2` |br|
*Maps to parameter*: ``defaultModelExpandDepth``
******************
``REDOC_SETTINGS``
******************
.. _redoc-ui-settings:
ReDoc UI settings
=================
ReDoc UI configuration settings. |br|
See https://github.com/Rebilly/ReDoc#redoc-tag-attributes.
LAZY_RENDERING
--------------
**Default**: :python:`True` |br|
*Maps to attribute*: ``lazy-rendering``
HIDE_HOSTNAME
-------------
**Default**: :python:`False` |br|
*Maps to attribute*: ``hide-hostname``
EXPAND_RESPONSES
----------------
**Default**: :python:`'all'` |br|
*Maps to attribute*: ``expand-responses``
PATH_IN_MIDDLE
--------------
**Default**: :python:`False` |br|
*Maps to attribute*: ``path-in-middle-panel``

View File

@ -0,0 +1,5 @@
Sphinx==1.6.5
sphinx_rtd_theme==0.2.4
Pillow==4.3.0
Django==1.11.8

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

View File

@ -1,5 +1,6 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import io
import os import os
from setuptools import setup, find_packages from setuptools import setup, find_packages
@ -10,7 +11,10 @@ def read_req(req_file):
return [line for line in req.readlines() if line and not line.isspace()] return [line for line in req.readlines() if line and not line.isspace()]
requirements = ['djangorestframework>=3.7.3'] + read_req('base.txt') with io.open('README.rst', encoding='utf-8') as readme:
description = readme.read()
requirements = ['djangorestframework>=3.7.0'] + read_req('base.txt')
requirements_validation = read_req('validation.txt') requirements_validation = read_req('validation.txt')
requirements_test = read_req('test.txt') requirements_test = read_req('test.txt')
@ -28,19 +32,29 @@ setup(
}, },
license='BSD License', license='BSD License',
description='Automated generation of real Swagger/OpenAPI 2.0 schemas from Django Rest Framework code.', description='Automated generation of real Swagger/OpenAPI 2.0 schemas from Django Rest Framework code.',
long_description='', long_description=description,
url='https://github.com/axnsan12/drf-swagger', url='https://github.com/axnsan12/drf-swagger',
author='Cristi V.', author='Cristi V.',
author_email='cristi@cvjd.me', author_email='cristi@cvjd.me',
keywords='drf-swagger drf django rest-framework schema swagger openapi ', keywords='drf django django-rest-framework schema swagger openapi codegen swagger-codegen '
'documentation drf-swagger django-rest-swagger drf-openapi',
classifiers=[ classifiers=[
'Framework :: Django',
'Intended Audience :: Developers', 'Intended Audience :: Developers',
'License :: OSI Approved :: BSD License', 'License :: OSI Approved :: BSD License',
'Development Status :: 4 - Beta',
'Operating System :: OS Independent', 'Operating System :: OS Independent',
'Environment :: Web Environment'
'Programming Language :: Python', 'Programming Language :: Python',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Framework :: Django',
'Framework :: Django :: 1.11',
'Framework :: Django :: 2.0',
'Topic :: Documentation',
'Topic :: Software Development :: Code Generators',
], ],
) )

View File

@ -30,12 +30,14 @@ REDOC_DEFAULTS = {
IMPORT_STRINGS = [] IMPORT_STRINGS = []
#:
swagger_settings = APISettings( swagger_settings = APISettings(
user_settings=getattr(settings, 'SWAGGER_SETTINGS', {}), user_settings=getattr(settings, 'SWAGGER_SETTINGS', {}),
defaults=SWAGGER_DEFAULTS, defaults=SWAGGER_DEFAULTS,
import_strings=IMPORT_STRINGS, import_strings=IMPORT_STRINGS,
) )
#:
redoc_settings = APISettings( redoc_settings = APISettings(
user_settings=getattr(settings, 'REDOC_SETTINGS', {}), user_settings=getattr(settings, 'REDOC_SETTINGS', {}),
defaults=REDOC_DEFAULTS, defaults=REDOC_DEFAULTS,

View File

@ -29,6 +29,7 @@ def _validate_swagger_spec_validator(spec, codec):
raise_from(SwaggerValidationError(str(ex), 'swagger_spec_validator', spec, codec), ex) raise_from(SwaggerValidationError(str(ex), 'swagger_spec_validator', spec, codec), ex)
#:
VALIDATORS = { VALIDATORS = {
'flex': _validate_flex, 'flex': _validate_flex,
'ssv': _validate_swagger_spec_validator, 'ssv': _validate_swagger_spec_validator,
@ -36,17 +37,28 @@ VALIDATORS = {
class _OpenAPICodec(object): class _OpenAPICodec(object):
format = 'openapi'
media_type = None media_type = None
#: Allows easier mocking of settings
settings = swagger_settings
def __init__(self, validators): def __init__(self, validators):
self._validators = validators self._validators = validators
@property @property
def validators(self): def validators(self):
"""List of validator names to apply"""
return self._validators return self._validators
def encode(self, document, **_): def encode(self, document):
"""Transform an :class:`.Swagger` object to a sequence of bytes.
Also performs validation and applies settings.
:param openapi.Swagger document: Swagger spec object as generated by :class:`.OpenAPISchemaGenerator`
:return: binary encoding of ``document``
:rtype: bytes
"""
if not isinstance(document, openapi.Swagger): if not isinstance(document, openapi.Swagger):
raise TypeError('Expected a `openapi.Swagger` instance') raise TypeError('Expected a `openapi.Swagger` instance')
@ -58,19 +70,26 @@ class _OpenAPICodec(object):
return force_bytes(self._dump_dict(spec)) return force_bytes(self._dump_dict(spec))
def encode_error(self, err): def encode_error(self, err):
"""Dump an error message into an encoding-appropriate sequence of bytes"""
return force_bytes(self._dump_dict(err)) return force_bytes(self._dump_dict(err))
def _dump_dict(self, spec): def _dump_dict(self, spec):
"""Dump the given dictionary into its string representation.
:param dict spec: a python dict
:return: string representation of ``spec``
:rtype: str
"""
raise NotImplementedError("override this method") raise NotImplementedError("override this method")
def generate_swagger_object(self, swagger): def generate_swagger_object(self, swagger):
""" """Generates the root Swagger object.
Generates the root Swagger object.
:param openapi.Swagger swagger: :param openapi.Swagger swagger: Swagger spec object as generated by :class:`.OpenAPISchemaGenerator`
:return OrderedDict: swagger spec as dict :return: swagger spec as dict
:rtype: OrderedDict
""" """
swagger.security_definitions = swagger_settings.SECURITY_DEFINITIONS swagger.security_definitions = self.settings.SECURITY_DEFINITIONS
return swagger return swagger
@ -78,12 +97,16 @@ class OpenAPICodecJson(_OpenAPICodec):
media_type = 'application/json' media_type = 'application/json'
def _dump_dict(self, spec): def _dump_dict(self, spec):
"""Dump ``spec`` into JSON."""
return json.dumps(spec) return json.dumps(spec)
class SaneYamlDumper(yaml.SafeDumper): class SaneYamlDumper(yaml.SafeDumper):
"""YamlDumper class usable for dumping ``OrderedDict`` and list instances in a standard way."""
def increase_indent(self, flow=False, indentless=False, **kwargs): def increase_indent(self, flow=False, indentless=False, **kwargs):
"""https://stackoverflow.com/a/39681672 """https://stackoverflow.com/a/39681672
Indent list elements. Indent list elements.
""" """
return super(SaneYamlDumper, self).increase_indent(flow=flow, indentless=False, **kwargs) return super(SaneYamlDumper, self).increase_indent(flow=flow, indentless=False, **kwargs)
@ -91,10 +114,10 @@ class SaneYamlDumper(yaml.SafeDumper):
@staticmethod @staticmethod
def represent_odict(dump, mapping, flow_style=None): # pragma: no cover def represent_odict(dump, mapping, flow_style=None): # pragma: no cover
"""https://gist.github.com/miracle2k/3184458 """https://gist.github.com/miracle2k/3184458
Make PyYAML output an OrderedDict. Make PyYAML output an OrderedDict.
It will do so fine if you use yaml.dump(), but that generates ugly, It will do so fine if you use yaml.dump(), but that generates ugly, non-standard YAML code.
non-standard YAML code.
To use yaml.safe_dump(), you need the following. To use yaml.safe_dump(), you need the following.
""" """
@ -130,4 +153,5 @@ class OpenAPICodecYaml(_OpenAPICodec):
media_type = 'application/yaml' media_type = 'application/yaml'
def _dump_dict(self, spec): def _dump_dict(self, spec):
"""Dump ``spec`` into YAML."""
return yaml.dump(spec, Dumper=SaneYamlDumper, default_flow_style=False, encoding='utf-8') return yaml.dump(spec, Dumper=SaneYamlDumper, default_flow_style=False, encoding='utf-8')

View File

@ -18,13 +18,29 @@ class OpenAPISchemaGenerator(object):
""" """
def __init__(self, info, version, url=None, patterns=None, urlconf=None): def __init__(self, info, version, url=None, patterns=None, urlconf=None):
"""
:param .Info info: information about the API
:param str version: API version string, takes preedence over the version in `info`
:param str url: API
:param patterns: if given, only these patterns will be enumerated for inclusion in the API spec
:param urlconf: if patterns is not given, use this urlconf to enumerate patterns;
if not given, the default urlconf is used
"""
self._gen = SchemaGenerator(info.title, url, info.get('description', ''), patterns, urlconf) self._gen = SchemaGenerator(info.title, url, info.get('description', ''), patterns, urlconf)
self.info = info self.info = info
self.version = version self.version = version
self.url = url
def get_schema(self, request=None, public=False): def get_schema(self, request=None, public=False):
"""Generate an openapi.Swagger representing the API schema.""" """Generate an :class:`.Swagger` representing the API schema.
:param rest_framework.request.Request request: the request used for filtering
accesible endpoints and finding the spec URI
:param bool public: if True, all endpoints are included regardless of access through `request`
:return: the generated Swagger specification
:rtype: openapi.Swagger
"""
endpoints = self.get_endpoints(None if public else request) endpoints = self.get_endpoints(None if public else request)
components = ReferenceResolver(openapi.SCHEMA_DEFINITIONS) components = ReferenceResolver(openapi.SCHEMA_DEFINITIONS)
paths = self.get_paths(endpoints, components) paths = self.get_paths(endpoints, components)
@ -35,11 +51,17 @@ class OpenAPISchemaGenerator(object):
return openapi.Swagger( return openapi.Swagger(
info=self.info, paths=paths, info=self.info, paths=paths,
_url=url, _version=self.version, **components _url=url, _version=self.version, **dict(components)
) )
def create_view(self, callback, method, request=None): def create_view(self, callback, method, request=None):
"""Create a view instance from a view callback as registered in urlpatterns.""" """Create a view instance from a view callback as registered in urlpatterns.
:param callable callback: view callback registered in urlpatterns
:param str method: HTTP method
:param rest_framework.request.Request request: request to bind to the view
:return: the view instance
"""
view = self._gen.create_view(callback, method, request) view = self._gen.create_view(callback, method, request)
overrides = getattr(callback, 'swagger_auto_schema', None) overrides = getattr(callback, 'swagger_auto_schema', None)
if overrides is not None: if overrides is not None:
@ -54,7 +76,9 @@ class OpenAPISchemaGenerator(object):
"""Iterate over all the registered endpoints in the API. """Iterate over all the registered endpoints in the API.
:param rest_framework.request.Request request: used for returning only endpoints available to the given request :param rest_framework.request.Request request: used for returning only endpoints available to the given request
:return: {path: (view_class, list[(http_method, view_instance)])""" :return: {path: (view_class, list[(http_method, view_instance)])
:rtype: dict
"""
inspector = self._gen.endpoint_inspector_cls(self._gen.patterns, self._gen.urlconf) inspector = self._gen.endpoint_inspector_cls(self._gen.patterns, self._gen.urlconf)
endpoints = inspector.get_api_endpoints() endpoints = inspector.get_api_endpoints()
@ -68,9 +92,9 @@ class OpenAPISchemaGenerator(object):
return {path: (view_cls[path], methods) for path, methods in view_paths.items()} return {path: (view_cls[path], methods) for path, methods in view_paths.items()}
def get_operation_keys(self, subpath, method, view): def get_operation_keys(self, subpath, method, view):
""" """Return a list of keys that should be used to group an operation within the specification.
Return a list of keys that should be used to layout a link within
the schema document. ::
/users/ ("users", "list"), ("users", "create") /users/ ("users", "list"), ("users", "create")
/users/{pk}/ ("users", "read"), ("users", "update"), ("users", "delete") /users/{pk}/ ("users", "read"), ("users", "update"), ("users", "delete")
@ -78,6 +102,11 @@ class OpenAPISchemaGenerator(object):
/users/{pk}/star/ ("users", "star") # custom viewset detail action /users/{pk}/star/ ("users", "star") # custom viewset detail action
/users/{pk}/groups/ ("users", "groups", "list"), ("users", "groups", "create") /users/{pk}/groups/ ("users", "groups", "list"), ("users", "groups", "create")
/users/{pk}/groups/{pk}/ ("users", "groups", "read"), ("users", "groups", "update") /users/{pk}/groups/{pk}/ ("users", "groups", "read"), ("users", "groups", "update")
:param str subpath: path to the operation with any common prefix/base path removed
:param str method: HTTP method
:param view: the view associated with the operation
:rtype: tuple
""" """
return self._gen.get_keys(subpath, method, view) return self._gen.get_keys(subpath, method, view)
@ -86,6 +115,7 @@ class OpenAPISchemaGenerator(object):
:param dict endpoints: endpoints as returned by get_endpoints :param dict endpoints: endpoints as returned by get_endpoints
:param ReferenceResolver components: resolver/container for Swagger References :param ReferenceResolver components: resolver/container for Swagger References
:rtype: openapi.Paths
""" """
if not endpoints: if not endpoints:
return openapi.Paths(paths={}) return openapi.Paths(paths={})
@ -112,6 +142,13 @@ class OpenAPISchemaGenerator(object):
return openapi.Paths(paths=paths) return openapi.Paths(paths=paths)
def get_overrides(self, view, method): def get_overrides(self, view, method):
"""Get overrides specified for a given operation.
:param view: the view associated with the operation
:param str method: HTTP method
:return: a dictionary containing any overrides set by :func:`@swagger_auto_schema <.swagger_auto_schema>`
:rtype: dict
"""
method = method.lower() method = method.lower()
action = getattr(view, 'action', method) action = getattr(view, 'action', method)
action_method = getattr(view, action, None) action_method = getattr(view, action, None)
@ -126,7 +163,8 @@ class OpenAPISchemaGenerator(object):
:param str path: templated request path :param str path: templated request path
:param type view_cls: the view class associated with the path :param type view_cls: the view class associated with the path
:return list[openapi.Parameter]: path parameters :return: path parameters
:rtype: list[openapi.Parameter]
""" """
parameters = [] parameters = []
model = getattr(getattr(view_cls, 'queryset', None), 'model', None) model = getattr(getattr(view_cls, 'queryset', None), 'model', None)

View File

@ -14,6 +14,12 @@ from .utils import serializer_field_to_swagger, no_body, is_list_view
def force_serializer_instance(serializer): def force_serializer_instance(serializer):
"""Force `serializer` into a ``Serializer`` instance. If it is not a ``Serializer`` class or instance, raises
an assertion error.
:param serializer: serializer class or instance
:return: serializer instance
"""
if inspect.isclass(serializer): if inspect.isclass(serializer):
assert issubclass(serializer, serializers.BaseSerializer), "Serializer required, not %s" % serializer.__name__ assert issubclass(serializer, serializers.BaseSerializer), "Serializer required, not %s" % serializer.__name__
return serializer() return serializer()
@ -25,12 +31,12 @@ def force_serializer_instance(serializer):
class SwaggerAutoSchema(object): class SwaggerAutoSchema(object):
def __init__(self, view, path, method, overrides, components): def __init__(self, view, path, method, overrides, components):
"""Inspector class responsible for providing Operation definitions given a """Inspector class responsible for providing :class:`.Operation` definitions given a
:param view: the view associated with this endpoint :param view: the view associated with this endpoint
:param str path: the path component of the operation URL :param str path: the path component of the operation URL
:param str method: the http method of the operation :param str method: the http method of the operation
:param dict overrides: manual overrides as passed to @swagger_auto_schema :param dict overrides: manual overrides as passed to :func:`@swagger_auto_schema <.swagger_auto_schema>`
:param openapi.ReferenceResolver components: referenceable components :param openapi.ReferenceResolver components: referenceable components
""" """
super(SwaggerAutoSchema, self).__init__() super(SwaggerAutoSchema, self).__init__()
@ -43,19 +49,18 @@ class SwaggerAutoSchema(object):
self._sch.view = view self._sch.view = view
def get_operation(self, operation_keys): def get_operation(self, operation_keys):
"""Get an Operation for the given API endpoint (path, method). """Get an :class:`.Operation` for the given API endpoint (path, method).
This includes query, body parameters and response schemas. This includes query, body parameters and response schemas.
:param tuple[str] operation_keys: an array of keys describing the hierarchical layout of this view in the API; :param tuple[str] operation_keys: an array of keys describing the hierarchical layout of this view in the API;
e.g. ('snippets', 'list'), ('snippets', 'retrieve'), etc. e.g. ``('snippets', 'list')``, ``('snippets', 'retrieve')``, etc.
:return: the resulting Operation object :rtype: openapi.Operation
""" """
consumes = self.get_consumes() consumes = self.get_consumes()
body = self.get_request_body_parameters(consumes) body = self.get_request_body_parameters(consumes)
query = self.get_query_parameters() query = self.get_query_parameters()
parameters = body + query parameters = body + query
parameters = [param for param in parameters if param is not None] parameters = [param for param in parameters if param is not None]
parameters = self.add_manual_parameters(parameters) parameters = self.add_manual_parameters(parameters)
@ -73,13 +78,14 @@ class SwaggerAutoSchema(object):
) )
def get_request_body_parameters(self, consumes): def get_request_body_parameters(self, consumes):
"""Return the request body parameters for this view. """Return the request body parameters for this view. |br|
This is either: This is either:
- a list with a single object Parameter with a Schema derived from the request serializer
- a list with a single object Parameter with a :class:`.Schema` derived from the request serializer
- a list of primitive Parameters parsed as form data - a list of primitive Parameters parsed as form data
:param list[str] consumes: a list of MIME types this request accepts as body :param list[str] consumes: a list of accepted MIME types as returned by :meth:`.get_consumes`
:return: a (potentially empty) list of openapi.Parameter in: either `body` or `formData` :return: a (potentially empty) list of :class:`.Parameter`\ s either ``in: body`` or ``in: formData``
:rtype: list[openapi.Parameter] :rtype: list[openapi.Parameter]
""" """
# only PUT, PATCH or POST can have a request body # only PUT, PATCH or POST can have a request body
@ -106,8 +112,7 @@ class SwaggerAutoSchema(object):
def get_request_serializer(self): def get_request_serializer(self):
"""Return the request serializer (used for parsing the request payload) for this endpoint. """Return the request serializer (used for parsing the request payload) for this endpoint.
:return: the request serializer :return: the request serializer, or one of :class:`.Schema`, :class:`.SchemaRef`, ``None``
:rtype: serializers.BaseSerializer
""" """
body_override = self.overrides.get('request_body', None) body_override = self.overrides.get('request_body', None)
@ -123,9 +128,9 @@ class SwaggerAutoSchema(object):
return self.view.get_serializer() return self.view.get_serializer()
def get_request_form_parameters(self, serializer): def get_request_form_parameters(self, serializer):
"""Given a Serializer, return a list of in: formData Parameters. """Given a Serializer, return a list of ``in: formData`` :class:`.Parameter`\ s.
:param serializer: the view's request serialzier :param serializer: the view's request serializer as returned by :meth:`.get_request_serializer`
:rtype: list[openapi.Parameter] :rtype: list[openapi.Parameter]
""" """
fields = getattr(serializer, 'fields', {}) fields = getattr(serializer, 'fields', {})
@ -136,18 +141,18 @@ class SwaggerAutoSchema(object):
] ]
def get_request_body_schema(self, serializer): def get_request_body_schema(self, serializer):
"""Return the Schema for a given request's body data. Only applies to PUT, PATCH and POST requests. """Return the :class:`.Schema` for a given request's body data. Only applies to PUT, PATCH and POST requests.
:param serializers.BaseSerializer serializer: the view's request serialzier :param serializer: the view's request serializer as returned by :meth:`.get_request_serializer`
:return: the request body schema
:rtype: openapi.Schema :rtype: openapi.Schema
""" """
return self.serializer_to_schema(serializer) return self.serializer_to_schema(serializer)
def make_body_parameter(self, schema): def make_body_parameter(self, schema):
"""Given a Schema object, create an in: body Parameter. """Given a :class:`.Schema` object, create an ``in: body`` :class:`.Parameter`.
:param openapi.Schema schema: the request body schema :param openapi.Schema schema: the request body schema
:rtype: openapi.Parameter
""" """
return openapi.Parameter(name='data', in_=openapi.IN_BODY, required=True, schema=schema) return openapi.Parameter(name='data', in_=openapi.IN_BODY, required=True, schema=schema)
@ -172,9 +177,10 @@ class SwaggerAutoSchema(object):
return list(parameters.values()) return list(parameters.values())
def get_responses(self): def get_responses(self):
"""Get the possible responses for this view as a swagger Responses object. """Get the possible responses for this view as a swagger :class:`.Responses` object.
:return: the documented responses :return: the documented responses
:rtype: openapi.Responses
""" """
response_serializers = self.get_response_serializers() response_serializers = self.get_response_serializers()
return openapi.Responses( return openapi.Responses(
@ -182,9 +188,10 @@ class SwaggerAutoSchema(object):
) )
def get_paged_response_schema(self, response_schema): def get_paged_response_schema(self, response_schema):
"""Add appropriate paging fields to a response Schema. """Add appropriate paging fields to a response :class:`.Schema`.
:param openapi.Schema response_schema: the response schema that must be paged. :param openapi.Schema response_schema: the response schema that must be paged.
:rtype: openapi.Schema
""" """
assert response_schema.type == openapi.TYPE_ARRAY, "array return expected for paged response" assert response_schema.type == openapi.TYPE_ARRAY, "array return expected for paged response"
paged_schema = openapi.Schema( paged_schema = openapi.Schema(
@ -201,6 +208,10 @@ class SwaggerAutoSchema(object):
return paged_schema return paged_schema
def get_default_responses(self): def get_default_responses(self):
"""Get the default responses determined for this view from the request serializer and request method.
:type: dict[str, openapi.Schema]
"""
method = self.method.lower() method = self.method.lower()
default_status = status.HTTP_200_OK default_status = status.HTTP_200_OK
@ -227,9 +238,11 @@ class SwaggerAutoSchema(object):
def get_response_serializers(self): def get_response_serializers(self):
"""Return the response codes that this view is expected to return, and the serializer for each response body. """Return the response codes that this view is expected to return, and the serializer for each response body.
The return value should be a dict where the keys are possible status codes, and values are either strings, The return value should be a dict where the keys are possible status codes, and values are either strings,
`Serializer` or `openapi.Response` objects. ``Serializer``\ s, :class:`.Schema`, :class:`.SchemaRef` or :class:`.Response` objects. See
:func:`@swagger_auto_schema <.swagger_auto_schema>` for more details.
:return dict: the response serializers :return: the response serializers
:rtype: dict
""" """
manual_responses = self.overrides.get('responses', None) or {} manual_responses = self.overrides.get('responses', None) or {}
manual_responses = OrderedDict((str(sc), resp) for sc, resp in manual_responses.items()) manual_responses = OrderedDict((str(sc), resp) for sc, resp in manual_responses.items())
@ -242,10 +255,11 @@ class SwaggerAutoSchema(object):
return responses return responses
def get_response_schemas(self, response_serializers): def get_response_schemas(self, response_serializers):
"""Return the `openapi.Response` objects calculated for this view. """Return the :class:`.openapi.Response` objects calculated for this view.
:param dict response_serializers: result of get_response_serializers :param dict response_serializers: response serializers as returned by :meth:`.get_response_serializers`
:return dict[str, openapi.Response]: a dictionary of status code to Response object :return: a dictionary of status code to :class:`.Response` object
:rtype: dict[str, openapi.Response]
""" """
responses = {} responses = {}
for sc, serializer in response_serializers.items(): for sc, serializer in response_serializers.items():
@ -277,10 +291,15 @@ class SwaggerAutoSchema(object):
def get_query_parameters(self): def get_query_parameters(self):
"""Return the query parameters accepted by this view. """Return the query parameters accepted by this view.
:rtype: list[openapi.Parameter]""" :rtype: list[openapi.Parameter]
"""
return self.get_filter_parameters() + self.get_pagination_parameters() return self.get_filter_parameters() + self.get_pagination_parameters()
def should_filter(self): def should_filter(self):
"""Determine whether filter backend parameters should be included for this request.
:rtype: bool
"""
if not getattr(self.view, 'filter_backends', None): if not getattr(self.view, 'filter_backends', None):
return False return False
@ -318,6 +337,10 @@ class SwaggerAutoSchema(object):
return fields return fields
def should_page(self): def should_page(self):
"""Determine whether paging parameters and structure should be added to this operation's request and response.
:rtype: bool
"""
if not hasattr(self.view, 'paginator'): if not hasattr(self.view, 'paginator'):
return False return False
@ -344,7 +367,8 @@ class SwaggerAutoSchema(object):
def get_pagination_parameters(self): def get_pagination_parameters(self):
"""Return the parameters added to the view by its paginator. """Return the parameters added to the view by its paginator.
:rtype: list[openapi.Parameter]""" :rtype: list[openapi.Parameter]
"""
if not self.should_page(): if not self.should_page():
return [] return []
@ -372,7 +396,7 @@ class SwaggerAutoSchema(object):
return media_types[:1] return media_types[:1]
def serializer_to_schema(self, serializer): def serializer_to_schema(self, serializer):
"""Convert a DRF Serializer instance to an openapi.Schema. """Convert a DRF Serializer instance to an :class:`.openapi.Schema`.
:param serializers.BaseSerializer serializer: :param serializers.BaseSerializer serializer:
:rtype: openapi.Schema :rtype: openapi.Schema
@ -381,7 +405,7 @@ class SwaggerAutoSchema(object):
return serializer_field_to_swagger(serializer, openapi.Schema, definitions) return serializer_field_to_swagger(serializer, openapi.Schema, definitions)
def field_to_parameter(self, field, name, in_): def field_to_parameter(self, field, name, in_):
"""Convert a DRF serializer Field to a swagger Parameter object. """Convert a DRF serializer Field to a swagger :class:`.Parameter` object.
:param coreapi.Field field: :param coreapi.Field field:
:param str name: the name of the parameter :param str name: the name of the parameter
@ -391,7 +415,7 @@ class SwaggerAutoSchema(object):
return serializer_field_to_swagger(field, openapi.Parameter, name=name, in_=in_) return serializer_field_to_swagger(field, openapi.Parameter, name=name, in_=in_)
def coreapi_field_to_parameter(self, field): def coreapi_field_to_parameter(self, field):
"""Convert an instance of `coreapi.Field` to a swagger Parameter object. """Convert an instance of `coreapi.Field` to a swagger :class:`.Parameter` object.
:param coreapi.Field field: :param coreapi.Field field:
:rtype: openapi.Parameter :rtype: openapi.Parameter

View File

@ -1,11 +1,16 @@
from django.http import HttpResponse from django.http import HttpResponse
from django.utils.deprecation import MiddlewareMixin
from .codecs import _OpenAPICodec from .codecs import _OpenAPICodec
from .errors import SwaggerValidationError from .errors import SwaggerValidationError
class SwaggerExceptionMiddleware(MiddlewareMixin): class SwaggerExceptionMiddleware(object):
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
return self.get_response(request)
def process_exception(self, request, exception): def process_exception(self, request, exception):
if isinstance(exception, SwaggerValidationError): if isinstance(exception, SwaggerValidationError):
err = {'errors': {exception.validator_name: str(exception)}} err = {'errors': {exception.validator_name: str(exception)}}

View File

@ -5,42 +5,43 @@ from coreapi.compat import urlparse
from future.utils import raise_from from future.utils import raise_from
from inflection import camelize from inflection import camelize
TYPE_OBJECT = "object"
TYPE_STRING = "string" TYPE_OBJECT = "object" #:
TYPE_NUMBER = "number" TYPE_STRING = "string" #:
TYPE_INTEGER = "integer" TYPE_NUMBER = "number" #:
TYPE_BOOLEAN = "boolean" TYPE_INTEGER = "integer" #:
TYPE_ARRAY = "array" TYPE_BOOLEAN = "boolean" #:
TYPE_FILE = "file" TYPE_ARRAY = "array" #:
TYPE_FILE = "file" #:
# officially supported by Swagger 2.0 spec # officially supported by Swagger 2.0 spec
FORMAT_DATE = "date" FORMAT_DATE = "date" #:
FORMAT_DATETIME = "date-time" FORMAT_DATETIME = "date-time" #:
FORMAT_PASSWORD = "password" FORMAT_PASSWORD = "password" #:
FORMAT_BINARY = "binary" FORMAT_BINARY = "binary" #:
FORMAT_BASE64 = "bytes" FORMAT_BASE64 = "bytes" #:
FORMAT_FLOAT = "float" FORMAT_FLOAT = "float" #:
FORMAT_DOUBLE = "double" FORMAT_DOUBLE = "double" #:
FORMAT_INT32 = "int32" FORMAT_INT32 = "int32" #:
FORMAT_INT64 = "int64" FORMAT_INT64 = "int64" #:
# defined in JSON-schema # defined in JSON-schema
FORMAT_EMAIL = "email" FORMAT_EMAIL = "email" #:
FORMAT_IPV4 = "ipv4" FORMAT_IPV4 = "ipv4" #:
FORMAT_IPV6 = "ipv6" FORMAT_IPV6 = "ipv6" #:
FORMAT_URI = "uri" FORMAT_URI = "uri" #:
# pulled out of my ass # pulled out of my ass
FORMAT_UUID = "uuid" FORMAT_UUID = "uuid" #:
FORMAT_SLUG = "slug" FORMAT_SLUG = "slug" #:
IN_BODY = 'body' IN_BODY = 'body' #:
IN_PATH = 'path' IN_PATH = 'path' #:
IN_QUERY = 'query' IN_QUERY = 'query' #:
IN_FORM = 'formData' IN_FORM = 'formData' #:
IN_HEADER = 'header' IN_HEADER = 'header' #:
SCHEMA_DEFINITIONS = 'definitions' SCHEMA_DEFINITIONS = 'definitions' #:
def make_swagger_name(attribute_name): def make_swagger_name(attribute_name):
@ -48,7 +49,7 @@ def make_swagger_name(attribute_name):
Convert a python variable name into a Swagger spec attribute name. Convert a python variable name into a Swagger spec attribute name.
In particular, In particular,
* if name starts with x_, return "x-{camelCase}" * if name starts with x\_, return "x-{camelCase}"
* if name is 'ref', return "$ref" * if name is 'ref', return "$ref"
* else return the name converted to camelCase, with trailing underscores stripped * else return the name converted to camelCase, with trailing underscores stripped
@ -63,6 +64,10 @@ def make_swagger_name(attribute_name):
class SwaggerDict(OrderedDict): class SwaggerDict(OrderedDict):
"""A particular type of OrderedDict, which maps all attribute accesses to dict lookups using
:func:`.make_swagger_name`. Used as a base class for all Swagger helper models.
"""
def __init__(self, **attrs): def __init__(self, **attrs):
super(SwaggerDict, self).__init__() super(SwaggerDict, self).__init__()
self._extras__ = attrs self._extras__ = attrs
@ -109,15 +114,15 @@ class SwaggerDict(OrderedDict):
class Contact(SwaggerDict): class Contact(SwaggerDict):
def __init__(self, name=None, url=None, email=None, **extra):
"""Swagger Contact object """Swagger Contact object
At least one of the following fields is required: At least one of the following fields is required:
:param str name: contact name :param str name: contact name
:param str url: contact url :param str url: contact url
:param str email: contact e-mail :param str email: contact e-mail
""" """
def __init__(self, name=None, url=None, email=None, **extra):
super(Contact, self).__init__(**extra) super(Contact, self).__init__(**extra)
if name is None and url is None and email is None: if name is None and url is None and email is None:
raise AssertionError("one of name, url or email is requires for Swagger Contact object") raise AssertionError("one of name, url or email is requires for Swagger Contact object")
@ -128,13 +133,12 @@ class Contact(SwaggerDict):
class License(SwaggerDict): class License(SwaggerDict):
def __init__(self, name, url=None, **extra):
"""Swagger License object """Swagger License object
:param str name: Requird. License name :param str name: Required. License name
:param str url: link to detailed license information :param str url: link to detailed license information
""" """
def __init__(self, name, url=None, **extra):
super(License, self).__init__(**extra) super(License, self).__init__(**extra)
if name is None: if name is None:
raise AssertionError("name is required for Swagger License object") raise AssertionError("name is required for Swagger License object")
@ -144,6 +148,8 @@ class License(SwaggerDict):
class Info(SwaggerDict): class Info(SwaggerDict):
def __init__(self, title, default_version, description=None, terms_of_service=None, contact=None, license=None,
**extra):
"""Swagger Info object """Swagger Info object
:param str title: Required. API title. :param str title: Required. API title.
@ -153,9 +159,6 @@ class Info(SwaggerDict):
:param Contact contact: contact object :param Contact contact: contact object
:param License license: license object :param License license: license object
""" """
def __init__(self, title, default_version, description=None, terms_of_service=None, contact=None, license=None,
**extra):
super(Info, self).__init__(**extra) super(Info, self).__init__(**extra)
if title is None or default_version is None: if title is None or default_version is None:
raise AssertionError("title and version are required for Swagger info object") raise AssertionError("title and version are required for Swagger info object")
@ -174,6 +177,14 @@ class Info(SwaggerDict):
class Swagger(SwaggerDict): class Swagger(SwaggerDict):
def __init__(self, info=None, _url=None, _version=None, paths=None, definitions=None, **extra): def __init__(self, info=None, _url=None, _version=None, paths=None, definitions=None, **extra):
"""Root Swagger object.
:param .Info info: info object
:param str _url: URL used for guessing the API host, scheme and basepath
:param str _version: version string to override Info
:param .Paths paths: paths object
:param dict[str,.Schema] definitions: named models
"""
super(Swagger, self).__init__(**extra) super(Swagger, self).__init__(**extra)
self.swagger = '2.0' self.swagger = '2.0'
self.info = info self.info = info
@ -194,6 +205,10 @@ class Swagger(SwaggerDict):
class Paths(SwaggerDict): class Paths(SwaggerDict):
def __init__(self, paths, **extra): def __init__(self, paths, **extra):
"""A listing of all the paths in the API.
:param dict[str,.PathItem] paths:
"""
super(Paths, self).__init__(**extra) super(Paths, self).__init__(**extra)
for path, path_obj in paths.items(): for path, path_obj in paths.items():
assert path.startswith("/") assert path.startswith("/")
@ -205,14 +220,25 @@ class Paths(SwaggerDict):
class PathItem(SwaggerDict): class PathItem(SwaggerDict):
def __init__(self, get=None, put=None, post=None, delete=None, options=None, def __init__(self, get=None, put=None, post=None, delete=None, options=None,
head=None, patch=None, parameters=None, **extra): head=None, patch=None, parameters=None, **extra):
"""Information about a single path
:param .Operation get: operation for GET
:param .Operation put: operation for PUT
:param .Operation post: operation for POST
:param .Operation delete: operation for DELETE
:param .Operation options: operation for OPTIONS
:param .Operation head: operation for HEAD
:param .Operation patch: operation for PATCH
:param list[.Parameter] parameters: parameters that apply to all operations
"""
super(PathItem, self).__init__(**extra) super(PathItem, self).__init__(**extra)
self.get = get self.get = get
self.put = put self.head = head
self.post = post self.post = post
self.put = put
self.patch = patch
self.delete = delete self.delete = delete
self.options = options self.options = options
self.head = head
self.patch = patch
self.parameters = parameters self.parameters = parameters
self._insert_extras__() self._insert_extras__()
@ -220,6 +246,16 @@ class PathItem(SwaggerDict):
class Operation(SwaggerDict): class Operation(SwaggerDict):
def __init__(self, operation_id, responses, parameters=None, consumes=None, def __init__(self, operation_id, responses, parameters=None, consumes=None,
produces=None, description=None, tags=None, **extra): produces=None, description=None, tags=None, **extra):
"""Information about an API operation (path + http method combination)
:param str operation_id: operation ID, should be unique across all operations
:param .Responses responses: responses returned
:param list[.Parameter] parameters: parameters accepted
:param list[str] consumes: content types accepted
:param list[str] produces: content types produced
:param str description: operation description
:param list[str] tags: operation tags
"""
super(Operation, self).__init__(**extra) super(Operation, self).__init__(**extra)
self.operation_id = operation_id self.operation_id = operation_id
self.description = description self.description = description
@ -233,6 +269,14 @@ class Operation(SwaggerDict):
class Items(SwaggerDict): class Items(SwaggerDict):
def __init__(self, type=None, format=None, enum=None, pattern=None, items=None, **extra): def __init__(self, type=None, format=None, enum=None, pattern=None, items=None, **extra):
"""Used when defining an array :class:`.Parameter` to describe the array elements.
:param str type: type of the array elements; must not be ``object``
:param str format: value format, see OpenAPI spec
:param list enum: restrict possible values
:param str pattern: pattern if type is ``string``
:param .Items items: only valid if `type` is ``array``
"""
super(Items, self).__init__(**extra) super(Items, self).__init__(**extra)
self.type = type self.type = type
self.format = format self.format = format
@ -245,6 +289,20 @@ class Items(SwaggerDict):
class Parameter(SwaggerDict): class Parameter(SwaggerDict):
def __init__(self, name, in_, description=None, required=None, schema=None, def __init__(self, name, in_, description=None, required=None, schema=None,
type=None, format=None, enum=None, pattern=None, items=None, **extra): type=None, format=None, enum=None, pattern=None, items=None, **extra):
"""Describe parameters accepted by an :class:`.Operation`. Each parameter should be a unique combination of
(`name`, `in_`). ``body`` and ``form`` parameters in the same operation are mutually exclusive.
:param str name: parameter name
:param str in_: parameter location
:param str description: parameter description
:param bool required: whether the parameter is required for the operation
:param .Schema,.SchemaRef schema: required if `in_` is ``body``
:param str type: parameter type; required if `in_` is not ``body``; must not be ``object``
:param str format: value format, see OpenAPI spec
:param list enum: restrict possible values
:param str pattern: pattern if type is ``string``
:param .Items items: only valid if `type` is ``array``
"""
super(Parameter, self).__init__(**extra) super(Parameter, self).__init__(**extra)
if (not schema and not type) or (schema and type): if (not schema and not type) or (schema and type):
raise AssertionError("either schema or type are required for Parameter object!") raise AssertionError("either schema or type are required for Parameter object!")
@ -266,11 +324,23 @@ class Schema(SwaggerDict):
def __init__(self, description=None, required=None, type=None, properties=None, additional_properties=None, def __init__(self, description=None, required=None, type=None, properties=None, additional_properties=None,
format=None, enum=None, pattern=None, items=None, **extra): format=None, enum=None, pattern=None, items=None, **extra):
"""Describes a complex object accepted as parameter or returned as a response.
:param description: schema description
:param list[str] required: list of requried property names
:param str type: value type; required
:param list[.Schema,.SchemaRef] properties: object properties; required if `type` is ``object``
:param bool,.Schema,.SchemaRef additional_properties: allow wildcard properties not listed in `properties`
:param str format: value format, see OpenAPI spec
:param list enum: restrict possible values
:param str pattern: pattern if type is ``string``
:param .Schema,.SchemaRef items: only valid if `type` is ``array``
"""
super(Schema, self).__init__(**extra) super(Schema, self).__init__(**extra)
if required is True or required is False: if required is True or required is False:
# common error # common error
raise AssertionError( raise AssertionError(
"the requires attribute of schema must be an array of required properties, not a boolean!") "the `requires` attribute of schema must be an array of required properties, not a boolean!")
self.description = description self.description = description
self.required = required self.required = required
self.type = type self.type = type
@ -285,6 +355,14 @@ class Schema(SwaggerDict):
class _Ref(SwaggerDict): class _Ref(SwaggerDict):
def __init__(self, resolver, name, scope, expected_type): def __init__(self, resolver, name, scope, expected_type):
"""Base class for all reference types. A reference object has only one property, ``$ref``, which must be a JSON
reference to a valid object in the specification, e.g. ``#/definitions/Article`` to refer to an article model.
:param .ReferenceResolver resolver: component resolver which must contain the referneced object
:param str name: referenced object name, e.g. "Article"
:param str scope: reference scope, e.g. "definitions"
:param type[.SwaggerDict] expected_type: the expected type that will be asserted on the object found in resolver
"""
super(_Ref, self).__init__() super(_Ref, self).__init__()
assert not type(self) == _Ref, "do not instantiate _Ref directly" assert not type(self) == _Ref, "do not instantiate _Ref directly"
ref_name = "#/{scope}/{name}".format(scope=scope, name=name) ref_name = "#/{scope}/{name}".format(scope=scope, name=name)
@ -304,9 +382,9 @@ class _Ref(SwaggerDict):
class SchemaRef(_Ref): class SchemaRef(_Ref):
def __init__(self, resolver, schema_name): def __init__(self, resolver, schema_name):
"""Add a reference to a named Schema defined in the #/definitions/ object. """Adds a reference to a named Schema defined in the ``#/definitions/`` object.
:param ReferenceResolver resolver: component resolver which must contain the definition :param .ReferenceResolver resolver: component resolver which must contain the definition
:param str schema_name: schema name :param str schema_name: schema name
""" """
assert SCHEMA_DEFINITIONS in resolver.scopes assert SCHEMA_DEFINITIONS in resolver.scopes
@ -318,6 +396,11 @@ Schema.OR_REF = (Schema, SchemaRef)
class Responses(SwaggerDict): class Responses(SwaggerDict):
def __init__(self, responses, default=None, **extra): def __init__(self, responses, default=None, **extra):
"""Describes the expected responses of an :class:`.Operation`.
:param dict[(str,int),.Response] responses: mapping of status code to response definition
:param .Response default: description of the response structure to expect if another status code is returned
"""
super(Responses, self).__init__(**extra) super(Responses, self).__init__(**extra)
for status, response in responses.items(): for status, response in responses.items():
if response is not None: if response is not None:
@ -328,6 +411,12 @@ class Responses(SwaggerDict):
class Response(SwaggerDict): class Response(SwaggerDict):
def __init__(self, description, schema=None, examples=None, **extra): def __init__(self, description, schema=None, examples=None, **extra):
"""Describes the structure of an operation's response.
:param str description: response description
:param .Schema,.SchemaRef schema: sturcture of the response body
:param dict examples: example bodies mapped by mime type
"""
super(Response, self).__init__(**extra) super(Response, self).__init__(**extra)
self.description = description self.description = description
self.schema = schema self.schema = schema
@ -340,6 +429,9 @@ class ReferenceResolver(object):
Provides support and checks for different refernce scopes, e.g. 'definitions'. Provides support and checks for different refernce scopes, e.g. 'definitions'.
For example: For example:
::
> components = ReferenceResolver('definitions', 'parameters') > components = ReferenceResolver('definitions', 'parameters')
> definitions = ReferenceResolver.with_scope('definitions') > definitions = ReferenceResolver.with_scope('definitions')
> definitions.set('Article', Schema(...)) > definitions.set('Article', Schema(...))
@ -348,6 +440,9 @@ class ReferenceResolver(object):
""" """
def __init__(self, *scopes): def __init__(self, *scopes):
"""
:param str scopes: an enumeration of the valid scopes this resolver will contain
"""
self._objects = OrderedDict() self._objects = OrderedDict()
self._force_scope = None self._force_scope = None
for scope in scopes: for scope in scopes:
@ -355,6 +450,12 @@ class ReferenceResolver(object):
self._objects[scope] = OrderedDict() self._objects[scope] = OrderedDict()
def with_scope(self, scope): def with_scope(self, scope):
"""Return a new :class:`.ReferenceResolver` whose scope is defaulted and forced to `scope`.
:param str scope: target scope, must be in this resolver's `scopes`
:return: the bound resolver
:rtype: .ReferenceResolver
"""
assert scope in self.scopes, "unknown scope %s" % scope assert scope in self.scopes, "unknown scope %s" % scope
ret = ReferenceResolver() ret = ReferenceResolver()
ret._objects = self._objects ret._objects = self._objects
@ -369,12 +470,24 @@ class ReferenceResolver(object):
return real_scope return real_scope
def set(self, name, obj, scope=None): def set(self, name, obj, scope=None):
"""Set an object in the given scope, raise an error if it already exists.
:param str name: reference name
:param obj: referenced object
:param str scope: reference scope
"""
scope = self._check_scope(scope) scope = self._check_scope(scope)
assert obj is not None, "referenced objects cannot be None/null" assert obj is not None, "referenced objects cannot be None/null"
assert name not in self._objects[scope], "#/%s/%s already exists" % (scope, name) assert name not in self._objects[scope], "#/%s/%s already exists" % (scope, name)
self._objects[scope][name] = obj self._objects[scope][name] = obj
def setdefault(self, name, maker, scope=None): def setdefault(self, name, maker, scope=None):
"""Set an object in the given scope only if it does not exist.
:param str name: reference name
:param callable maker: object factory, called only if necessary
:param str scope: reference scope
"""
scope = self._check_scope(scope) scope = self._check_scope(scope)
assert callable(maker), "setdefault expects a callable, not %s" % type(maker).__name__ assert callable(maker), "setdefault expects a callable, not %s" % type(maker).__name__
ret = self.getdefault(name, None, scope) ret = self.getdefault(name, None, scope)
@ -386,15 +499,35 @@ class ReferenceResolver(object):
return ret return ret
def get(self, name, scope=None): def get(self, name, scope=None):
"""Get an object from the given scope, raise an error if it does not exist.
:param str name: reference name
:param str scope: reference scope
:return: the object
"""
scope = self._check_scope(scope) scope = self._check_scope(scope)
assert name in self._objects[scope], "#/%s/%s is not defined" % (scope, name) assert name in self._objects[scope], "#/%s/%s is not defined" % (scope, name)
return self._objects[scope][name] return self._objects[scope][name]
def getdefault(self, name, default=None, scope=None): def getdefault(self, name, default=None, scope=None):
"""Get an object from the given scope or a default value if it does not exist.
:param str name: reference name
:param default: the default value
:param str scope: reference scope
:return: the object or `default`
"""
scope = self._check_scope(scope) scope = self._check_scope(scope)
return self._objects[scope].get(name, default) return self._objects[scope].get(name, default)
def has(self, name, scope=None): def has(self, name, scope=None):
"""Check if an object exists in the given scope.
:param str name: reference name
:param str scope: reference scope
:return: True if the object exists
:rtype: bool
"""
scope = self._check_scope(scope) scope = self._check_scope(scope)
return name in self._objects[scope] return name in self._objects[scope]

View File

@ -7,8 +7,9 @@ from .codecs import OpenAPICodecJson, VALIDATORS, OpenAPICodecYaml
class _SpecRenderer(BaseRenderer): class _SpecRenderer(BaseRenderer):
"""Base class for text renderers. Handles encoding and validation."""
charset = None charset = None
validators = ['flex', 'ssv'] validators = ['ssv', 'flex']
codec_class = None codec_class = None
@classmethod @classmethod
@ -23,24 +24,28 @@ class _SpecRenderer(BaseRenderer):
class OpenAPIRenderer(_SpecRenderer): class OpenAPIRenderer(_SpecRenderer):
"""Renders the schema as a JSON document with the ``application/openapi+json`` specific mime type."""
media_type = 'application/openapi+json' media_type = 'application/openapi+json'
format = 'openapi' format = 'openapi'
codec_class = OpenAPICodecJson codec_class = OpenAPICodecJson
class SwaggerJSONRenderer(_SpecRenderer): class SwaggerJSONRenderer(_SpecRenderer):
"""Renders the schema as a JSON document with the generic ``application/json`` mime type."""
media_type = 'application/json' media_type = 'application/json'
format = '.json' format = '.json'
codec_class = OpenAPICodecJson codec_class = OpenAPICodecJson
class SwaggerYAMLRenderer(_SpecRenderer): class SwaggerYAMLRenderer(_SpecRenderer):
"""Renders the schema as a YAML document."""
media_type = 'application/yaml' media_type = 'application/yaml'
format = '.yaml' format = '.yaml'
codec_class = OpenAPICodecYaml codec_class = OpenAPICodecYaml
class _UIRenderer(BaseRenderer): class _UIRenderer(BaseRenderer):
"""Base class for web UI renderers. Handles loading an passing settings to the appropriate template."""
media_type = 'text/html' media_type = 'text/html'
charset = 'utf-8' charset = 'utf-8'
template = '' template = ''
@ -98,10 +103,16 @@ class _UIRenderer(BaseRenderer):
class SwaggerUIRenderer(_UIRenderer): class SwaggerUIRenderer(_UIRenderer):
"""Renders a swagger-ui web interface for schema browisng.
Also requires :class:`.OpenAPIRenderer` as an available renderer on the same view.
"""
template = 'drf-swagger/swagger-ui.html' template = 'drf-swagger/swagger-ui.html'
format = 'swagger' format = 'swagger'
class ReDocRenderer(_UIRenderer): class ReDocRenderer(_UIRenderer):
"""Renders a ReDoc web interface for schema browisng.
Also requires :class:`.OpenAPIRenderer` as an available renderer on the same view.
"""
template = 'drf-swagger/redoc.html' template = 'drf-swagger/redoc.html'
format = 'redoc' format = 'redoc'

View File

@ -0,0 +1,95 @@
<!-- HTML for static distribution bundle build -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Swagger UI</title>
<link href="https://fonts.googleapis.com/css?family=Open+Sans:400,700|Source+Code+Pro:300,600|Titillium+Web:400,600,700" rel="stylesheet">
<link rel="stylesheet" type="text/css" href="./swagger-ui.css" >
<link rel="icon" type="image/png" href="./favicon-32x32.png" sizes="32x32" />
<link rel="icon" type="image/png" href="./favicon-16x16.png" sizes="16x16" />
<style>
html
{
box-sizing: border-box;
overflow: -moz-scrollbars-vertical;
overflow-y: scroll;
}
*,
*:before,
*:after
{
box-sizing: inherit;
}
body {
margin:0;
background: #fafafa;
}
</style>
</head>
<body>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="position:absolute;width:0;height:0">
<defs>
<symbol viewBox="0 0 20 20" id="unlocked">
<path d="M15.8 8H14V5.6C14 2.703 12.665 1 10 1 7.334 1 6 2.703 6 5.6V6h2v-.801C8 3.754 8.797 3 10 3c1.203 0 2 .754 2 2.199V8H4c-.553 0-1 .646-1 1.199V17c0 .549.428 1.139.951 1.307l1.197.387C5.672 18.861 6.55 19 7.1 19h5.8c.549 0 1.428-.139 1.951-.307l1.196-.387c.524-.167.953-.757.953-1.306V9.199C17 8.646 16.352 8 15.8 8z"></path>
</symbol>
<symbol viewBox="0 0 20 20" id="locked">
<path d="M15.8 8H14V5.6C14 2.703 12.665 1 10 1 7.334 1 6 2.703 6 5.6V8H4c-.553 0-1 .646-1 1.199V17c0 .549.428 1.139.951 1.307l1.197.387C5.672 18.861 6.55 19 7.1 19h5.8c.549 0 1.428-.139 1.951-.307l1.196-.387c.524-.167.953-.757.953-1.306V9.199C17 8.646 16.352 8 15.8 8zM12 8H8V5.199C8 3.754 8.797 3 10 3c1.203 0 2 .754 2 2.199V8z"/>
</symbol>
<symbol viewBox="0 0 20 20" id="close">
<path d="M14.348 14.849c-.469.469-1.229.469-1.697 0L10 11.819l-2.651 3.029c-.469.469-1.229.469-1.697 0-.469-.469-.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-.469-.469-.469-1.228 0-1.697.469-.469 1.228-.469 1.697 0L10 8.183l2.651-3.031c.469-.469 1.228-.469 1.697 0 .469.469.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c.469.469.469 1.229 0 1.698z"/>
</symbol>
<symbol viewBox="0 0 20 20" id="large-arrow">
<path d="M13.25 10L6.109 2.58c-.268-.27-.268-.707 0-.979.268-.27.701-.27.969 0l7.83 7.908c.268.271.268.709 0 .979l-7.83 7.908c-.268.271-.701.27-.969 0-.268-.269-.268-.707 0-.979L13.25 10z"/>
</symbol>
<symbol viewBox="0 0 20 20" id="large-arrow-down">
<path d="M17.418 6.109c.272-.268.709-.268.979 0s.271.701 0 .969l-7.908 7.83c-.27.268-.707.268-.979 0l-7.908-7.83c-.27-.268-.27-.701 0-.969.271-.268.709-.268.979 0L10 13.25l7.418-7.141z"/>
</symbol>
<symbol viewBox="0 0 24 24" id="jump-to">
<path d="M19 7v4H5.83l3.58-3.59L8 6l-6 6 6 6 1.41-1.41L5.83 13H21V7z"/>
</symbol>
<symbol viewBox="0 0 24 24" id="expand">
<path d="M10 18h4v-2h-4v2zM3 6v2h18V6H3zm3 7h12v-2H6v2z"/>
</symbol>
</defs>
</svg>
<div id="swagger-ui"></div>
<script src="./swagger-ui-bundle.js"> </script>
<script src="./swagger-ui-standalone-preset.js"> </script>
<script>
window.onload = function() {
// Build a system
const ui = SwaggerUIBundle({
url: "http://petstore.swagger.io/v2/swagger.json",
dom_id: '#swagger-ui',
deepLinking: true,
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: "StandaloneLayout"
})
window.ui = ui
}
</script>
</body>
</html>

File diff suppressed because one or more lines are too long

View File

@ -8,17 +8,24 @@ from rest_framework.mixins import RetrieveModelMixin, DestroyModelMixin, UpdateM
from . import openapi from . import openapi
from .errors import SwaggerGenerationError from .errors import SwaggerGenerationError
#: used to forcibly remove the body of a request via :func:`.swagger_auto_schema`
no_body = object() no_body = object()
def is_list_view(path, method, view): def is_list_view(path, method, view):
"""Return True if the given path/method appears to represent a list view (as opposed to a detail/instance view).""" """Check if the given path/method appears to represent a list view (as opposed to a detail/instance view).
# for ViewSets, it could be the default 'list' view, or a list_route
:param str path: view path
:param str method: http method
:param APIView view: target view
:rtype: bool
"""
# for ViewSets, it could be the default 'list' action, or a list_route
action = getattr(view, 'action', '') action = getattr(view, 'action', '')
method = getattr(view, action, None) method = getattr(view, action, None)
detail = getattr(method, 'detail', None) detail = getattr(method, 'detail', None)
suffix = getattr(view, 'suffix', None) suffix = getattr(view, 'suffix', None)
if action == 'list' or detail is False or suffix == 'List': if action in ('list', 'create') or detail is False or suffix == 'List':
return True return True
if action in ('retrieve', 'update', 'partial_update', 'destroy') or detail is True or suffix == 'Instance': if action in ('retrieve', 'update', 'partial_update', 'destroy') or detail is True or suffix == 'Instance':
@ -34,12 +41,54 @@ def is_list_view(path, method, view):
if path_components and '{' in path_components[-1]: if path_components and '{' in path_components[-1]:
return False return False
# otherwise assume it's a list route # otherwise assume it's a list view
return True return True
def swagger_auto_schema(method=None, methods=None, auto_schema=None, request_body=None, manual_parameters=None, def swagger_auto_schema(method=None, methods=None, auto_schema=None, request_body=None, manual_parameters=None,
operation_description=None, responses=None): operation_description=None, responses=None):
"""Decorate a view method to customize the :class:`.Operation` object generated from it.
`method` and `methods` are mutually exclusive and must only be present when decorating a view method that accepts
more than one HTTP request method.
The `auto_schema` and `operation_description` arguments take precendence over view- or method-level values.
:param str method: for multi-method views, the http method the options should apply to
:param list[str] methods: for multi-method views, the http methods the options should apply to
:param .SwaggerAutoSchema auto_schema: custom class to use for generating the Operation object
:param .Schema,.SchemaRef,.Serializer request_body: custom request body, or :data:`.no_body`. The value given here
will be used as the ``schema`` property of a :class:`.Parameter` with ``in: 'body'``.
A Schema or SchemaRef is not valid if this request consumes form-data, because ``form`` and ``body`` parameters
are mutually exclusive in an :class:`.Operation`. If you need to set custom ``form`` parameters, you can use
the `manual_parameters` argument.
If a ``Serializer`` class or instance is given, it will be automatically converted into a :class:`.Schema`
used as a ``body`` :class:`.Parameter`, or into a list of ``form`` :class:`.Parameter`\ s, as appropriate.
:param list[.Parameter] manual_parameters: a list of manual parameters to override the automatically generated ones
:class:`.Parameter`\ s are identified by their (``name``, ``in``) combination, and any parameters given
here will fully override automatically generated parameters if they collide.
It is an error to supply ``form`` parameters when the request does not consume form-data.
:param str operation_description: operation description override
:param dict[str,(.Schema,.SchemaRef,.Response,str,Serializer)] responses: a dict of documented manual responses
keyed on response status code. If no success (``2xx``) response is given, one will automatically be
generated from the request body and http method. If any ``2xx`` response is given the automatic response is
suppressed.
* if a plain string is given as value, a :class:`.Response` with no body and that string as its description
will be generated
* if a :class:`.Schema`, :class:`.SchemaRef` is given, a :class:`.Response` with the schema as its body and
an empty description will be generated
* a ``Serializer`` class or instance will be converted into a :class:`.Schema` and treated as above
* a :class:`.Response` object will be used as-is; however if its ``schema`` attribute is a ``Serializer``,
it will automatically be converted into a :class:`.Schema`
"""
def decorator(view_method): def decorator(view_method):
data = { data = {
'auto_schema': auto_schema, 'auto_schema': auto_schema,
@ -98,11 +147,12 @@ def serializer_field_to_swagger(field, swagger_object_type, definitions=None, **
"""Convert a drf Serializer or Field instance into a Swagger object. """Convert a drf Serializer or Field instance into a Swagger object.
:param rest_framework.serializers.Field field: the source field :param rest_framework.serializers.Field field: the source field
:param type swagger_object_type: should be one of Schema, Parameter, Items :param type[openapi.SwaggerDict] swagger_object_type: should be one of Schema, Parameter, Items
:param drf_swagger.openapi.ReferenceResolver definitions: used to serialize Schemas by reference :param .ReferenceResolver definitions: used to serialize Schemas by reference
:param kwargs: extra attributes for constructing the object; :param kwargs: extra attributes for constructing the object;
if swagger_object_type is Parameter, `name` and `in_` should be provided if swagger_object_type is Parameter, ``name`` and ``in_`` should be provided
:return Swagger,Parameter,Items: the swagger object :return: the swagger object
:rtype: openapi.Parameter, openapi.Items, openapi.Schema
""" """
assert swagger_object_type in (openapi.Schema, openapi.Parameter, openapi.Items) assert swagger_object_type in (openapi.Schema, openapi.Parameter, openapi.Items)
assert not isinstance(field, openapi.SwaggerDict), "passed field is already a SwaggerDict object" assert not isinstance(field, openapi.SwaggerDict), "passed field is already a SwaggerDict object"
@ -241,6 +291,12 @@ def serializer_field_to_swagger(field, swagger_object_type, definitions=None, **
def find_regex(regex_field): def find_regex(regex_field):
"""Given a ``Field``, look for a ``RegexValidator`` and try to extract its pattern and return it as a string.
:param serializers.Field regex_field: the field instance
:return: the extracted pattern, or ``None``
:rtype: str
"""
regex_validator = None regex_validator = None
for validator in regex_field.validators: for validator in regex_field.validators:
if isinstance(validator, RegexValidator): if isinstance(validator, RegexValidator):

View File

@ -53,16 +53,17 @@ def get_schema_view(info, url=None, patterns=None, urlconf=None, public=False, v
""" """
Create a SchemaView class with default renderers and generators. Create a SchemaView class with default renderers and generators.
:param drf_swagger.openapi.Info info: Required. Swagger API Info object :param .Info info: Required. Swagger API Info object
:param str url: API base url; if left blank will be deduced from the location the view is served at :param str url: API base url; if left blank will be deduced from the location the view is served at
:param str patterns: passed to SchemaGenerator :param patterns: passed to SchemaGenerator
:param str urlconf: passed to SchemaGenerator :param urlconf: passed to SchemaGenerator
:param bool public: if False, includes only endpoints the current user has access to :param bool public: if False, includes only endpoints the current user has access to
:param list validators: a list of validator names to apply on the generated schema; allowed values are `flex`, `ssv` :param list validators: a list of validator names to apply; allowed values are ``flex``, ``ssv``
:param type generator_class: schema generator class to use; should be a subclass of OpenAPISchemaGenerator :param type generator_class: schema generator class to use; should be a subclass of :class:`.OpenAPISchemaGenerator`
:param tuple authentication_classes: authentication classes for the schema view itself :param tuple authentication_classes: authentication classes for the schema view itself
:param tuple permission_classes: permission classes for the schema view itself :param tuple permission_classes: permission classes for the schema view itself
:return: SchemaView class :return: SchemaView class
:rtype: type[.SchemaView]
""" """
_public = public _public = public
_generator_class = generator_class _generator_class = generator_class
@ -101,7 +102,8 @@ def get_schema_view(info, url=None, patterns=None, urlconf=None, public=False, v
cache_kwargs = cache_kwargs or {} cache_kwargs = cache_kwargs or {}
view = cls.as_view(**initkwargs) view = cls.as_view(**initkwargs)
if cache_timeout != 0: if cache_timeout != 0:
view = vary_on_headers('Cookie', 'Authorization', 'Accept')(view) if not public:
view = vary_on_headers('Cookie', 'Authorization')(view)
view = cache_page(cache_timeout, **cache_kwargs)(view) view = cache_page(cache_timeout, **cache_kwargs)(view)
view = deferred_never_cache(view) # disable in-browser caching view = deferred_never_cache(view) # disable in-browser caching
elif cache_kwargs: elif cache_kwargs:
@ -126,7 +128,7 @@ def get_schema_view(info, url=None, patterns=None, urlconf=None, public=False, v
Instantiate this view with a Web UI renderer, optionally wrapped with cache_page. Instantiate this view with a Web UI renderer, optionally wrapped with cache_page.
See https://docs.djangoproject.com/en/1.11/topics/cache/. See https://docs.djangoproject.com/en/1.11/topics/cache/.
:param str renderer: UI renderer; allowed values are `swagger`, `redoc` :param str renderer: UI renderer; allowed values are ``swagger``, ``redoc``
:param int cache_timeout: same as cache_page; set to 0 for no cache :param int cache_timeout: same as cache_page; set to 0 for no cache
:param dict cache_kwargs: dictionary of kwargs to be passed to cache_page :param dict cache_kwargs: dictionary of kwargs to be passed to cache_page
:return: a view instance :return: a view instance

11
tox.ini
View File

@ -3,7 +3,7 @@ envlist =
py27-drf37, py27-drf37,
py{34,35,36,37}-drf37, py{34,35,36,37}-drf37,
py36-drfmaster, py36-drfmaster,
flake8 flake8, docs
[travis:env] [travis:env]
DRF = DRF =
@ -39,9 +39,16 @@ pip_pre = True
skip_install = true skip_install = true
deps = deps =
flake8 flake8
commands= commands =
flake8 src/drf_swagger testproj tests setup.py flake8 src/drf_swagger testproj tests setup.py
[testenv:docs]
deps =
-rrequirements/docs.txt
commands =
python setup.py check --restructuredtext --metadata --strict
sphinx-build -WnEa -b html docs docs\_build\html
[pytest] [pytest]
DJANGO_SETTINGS_MODULE = testproj.settings DJANGO_SETTINGS_MODULE = testproj.settings
python_paths = testproj python_paths = testproj