diff --git a/docs/_static/css/default.css b/docs/_static/css/default.css index cacda89..57f334c 100644 --- a/docs/_static/css/default.css +++ b/docs/_static/css/default.css @@ -1,4 +1,4 @@ .rst-content .code-block-caption { text-align: left; - padding: 0px, 0px, 5px, 5px; + padding: 0px 0px 5px 5px; } diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 87a8d80..79321de 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -46,6 +46,7 @@ built using either the :code:`AsyncApp` or :code:`FlaskApp`. :sync: AsyncApp .. code-block:: python + :caption: **app.py** from connexion import AsyncApp @@ -65,6 +66,7 @@ built using either the :code:`AsyncApp` or :code:`FlaskApp`. :code:`flask` extra. .. code-block:: python + :caption: **app.py** from connexion import FlaskApp @@ -80,6 +82,7 @@ built using either the :code:`AsyncApp` or :code:`FlaskApp`. :sync: ConnexionMiddleware .. code-block:: python + :caption: **app.py** from asgi_framework import App from connexion import ConnexionMiddleware @@ -91,6 +94,7 @@ built using either the :code:`AsyncApp` or :code:`FlaskApp`. You can also wrap a WSGI application leveraging the :code:`a2wsgi.WSGIMiddleware`: .. code-block:: python + :caption: **app.py** from wsgi_framework import App from connexion import ConnexionMiddleware diff --git a/docs/request.rst b/docs/request.rst index a10b36f..5c775a3 100644 --- a/docs/request.rst +++ b/docs/request.rst @@ -1,125 +1,359 @@ -Request Handling +Request handling ================ -Connexion validates incoming requests for conformance with the schemas -described in swagger specification. -Request parameters will be provided to the handler functions as keyword -arguments if they are included in the function's signature, otherwise body -parameters can be accessed from ``connexion.request.json`` and query parameters -can be accessed from ``connexion.request.args``. +When your application receives a request, Connexion provides a lot of functionality based on your +OpenAPI spec: -Request Validation ------------------- -Both the request body and parameters are validated against the specification, -using `jsonschema`_. +- It checks the security (see :doc:`security`) +- It routes the request to the correct endpoint (see :doc:`routing`) +- It validates the body and parameters (see :doc:`validation`) +- It parses and passes the body and parameters to your python function -If the request doesn't match the specification connexion will return a 400 -error. +On this page, we zoom in on the final part. -Automatic Parameter Handling +Automatic parameter handling ---------------------------- -Connexion automatically maps the parameters defined in your endpoint -specification to arguments of your Python views as named parameters -and with value casting whenever possible. All you need to do is define -the endpoint's parameters with matching names with your views arguments. -As example you have an endpoint specified as: +.. tab-set:: -.. code-block:: yaml + .. tab-item:: AsyncApp + :sync: AsyncApp - paths: - /foo: - get: - operationId: api.foo_get - parameters: - - name: message - description: Some message. - in: query - type: string - required: true + Connexion automatically maps the parameters defined in your endpoint specification to the + arguments defined your associated Python function, parsing and casting values when + possible. All you need to do, is make sure the arguments of your function match the + parameters in your specification. -And the view function: + .. tab-item:: FlaskApp + :sync: FlaskApp + + Connexion automatically maps the parameters defined in your endpoint specification to the + arguments defined your associated Python function, parsing and casting values when + possible. All you need to do, is make sure the arguments of your function match the + parameters in your specification. + + .. tab-item:: ConnexionMiddleware + :sync: ConnexionMiddleware + + Connexion can automatically map the parameters defined in your endpoint specification to + the arguments defined your associated Python function, parsing and casting values when + possible. All you need to do, is make sure the arguments of your function match the + parameters in your specification. + + To activate this behavior when using the ``ConnexionMiddleware`` wrapping a third party + application, you can leverage the following decorators provided by Connexion: + + * FlaskDecorator: provides automatic parameter injection and response serialization for + Flask applications. + + * ASGIDecorator: provides automatic parameter injection for ASGI applications. Note that + this decorator injects Starlette datastructures (such as UploadFile). + + * StarletteDecorator: provides automatic parameter injection and response serialization + for Starlette applications. + + .. code-block:: python + :caption: **app.py** + + from asgi_framework import App + from connexion import ConnexionMiddleware + + @app.route("/greeting/", methods=["POST"]) + @ASGIDecorator() + def post_greeting(name): + ... + + app = App(__name__) + app = ConnexionMiddleware(app) + app.add_api("openapi.yaml") + + For a full example, see our `Frameworks`_ example. + +For example, if you have an endpoint specified as: + +.. tab-set:: + + .. tab-item:: OpenAPI 3 + :sync: OpenAPI 3 + + .. code-block:: yaml + :caption: **openapi.yaml** + + paths: + /foo: + get: + operationId: api.foo_get + parameters: + - name: message + description: Some message. + in: query + schema: + type: string + required: true + + .. tab-item:: Swagger 2 + :sync: Swagger 2 + + .. code-block:: yaml + :caption: **swagger.yaml** + + paths: + /foo: + get: + operationId: api.foo_get + parameters: + - name: message + description: Some message. + in: query + type: string + required: true + +And the view function as: .. code-block:: python - - # api.py file + :caption: **api.py** def foo_get(message): - # do something - return 'You send the message: {}'.format(message), 200 + ... -In this example Connexion will automatically identify that your view -function expects an argument named `message` and will assign the value -of the endpoint parameter `message` to your view function. +Connexion will automatically identify that your view function expects an argument named ``message`` +and will pass in the value of the endpoint parameter ``message``. -Connexion will also use default values if they are provided. +This works for both path and query parameters. -If you want to use a parameter name that collides with a Python built-in, -you can enable the `pythonic_params` option: +Body +---- + +The body will also be passed to your function. + +.. tab-set:: + + .. tab-item:: OpenAPI 3 + :sync: OpenAPI 3 + + In the OpenAPI 3 spec, the ``requestBody`` does not have a name. By default it will be + passed into your function as ``body``. You can use ``x-body-name`` in your operation to + override this name. + + .. code-block:: yaml + :caption: **openapi.yaml** + + paths: + /path + post: + operationId: api.foo_get + requestBody: + x-body-name: payload + content: + application/json: + schema: + ... + + .. code-block:: python + :caption: **api.py** + + # Default + def foo_get(body) + ... + + # Based on x-body-name + def foo_get(payload) + ... + + .. tab-item:: Swagger 2 + :sync: Swagger 2 + + In the Swagger 2 specification, you can define the name of your body. Connexion will pass + the body to your function using this name. + + .. code-block:: yaml + :caption: **swagger.yaml** + + paths: + /path + post: + consumes: + - application/json + parameters: + - in: body + name: payload + schema: + ... + + .. code-block:: python + :caption: **api.py** + + def foo_get(payload) + ... + + Form data + ````````` + + In Swagger 2, form data is defined as parameters in your specification, and Connexion + passes these parameters individually: + + + .. code-block:: yaml + :caption: **swagger.yaml** + + paths: + /path + post: + operationId: api.foo_get + consumes: + - application/json + parameters: + - in: formData + name: field1 + type: string + - in: formData + name: field2 + type: string + + .. code-block:: python + :caption: **api.py** + + def foo_get(field1, field2) + ... + +Optional arguments & Defaults +----------------------------- + +If a default value is defined for a parameter in the OpenAPI specification, Connexion will +automatically pass it in if no value was included in the request. If a default is defined in the +specification, you should not define a default in your Python function, as it will never be +triggered. + +If an endpoint parameter is optional and no default is defined in the specification, you should +make sure the corresponding argument is optional in your Python function as well, by assigning a +default value: .. code-block:: python + :caption: **api.py** - app = connexion.FlaskApp(__name__) - app.add_api('api.yaml', ..., pythonic_params=True) + def foo_get(optional_argument=None) + ... -With this option enabled, Connexion firstly converts *CamelCase* names -to *snake_case*. Secondly it looks to see if the name matches a known built-in -and if it does it appends an underscore to the name. +Missing arguments and kwargs +---------------------------- -As example you have an endpoint specified as: +Connexion will inspect your function signature and only pass in the arguments that it defines. If +an argument is defined in your specification, but not in your function, Connexion will ignore it. -.. code-block:: yaml +If you do define a ``**kwargs`` argument in your function signature, Connexion will pass in all +arguments, and the ones not explicitly defined in your signature will be collected in the +``kwargs`` argument. - paths: - /foo: - get: - operationId: api.foo_get - parameters: - - name: filter - description: Some filter. - in: query - type: string - required: true +Pythonic parameters +------------------- -And the view function: +You can activate Pythonic parameters by setting the ``pythonic_params`` option to ``True`` on +either the application or the API: + +.. tab-set:: + + .. tab-item:: AsyncApp + :sync: AsyncApp + + .. code-block:: python + :caption: **app.py** + + from connexion import AsyncApp + + app = AsyncApp(__name__, pythonic_params=True) + app.add_api("openapi.yaml", pythonic_params=True) + + + .. tab-item:: FlaskApp + :sync: FlaskApp + + .. code-block:: python + :caption: **app.py** + + from connexion import FlaskApp + + app = FlaskApp(__name__, pythonic_params=True) + app.add_api("openapi.yaml", pythonic_params=True): + + .. tab-item:: ConnexionMiddleware + :sync: ConnexionMiddleware + + .. code-block:: python + :caption: **app.py** + + from asgi_framework import App + from connexion import ConnexionMiddleware + + app = App(__name__) + app = ConnexionMiddleware(app, pythonic_params=True) + app.add_api("openapi.yaml", pythonic_params=True) + +This does two things: + +* *CamelCase* arguments are converted to *snake_case* +* If the argument name matches a Python builtin, an underscore is appended. + +When ``pythonic_params`` is activated, the following specification: + +.. tab-set:: + + .. tab-item:: OpenAPI 3 + :sync: OpenAPI 3 + + .. code-block:: yaml + :caption: **openapi.yaml** + + paths: + /foo: + get: + operationId: api.foo_get + parameters: + - name: filter + description: Some filter. + in: query + schema: + type: string + required: true + - name: FilterOption + description: Some filter option. + in: query + schema: + type: string + + .. tab-item:: Swagger 2 + :sync: Swagger 2 + + .. code-block:: yaml + :caption: **swagger.yaml** + + paths: + /foo: + get: + operationId: api.foo_get + parameters: + - name: filter + description: Some filter. + in: query + type: string + required: true + - name: FilterOption + description: Some filter option. + in: query + type: string + +Maps to the following Python function: .. code-block:: python + :caption: **api.py** - # api.py file - def foo_get(filter_): - # do something - return 'You send the filter: {}'.format(filter_), 200 - -.. note:: In the OpenAPI 3.x.x spec, the requestBody does not have a name. - By default it will be passed in as 'body'. You can optionally - provide the x-body-name parameter in your operation - (or legacy position within the requestBody schema) - to override the name of the parameter that will be passed to your - handler function. - -.. code-block:: yaml - - /path - post: - requestBody: - x-body-name: body - content: - application/json: - schema: - # legacy location here should be ignored because the preferred location for x-body-name is at the requestBody level above - x-body-name: this_should_be_ignored - -.. warning:: Please note that when you have a parameter defined as - *not* required at your endpoint and your Python view have - a non-named argument, when you call this endpoint WITHOUT - the parameter you will get an exception of missing - positional argument. + def foo_get(filter_, filter_option=None): + ... Type casting -^^^^^^^^^^^^ -Whenever possible Connexion will try to parse your argument values and -do type casting to related Python natives values. The current -available type castings are: +------------ + +Whenever possible Connexion will try to parse your argument values and cast them to the correct +Python type: +--------------+-------------+ | OpenAPI Type | Python Type | @@ -135,143 +369,108 @@ available type castings are: +--------------+-------------+ | array | list | +--------------+-------------+ -| null | None | -+--------------+-------------+ | object | dict | +--------------+-------------+ +| null | None | ++--------------+-------------+ -.. note:: For more details about `collectionFormat`\ s please check the - official `OpenAPI 2.0 Specification`_. +Parameter serialization +----------------------- +Array and object parameters need to be serialized into lists and dicts. -In the `OpenAPI 2.0 Specification`_ if you use the ``array`` type, -you can define the ``collectionFormat`` to set the deserialization behavior. -Connexion currently supports "pipes" and "csv" as collection formats. -The default format is "csv". +.. tab-set:: -Connexion is opinionated about how the URI is parsed for ``array`` types. -The default behavior for query parameters that have been defined multiple -times is to join them all together. For example, if you provide a URI with -the query string ``?letters=a,b,c&letters=d,e,f``, connexion will set -``letters = ['a', 'b', 'c', 'd', 'e', 'f']``. + .. tab-item:: OpenAPI 3 + :sync: OpenAPI 3 -You can override this behavior by specifying the URI parser in the app or -api options. + The `OpenAPI 3 specification`_ defines the `style` and `explode` keywords which specify how + these parameters should be serialized. + + To handle these, Connexion provides the ``OpenAPIUriParser`` class, which is enabled by + default when using an OpenAPI 3 spec. + + Not all combinations of `style` and `explode` are supported yet. Please open an `issue`_ if + you run into any problems. + + .. tab-item:: Swagger 2 + :sync: Swagger 2 + + The `Swagger 2 specification`_ defines the `collectionFormat` keyword to specify how + these parameters should be serialized. + + To handle this for you, Connexion provides the ``Swagger2URIParser`` class, which is + enabled by default when using a Swagger 2 spec. It currently supports the `pipes`, `csv`, + and `multi` collection formats. + + This parser adheres to the Swagger 2.0 spec, and will only join together multiple instance + of the same query parameter if the collectionFormat is set to `multi`. Query parameters + are parsed from left to right, so if a query parameter is defined twice, then the + right-most definition wins. For example, if you provided a URI with the query string + ``?letters=a,b,c&letters=d,e,f`` and ``collectionFormat: csv``, then connexion will set + ``letters = ['d', 'e', 'f']``. + + Connexion also provides two alternative parsers: + + * The ``FirstValueURIParser``, which behaves like the ``Swagger2URIParser``, except that it + prefers the first defined value. + * The ``AlwaysMultiURIParser``, which behaves like the ``Swagger2URIParser``, except that + it always joins together multiple instances of the same query parameter. + +Context +------- + +Connexion can pass in some additional context. By default, this contains the following information: .. code-block:: python - from connexion.decorators.uri_parsing import Swagger2URIParser - options = {'uri_parser_class': Swagger2URIParser} - app = connexion.App(__name__, specification_dir='swagger/', options=options) - -You can implement your own URI parsing behavior by inheriting from -``connexion.decorators.uri_parsing.AbstractURIParser``. - -There are a handful of URI parsers included with connection. - -+----------------------+---------------------------------------------------------------------------+ -| OpenAPIURIParser | This parser adheres to the OpenAPI 3.x.x spec, and uses the ``style`` | -| default: OpenAPI 3.0 | parameter. Query parameters are parsed from left to right, so if a query | -| | parameter is defined twice, then the right-most definition will take | -| | precedence. For example, if you provided a URI with the query string | -| | ``?letters=a,b,c&letters=d,e,f``, and ``style: simple``, then connexion | -| | will set ``letters = ['d', 'e', 'f']``. For additional information see | -| | `OpenAPI 3.0 Style Values`_. | -+----------------------+---------------------------------------------------------------------------+ -| Swagger2URIParser | This parser adheres to the Swagger 2.0 spec, and will only join together | -| default: OpenAPI 2.0 | multiple instance of the same query parameter if the ``collectionFormat`` | -| | is set to ``multi``. Query parameters are parsed from left to right, so | -| | if a query parameter is defined twice, then the right-most definition | -| | wins. For example, if you provided a URI with the query string | -| | ``?letters=a,b,c&letters=d,e,f``, and ``collectionFormat: csv``, then | -| | connexion will set ``letters = ['d', 'e', 'f']`` | -+----------------------+---------------------------------------------------------------------------+ -| FirstValueURIParser | This parser behaves like the Swagger2URIParser, except that it prefers | -| | the first defined value. For example, if you provided a URI with the query| -| | string ``?letters=a,b,c&letters=d,e,f`` and ``collectionFormat: csv`` | -| | hen connexion will set ``letters = ['a', 'b', 'c']`` | -+----------------------+---------------------------------------------------------------------------+ -| AlwaysMultiURIParser | This parser is backwards compatible with Connexion 1.x. It joins together | -| | multiple instances of the same query parameter. | -+----------------------+---------------------------------------------------------------------------+ - - -.. _jsonschema: https://pypi.python.org/pypi/jsonschema -.. _`OpenAPI 2.0 Specification`: https://github.com/OAI/OpenAPI-Specification/blob/OpenAPI.next/versions/2.0.md#fixed-fields-7 -.. _OpenAPI 3.0 Style Values: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.1.md#style-values - -Parameter validation -^^^^^^^^^^^^^^^^^^^^ - -Connexion can apply strict parameter validation for query and form data -parameters. When this is enabled, requests that include parameters not defined -in the swagger spec return a 400 error. You can enable it when adding the API -to your application: - -.. code-block:: python - - app.add_api('my_apy.yaml', strict_validation=True) - - -Nullable parameters -^^^^^^^^^^^^^^^^^^^ - -Sometimes your API should explicitly accept `nullable parameters`_. However -OpenAPI specification currently does `not support`_ officially a way to serve -this use case, Connexion adds the `x-nullable` vendor extension to parameter -definitions. Its usage would be: - -.. code-block:: yaml - - /countries/cities: - parameters: - - name: name - in: query - type: string - x-nullable: true - required: true - -It is supported by Connexion in all parameter types: `body`, `query`, -`formData`, and `path`. Nullable values are the strings `null` and `None`. - -.. warning:: Be careful on nullable parameters for sensitive data where the - strings "null" or "None" can be `valid values`_. - -.. note:: This extension will be removed as soon as OpenAPI/Swagger - Specification provide an official way of supporting nullable - values. - -.. _`nullable parameters`: https://github.com/zalando/connexion/issues/182 -.. _`not support`: https://github.com/OAI/OpenAPI-Specification/issues/229 -.. _`valid values`: http://www.bbc.com/future/story/20160325-the-names-that-break-computer-systems - -Header Parameters ------------------ - -Currently, header parameters are not passed to the handler functions as parameters. But they can be accessed through the underlying -``connexion.request.headers`` object which aliases the ``flask.request.headers`` object. - -.. code-block:: python - - def index(): - page_number = connexion.request.headers['Page-Number'] - - -Custom Validators ------------------ - -By default, body and parameters contents are validated against OpenAPI schema -via ``connexion.decorators.validation.RequestBodyValidator`` -or ``connexion.decorators.validation.ParameterValidator``, if you want to -change the validation, you can override the defaults with: - -.. code-block:: python - - validator_map = { - 'body': CustomRequestBodyValidator, - 'parameter': CustomParameterValidator + { + "api_base_path": ... # The base path of the matched API + "operation_id": ... # The operation id of matched operation + "user": ... # User information from authentication + "token_info": ... # Token information from authentication } - app = connexion.FlaskApp(__name__) - app.add_api('api.yaml', ..., validator_map=validator_map) -See custom validator example in ``examples/enforcedefaults``. +Third party or custom middleware might add additional fields to this. + +To receive this in your function, you can either: + +* Specify the ``context_`` argument in your function signature, and the context dict will be + passed in as a whole: + + .. code-block:: python + :caption: **api.py** + + def foo_get(context_): + ... + +* Specify the keys individually in your function signature: + + .. code-block:: python + :caption: **api.py** + + def foo_get(user, token_info): + ... + +Request object +-------------- + +Connexion also exposes a ``Request`` class which holds all the information about the incoming +request. + +.. code-block:: python + + from connexion import request + +.. dropdown:: View a detailed reference of the ``connexion.request`` class + :icon: eye + + .. autoclass:: connexion.lifecycle.ASGIRequest + :members: + :undoc-members: + :inherited-members: + +.. _Frameworks: https://github.com/spec-first/connexion/tree/main/examples/frameworks +.. _OpenAPI 3 specification: https://swagger.io/docs/specification/serialization +.. _Swagger 2 specification: https://swagger.io/docs/specification/2-0/describing-parameters/#array +.. _issue: https://github.com/spec-first/connexion/issues \ No newline at end of file diff --git a/docs/routing.rst b/docs/routing.rst index 018b182..f01f970 100644 --- a/docs/routing.rst +++ b/docs/routing.rst @@ -59,15 +59,7 @@ operation: Note that :code:`HEAD` requests will be handled by the :code:`operationId` specified under the :code:`GET` operation in the specification. :code:`Connexion.request.method` can be used to -determine which request was made. - -.. dropdown:: View a detailed reference of the :code:`connexion.request` class - :icon: eye - - .. autoclass:: connexion.lifecycle.ASGIRequest - :members: - :undoc-members: - :inherited-members: +determine which request was made. See :class:`.ASGIRequest`. Automatic routing ----------------- @@ -354,8 +346,10 @@ specification. .. tab-set:: .. tab-item:: OpenAPI 3 + :sync: OpenAPI 3 .. code-block:: yaml + :caption: **openapi.yaml** paths: /users/{id}: @@ -368,14 +362,16 @@ specification. description: The user ID .. tab-item:: Swagger 2 + :sync: Swagger 2 .. code-block:: yaml + :caption: **swagger.yaml** paths: /users/{id}: parameters: - in: path - name: id + name: id # Note the name is the same as in the path required: true type: integer description: The user ID. @@ -449,32 +445,41 @@ API Versioning and basePath Setting a base path is useful for versioned APIs. An example of a base path would be the ``1.0`` in ``http://{HOST}/1.0/hello_world``. -If you are using OpenAPI 3, you set your base URL path in the -servers block of the specification. You can either specify a full -URL, or just a relative path. -.. code-block:: yaml - :caption: **openapi.yaml** +.. tab-set:: - servers: - - url: https://{{HOST}}/1.0 - description: full url example - - url: /1.0 - description: relative path example + .. tab-item:: OpenAPI 3 + :sync: OpenAPI 3 - paths: - ... + If you are using OpenAPI 3, you set your base URL path in the + servers block of the specification. You can either specify a full + URL, or just a relative path. -If you are using Swagger 2.0, you can define a ``basePath`` on the top level -of your Swagger 2.0 specification. + .. code-block:: yaml + :caption: **openapi.yaml** -.. code-block:: yaml - :caption: **swagger.yaml** + servers: + - url: https://{{HOST}}/1.0 + description: full url example + - url: /1.0 + description: relative path example - basePath: /1.0 + paths: + ... - paths: - ... + .. tab-item:: Swagger 2 + :sync: Swagger 2 + + If you are using Swagger 2.0, you can define a ``basePath`` on the top level + of your Swagger 2.0 specification. + + .. code-block:: yaml + :caption: **swagger.yaml** + + basePath: /1.0 + + paths: + ... If you don't want to include the base path in your specification, you can provide it when adding the API to your application: @@ -482,7 +487,7 @@ can provide it when adding the API to your application: .. code-block:: python :caption: **app.py** - app.add_api('my_api.yaml', base_path='/1.0') + app.add_api('openapi.yaml', base_path='/1.0') .. _operation: https://swagger.io/docs/specification/paths-and-operations/#operations .. _Path parameters: https://swagger.io/docs/specification/describing-parameters/#path-parameters diff --git a/docs/v3.rst b/docs/v3.rst index 2069cef..80ebd9c 100644 --- a/docs/v3.rst +++ b/docs/v3.rst @@ -139,7 +139,7 @@ Non-breaking changes request bodies. Feedback -======== +-------- We would really love to hear from you. By trying Connexion 3 now and providing feedback, you get the opportunity to make sure that updating your application will be as painless as possible, while helping to create a more stable GA