1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300
|
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:
.. code-block:: python
registry = rebar.create_handler_registry(
spec_path='/apidocs',
spec_ui_path='/apidocs-ui'
)
The HTML documentation is generated with `Swagger UI <https://swagger.io/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:
.. code-block:: python
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.
.. code-block:: python
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", a 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, specify an ``endpoint`` value.
description
^^^^^^^^^^^
Swagger operations can have descriptions. If a handler function has a docstring, the generator will use this as a description.
You can also set the `description` argument for a `marshmallow.Field` and the generator will add this to the swagger file.
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 extends Marshmallow's abstract field, or you want a particular Marshmallow type to have a more specific Swagger definition, you can add a custom converter.
Here's an example of a custom converter for a custom Marshmallow converter:
.. code-block:: python
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 indicating 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 holds a reference to a convert method that can be used to make recursive calls
* ``memo`` - 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 to ``obj``, which might be a specific field in the schema).
* ``openapi_version`` - This is the major version of OpenAPI the converter is written 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:
.. code-block:: python
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:
.. code-block:: python
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 <https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#securitySchemeObject>`_.
To convert an old-style function into a new-style converter:
.. code-block:: python
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:
.. code-block:: python
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:
.. code-block:: python
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:
.. code-block:: python
@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:
.. code-block:: python
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 <https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#serverObject>`_.
Servers can be specified by creating ``Server`` instances and passing them to the generator:
.. code-block:: python
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"),
},
)
]
)
|