File: swagger_generation.rst

package info (click to toggle)
flask-rebar 3.4.0-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 15,852 kB
  • sloc: python: 9,539; javascript: 20; makefile: 16
file content (300 lines) | stat: -rw-r--r-- 12,252 bytes parent folder | download
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"),
               },
           )
       ]
   )