Swagger Generation¶
Changed in 2.0: Deprecated functions that supported attaching a “converter function” for a custom authenticator to a generator were removed. We now only support a “registry of converters” approach (consistent with approaches used elsewhere in Flask-rebar)
Swagger Endpoints¶
Flask-Rebar aims to make Swagger generation and documentation a side effect of building the API. The same Marshmallow schemas used to actually validate and marshal in the live API are used to generate a Swagger specification, which can be used to generate API documentation and client libraries in many languages.
Flask-Rebar adds two additional endpoints for every handler registry:
/<registry prefix>/swagger
/<registry prefix>/swagger/ui
/swagger
and /swagger/ui
are configurable:
registry = rebar.create_handler_registry(
spec_path='/apidocs',
spec_ui_path='/apidocs-ui'
)
The HTML documentation is generated with Swagger UI.
Swagger Version¶
Flask-Rebar supports both Swagger v2 and Swagger v3 (synonymous with OpenAPI v2 and OpenAPI v3, respectively).
For backwards compatibility, handler registries will generate Swagger v2 by default. To have the registries generate Swagger v3 instead, specify an instance SwaggerV3Generator
when instantiating the registry:
from flask_rebar import SwaggerV3Generator
registry = rebar.create_handler_registry(
swagger_generator=SwaggerV3Generator()
)
Serverless Generation¶
It is possible to generate the Swagger specification without running the application by using SwaggerV2Generator
or SwaggerV3Generator
directly. This is helpful for generating static Swagger specifications.
from flask_rebar import SwaggerV3Generator
from flask_rebar import Rebar
rebar = Rebar()
registry = rebar.create_handler_registry()
# ...
# register handlers and what not
generator = SwaggerV3Generator()
swagger = generator.generate(registry)
Extending Swagger Generation¶
Flask-Rebar does its very best to free developers from having to think about how their applications map to Swagger, but sometimes it needs some hints.
flask_rebar.swagger_generation.SwaggerV2Generator
is responsible for converting a registry to a Swagger specification.
operationId¶
All Swagger operations (i.e. a combination of a URL route and method) can have an “operationId”, which is name that is unique to the specification. This operationId is very useful for code generation tools, e.g. swagger-codegen, that use the operationId to generate method names.
The generator first checks for the value of endpoint
specified when declaring the handler with a handler registry. If this is not included, the generator defaults to the name of the function.
In many cases, the name of the function will be good enough. If you need more control over the operationId, specific an endpoint
value.
description¶
Swagger operations can have descriptions. If a handler function has a docstring, the generator will use this as a description.
definition names¶
The generator makes use of Swagger “definitions” when representing schemas in the specification.
The generator first checks for a __swagger_title__
on Marshmallow schemas when determining a name for its Swagger definition. If this is not specified, the generator defaults to the name of the schema’s class.
Custom Marshmallow types¶
The generator knows how to convert most built in Marshmallow types to their corresponding Swagger representations, and it checks for the appropriate converter by iterating through a schema/field/validator’s method resolution order, so simple extensions of Marshmallow fields should work out of the box.
If a field the extends Marshmallow’s abstract field, or want to a particular Marshmallow type to have a more specific Swagger definition, you can add a customer converter.
Here’s an example of a custom converter for a custom Marshmallow converter:
import base64
from flask_rebar.swagger_generation import swagger_words
from flask_rebar.swagger_generation.marshmallow_to_swagger import sets_swagger_attr
from flask_rebar.swagger_generation.marshmallow_to_swagger import request_body_converter_registry
from flask_rebar.swagger_generation.marshmallow_to_swagger import StringConverter
from marshmallow import fields, ValidationError
class Base64EncodedString(fields.String):
def _serialize(self, value, attr, obj):
return base64.b64encode(value).encode('utf-8')
def _deserialize(self, value, attr, data):
try:
return base64.b64decode(value.decode('utf-8'))
except UnicodeDecodeError:
raise ValidationError()
class Base64EncodedStringConverter(StringConverter):
@sets_swagger_attr(swagger_words.format)
def get_format(self, obj, context):
return swagger_words.byte
request_body_converter_registry.register_type(Base64EncodedStringConverter())
First we’ve defined a Base64EncodedString
that handles serializing/deserializing a string to/from base64. We want this field to be represented more specifically in our Swagger spec with a “byte” format.
We extend the StringConverter
, which handles setting the “type”.
Methods on the new converter class can be decorated with sets_swagger_attr
, which accepts a single argument for which attribute on the JSON document to set with the result of the method.
The method should take two arguments in addition to self
: obj
and context
.
obj
is the current Marshmallow object being converted. In the above case, it will be an instance of Base64EncodedString
.
context
is a namedtuple that holds some helpful information for more complex conversions:
convert
- This will hold a reference to a convert method that can be used to make recursive callsmemo
- This holds the JSONSchema object that’s been converted so far. This helps convert Validators, which might depend on the type of the object they are validating.schema
- This is the full schema being converted (as opposed toobj
, which might be a specific field in the schema).openapi_version
- This is the major version of OpenAPI being converter for
We then add an instance of the new converter to the request_body_converter_registry
, meaning this field will only be valid for request bodies. We can add it to multiple converter registries or choose to omit it from some if we don’t think a particular type of field should be valid in certain situations (e.g. the query_string_converter_registry doesn’t support Nested
fields).
Default response¶
Another really tricky bit of the Swagger specification to automatically generate is the default response to operations. The generator needs a little hand-holding to get this right, and accepts a default_response_schema
. By default this is set to a schema for the default error handling response.
To customize it:
from marshmallow import Schema, fields
from flask_rebar import SwaggerV2Generator
from flask_rebar import Rebar
class DefaultResponseSchema(Schema):
text = fields.String()
generator = SwaggerV2Generator(
default_response_schema=DefaultResponseSchema()
)
rebar = Rebar()
registry = rebar.create_handler_registry(swagger_generator=generator)
Notice that since we’ve started to customize the swagger generator, we should specify the generator instance when instantiating our Registry instance so our swagger endpoints get this same default response.
Authenticators¶
Changed in 2.0
We also need to tell the generator how to represent custom Authenticators as Swagger.
To create a proper converter:
from flask_rebar.swagger_generation import swagger_words as sw
from flask_rebar.swagger_generation.authenticator_to_swagger import AuthenticatorConverter
class MyAuthConverter(AuthenticatorConverter):
AUTHENTICATOR_TYPE=MyAuthenticator
def get_security_schemes(self, obj, context):
return {
obj.name: {sw.type_: sw.api_key, sw.in_: sw.header, sw.name: obj.header}
}
def get_security_requirements(self, obj, context):
return [{obj.name: []}]
auth_converter = MyAuthConverter()
The converter function should take an instance of the authenticator as a single positional argument and return a dictionary representing the security schema object.
To convert an old-style function into a new-style converter:
from flask_rebar.swagger_generation.authenticator_to_swagger import make_class_from_method
from my_custom_stuff import MyAuthenticator
def my_conversion_function(authenticator):
return {
"name": MyAuthenticator._HEADER_NAME,
"type": "apiKey",
"in": "header"
}
auth_converter = make_class_from_method(MyAuthenticator, my_conversion_function)
There are two supported methods of registering a custom AuthenticatorConverter
:
You can either instantiate your own registry and pass that in when instantiating the generator:
from flask_rebar import SwaggerV3Generator
from flask_rebar.swagger_generation.authenticator_to_swagger import AuthenticatorConverterRegistry
from my_custom_stuff import auth_converter
my_auth_registry = AuthenticatorConverterRegistry()
my_auth_registry.register_type(auth_converter)
generator = SwaggerV3Generator(authenticator_converter_registry=my_auth_registry)
or, you can register your converter with the global default registry:
from flask_rebar.swagger_generation.authenticator_to_swagger import authenticator_converter_registry as global_authenticator_converter_registry
from my_custom_stuff import auth_converter
global_authenticator_converter_registry.register_type(auth_converter)
Tags¶
Swagger supports tagging operations with arbitrary strings, and then optionally including additional metadata about those tags at the root Swagger Object.
Handlers can be tagged, which will translate to tags on the Operation Object:
@registry.handles(
rule='/todos',
method='GET',
tags=['beta']
)
def get_todos():
...
Optionally, to include additional metadata about tags, pass the metadata directly to the swagger generator:
from flask_rebar import Tag
generator = SwaggerV2Generator(
tags=[
Tag(
name='beta',
description='These operations are still in beta!'
)
]
)
Servers¶
OpenAPI 3+ replaces “host” with servers.
Servers can be specified by creating Server
instances and passing them to the generator:
from flask_rebar import Server, ServerVariable
generator = SwaggerV3Generator(
servers=[
Server(
url="https://{username}.gigantic-server.com:{port}/{basePath}",
description="The production API server",
variables={
"username": ServerVariable(
default="demo",
description="this value is assigned by the service provider, in this example `gigantic-server.com`",
),
"port": ServerVariable(default="8443", enum=["8443", "443"]),
"basePath": ServerVariable(default="v2"),
},
)
]
)