oggit/packages/flask-restx/opengnsys-flask-restx-1.3.0/doc/swagger.rst

1071 lines
28 KiB
ReStructuredText

.. _swagger:
Swagger documentation
=====================
.. currentmodule:: flask_restx
Swagger API documentation is automatically generated and available from your API's root URL. You can configure the documentation using the :meth:`@api.doc() <Api.doc>` decorator.
Documenting with the ``@api.doc()`` decorator
---------------------------------------------
The ``api.doc()`` decorator allows you to include additional information in the documentation.
You can document a class or a method:
.. code-block:: python
@api.route('/my-resource/<id>', endpoint='my-resource')
@api.doc(params={'id': 'An ID'})
class MyResource(Resource):
def get(self, id):
return {}
@api.doc(responses={403: 'Not Authorized'})
def post(self, id):
api.abort(403)
Automatically documented models
-------------------------------
All models instantiated with :meth:`~Namespace.model`, :meth:`~Namespace.clone` and :meth:`~Namespace.inherit`
will be automatically documented in your Swagger specifications.
The :meth:`~Namespace.inherit` method will register both the parent and the child in the Swagger models definitions:
.. code-block:: python
parent = api.model('Parent', {
'name': fields.String,
'class': fields.String(discriminator=True)
})
child = api.inherit('Child', parent, {
'extra': fields.String
})
The above configuration will produce these Swagger definitions:
.. code-block:: json
{
"Parent": {
"properties": {
"name": {"type": "string"},
"class": {"type": "string"}
},
"discriminator": "class",
"required": ["class"]
},
"Child": {
"allOf": [
{
"$ref": "#/definitions/Parent"
}, {
"properties": {
"extra": {"type": "string"}
}
}
]
}
}
The ``@api.marshal_with()`` decorator
-------------------------------------
This decorator works like the raw :func:`marshal_with` decorator
with the difference that it documents the methods.
The optional parameter ``code`` allows you to specify the expected HTTP status code (200 by default).
The optional parameter ``as_list`` allows you to specify whether or not the objects are returned as a list.
.. code-block:: python
resource_fields = api.model('Resource', {
'name': fields.String,
})
@api.route('/my-resource/<id>', endpoint='my-resource')
class MyResource(Resource):
@api.marshal_with(resource_fields, as_list=True)
def get(self):
return get_objects()
@api.marshal_with(resource_fields, code=201)
def post(self):
return create_object(), 201
The :meth:`Api.marshal_list_with` decorator is strictly equivalent to :meth:`Api.marshal_with(fields, as_list=True)`.
.. code-block:: python
resource_fields = api.model('Resource', {
'name': fields.String,
})
@api.route('/my-resource/<id>', endpoint='my-resource')
class MyResource(Resource):
@api.marshal_list_with(resource_fields)
def get(self):
return get_objects()
@api.marshal_with(resource_fields)
def post(self):
return create_object()
The ``@api.expect()`` decorator
-------------------------------
The ``@api.expect()`` decorator allows you to specify the expected input fields.
It accepts an optional boolean parameter ``validate`` indicating whether the payload should be validated.
The validation behavior can be customized globally either
by setting the ``RESTX_VALIDATE`` configuration to ``True``
or passing ``validate=True`` to the API constructor.
The following examples are equivalent:
* Using the ``@api.expect()`` decorator:
.. code-block:: python
resource_fields = api.model('Resource', {
'name': fields.String,
})
@api.route('/my-resource/<id>')
class MyResource(Resource):
@api.expect(resource_fields)
def get(self):
pass
* Using the ``api.doc()`` decorator:
.. code-block:: python
resource_fields = api.model('Resource', {
'name': fields.String,
})
@api.route('/my-resource/<id>')
class MyResource(Resource):
@api.doc(body=resource_fields)
def get(self):
pass
You can specify lists as the expected input:
.. code-block:: python
resource_fields = api.model('Resource', {
'name': fields.String,
})
@api.route('/my-resource/<id>')
class MyResource(Resource):
@api.expect([resource_fields])
def get(self):
pass
You can use :exc:`~flask_restx.reqparse.RequestParser` to define the expected input:
.. code-block:: python
parser = api.parser()
parser.add_argument('param', type=int, help='Some param', location='form')
parser.add_argument('in_files', type=FileStorage, location='files')
@api.route('/with-parser/', endpoint='with-parser')
class WithParserResource(restx.Resource):
@api.expect(parser)
def get(self):
return {}
Validation can be enabled or disabled on a particular endpoint:
.. code-block:: python
resource_fields = api.model('Resource', {
'name': fields.String,
})
@api.route('/my-resource/<id>')
class MyResource(Resource):
# Payload validation disabled
@api.expect(resource_fields)
def post(self):
pass
# Payload validation enabled
@api.expect(resource_fields, validate=True)
def post(self):
pass
An example of application-wide validation by config:
.. code-block:: python
app.config['RESTX_VALIDATE'] = True
api = Api(app)
resource_fields = api.model('Resource', {
'name': fields.String,
})
@api.route('/my-resource/<id>')
class MyResource(Resource):
# Payload validation enabled
@api.expect(resource_fields)
def post(self):
pass
# Payload validation disabled
@api.expect(resource_fields, validate=False)
def post(self):
pass
An example of application-wide validation by constructor:
.. code-block:: python
api = Api(app, validate=True)
resource_fields = api.model('Resource', {
'name': fields.String,
})
@api.route('/my-resource/<id>')
class MyResource(Resource):
# Payload validation enabled
@api.expect(resource_fields)
def post(self):
pass
# Payload validation disabled
@api.expect(resource_fields, validate=False)
def post(self):
pass
Documenting with the ``@api.response()`` decorator
--------------------------------------------------
The ``@api.response()`` decorator allows you to document the known responses
and is a shortcut for ``@api.doc(responses='...')``.
The following two definitions are equivalent:
.. code-block:: python
@api.route('/my-resource/')
class MyResource(Resource):
@api.response(200, 'Success')
@api.response(400, 'Validation Error')
def get(self):
pass
@api.route('/my-resource/')
class MyResource(Resource):
@api.doc(responses={
200: 'Success',
400: 'Validation Error'
})
def get(self):
pass
You can optionally specify a response model as the third argument:
.. code-block:: python
model = api.model('Model', {
'name': fields.String,
})
@api.route('/my-resource/')
class MyResource(Resource):
@api.response(200, 'Success', model)
def get(self):
pass
The ``@api.marshal_with()`` decorator automatically documents the response:
.. code-block:: python
model = api.model('Model', {
'name': fields.String,
})
@api.route('/my-resource/')
class MyResource(Resource):
@api.response(400, 'Validation error')
@api.marshal_with(model, code=201, description='Object created')
def post(self):
pass
You can specify a default response sent without knowing the response code:
.. code-block:: python
@api.route('/my-resource/')
class MyResource(Resource):
@api.response('default', 'Error')
def get(self):
pass
The ``@api.route()`` decorator
------------------------------
You can provide class-wide documentation using the ``doc`` parameter of ``Api.route()``. This parameter accepts the same values as the ``Api.doc()`` decorator.
For example, these two declarations are equivalent:
* Using ``@api.doc()``:
.. code-block:: python
@api.route('/my-resource/<id>', endpoint='my-resource')
@api.doc(params={'id': 'An ID'})
class MyResource(Resource):
def get(self, id):
return {}
* Using ``@api.route()``:
.. code-block:: python
@api.route('/my-resource/<id>', endpoint='my-resource', doc={'params':{'id': 'An ID'}})
class MyResource(Resource):
def get(self, id):
return {}
Multiple Routes per Resource
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Multiple ``Api.route()`` decorators can be used to add multiple routes for a ``Resource``.
The ``doc`` parameter provides documentation **per route**.
For example, here the ``description`` is applied only to the ``/also-my-resource/<id>`` route:
.. code-block:: python
@api.route("/my-resource/<id>")
@api.route(
"/also-my-resource/<id>",
doc={"description": "Alias for /my-resource/<id>"},
)
class MyResource(Resource):
def get(self, id):
return {}
Here, the ``/also-my-resource/<id>`` route is marked as deprecated:
.. code-block:: python
@api.route("/my-resource/<id>")
@api.route(
"/also-my-resource/<id>",
doc={
"description": "Alias for /my-resource/<id>, this route is being phased out in V2",
"deprecated": True,
},
)
class MyResource(Resource):
def get(self, id):
return {}
Documentation applied to the ``Resource`` using ``Api.doc()`` is `shared` amongst all
routes unless explicitly overridden:
.. code-block:: python
@api.route("/my-resource/<id>")
@api.route(
"/also-my-resource/<id>",
doc={"description": "Alias for /my-resource/<id>"},
)
@api.doc(params={"id": "An ID", description="My resource"})
class MyResource(Resource):
def get(self, id):
return {}
Here, the ``id`` documentation from the ``@api.doc()`` decorator is present in both routes,
``/my-resource/<id>`` inherits the ``My resource`` description from the ``@api.doc()``
decorator and ``/also-my-resource/<id>`` overrides the description with ``Alias for /my-resource/<id>``.
Routes with a ``doc`` parameter are given a `unique` Swagger ``operationId``. Routes without
``doc`` parameter have the same Swagger ``operationId`` as they are deemed the same operation.
Documenting the fields
----------------------
Every Flask-RESTX field accepts optional arguments used to document the field:
- ``required``: a boolean indicating if the field is always set (*default*: ``False``)
- ``description``: some details about the field (*default*: ``None``)
- ``example``: an example to use when displaying (*default*: ``None``)
There are also field-specific attributes:
* The ``String`` field accepts the following optional arguments:
- ``enum``: an array restricting the authorized values.
- ``min_length``: the minimum length expected.
- ``max_length``: the maximum length expected.
- ``pattern``: a RegExp pattern used to validate the string.
* The ``Integer``, ``Float`` and ``Arbitrary`` fields accept the following optional arguments:
- ``min``: restrict the minimum accepted value.
- ``max``: restrict the maximum accepted value.
- ``exclusiveMin``: if ``True``, minimum value is not in allowed interval.
- ``exclusiveMax``: if ``True``, maximum value is not in allowed interval.
- ``multiple``: specify that the number must be a multiple of this value.
* The ``DateTime`` field accepts the ``min``, ``max``, ``exclusiveMin`` and ``exclusiveMax`` optional arguments. These should be dates or datetimes (either ISO strings or native objects).
.. code-block:: python
my_fields = api.model('MyModel', {
'name': fields.String(description='The name', required=True),
'type': fields.String(description='The object type', enum=['A', 'B']),
'age': fields.Integer(min=0),
})
Documenting the methods
-----------------------
Each resource will be documented as a Swagger path.
Each resource method (``get``, ``post``, ``put``, ``delete``, ``path``, ``options``, ``head``)
will be documented as a Swagger operation.
You can specify a unique Swagger ``operationId`` with the ``id`` keyword argument:
.. code-block:: python
@api.route('/my-resource/')
class MyResource(Resource):
@api.doc(id='get_something')
def get(self):
return {}
You can also use the first argument for the same purpose:
.. code-block:: python
@api.route('/my-resource/')
class MyResource(Resource):
@api.doc('get_something')
def get(self):
return {}
If not specified, a default ``operationId`` is provided with the following pattern::
{{verb}}_{{resource class name | camelCase2dashes }}
In the previous example, the default generated ``operationId`` would be ``get_my_resource``.
You can override the default ``operationId`` generator by providing a callable for the ``default_id`` parameter.
This callable accepts two positional arguments:
* The resource class name
* The HTTP method (lower-case)
.. code-block:: python
def default_id(resource, method):
return ''.join((method, resource))
api = Api(app, default_id=default_id)
In the previous example, the generated ``operationId`` would be ``getMyResource``.
Each operation will automatically receive the namespace tag.
If the resource is attached to the root API, it will receive the default namespace tag.
Method parameters
~~~~~~~~~~~~~~~~~
Parameters from the URL path are documented automatically.
You can provide additional information using the ``params`` keyword argument of the ``api.doc()`` decorator:
.. code-block:: python
@api.route('/my-resource/<id>', endpoint='my-resource')
@api.doc(params={'id': 'An ID'})
class MyResource(Resource):
pass
or by using the ``api.param`` shortcut decorator:
.. code-block:: python
@api.route('/my-resource/<id>', endpoint='my-resource')
@api.param('id', 'An ID')
class MyResource(Resource):
pass
Input and output models
~~~~~~~~~~~~~~~~~~~~~~~
You can specify the serialized output model using the ``model`` keyword argument of the ``api.doc()`` decorator.
For ``POST`` and ``PUT`` methods, use the ``body`` keyword argument to specify the input model.
.. code-block:: python
my_model = api.model('MyModel', {
'name': fields.String(description='The name', required=True),
'type': fields.String(description='The object type', enum=['A', 'B']),
'age': fields.Integer(min=0),
})
class Person(fields.Raw):
def format(self, value):
return {'name': value.name, 'age': value.age}
@api.route('/my-resource/<id>', endpoint='my-resource')
@api.doc(params={'id': 'An ID'})
class MyResource(Resource):
@api.doc(model=my_model)
def get(self, id):
return {}
@api.doc(model=my_model, body=Person)
def post(self, id):
return {}
If both ``body`` and ``formData`` parameters are used, a :exc:`~flask_restx.errors.SpecsError` will be raised.
Models can also be specified with a :class:`~flask_restx.reqparse.RequestParser`.
.. code-block:: python
parser = api.parser()
parser.add_argument('param', type=int, help='Some param', location='form')
parser.add_argument('in_files', type=FileStorage, location='files')
@api.route('/with-parser/', endpoint='with-parser')
class WithParserResource(restx.Resource):
@api.expect(parser)
def get(self):
return {}
.. note:: The decoded payload will be available as a dictionary in the payload attribute
in the request context.
.. code-block:: python
@api.route('/my-resource/')
class MyResource(Resource):
def get(self):
data = api.payload
.. note::
Using :class:`~flask_restx.reqparse.RequestParser` is preferred over the ``api.param()`` decorator
to document form fields as it also perform validation.
Headers
~~~~~~~
You can document response headers with the ``@api.header()`` decorator shortcut.
.. code-block:: python
@api.route('/with-headers/')
@api.header('X-Header', 'Some class header')
class WithHeaderResource(restx.Resource):
@api.header('X-Collection', type=[str], collectionType='csv')
def get(self):
pass
If you need to specify an header that appear only on a given response,
just use the `@api.response` `headers` parameter.
.. code-block:: python
@api.route('/response-headers/')
class WithHeaderResource(restx.Resource):
@api.response(200, 'Success', headers={'X-Header': 'Some header'})
def get(self):
pass
Documenting expected/request headers is done through the `@api.expect` decorator
.. code-block:: python
parser = api.parser()
parser.add_argument('Some-Header', location='headers')
@api.route('/expect-headers/')
@api.expect(parser)
class ExpectHeaderResource(restx.Resource):
def get(self):
pass
Cascading
---------
Method documentation takes precedence over class documentation,
and inherited documentation takes precedence over parent documentation.
For example, these two declarations are equivalent:
* Class documentation is inherited by methods:
.. code-block:: python
@api.route('/my-resource/<id>', endpoint='my-resource')
@api.params('id', 'An ID')
class MyResource(Resource):
def get(self, id):
return {}
* Class documentation is overridden by method-specific documentation:
.. code-block:: python
@api.route('/my-resource/<id>', endpoint='my-resource')
@api.param('id', 'Class-wide description')
class MyResource(Resource):
@api.param('id', 'An ID')
def get(self, id):
return {}
You can also provide method-specific documentation from a class decorator.
The following example will produce the same documentation as the two previous examples:
.. code-block:: python
@api.route('/my-resource/<id>', endpoint='my-resource')
@api.params('id', 'Class-wide description')
@api.doc(get={'params': {'id': 'An ID'}})
class MyResource(Resource):
def get(self, id):
return {}
Marking as deprecated
---------------------
You can mark resources or methods as deprecated with the ``@api.deprecated`` decorator:
.. code-block:: python
# Deprecate the full resource
@api.deprecated
@api.route('/resource1/')
class Resource1(Resource):
def get(self):
return {}
# Deprecate methods
@api.route('/resource4/')
class Resource4(Resource):
def get(self):
return {}
@api.deprecated
def post(self):
return {}
def put(self):
return {}
Hiding from documentation
-------------------------
You can hide some resources or methods from documentation using any of the following:
.. code-block:: python
# Hide the full resource
@api.route('/resource1/', doc=False)
class Resource1(Resource):
def get(self):
return {}
@api.route('/resource2/')
@api.doc(False)
class Resource2(Resource):
def get(self):
return {}
@api.route('/resource3/')
@api.hide
class Resource3(Resource):
def get(self):
return {}
# Hide methods
@api.route('/resource4/')
@api.doc(delete=False)
class Resource4(Resource):
def get(self):
return {}
@api.doc(False)
def post(self):
return {}
@api.hide
def put(self):
return {}
def delete(self):
return {}
.. note::
Namespace tags without attached resources will be hidden automatically from the documentation.
Documenting authorizations
--------------------------
You can use the ``authorizations`` keyword argument to document authorization information.
See `Swagger Authentication documentation <https://swagger.io/docs/specification/2-0/authentication/>`_
for configuration details.
- ``authorizations`` is a Python dictionary representation of the Swagger ``securityDefinitions`` configuration.
.. code-block:: python
authorizations = {
'apikey': {
'type': 'apiKey',
'in': 'header',
'name': 'X-API-KEY'
}
}
api = Api(app, authorizations=authorizations)
Then decorate each resource and method that requires authorization:
.. code-block:: python
@api.route('/resource/')
class Resource1(Resource):
@api.doc(security='apikey')
def get(self):
pass
@api.doc(security='apikey')
def post(self):
pass
You can apply this requirement globally with the ``security`` parameter on the ``Api`` constructor:
.. code-block:: python
authorizations = {
'apikey': {
'type': 'apiKey',
'in': 'header',
'name': 'X-API-KEY'
}
}
api = Api(app, authorizations=authorizations, security='apikey')
You can have multiple security schemes:
.. code-block:: python
authorizations = {
'apikey': {
'type': 'apiKey',
'in': 'header',
'name': 'X-API'
},
'oauth2': {
'type': 'oauth2',
'flow': 'accessCode',
'tokenUrl': 'https://somewhere.com/token',
'authorizationUrl': 'https://somewhere.com/auth',
'scopes': {
'read': 'Grant read-only access',
'write': 'Grant read-write access',
}
}
}
api = Api(self.app, security=['apikey', {'oauth2': 'read'}], authorizations=authorizations)
Security schemes can be overridden for a particular method:
.. code-block:: python
@api.route('/authorizations/')
class Authorized(Resource):
@api.doc(security=[{'oauth2': ['read', 'write']}])
def get(self):
return {}
You can disable security on a given resource or method by passing ``None`` or an empty list as the ``security`` parameter:
.. code-block:: python
@api.route('/without-authorization/')
class WithoutAuthorization(Resource):
@api.doc(security=[])
def get(self):
return {}
@api.doc(security=None)
def post(self):
return {}
Expose vendor Extensions
------------------------
Swaggers allows you to expose custom `vendor extensions`_ and you can use them
in Flask-RESTX with the `@api.vendor` decorator.
It supports both extensions as `dict` or `kwargs` and perform automatique `x-` prefix:
.. code-block:: python
@api.route('/vendor/')
@api.vendor(extension1='any authorized value')
class Vendor(Resource):
@api.vendor({
'extension-1': {'works': 'with complex values'},
'x-extension-3': 'x- prefix is optional',
})
def get(self):
return {}
Export Swagger specifications
-----------------------------
You can export the Swagger specifications for your API:
.. code-block:: python
from flask import json
from myapp import api
print(json.dumps(api.__schema__))
.. _swaggerui:
Swagger UI
----------
By default ``flask-restx`` provides Swagger UI documentation, served from the root URL of the API.
.. code-block:: python
from flask import Flask
from flask_restx import Api, Resource, fields
app = Flask(__name__)
api = Api(app, version='1.0', title='Sample API',
description='A sample API',
)
@api.route('/my-resource/<id>')
@api.doc(params={'id': 'An ID'})
class MyResource(Resource):
def get(self, id):
return {}
@api.response(403, 'Not Authorized')
def post(self, id):
api.abort(403)
if __name__ == '__main__':
app.run(debug=True)
If you run the code below and visit your API's root URL (http://localhost:5000)
you can view the automatically-generated Swagger UI documentation.
.. image:: _static/screenshot-apidoc-quickstart.png
Customization
~~~~~~~~~~~~~
You can control the Swagger UI path with the ``doc`` parameter (defaults to the API root):
.. code-block:: python
from flask import Flask, Blueprint
from flask_restx import Api
app = Flask(__name__)
blueprint = Blueprint('api', __name__, url_prefix='/api')
api = Api(blueprint, doc='/doc/')
app.register_blueprint(blueprint)
assert url_for('api.doc') == '/api/doc/'
You can specify a custom validator URL by setting ``config.SWAGGER_VALIDATOR_URL``:
.. code-block:: python
from flask import Flask
from flask_restx import Api
app = Flask(__name__)
app.config.SWAGGER_VALIDATOR_URL = 'http://domain.com/validator'
api = Api(app)
You can enable [OAuth2 Implicit Flow](https://oauth.net/2/grant-types/implicit/) for retrieving an
authorization token for testing api endpoints interactively within Swagger UI.
The ``config.SWAGGER_UI_OAUTH_CLIENT_ID`` and ``authorizationUrl`` and ``scopes``
will be specific to your OAuth2 IDP configuration.
The realm string is added as a query parameter to authorizationUrl and tokenUrl.
These values are all public knowledge. No *client secret* is specified here.
.. Using PKCE instead of Implicit Flow depends on https://github.com/swagger-api/swagger-ui/issues/5348
.. code-block:: python
from flask import Flask
from flask_restx import Api
app = Flask(__name__)
app.config.SWAGGER_UI_OAUTH_CLIENT_ID = 'MyClientId'
app.config.SWAGGER_UI_OAUTH_REALM = '-'
app.config.SWAGGER_UI_OAUTH_APP_NAME = 'Demo'
api = Api(
app,
title=app.config.SWAGGER_UI_OAUTH_APP_NAME,
security={'OAuth2': ['read', 'write']},
authorizations={
'OAuth2': {
'type': 'oauth2',
'flow': 'implicit',
'authorizationUrl': 'https://idp.example.com/authorize?audience=https://app.example.com',
'clientId': app.config.SWAGGER_UI_OAUTH_CLIENT_ID,
'scopes': {
'openid': 'Get ID token',
'profile': 'Get identity',
}
}
}
)
You can also specify the initial expansion state with the ``config.SWAGGER_UI_DOC_EXPANSION``
setting (``'none'``, ``'list'`` or ``'full'``):
.. code-block:: python
from flask import Flask
from flask_restx import Api
app = Flask(__name__)
app.config.SWAGGER_UI_DOC_EXPANSION = 'list'
api = Api(app)
By default, operation ID is hidden as well as request duration, you can enable them respectively with:
.. code-block:: python
from flask import Flask
from flask_restx import Api
app = Flask(__name__)
app.config.SWAGGER_UI_OPERATION_ID = True
app.config.SWAGGER_UI_REQUEST_DURATION = True
api = Api(app)
If you need a custom UI,
you can register a custom view function with the :meth:`~Api.documentation` decorator:
.. code-block:: python
from flask import Flask
from flask_restx import Api, apidoc
app = Flask(__name__)
api = Api(app)
@api.documentation
def custom_ui():
return apidoc.ui_for(api)
Configuring "Try it Out"
~~~~~~~~~~~~~~~~~~~~~~~~~
By default, all paths and methods have a "Try it Out" button for performing API requests in the browser.
These can be disable **per method** with the ``SWAGGER_SUPPORTED_SUBMIT_METHODS`` configuration option,
supporting the same values as the ``supportedSubmitMethods`` `Swagger UI parameter <https://github.com/swagger-api/swagger-ui/blob/master/docs/usage/configuration.md#network/>`_.
.. code-block:: python
from flask import Flask
from flask_restx import Api
app = Flask(__name__)
# disable Try it Out for all methods
app.config.SWAGGER_SUPPORTED_SUBMIT_METHODS = []
# enable Try it Out for specific methods
app.config.SWAGGER_SUPPORTED_SUBMIT_METHODS = ["get", "post"]
api = Api(app)
Disabling the documentation
~~~~~~~~~~~~~~~~~~~~~~~~~~~
To disable Swagger UI entirely, set ``doc=False``:
.. code-block:: python
from flask import Flask
from flask_restx import Api
app = Flask(__name__)
api = Api(app, doc=False)
.. _vendor extensions: https://swagger.io/specification/#specification-extensions