OpenAPI (originally known as the
Swagger Specification) is a popular description specification for REST API. APIFlask
has built-in support for it. This chapter will cover the basic usage of OpenAPI generating
in APIFlask.
Code-first or Design-first
There are two approaches when working with OpenAPI: Code-first and Design-first.
APIFlask currently only supports the first way. It generates the OpenAPI spec
for you after you write the code. We will try to support the Design-first
approach after the 1.0 version is released.
APIFlask collects the information from the configuration values, registered routes, and
the information you passed through decorators, then generates the OpenAPI spec based
on these information.
It provides three ways to obtain the spec document file:
An app.spec attribute that returns the dict spec.
A spec endpoint that serves the spec.
A flask spec command to output the spec to stdout or file.
Besides, it also provides an app.spec_processor decorator, which you can use to register
a spec process function to update the spec before it returns. See
Register a spec processor for more details.
The default format of the OpenAPI spec is JSON, while YAML is also supported.
If you want to enable the YAML support, install APIFlask with the yaml extra
(it will install PyYAML):
$ pip install "apiflask[yaml]"
Now you can change the format via the SPEC_FORMAT config:
When you view the spec from your browser via /openapi.json, if you enabled the
debug mode or set the configuration variable JSONIFY_PRETTYPRINT_REGULAR to
True, the indentation will set to 2. Otherwise, the JSON spec will be sent
without indentation and spaces to save the bandwidth and speed the request.
The indentation of the local spec file is enabled by default. The default indentation
is the default value of the LOCAL_SPEC_JSON_INDENT config (i.e., 2). When you
use the flask spec command, you can change the indentation with the --indent
or -i option.
The indentation of the YAML spec is always 2, and it can't be changed for now.
By default, the spec is in JSON format and available at the URL path /openapi.json,
you can change the URL rule of the spec endpoint with the spec_path parameter:
Then the spec will be available at http://localhost:5000/spec.
Tip
You can configure the MIME type of the spec response with the configuration
variable YAML_SPEC_MIMETYPE and JSON_SPEC_MIMETYPE, see details in the
configuration docs.
If you provide a path with the --output or -o option, APIFlask will write
the spec to the given path:
$ flask spec --output openapi.json
No such file or directory?
If the given path does not exist, you have to create the directory by yourself,
then APIFlask will create the file for you.
You can also set the path with the configuration variable LOCAL_SPEC_PATH, then the
value will be used in flask spec command when the --output/-o option is not passed:
Similarly, the spec format can be set with the --format or -f option
(defaults to json):
$ flask spec --format json
You can also set the format with the configuration variable SPEC_FORMAT (defaults
to 'json'), then the value will be used in flask spec command when the
--format/-f option is not passed:
For the local spec file, the indentation is always needed for readability and
easy to trace the changes. The indentation can be set with the --indent or
-i option:
$ flask spec --indent 4
You can also set the indentation with the configuration variable
LOCAL_SPEC_JSON_INDENT (defaults to 2), then the value will be used in
the flask spec command when the --indent/-i option is not passed:
With the flask spec command, you can easily generate the spec to a local file.
While it will be handy if the spec file is in sync with the project code.
To achieve this, you need to set a path to the config LOCAL_SPEC_PATH,
then enable the sync by setting the config SYNC_LOCAL_SPEC to True:
If the path you passed is relative, do not put a leading slash in it.
APIFlask will create the file at your current working directory (where you execute the
flask run command). We recommend using an absolute path. For example, you can use
app.root_path, which stores the absolute root path to your app module:
You can also use
app.instance_path,
it will be useful if your app is inside a package since it returns the path to
the instance folder located at the project root path.
You can also find the project root path manually based on the current module's
__file__ variable when you are using an application factory. In this case,
you normally put the config into a file called config.py located at the
project root path:
frompathlibimportPathbase_path=Path(__file__).parent# you may need to use the following if your config module is# inside the application package:# base_path = Path(__file__).parent.parentSYNC_LOCAL_SPEC=TrueLOCAL_SPEC_PATH=base_path/'openapi.json'
Or use the os module:
importosbase_path=os.path.dirname(__file__)# you may need to use the following if your config module is# inside the application package:# base_path = os.path.dirname(os.path.dirname(__file__))SYNC_LOCAL_SPEC=TrueLOCAL_SPEC_PATH=os.path.join(base_path,'openapi.json')
By default, the tag object is generated automatically based on the blueprints:
A blueprint generates a tag, the name of the blueprint in title form will become
the name of the tag.
All routes under the blueprint will be tagged with the corresponding tag automatically.
If you want to use a custom tag name for a blueprint or want to add more details for
the tag, you can use the APIBlueprint(tag=...) parameter to pass a new name:
If you don't like this blueprint-based tagging system, surely you can do it manually.
You can pass a list of tag names to the configuration variable TAGS:
app.config['TAGS']=['foo','bar','baz']
It also accepts a list of dicts if you want to add details about tags:
app.config['TAGS']=[{'name':'foo','description':'The description of foo'},{'name':'bar','description':'The description of bar'},{'name':'baz','description':'The description of baz'}]
Tip
The app.tags attribute is equals to the configuration variable TAGS, so you
can also use:
app.tags=['foo','bar','baz']
When the TAGS is set, you can now add tags for each route (OpenAPI operation) with
the doc decorator, see Operation tags
Most of the information in path and operation object is generated from
your view functions or view classes automatically, while you may want to change some of them.
By default, APIFlask will use the name of the view function as the operation summary.
If your view function is named with get_pet, then the summary will be "Get Pet".
If the view function has docstring, then the first line of the docstring will be used
as the summary, the lines after the empty line of the docstring will be used as
the description.
The precedence of summary setting
@app.doc(summary='blah') > the first line of docstring > the view function name
Here is an example of set summary and description with docstring:
@app.get('/hello')defhello():"""Say hello Some description for the /hello """return'Hello'
However, most of these fields will be generated when you set up the schema field.
For example, if you set required to True, pass a Length(0, 10) validator
to validate:
fromapiflaskimportSchemafromapiflask.fieldsimportStringclassPetIn(Schema):name=String(required=True,validate=Length(0,10),metatdata={'description':'The name of the pet.'})
Then in the final spec, the type, maxLength, minLength and required field will have
the right value:
"PetIn":{"properties":{"name":{"description":"The name of the pet.","maxLength":10,"minLength":0,"type":"string"}},"required":["name"],"type":"object"}
Normally, you only need to set the following fields manually with the metadata dict:
description: Some description for this field.
title: The title of the field.
example: A example value for this field (property-level example).
deprecated: If true, indicates this field is deprecated.
externalDocs: A link points to the external documentation for this field.
xml: Adds additional metadata to describe the XML representation format of this field.
See details in
OpenAPI XML object.
Tip
If the schema class' name ends with Schema, then it will be stripped in the spec.
When rendering the spec in the API documentation, the docs tool will generate a default
example for you. If you want to add a custom example, you can use the example parameter to pass a dict as the response example in the input/output decorator:
For multiple examples, use the examples parameter and pass a dict of dict, every
example dict maps a unique name:
fromapiflaskimportAPIFlaskapp=APIFlask(__name__)examples={'example foo':{'summary':'an example of foo','value':{'name':'foo','category':'cat','id':1}},'example bar':{'summary':'an example of bar','value':{'name':'bar','category':'dog','id':2}},}@app.get('/pets')@app.output(PetOut,examples=examples)defget_pets():pass
Note
Currently, the example/examples parameter in the input decorator is only
support JSON body. When you need to set a custom example for query data,
you can set the field example (property-level example) in the data schema:
For request, the content type is set automatically based on the input location:
json: application/json
form: application/x-www-form-urlencoded
files, form_and_files: multipart/form-data
For response, the default content type is application/json. You can set a custom content type with the
content_type parameter (APIFlask >= 1.3.0) in the output() decorator:
Here is the example of using the doc decorator to set summary and description:
fromapiflaskimportAPIFlaskapp=APIFlask(__name__)@app.get('/hello')@app.doc(summary='Say hello',description='Some description for the /hello')defhello():return'Hello'
When you are using blueprints in your application, APIFlask provides an automatic tagging system,
see Tags for more details.
You only need to set the tag if you are not using a blueprint or you want to control the tags by
yourself. The tags parameter accepts a list of tag name string, they should match the values you
passed in TAGS config or app.tags attribute:
As described above, APIFlask will add some responses based on the decorators you added
on the view function (200, 400, 401, 404). Sometimes you may want to add alternative
responses the view function will return, then you can use the @app.doc(responses=...)
parameter:
The responses parameter accepts the following values:
A list of status code int, for example, [404, 418].
A dict in a format of {<STATUS_CODE>: <DESCRIPTION>}, this will allow you to
set a custom description for each status, for example,
{404: 'Not Found', 418: 'Blah...'}. If a response with the same status code is
already exist, the existing description will be overwritten.
A dict in a format of {<STATUS_CODE>: {RESPONSE}}, where the {RESPONSE} is
a complete spec dict for the response. For example:
You can add additional media types for a response and these will be added to
the OpenAPI document. This allows you to describe situations where you may
return a different representation of a resource, perhaps by performing
content negotiation based on some parameter of the
request. This is done by supplying custom responses and including
additional media types in the content section of each response, as
in the following:
@app.get("/pets/<int:pet_id>")@app.input(Accept,location="headers")@app.output(PetOut)@app.doc(responses={200:{'description':'Return the resource in either JSON or HTML','content':{'text/html':{}}}})defget_pet(pet_id,headers_data):pet=pets[pet_id]if"html"inheaders_data.get('accept'):result=render_template('pets/pet-detail.j2.html',pet=pet)else:result=petreturnresult
The previous snippet shows how you could inspect the request's Accept
header and then return either an HTML representation or a JSON representation
of the same resource.
Note that APIFlask thus allows you to complement the default application/json
media type, which is automatically added by APIFlask for views that include the
@app.output decorator with additional media types.
APIFlask supports to generate operationId automatically. The auto-generating behavior is disabled
as default, you can enable it by setting the following configuration variable to True:
app.config['AUTO_OPERATION_ID']=True
The auto-operationId will in the format of {HTTP method}_{endpoint of the view} (e.g. get_hello).
If you want to apply an authentication or custom handling logic
to the OpenAPI endpoints, you can use the SPEC_DECORATORS,
DOCS_DECORATORS and SWAGGER_UI_OAUTH_REDIRECT_DECORATORS configuration options:
If you want to disable the whole OpenAPI support for the whole application, you
can set enable_openapi parameter to False when creating the APIFlask instance:
By default, APIFlask will add a view function into API documentations
(and OpenAPI spec) even if the view function doesn't use input, output,
and doc decorator. If you want to disable this behavior, set configruration
variable AUTO_200_RESPONSE to False:
You can register a function with the app.spec_processor decorator to update the
spec. The callback function should accept the spec as an argument and return it
in the end. The callback function will be called when generating the spec file.