跳转至

APIFlask

The Flask object with some web API support.

Examples:

from apiflask import APIFlask

app = APIFlask(__name__)

Attributes:

Name Type Description
openapi_version

The version of OpenAPI Specification (openapi.openapi). This attribute can also be configured from the config with the OPENAPI_VERSION configuration key. Defaults to '3.0.3'.

servers

The servers information of the API (openapi.servers), accepts multiple server dicts. Example value:

app.servers = [
    {
        'name': 'Production Server',
        'url': 'http://api.example.com'
    }
]

This attribute can also be configured from the config with the SERVERS configuration key. Defaults to None.

tags

The list of tags of the OpenAPI spec documentation (openapi.tags), accepts a list of dicts. You can also pass a simple list contains the tag name:

app.tags = ['foo', 'bar', 'baz']

A standard OpenAPI tags list will look like this:

app.tags = [
    {'name': 'foo', 'description': 'The description of foo'},
    {'name': 'bar', 'description': 'The description of bar'},
    {'name': 'baz', 'description': 'The description of baz'}
]

If not set, the blueprint names will be used as tags.

This attribute can also be configured from the config with the TAGS configuration key. Defaults to None.

external_docs

The external documentation information of the API (openapi.externalDocs). Example:

app.external_docs = {
    'description': 'Find more info here',
    'url': 'http://docs.example.com'
}

This attribute can also be configured from the config with the EXTERNAL_DOCS configuration key. Defaults to None.

info

The info object (openapi.info), it accepts a dict contains following info fields: description, termsOfService, contact, license. You can use separate configuration variables to overwrite this dict. Example:

app.info = {
    'description': '...',
    'termsOfService': 'http://example.com',
    'contact': {
        'name': 'API Support',
        'url': 'http://www.example.com/support',
        'email': 'support@example.com'
    },
    'license': {
        'name': 'Apache 2.0',
        'url': 'http://www.apache.org/licenses/LICENSE-2.0.html'
    }
}
description

The description of the API (openapi.info.description). This attribute can also be configured from the config with the DESCRIPTION configuration key. Defaults to None.

contact

The contact information of the API (openapi.info.contact). Example:

app.contact = {
    'name': 'API Support',
    'url': 'http://www.example.com/support',
    'email': 'support@example.com'
}

This attribute can also be configured from the config with the CONTACT configuration key. Defaults to None.

license

The license of the API (openapi.info.license). Example:

app.license = {
    'name': 'Apache 2.0',
    'url': 'http://www.apache.org/licenses/LICENSE-2.0.html'
}

This attribute can also be configured from the config with the LICENSE configuration key. Defaults to None.

terms_of_service

The terms of service URL of the API (openapi.info.termsOfService). Example:

app.terms_of_service = 'http://example.com/terms/'

This attribute can also be configured from the config with the TERMS_OF_SERVICE configuration key. Defaults to None.

security_schemes

The security schemes of the API (openapi.components.securitySchemes). Example:

app.security_schemes = [
    'ApiKeyAuth': {
        'type': 'apiKey',
        'in': 'header',
        'name': 'X-API-Key'
    }
]

This attribute can also be configured from the config with the SECURITY_SCHEMES configuration key. Defaults to None.

spec_callback t.Optional[SpecCallbackType]

It stores the function object registerd by spec_processor. You can also pass a callback function to it directly without using spec_processor. Example:

def update_spec(spec):
    spec['title'] = 'Updated Title'
    return spec

app.spec_callback = update_spec
error_callback ErrorCallbackType

It stores the function object registerd by error_processor. You can also pass a callback function to it directly without using error_processor. See the docstring of error_processor for more details. Example:

def my_error_handler(error):
    return {
        'status_code': error.status_code,
        'message': error.message,
        'detail': error.detail
    }, error.status_code, error.headers

app.error_processor = my_error_handler
schema_name_resolver

It stores the function that used to decided the schema name. The schema name resolver should accept the schema object as argument and return the name. Example:

# this is the default schema name resolver used in APIFlask
def schema_name_resolver(schema):
    name = schema.__class__.__name__  # get schema class name
    if name.endswith('Schema'):  # remove the "Schema" suffix
        name = name[:-6] or name
    if schema.partial:  # add a "Update" suffix for partial schema
        name += 'Update'
    return name

app.schema_name_resolver = schema_name_resolver

Version changed: 1.0

  • Add instance attribute security_schemes as an alias of config SECURITY_SCHEMES.

Version changed: 0.9.0

  • Add instance attribute schema_name_resolver.
Source code in apiflask/app.py
class APIFlask(APIScaffold, Flask):
    """The `Flask` object with some web API support.

    Examples:

    ```python
    from apiflask import APIFlask

    app = APIFlask(__name__)
    ```

    Attributes:
        openapi_version: The version of OpenAPI Specification (openapi.openapi).
            This attribute can also be configured from the config with the
            `OPENAPI_VERSION` configuration key. Defaults to `'3.0.3'`.
        servers: The servers information of the API (openapi.servers), accepts
            multiple server dicts. Example value:

            ```python
            app.servers = [
                {
                    'name': 'Production Server',
                    'url': 'http://api.example.com'
                }
            ]
            ```

            This attribute can also be configured from the config with the
            `SERVERS` configuration key. Defaults to `None`.
        tags: The list of tags of the OpenAPI spec documentation (openapi.tags),
            accepts a list of dicts. You can also pass a simple list contains the
            tag name:

            ```python
            app.tags = ['foo', 'bar', 'baz']
            ```

            A standard OpenAPI tags list will look like this:

            ```python
            app.tags = [
                {'name': 'foo', 'description': 'The description of foo'},
                {'name': 'bar', 'description': 'The description of bar'},
                {'name': 'baz', 'description': 'The description of baz'}
            ]
            ```

            If not set, the blueprint names will be used as tags.

            This attribute can also be configured from the config with the
            `TAGS` configuration key. Defaults to `None`.
        external_docs: The external documentation information of the API
            (openapi.externalDocs). Example:

            ```python
            app.external_docs = {
                'description': 'Find more info here',
                'url': 'http://docs.example.com'
            }
            ```

            This attribute can also be configured from the config with the
            `EXTERNAL_DOCS` configuration key. Defaults to `None`.
        info: The info object (openapi.info), it accepts a dict contains following info fields:
            `description`, `termsOfService`, `contact`, `license`. You can use separate
            configuration variables to overwrite this dict. Example:

            ```python
            app.info = {
                'description': '...',
                'termsOfService': 'http://example.com',
                'contact': {
                    'name': 'API Support',
                    'url': 'http://www.example.com/support',
                    'email': 'support@example.com'
                },
                'license': {
                    'name': 'Apache 2.0',
                    'url': 'http://www.apache.org/licenses/LICENSE-2.0.html'
                }
            }
            ```

        description: The description of the API (openapi.info.description).
            This attribute can also be configured from the config with the
            `DESCRIPTION` configuration key. Defaults to `None`.
        contact: The contact information of the API (openapi.info.contact). Example:

            ```python
            app.contact = {
                'name': 'API Support',
                'url': 'http://www.example.com/support',
                'email': 'support@example.com'
            }
            ```

            This attribute can also be configured from the config with the
            `CONTACT` configuration key. Defaults to `None`.
        license: The license of the API (openapi.info.license). Example:

            ```python
            app.license = {
                'name': 'Apache 2.0',
                'url': 'http://www.apache.org/licenses/LICENSE-2.0.html'
            }
            ```

            This attribute can also be configured from the config with the
            `LICENSE` configuration key. Defaults to `None`.
        terms_of_service: The terms of service URL of the API
            (openapi.info.termsOfService). Example:

            ```python
            app.terms_of_service = 'http://example.com/terms/'
            ```

            This attribute can also be configured from the config with the
            `TERMS_OF_SERVICE` configuration key. Defaults to `None`.
        security_schemes: The security schemes of the API
            (openapi.components.securitySchemes). Example:

            ```python
            app.security_schemes = [
                'ApiKeyAuth': {
                    'type': 'apiKey',
                    'in': 'header',
                    'name': 'X-API-Key'
                }
            ]
            ```

            This attribute can also be configured from the config with the
            `SECURITY_SCHEMES` configuration key. Defaults to `None`.
        spec_callback: It stores the function object registerd by
            [`spec_processor`][apiflask.APIFlask.spec_processor]. You can also
            pass a callback function to it directly without using `spec_processor`.
            Example:

            ```python
            def update_spec(spec):
                spec['title'] = 'Updated Title'
                return spec

            app.spec_callback = update_spec
            ```

        error_callback: It stores the function object registerd by
            [`error_processor`][apiflask.APIFlask.error_processor]. You can also
            pass a callback function to it directly without using `error_processor`.
            See the docstring of `error_processor` for more details.
            Example:

            ```python
            def my_error_handler(error):
                return {
                    'status_code': error.status_code,
                    'message': error.message,
                    'detail': error.detail
                }, error.status_code, error.headers

            app.error_processor = my_error_handler
            ```

        schema_name_resolver: It stores the function that used to decided the schema name.
            The schema name resolver should accept the schema object as argument and return
            the name.
            Example:

            ```python
            # this is the default schema name resolver used in APIFlask
            def schema_name_resolver(schema):
                name = schema.__class__.__name__  # get schema class name
                if name.endswith('Schema'):  # remove the "Schema" suffix
                    name = name[:-6] or name
                if schema.partial:  # add a "Update" suffix for partial schema
                    name += 'Update'
                return name

            app.schema_name_resolver = schema_name_resolver
            ```

    *Version changed: 1.0*

    - Add instance attribute `security_schemes` as an alias of config `SECURITY_SCHEMES`.

    *Version changed: 0.9.0*

    - Add instance attribute `schema_name_resolver`.
    """

    openapi_version: str = ConfigAttribute('OPENAPI_VERSION')  # type: ignore
    tags: t.Optional[TagsType] = ConfigAttribute('TAGS')  # type: ignore
    servers: t.Optional[t.List[t.Dict[str, str]]] = ConfigAttribute('SERVERS')  # type: ignore
    info: t.Optional[t.Dict[str, t.Union[str, dict]]] = ConfigAttribute('INFO')  # type: ignore
    description: t.Optional[str] = ConfigAttribute('DESCRIPTION')  # type: ignore
    contact: t.Optional[t.Dict[str, str]] = ConfigAttribute('CONTACT')  # type: ignore
    license: t.Optional[t.Dict[str, str]] = ConfigAttribute('LICENSE')  # type: ignore
    external_docs: t.Optional[t.Dict[str, str]] = ConfigAttribute('EXTERNAL_DOCS')  # type: ignore
    terms_of_service: t.Optional[str] = ConfigAttribute('TERMS_OF_SERVICE')  # type: ignore
    security_schemes: t.Optional[t.Dict[str, t.Any]] = \
        ConfigAttribute('SECURITY_SCHEMES')  # type: ignore

    def __init__(
        self,
        import_name: str,
        title: str = 'APIFlask',
        version: str = '0.1.0',
        spec_path: str = '/openapi.json',
        docs_path: str = '/docs',
        docs_oauth2_redirect_path: str = '/docs/oauth2-redirect',
        docs_ui: str = 'swagger-ui',
        redoc_path: str = '/redoc',
        openapi_blueprint_url_prefix: t.Optional[str] = None,
        json_errors: bool = True,
        enable_openapi: bool = True,
        static_url_path: t.Optional[str] = None,
        static_folder: str = 'static',
        static_host: t.Optional[str] = None,
        host_matching: bool = False,
        subdomain_matching: bool = False,
        template_folder: str = 'templates',
        instance_path: t.Optional[str] = None,
        instance_relative_config: bool = False,
        root_path: t.Optional[str] = None
    ) -> None:
        """Make an app instance.

        Arguments:
            import_name: The name of the application package, usually
                `__name__`. This helps locate the `root_path` for the
                application.
            title: The title of the API (openapi.info.title), defaults to "APIFlask".
                You can change it to the name of your API (e.g., "Pet API").
            version: The version of the API (openapi.info.version), defaults to "0.1.0".
            spec_path: The path to OpenAPI Spec documentation. It
                defaults to `/openapi.json`, if the path ends with `.yaml`
                or `.yml`, the YAML format of the OAS will be returned.
            docs_path: The path to API UI documentation, defaults to `/docs`.
            docs_ui: The UI of API documentation, one of `swagger-ui` (default), `redoc`,
                `elements`, `rapidoc`, and `rapipdf`.
            docs_oauth2_redirect_path: The path to Swagger UI OAuth redirect.
            redoc_path: Deprecated since 1.1, set `APIFlask(docs_ui='redoc')` to use Redoc.
                The path to Redoc documentation, defaults to `/redoc`.
            openapi_blueprint_url_prefix: The url prefix of the OpenAPI blueprint. This
                prefix will append before all the OpenAPI-related paths (`sepc_path`,
                `docs_path`, etc.), defaults to `None`.
            json_errors: If `True`, APIFlask will return a JSON response for HTTP errors.
            enable_openapi: If `False`, will disable OpenAPI spec and API docs views.

        Other keyword arguments are directly passed to `flask.Flask`.

        *Version changed: 1.1.0*

        - Add `docs_ui` parameter.

        *Version changed: 0.7.0*

        - Add `openapi_blueprint_url_prefix` parameter.
        """
        super().__init__(
            import_name,
            static_url_path=static_url_path,
            static_folder=static_folder,
            static_host=static_host,
            host_matching=host_matching,
            subdomain_matching=subdomain_matching,
            template_folder=template_folder,
            instance_path=instance_path,
            instance_relative_config=instance_relative_config,
            root_path=root_path
        )

        # Set default config
        self.config.from_object('apiflask.settings')

        self.title = title
        self.version = version
        self.spec_path = spec_path
        self.docs_ui = docs_ui
        self.docs_path = docs_path
        self.redoc_path = redoc_path
        self.docs_oauth2_redirect_path = docs_oauth2_redirect_path
        self.openapi_blueprint_url_prefix = openapi_blueprint_url_prefix
        self.enable_openapi = enable_openapi
        self.json_errors = json_errors

        self.spec_callback: t.Optional[SpecCallbackType] = None
        self.error_callback: ErrorCallbackType = self._error_handler
        self.schema_name_resolver = self._schema_name_resolver

        self._spec: t.Optional[t.Union[dict, str]] = None
        self._auth_blueprints: t.Dict[str, t.Dict[str, t.Any]] = {}

        self._register_openapi_blueprint()
        self._register_error_handlers()

    def _register_error_handlers(self) -> None:
        """Register default error handlers for HTTPError and WerkzeugHTTPException.

        *Version changed: 0.9.0*

        - Always pass an `HTTPError` instance to error handlers.
        """
        @self.errorhandler(HTTPError)  # type: ignore
        def handle_http_errors(
            error: HTTPError
        ) -> ResponseReturnValueType:
            return self.error_callback(error)

        if self.json_errors:
            self._apply_error_callback_to_werkzeug_errors()

    def _apply_error_callback_to_werkzeug_errors(self) -> None:
        @self.errorhandler(WerkzeugHTTPException)  # type: ignore
        def handle_werkzeug_errors(
            e: WerkzeugHTTPException
        ) -> ResponseReturnValueType:
            headers = dict(e.get_headers())
            # remove the original MIME header
            del headers['Content-Type']
            error = HTTPError(
                e.code,
                message=e.name,
                headers=headers
            )
            return self.error_callback(error)

    def dispatch_request(self) -> ResponseReturnValueType:  # type: ignore
        """Overwrite the default dispatch method in Flask.

        With this overwrite, view arguments are passed as positional
        arguments so that the view function can intuitively accept the
        parameters (i.e., from top to bottom, from left to right).

        Examples:

        ```python
        @app.get('/pets/<name>/<int:pet_id>/<age>')  # -> name, pet_id, age
        @app.input(QuerySchema)  # -> query
        @app.output(PetSchema)  # -> pet
        def get_pet(name, pet_id, age, query, pet):
            pass
        ```

        From Flask, see the NOTICE file for license information.

        *Version added: 0.2.0*
        """
        req = _request_ctx_stack.top.request
        if req.routing_exception is not None:
            self.raise_routing_exception(req)
        rule = req.url_rule
        # if we provide automatic options for this URL and the
        # request came with the OPTIONS method, reply automatically
        if (  # pragma: no cover
            getattr(rule, 'provide_automatic_options', False)
            and req.method == 'OPTIONS'
        ):
            return self.make_default_options_response()  # pragma: no cover
        # otherwise dispatch to the handler for that endpoint
        view_function = self.view_functions[rule.endpoint]
        if hasattr(self, 'ensure_sync'):  # pragma: no cover
            view_function = self.ensure_sync(view_function)
        if rule.endpoint == 'static':
            # app static route only accepts keyword arguments, see flask#3762
            return view_function(**req.view_args)  # type: ignore
        else:
            return view_function(*req.view_args.values())  # type: ignore

    # TODO: remove this function when we dropped the Flask 1.x support
    # the list return values are supported in Flask 2.2
    def make_response(self, rv) -> Response:
        """Patch the make_response form Flask to allow returning list as JSON.

        *Version added: 1.1.0*
        """
        if isinstance(rv, list):
            rv = jsonify(rv)
        elif isinstance(rv, tuple) and isinstance(rv[0], list):
            rv = (jsonify(rv[0]), *rv[1:])
        return super().make_response(rv)

    @staticmethod
    def _error_handler(
        error: HTTPError
    ) -> ResponseReturnValueType:
        """The default error handler.

        Arguments:
            error: An instance of [`HTTPError`][apiflask.exceptions.HTTPError].

        *Version changed: 0.10.0*

        - Remove the `status_code` field from the response.
        - Add `HTTPError.extra_data` to the reponse body.
        """
        body = {
            'detail': error.detail,
            'message': error.message,
            **error.extra_data
        }
        return body, error.status_code, error.headers

    def error_processor(
        self,
        f: ErrorCallbackType
    ) -> ErrorCallbackType:
        """A decorator to register a custom error response processor function.

        The decorated callback function will be called in the following situations:

        - Any HTTP exception is raised by Flask when handling request.
        - A validation error happened when parsing a request.
        - An exception triggered with [`HTTPError`][apiflask.exceptions.HTTPError]
        - An exception triggered with [`abort`][apiflask.exceptions.abort].

        You can still register a specific error handler for a specific error code
        or exception with the `app.errorhandler(code_or_exection)` decorator,
        in that case, the return value of the specific error handler will be used as the
        response when the corresponding error or exception happened.

        The callback function must accept an error object as argument and return a valid
        response.

        Examples:

        ```python
        @app.error_processor
        def my_error_processor(error):
            return {
                'status_code': error.status_code,
                'message': error.message,
                'detail': error.detail,
                **error.extra_data
            }, error.status_code, error.headers
        ```

        The error object is an instance of [`HTTPError`][apiflask.exceptions.HTTPError],
        so you can get error information via it's attributes:

        - status_code: If the error is triggered by a validation error, the value will be
          400 (default) or the value you passed in config `VALIDATION_ERROR_STATUS_CODE`.
          If the error is triggered by [`HTTPError`][apiflask.exceptions.HTTPError]
          or [`abort`][apiflask.exceptions.abort], it will be the status code
          you passed. Otherwise, it will be the status code set by Werkzueg when
          processing the request.
        - message: The error description for this error, either you passed or grabbed from
          Werkzeug.
        - detail: The detail of the error. When the validation error happens, it will
          be filled automatically in the following structure:

            ```python
            "<location>": {
                "<field_name>": ["<error_message>", ...],
                "<field_name>": ["<error_message>", ...],
                ...
            },
            "<location>": {
                ...
            },
            ...
            ```

          The value of `location` can be `json` (i.e., request body) or `query`
          (i.e., query string) depending on the place where the validation error
          happened.
        - headers: The value will be `{}` unless you pass it in `HTTPError` or `abort`.
        - extra_data: Additional error information.

        If you want, you can rewrite the whole response body to anything you like:

        ```python
        @app.error_processor
        def my_error_processor(error):
            body = {'error_detail': error.detail, **error.extra_data}
            return body, error.status_code, error.headers
        ```

        However, I would recommend keeping the `detail` in the response since it contains
        the detailed information about the validation error when the validation error
        happened.

        *Version changed: 1.0*

        - Apply this error processor to normal HTTP errors even when
          `json_error` is set to `False` when creating `APIFlask` instance.

        *Version changed: 0.7.0*

        - Support registering an async callback function.
        """
        if hasattr(self, 'ensure_sync'):  # pragma: no cover
            self.error_callback = self.ensure_sync(f)
        else:  # pragma: no cover
            self.error_callback = f
        self._apply_error_callback_to_werkzeug_errors()
        return f

    def _register_openapi_blueprint(self) -> None:
        """Register a blueprint for OpenAPI support.

        The name of the blueprint is "openapi". This blueprint will hold the view
        functions for spec file, Swagger UI and Redoc.

        *Version changed: 1.1.0*

        - Deprecate the redoc view at /redoc path.

        *Version changed: 0.7.0*

        - The format of the spec now rely on the `SPEC_FORMAT` config.
        """
        bp = Blueprint(
            'openapi',
            __name__,
            url_prefix=self.openapi_blueprint_url_prefix
        )

        if self.spec_path:
            @bp.route(self.spec_path)
            def spec() -> ResponseReturnValueType:
                if self.config['SPEC_FORMAT'] == 'json':
                    response = jsonify(self._get_spec('json'))
                    response.mimetype = self.config['JSON_SPEC_MIMETYPE']
                    return response
                return self._get_spec('yaml'), 200, \
                    {'Content-Type': self.config['YAML_SPEC_MIMETYPE']}

        if self.docs_path:
            if self.docs_ui not in ui_templates:
                valid_values = list(ui_templates.keys())
                raise ValueError(
                    f'Invalid docs_ui value, expected one of {valid_values}, got "{self.docs_ui}".'
                )

            @bp.route(self.docs_path)
            def docs() -> str:
                return render_template_string(
                    ui_templates[self.docs_ui],
                    title=self.title,
                    version=self.version,
                    oauth2_redirect_path=self.docs_oauth2_redirect_path
                )

            if self.docs_ui == 'swagger-ui':
                if self.docs_oauth2_redirect_path:
                    @bp.route(self.docs_oauth2_redirect_path)
                    def swagger_ui_oauth_redirect() -> str:
                        return render_template_string(swagger_ui_oauth2_redirect_template)

        if self.redoc_path:
            @bp.route(self.redoc_path)
            def redoc() -> str:
                warnings.warn(
                    'The `/redoc` path and `redoc_path` parameter is deprecated '
                    'and will be removed in 2.0, Set `APIFlask(docs_ui="redoc")` '
                    'to use Redoc and then visit "/docs" instead.',
                    UserWarning,
                    stacklevel=2,
                )
                return render_template_string(
                    redoc_template,
                    title=self.title,
                    version=self.version
                )

        if self.enable_openapi and (
            self.spec_path or self.docs_path or self.redoc_path
        ):
            self.register_blueprint(bp)

    def _get_spec(
        self,
        spec_format: t.Optional[str] = None,
        force_update: bool = False
    ) -> t.Union[dict, str]:
        """Get the current OAS document file.

        This method will return the cached spec on the first call. If you want
        to get the latest spec, set the `force_update` to `True` or use the
        public attribute `app.spec`, which will always return the newly generated
        spec when you call it.

        If the config `SYNC_LOCAL_SPEC` is `True`, the local spec
        specified in config `LOCAL_SPEC_PATH` will be automatically updated
        when the spec changes.

        Arguments:
            spec_format: The format of the spec file, one of `'json'`, `'yaml'`
                and `'yml'`, defaults to the `SPEC_FORMAT` config.
            force_update: If ture, will generate the spec for every call instead
                of using the cache.

        *Version changed: 0.7.0*

        - The default format now rely on the `SPEC_FORMAT` config.
        - Support to sync local spec file.

        *Version changed: 0.7.1*

        - Rename the method name to `_get_spec`.
        - Add the `force_update` parameter.
        """
        if spec_format is None:
            spec_format = self.config['SPEC_FORMAT']
        if self._spec is None or force_update:
            if spec_format == 'json':
                self._spec = self._generate_spec().to_dict()
            else:
                self._spec = self._generate_spec().to_yaml()
            if self.spec_callback:
                self._spec = self.spec_callback(self._spec)  # type: ignore
        # sync local spec
        if self.config['SYNC_LOCAL_SPEC']:
            spec_path = self.config['LOCAL_SPEC_PATH']
            if spec_path is None:
                raise TypeError(
                    'The spec path (LOCAL_SPEC_PATH) should be a valid path string.'
                )
            spec: str
            if spec_format == 'json':
                spec = json.dumps(
                    self._spec, indent=self.config['LOCAL_SPEC_JSON_INDENT']
                )
            else:
                spec = str(self._spec)
            with open(spec_path, 'w') as f:
                f.write(spec)
        return self._spec  # type: ignore

    def spec_processor(self, f: SpecCallbackType) -> SpecCallbackType:
        """A decorator to register a spec handler callback function.

        You can register a function 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.

        Examples:

        ```python
        @app.spec_processor
        def update_spec(spec):
            spec['info']['title'] = 'Updated Title'
            return spec
        ```

        Notice the format of the spec is depends on the the value of configuration
        variable `SPEC_FORMAT` (defaults to `'json'`):

        - `'json'` -> dict
        - `'yaml'` -> string

        *Version Changed: 0.7.0*

        - Support registering an async callback function.
        """
        if hasattr(self, 'ensure_sync'):  # pragma: no cover
            self.spec_callback = self.ensure_sync(f)
        else:  # pragma: no cover
            self.spec_callback = f
        return f

    @property
    def spec(self) -> t.Union[dict, str]:
        """Get the current OAS document file.

        This property will call `app._get_spec()` method and set the
        `force_update` parameter to `True`.

        *Version changed: 0.7.1*

        - Generate the spec on every call.
        """
        return self._get_spec(force_update=True)

    @staticmethod
    def _schema_name_resolver(schema: t.Type[Schema]) -> str:
        """Default schema name resovler."""
        name = schema.__class__.__name__
        if name.endswith('Schema'):
            name = name[:-6] or name
        if schema.partial:
            name += 'Update'
        return name

    def _make_info(self) -> dict:
        """Make OpenAPI info object."""
        info: dict
        if self.info:
            info = self.info
        else:
            info = {}
        if self.contact:
            info['contact'] = self.contact
        if self.license:
            info['license'] = self.license
        if self.terms_of_service:
            info['termsOfService'] = self.terms_of_service
        if self.description:
            info['description'] = self.description
        return info

    def _make_tags(self) -> t.List[t.Dict[str, t.Any]]:
        """Make OpenAPI tags object."""
        tags: t.Optional[TagsType] = self.tags
        if tags is not None:
            # convert simple tags list into standard OpenAPI tags
            if isinstance(tags[0], str):
                for index, tag_name in enumerate(tags):
                    tags[index] = {'name': tag_name}  # type: ignore
        else:
            tags: t.List[t.Dict[str, t.Any]] = []  # type: ignore
            if self.config['AUTO_TAGS']:
                # auto-generate tags from blueprints
                for blueprint_name, blueprint in self.blueprints.items():
                    if blueprint_name == 'openapi' or \
                       not hasattr(blueprint, 'enable_openapi') or \
                       not blueprint.enable_openapi:  # type: ignore
                        continue
                    tag: t.Dict[str, t.Any] = get_tag(blueprint, blueprint_name)  # type: ignore
                    tags.append(tag)  # type: ignore
        return tags  # type: ignore

    def _collect_security_info(self) -> t.Tuple[t.List[str], t.List[HTTPAuthType]]:
        """Detect `auth_required` on blueprint before_request functions and view functions."""
        # security schemes
        auth_names: t.List[str] = []
        auth_schemes: t.List[HTTPAuthType] = []

        def _update_auth_info(auth: HTTPAuthType) -> None:
            # update auth_schemes and auth_names
            auth_schemes.append(auth)
            auth_name: str = get_auth_name(auth, auth_names)
            auth_names.append(auth_name)

        # collect auth info on blueprint before_request functions
        for blueprint_name, funcs in self.before_request_funcs.items():
            # skip app-level before_request functions (blueprint_name is None)
            if blueprint_name is None or \
               not self.blueprints[blueprint_name].enable_openapi:  # type: ignore
                continue
            for f in funcs:
                if hasattr(f, '_spec'):  # pragma: no cover
                    auth = f._spec.get('auth')  # type: ignore
                    if auth is not None and auth not in auth_schemes:
                        self._auth_blueprints[blueprint_name] = {
                            'auth': auth,
                            'roles': f._spec.get('roles')  # type: ignore
                        }
                        _update_auth_info(auth)
        # collect auth info on view functions
        for rule in self.url_map.iter_rules():
            view_func: ViewFuncType = self.view_functions[rule.endpoint]  # type: ignore
            if hasattr(view_func, '_spec'):
                auth = view_func._spec.get('auth')
                if auth is not None and auth not in auth_schemes:
                    _update_auth_info(auth)
            # method views
            if hasattr(view_func, '_method_spec'):
                for method_spec in view_func._method_spec.values():
                    auth = method_spec.get('auth')
                    if auth is not None and auth not in auth_schemes:
                        _update_auth_info(auth)

        return auth_names, auth_schemes

    def _generate_spec(self) -> APISpec:
        """Generate the spec, return an instance of `apispec.APISpec`.

        *Version changed: 0.10.0*

        - Add support for `operationId`.
        - Add support for response `links`.

        *Version changed: 0.9.0*

        - Add base response customization support.

        *Version changed: 0.8.0*

        - Add automatic 404 response support.
        """
        kwargs: dict = {}
        if self.servers:
            kwargs['servers'] = self.servers
        if self.external_docs:
            kwargs['externalDocs'] = self.external_docs

        ma_plugin: MarshmallowPlugin = MarshmallowPlugin(
            schema_name_resolver=self.schema_name_resolver
        )
        spec: APISpec = APISpec(
            title=self.title,
            version=self.version,
            openapi_version=self.config['OPENAPI_VERSION'],
            plugins=[ma_plugin],
            info=self._make_info(),
            tags=self._make_tags(),
            **kwargs
        )

        # configure flask-marshmallow URL types
        ma_plugin.converter.field_mapping[fields.URLFor] = ('string', 'url')  # type: ignore
        ma_plugin.converter.field_mapping[fields.AbsoluteURLFor] = (  # type: ignore
            'string', 'url'
        )
        if sqla is not None:  # pragma: no cover
            ma_plugin.converter.field_mapping[sqla.HyperlinkRelated] = (  # type: ignore
                'string', 'url'
            )

        auth_names, auth_schemes = self._collect_security_info()
        security, security_schemes = get_security_and_security_schemes(
            auth_names, auth_schemes
        )

        if self.config['SECURITY_SCHEMES'] is not None:
            security_schemes.update(self.config['SECURITY_SCHEMES'])

        for name, scheme in security_schemes.items():
            spec.components.security_scheme(name, scheme)

        # paths
        paths: t.Dict[str, t.Dict[str, t.Any]] = {}
        rules: t.List[t.Any] = sorted(
            list(self.url_map.iter_rules()), key=lambda rule: len(rule.rule)
        )
        for rule in rules:
            operations: t.Dict[str, t.Any] = {}
            view_func: ViewFuncType = self.view_functions[rule.endpoint]  # type: ignore
            # skip endpoints from openapi blueprint and the built-in static endpoint
            if rule.endpoint.startswith('openapi') or \
               rule.endpoint.startswith('static'):
                continue
            blueprint_name: t.Optional[str] = None  # type: ignore
            if '.' in rule.endpoint:
                blueprint_name: str = rule.endpoint.rsplit('.', 1)[0]  # type: ignore
                blueprint = self.blueprints.get(blueprint_name)  # type: ignore
                if blueprint is None:
                    # just a normal view with dots in its endpoint, reset blueprint_name
                    blueprint_name = None
                else:
                    if not hasattr(blueprint, 'enable_openapi') or \
                       not blueprint.enable_openapi:  # type: ignore
                        continue
            # add a default 200 response for bare views
            if not hasattr(view_func, '_spec'):
                if self.config['AUTO_200_RESPONSE']:
                    view_func._spec = {'response': default_response}
                else:
                    continue  # pragma: no cover
            # method views
            if hasattr(view_func, '_method_spec'):
                skip = True
                for method, method_spec in view_func._method_spec.items():
                    if method_spec.get('no_spec'):
                        if self.config['AUTO_200_RESPONSE']:
                            view_func._method_spec[method]['response'] = default_response
                            skip = False
                    else:
                        skip = False
                if skip:
                    continue
            # skip views flagged with @app.doc(hide=True)
            if view_func._spec.get('hide'):
                continue

            # operation tags
            operation_tags: t.Optional[t.List[str]] = None
            if view_func._spec.get('tags'):
                operation_tags = view_func._spec.get('tags')
            else:
                # use blueprint name as tag
                if self.tags is None and self.config['AUTO_TAGS'] and \
                   blueprint_name is not None:
                    blueprint = self.blueprints[blueprint_name]
                    operation_tags = \
                        get_operation_tags(blueprint, blueprint_name)  # type: ignore

            for method in ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']:
                if method not in rule.methods:
                    continue
                # method views
                if hasattr(view_func, '_method_spec'):
                    if method not in view_func._method_spec:
                        continue  # pragma: no cover
                    view_func._spec = view_func._method_spec[method]

                    if view_func._spec.get('no_spec') and \
                       not self.config['AUTO_200_RESPONSE']:
                        continue
                    if view_func._spec.get('generated_summary') and \
                       not self.config['AUTO_OPERATION_SUMMARY']:
                        view_func._spec['summary'] = ''
                    if view_func._spec.get('generated_description') and \
                       not self.config['AUTO_OPERATION_DESCRIPTION']:
                        view_func._spec['description'] = ''
                    if view_func._spec.get('hide'):
                        continue
                    if view_func._spec.get('tags'):
                        operation_tags = view_func._spec.get('tags')
                    else:
                        if self.tags is None and self.config['AUTO_TAGS'] and \
                           blueprint_name is not None:
                            blueprint = self.blueprints[blueprint_name]
                            operation_tags = \
                                get_operation_tags(blueprint, blueprint_name)  # type: ignore

                operation: t.Dict[str, t.Any] = {
                    'parameters': [
                        {'in': location, 'schema': schema}
                        for schema, location in view_func._spec.get('args', [])
                    ],
                    'responses': {},
                }
                if operation_tags:
                    operation['tags'] = operation_tags

                # summary
                if view_func._spec.get('summary'):
                    operation['summary'] = view_func._spec.get('summary')
                else:
                    # auto-generate summary from dotstring or view function name
                    if self.config['AUTO_OPERATION_SUMMARY']:
                        operation['summary'] = get_path_summary(view_func)  # type: ignore

                # description
                if view_func._spec.get('description'):
                    operation['description'] = view_func._spec.get('description')
                else:
                    # auto-generate description from dotstring
                    if self.config['AUTO_OPERATION_DESCRIPTION']:
                        docs = [
                            line.strip() for line in (view_func.__doc__ or '').strip().split('\n')
                        ]
                        if len(docs) > 1:
                            # use the remain lines of docstring as description
                            operation['description'] = '\n'.join(docs[1:]).strip()

                # deprecated
                if view_func._spec.get('deprecated'):
                    operation['deprecated'] = view_func._spec.get('deprecated')

                # operationId
                operation_id = view_func._spec.get('operation_id')
                if operation_id is None:
                    if self.config['AUTO_OPERATION_ID']:
                        operation['operationId'] = \
                            f"{method.lower()}_{rule.endpoint.replace('.', '_')}"
                else:
                    operation['operationId'] = operation_id

                # responses
                if view_func._spec.get('response'):
                    schema = view_func._spec.get('response')['schema']
                    base_schema: OpenAPISchemaType = self.config['BASE_RESPONSE_SCHEMA']
                    if base_schema is not None:
                        base_schema_spec: dict
                        if isinstance(base_schema, type):
                            base_schema_spec = \
                                ma_plugin.converter.schema2jsonschema(  # type: ignore
                                    base_schema()
                                )
                        elif isinstance(base_schema, dict):
                            base_schema_spec = base_schema
                        else:
                            raise TypeError(_bad_schema_message)
                        data_key: str = self.config['BASE_RESPONSE_DATA_KEY']
                        if data_key not in base_schema_spec['properties']:
                            raise RuntimeError(
                                f'The data key "{data_key}" is not found in'
                                ' the base response schema spec.'
                            )
                        base_schema_spec['properties'][data_key] = schema
                        schema = base_schema_spec

                    status_code: str = str(view_func._spec.get('response')['status_code'])
                    description: str = view_func._spec.get('response')['description'] or \
                        self.config['SUCCESS_DESCRIPTION']
                    example = view_func._spec.get('response')['example']
                    examples = view_func._spec.get('response')['examples']
                    links = view_func._spec.get('response')['links']
                    add_response(
                        operation, status_code, schema, description, example, examples, links
                    )
                else:
                    # add a default 200 response for views without using @app.output
                    # or @app.doc(responses={...})
                    if not view_func._spec.get('responses') and self.config['AUTO_200_RESPONSE']:
                        add_response(
                            operation, '200', {}, self.config['SUCCESS_DESCRIPTION']
                        )

                # add validation error response
                if self.config['AUTO_VALIDATION_ERROR_RESPONSE'] and \
                   (view_func._spec.get('body') or view_func._spec.get('args')):
                    status_code: str = str(  # type: ignore
                        self.config['VALIDATION_ERROR_STATUS_CODE']
                    )
                    description: str = self.config[  # type: ignore
                        'VALIDATION_ERROR_DESCRIPTION'
                    ]
                    schema: SchemaType = self.config['VALIDATION_ERROR_SCHEMA']  # type: ignore
                    add_response_with_schema(
                        spec, operation, status_code, schema, 'ValidationError', description
                    )

                # add authentication error response
                has_bp_level_auth = blueprint_name is not None and \
                    blueprint_name in self._auth_blueprints
                view_func_auth = view_func._spec.get('auth')
                custom_security = view_func._spec.get('security')
                if self.config['AUTO_AUTH_ERROR_RESPONSE'] and \
                   (has_bp_level_auth or view_func_auth or custom_security):
                    status_code: str = str(  # type: ignore
                        self.config['AUTH_ERROR_STATUS_CODE']
                    )
                    description: str = self.config['AUTH_ERROR_DESCRIPTION']  # type: ignore
                    schema: SchemaType = self.config['HTTP_ERROR_SCHEMA']  # type: ignore
                    add_response_with_schema(
                        spec, operation, status_code, schema, 'HTTPError', description
                    )

                # add 404 error response
                if self.config['AUTO_404_RESPONSE'] and rule.arguments:
                    description: str = self.config['NOT_FOUND_DESCRIPTION']  # type: ignore
                    schema: SchemaType = self.config['HTTP_ERROR_SCHEMA']  # type: ignore
                    add_response_with_schema(
                        spec, operation, '404', schema, 'HTTPError', description
                    )

                if view_func._spec.get('responses'):
                    responses: t.Union[t.List[int], t.Dict[int, str]] \
                        = view_func._spec.get('responses')
                    if isinstance(responses, list):
                        responses: t.Dict[int, str] = {}  # type: ignore
                        for status_code in view_func._spec.get('responses'):
                            responses[  # type: ignore
                                status_code
                            ] = get_reason_phrase(int(status_code), '')
                    for status_code, description in responses.items():  # type: ignore
                        status_code: str = str(status_code)  # type: ignore
                        if status_code in operation['responses']:
                            if not isinstance(
                                view_func._spec.get('responses'), list
                            ):  # pragma: no cover
                                operation['responses'][status_code]['description'] = description
                            continue
                        if status_code.startswith('4') or status_code.startswith('5'):
                            # add error response schema for error responses
                            schema: SchemaType = self.config['HTTP_ERROR_SCHEMA']  # type: ignore
                            add_response_with_schema(
                                spec, operation, status_code, schema, 'HTTPError', description
                            )
                        else:
                            add_response(operation, status_code, {}, description)

                # requestBody
                if view_func._spec.get('body'):
                    content_type = view_func._spec.get('content_type', 'application/json')
                    operation['requestBody'] = {
                        'content': {
                            content_type: {
                                'schema': view_func._spec['body'],
                            }
                        }
                    }
                    if view_func._spec.get('body_example'):
                        example = view_func._spec.get('body_example')
                        operation['requestBody']['content'][
                            content_type]['example'] = example
                    if view_func._spec.get('body_examples'):
                        examples = view_func._spec.get('body_examples')
                        operation['requestBody']['content'][
                            content_type]['examples'] = examples

                # security
                if custom_security:  # custom security
                    # TODO: validate the security name and the format
                    operation['security'] = []
                    operation_security = custom_security
                    if isinstance(operation_security, str):  # 'A' -> [{'A': []}]
                        operation['security'] = [{operation_security: []}]
                    elif isinstance(operation_security, list):
                        # ['A', 'B'] -> [{'A': []}, {'B': []}]
                        if isinstance(operation_security[0], str):
                            operation['security'] = [{name: []} for name in operation_security]
                        else:
                            operation['security'] = operation_security
                    else:
                        raise ValueError(
                            'The operation security must be a string or a list.'
                        )
                else:
                    if has_bp_level_auth:
                        bp_auth_info = self._auth_blueprints[blueprint_name]  # type: ignore
                        operation['security'] = [{
                            security[bp_auth_info['auth']]: bp_auth_info['roles']
                        }]

                    # view-wide auth
                    if view_func_auth:
                        operation['security'] = [{
                            security[view_func_auth]: view_func._spec['roles']
                        }]

                operations[method.lower()] = operation

            # parameters
            path_arguments: t.Iterable = re.findall(r'<(([^<:]+:)?([^>]+))>', rule.rule)
            if path_arguments:
                arguments: t.List[t.Dict[str, str]] = []
                for _, argument_type, argument_name in path_arguments:
                    argument = get_argument(argument_type, argument_name)
                    arguments.append(argument)

                for _method, operation in operations.items():
                    operation['parameters'] = arguments + operation['parameters']

            path: str = re.sub(r'<([^<:]+:)?', '{', rule.rule).replace('>', '}')
            if path not in paths:
                paths[path] = operations
            else:
                paths[path].update(operations)

        for path, operations in paths.items():
            # sort by method before adding them to the spec
            sorted_operations: t.Dict[str, t.Any] = {}
            for method in ['get', 'post', 'put', 'patch', 'delete']:
                if method in operations:
                    sorted_operations[method] = operations[method]
            # TODO: remove the type ignore comment when apispec 5.3.0 released
            spec.path(path=path, operations=sorted_operations)  # type: ignore

        return spec

spec: Union[dict, str] property readonly

Get the current OAS document file.

This property will call app._get_spec() method and set the force_update parameter to True.

Version changed: 0.7.1

  • Generate the spec on every call.

__init__(self, import_name, title='APIFlask', version='0.1.0', spec_path='/openapi.json', docs_path='/docs', docs_oauth2_redirect_path='/docs/oauth2-redirect', docs_ui='swagger-ui', redoc_path='/redoc', openapi_blueprint_url_prefix=None, json_errors=True, enable_openapi=True, static_url_path=None, static_folder='static', static_host=None, host_matching=False, subdomain_matching=False, template_folder='templates', instance_path=None, instance_relative_config=False, root_path=None) special

Make an app instance.

Parameters:

Name Type Description Default
import_name str

The name of the application package, usually __name__. This helps locate the root_path for the application.

required
title str

The title of the API (openapi.info.title), defaults to "APIFlask". You can change it to the name of your API (e.g., "Pet API").

'APIFlask'
version str

The version of the API (openapi.info.version), defaults to "0.1.0".

'0.1.0'
spec_path str

The path to OpenAPI Spec documentation. It defaults to /openapi.json, if the path ends with .yaml or .yml, the YAML format of the OAS will be returned.

'/openapi.json'
docs_path str

The path to API UI documentation, defaults to /docs.

'/docs'
docs_ui str

The UI of API documentation, one of swagger-ui (default), redoc, elements, rapidoc, and rapipdf.

'swagger-ui'
docs_oauth2_redirect_path str

The path to Swagger UI OAuth redirect.

'/docs/oauth2-redirect'
redoc_path str

Deprecated since 1.1, set APIFlask(docs_ui='redoc') to use Redoc. The path to Redoc documentation, defaults to /redoc.

'/redoc'
openapi_blueprint_url_prefix Optional[str]

The url prefix of the OpenAPI blueprint. This prefix will append before all the OpenAPI-related paths (sepc_path, docs_path, etc.), defaults to None.

None
json_errors bool

If True, APIFlask will return a JSON response for HTTP errors.

True
enable_openapi bool

If False, will disable OpenAPI spec and API docs views.

True

Other keyword arguments are directly passed to flask.Flask.

Version changed: 1.1.0

  • Add docs_ui parameter.

Version changed: 0.7.0

  • Add openapi_blueprint_url_prefix parameter.
Source code in apiflask/app.py
def __init__(
    self,
    import_name: str,
    title: str = 'APIFlask',
    version: str = '0.1.0',
    spec_path: str = '/openapi.json',
    docs_path: str = '/docs',
    docs_oauth2_redirect_path: str = '/docs/oauth2-redirect',
    docs_ui: str = 'swagger-ui',
    redoc_path: str = '/redoc',
    openapi_blueprint_url_prefix: t.Optional[str] = None,
    json_errors: bool = True,
    enable_openapi: bool = True,
    static_url_path: t.Optional[str] = None,
    static_folder: str = 'static',
    static_host: t.Optional[str] = None,
    host_matching: bool = False,
    subdomain_matching: bool = False,
    template_folder: str = 'templates',
    instance_path: t.Optional[str] = None,
    instance_relative_config: bool = False,
    root_path: t.Optional[str] = None
) -> None:
    """Make an app instance.

    Arguments:
        import_name: The name of the application package, usually
            `__name__`. This helps locate the `root_path` for the
            application.
        title: The title of the API (openapi.info.title), defaults to "APIFlask".
            You can change it to the name of your API (e.g., "Pet API").
        version: The version of the API (openapi.info.version), defaults to "0.1.0".
        spec_path: The path to OpenAPI Spec documentation. It
            defaults to `/openapi.json`, if the path ends with `.yaml`
            or `.yml`, the YAML format of the OAS will be returned.
        docs_path: The path to API UI documentation, defaults to `/docs`.
        docs_ui: The UI of API documentation, one of `swagger-ui` (default), `redoc`,
            `elements`, `rapidoc`, and `rapipdf`.
        docs_oauth2_redirect_path: The path to Swagger UI OAuth redirect.
        redoc_path: Deprecated since 1.1, set `APIFlask(docs_ui='redoc')` to use Redoc.
            The path to Redoc documentation, defaults to `/redoc`.
        openapi_blueprint_url_prefix: The url prefix of the OpenAPI blueprint. This
            prefix will append before all the OpenAPI-related paths (`sepc_path`,
            `docs_path`, etc.), defaults to `None`.
        json_errors: If `True`, APIFlask will return a JSON response for HTTP errors.
        enable_openapi: If `False`, will disable OpenAPI spec and API docs views.

    Other keyword arguments are directly passed to `flask.Flask`.

    *Version changed: 1.1.0*

    - Add `docs_ui` parameter.

    *Version changed: 0.7.0*

    - Add `openapi_blueprint_url_prefix` parameter.
    """
    super().__init__(
        import_name,
        static_url_path=static_url_path,
        static_folder=static_folder,
        static_host=static_host,
        host_matching=host_matching,
        subdomain_matching=subdomain_matching,
        template_folder=template_folder,
        instance_path=instance_path,
        instance_relative_config=instance_relative_config,
        root_path=root_path
    )

    # Set default config
    self.config.from_object('apiflask.settings')

    self.title = title
    self.version = version
    self.spec_path = spec_path
    self.docs_ui = docs_ui
    self.docs_path = docs_path
    self.redoc_path = redoc_path
    self.docs_oauth2_redirect_path = docs_oauth2_redirect_path
    self.openapi_blueprint_url_prefix = openapi_blueprint_url_prefix
    self.enable_openapi = enable_openapi
    self.json_errors = json_errors

    self.spec_callback: t.Optional[SpecCallbackType] = None
    self.error_callback: ErrorCallbackType = self._error_handler
    self.schema_name_resolver = self._schema_name_resolver

    self._spec: t.Optional[t.Union[dict, str]] = None
    self._auth_blueprints: t.Dict[str, t.Dict[str, t.Any]] = {}

    self._register_openapi_blueprint()
    self._register_error_handlers()

add_url_rule(self, rule, endpoint=None, view_func=None, **options)

Record the spec for view classes before calling the actual add_url_rule method.

When calling this method directly, the view_func argument can be a view function or a view function created by ViewClass.as_view(). It only accepts a view class when using the route decorator on a view class.

Source code in apiflask/app.py
def add_url_rule(
    self,
    rule: str,
    endpoint: t.Optional[str] = None,
    view_func: t.Optional[ViewFuncOrClassType] = None,
    **options: t.Any,
):
    """Record the spec for view classes before calling the actual `add_url_rule` method.

    When calling this method directly, the `view_func` argument can be a view function or
    a view function created by `ViewClass.as_view()`. It only accepts a view class when
    using the route decorator on a view class.
    """
    view_class: ViewClassType
    is_view_class: bool = False

    if hasattr(view_func, 'view_class'):
        # a function returned by MethodViewClass.as_view()
        is_view_class = True
        view_class = view_func.view_class  # type: ignore
    elif isinstance(view_func, MethodViewType):
        # a MethodView class passed with the route decorator
        is_view_class = True
        view_class = view_func  # type: ignore
        if endpoint is None:
            endpoint = view_class.__name__
        view_func = view_class.as_view(endpoint)

    if is_view_class and hasattr(self, 'enable_openapi') and self.enable_openapi:
        view_func = record_spec_for_view_class(view_func, view_class)  # type: ignore

    super(cls, self).add_url_rule(rule, endpoint, view_func, **options)

dispatch_request(self)

Overwrite the default dispatch method in Flask.

With this overwrite, view arguments are passed as positional arguments so that the view function can intuitively accept the parameters (i.e., from top to bottom, from left to right).

Examples:

@app.get('/pets/<name>/<int:pet_id>/<age>')  # -> name, pet_id, age
@app.input(QuerySchema)  # -> query
@app.output(PetSchema)  # -> pet
def get_pet(name, pet_id, age, query, pet):
    pass

From Flask, see the NOTICE file for license information.

Version added: 0.2.0

Source code in apiflask/app.py
def dispatch_request(self) -> ResponseReturnValueType:  # type: ignore
    """Overwrite the default dispatch method in Flask.

    With this overwrite, view arguments are passed as positional
    arguments so that the view function can intuitively accept the
    parameters (i.e., from top to bottom, from left to right).

    Examples:

    ```python
    @app.get('/pets/<name>/<int:pet_id>/<age>')  # -> name, pet_id, age
    @app.input(QuerySchema)  # -> query
    @app.output(PetSchema)  # -> pet
    def get_pet(name, pet_id, age, query, pet):
        pass
    ```

    From Flask, see the NOTICE file for license information.

    *Version added: 0.2.0*
    """
    req = _request_ctx_stack.top.request
    if req.routing_exception is not None:
        self.raise_routing_exception(req)
    rule = req.url_rule
    # if we provide automatic options for this URL and the
    # request came with the OPTIONS method, reply automatically
    if (  # pragma: no cover
        getattr(rule, 'provide_automatic_options', False)
        and req.method == 'OPTIONS'
    ):
        return self.make_default_options_response()  # pragma: no cover
    # otherwise dispatch to the handler for that endpoint
    view_function = self.view_functions[rule.endpoint]
    if hasattr(self, 'ensure_sync'):  # pragma: no cover
        view_function = self.ensure_sync(view_function)
    if rule.endpoint == 'static':
        # app static route only accepts keyword arguments, see flask#3762
        return view_function(**req.view_args)  # type: ignore
    else:
        return view_function(*req.view_args.values())  # type: ignore

error_processor(self, f)

A decorator to register a custom error response processor function.

The decorated callback function will be called in the following situations:

  • Any HTTP exception is raised by Flask when handling request.
  • A validation error happened when parsing a request.
  • An exception triggered with HTTPError
  • An exception triggered with abort.

You can still register a specific error handler for a specific error code or exception with the app.errorhandler(code_or_exection) decorator, in that case, the return value of the specific error handler will be used as the response when the corresponding error or exception happened.

The callback function must accept an error object as argument and return a valid response.

Examples:

@app.error_processor
def my_error_processor(error):
    return {
        'status_code': error.status_code,
        'message': error.message,
        'detail': error.detail,
        **error.extra_data
    }, error.status_code, error.headers

The error object is an instance of HTTPError, so you can get error information via it's attributes:

  • status_code: If the error is triggered by a validation error, the value will be 400 (default) or the value you passed in config VALIDATION_ERROR_STATUS_CODE. If the error is triggered by HTTPError or abort, it will be the status code you passed. Otherwise, it will be the status code set by Werkzueg when processing the request.
  • message: The error description for this error, either you passed or grabbed from Werkzeug.
  • detail: The detail of the error. When the validation error happens, it will be filled automatically in the following structure:

    "<location>": {
        "<field_name>": ["<error_message>", ...],
        "<field_name>": ["<error_message>", ...],
        ...
    },
    "<location>": {
        ...
    },
    ...
    

The value of location can be json (i.e., request body) or query (i.e., query string) depending on the place where the validation error happened. - headers: The value will be {} unless you pass it in HTTPError or abort. - extra_data: Additional error information.

If you want, you can rewrite the whole response body to anything you like:

@app.error_processor
def my_error_processor(error):
    body = {'error_detail': error.detail, **error.extra_data}
    return body, error.status_code, error.headers

However, I would recommend keeping the detail in the response since it contains the detailed information about the validation error when the validation error happened.

Version changed: 1.0

  • Apply this error processor to normal HTTP errors even when json_error is set to False when creating APIFlask instance.

Version changed: 0.7.0

  • Support registering an async callback function.
Source code in apiflask/app.py
def error_processor(
    self,
    f: ErrorCallbackType
) -> ErrorCallbackType:
    """A decorator to register a custom error response processor function.

    The decorated callback function will be called in the following situations:

    - Any HTTP exception is raised by Flask when handling request.
    - A validation error happened when parsing a request.
    - An exception triggered with [`HTTPError`][apiflask.exceptions.HTTPError]
    - An exception triggered with [`abort`][apiflask.exceptions.abort].

    You can still register a specific error handler for a specific error code
    or exception with the `app.errorhandler(code_or_exection)` decorator,
    in that case, the return value of the specific error handler will be used as the
    response when the corresponding error or exception happened.

    The callback function must accept an error object as argument and return a valid
    response.

    Examples:

    ```python
    @app.error_processor
    def my_error_processor(error):
        return {
            'status_code': error.status_code,
            'message': error.message,
            'detail': error.detail,
            **error.extra_data
        }, error.status_code, error.headers
    ```

    The error object is an instance of [`HTTPError`][apiflask.exceptions.HTTPError],
    so you can get error information via it's attributes:

    - status_code: If the error is triggered by a validation error, the value will be
      400 (default) or the value you passed in config `VALIDATION_ERROR_STATUS_CODE`.
      If the error is triggered by [`HTTPError`][apiflask.exceptions.HTTPError]
      or [`abort`][apiflask.exceptions.abort], it will be the status code
      you passed. Otherwise, it will be the status code set by Werkzueg when
      processing the request.
    - message: The error description for this error, either you passed or grabbed from
      Werkzeug.
    - detail: The detail of the error. When the validation error happens, it will
      be filled automatically in the following structure:

        ```python
        "<location>": {
            "<field_name>": ["<error_message>", ...],
            "<field_name>": ["<error_message>", ...],
            ...
        },
        "<location>": {
            ...
        },
        ...
        ```

      The value of `location` can be `json` (i.e., request body) or `query`
      (i.e., query string) depending on the place where the validation error
      happened.
    - headers: The value will be `{}` unless you pass it in `HTTPError` or `abort`.
    - extra_data: Additional error information.

    If you want, you can rewrite the whole response body to anything you like:

    ```python
    @app.error_processor
    def my_error_processor(error):
        body = {'error_detail': error.detail, **error.extra_data}
        return body, error.status_code, error.headers
    ```

    However, I would recommend keeping the `detail` in the response since it contains
    the detailed information about the validation error when the validation error
    happened.

    *Version changed: 1.0*

    - Apply this error processor to normal HTTP errors even when
      `json_error` is set to `False` when creating `APIFlask` instance.

    *Version changed: 0.7.0*

    - Support registering an async callback function.
    """
    if hasattr(self, 'ensure_sync'):  # pragma: no cover
        self.error_callback = self.ensure_sync(f)
    else:  # pragma: no cover
        self.error_callback = f
    self._apply_error_callback_to_werkzeug_errors()
    return f

make_response(self, rv)

Patch the make_response form Flask to allow returning list as JSON.

Version added: 1.1.0

Source code in apiflask/app.py
def make_response(self, rv) -> Response:
    """Patch the make_response form Flask to allow returning list as JSON.

    *Version added: 1.1.0*
    """
    if isinstance(rv, list):
        rv = jsonify(rv)
    elif isinstance(rv, tuple) and isinstance(rv[0], list):
        rv = (jsonify(rv[0]), *rv[1:])
    return super().make_response(rv)

spec_processor(self, f)

A decorator to register a spec handler callback function.

You can register a function 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.

Examples:

@app.spec_processor
def update_spec(spec):
    spec['info']['title'] = 'Updated Title'
    return spec

Notice the format of the spec is depends on the the value of configuration variable SPEC_FORMAT (defaults to 'json'):

  • 'json' -> dict
  • 'yaml' -> string

Version Changed: 0.7.0

  • Support registering an async callback function.
Source code in apiflask/app.py
def spec_processor(self, f: SpecCallbackType) -> SpecCallbackType:
    """A decorator to register a spec handler callback function.

    You can register a function 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.

    Examples:

    ```python
    @app.spec_processor
    def update_spec(spec):
        spec['info']['title'] = 'Updated Title'
        return spec
    ```

    Notice the format of the spec is depends on the the value of configuration
    variable `SPEC_FORMAT` (defaults to `'json'`):

    - `'json'` -> dict
    - `'yaml'` -> string

    *Version Changed: 0.7.0*

    - Support registering an async callback function.
    """
    if hasattr(self, 'ensure_sync'):  # pragma: no cover
        self.spec_callback = self.ensure_sync(f)
    else:  # pragma: no cover
        self.spec_callback = f
    return f

APIScaffold

A base class for APIFlask and APIBlueprint.

This class contains the route shortcut decorators (i.e. get, post, etc.) and API-related decorators (i.e. auth_required, input, output, doc).

Version added: 1.0

Source code in apiflask/scaffold.py
class APIScaffold:
    """A base class for [`APIFlask`][apiflask.app.APIFlask] and
    [`APIBlueprint`][apiflask.blueprint.APIBlueprint].

    This class contains the route shortcut decorators (i.e. `get`, `post`, etc.) and
    API-related decorators (i.e. `auth_required`, `input`, `output`, `doc`).

    *Version added: 1.0*
    """
    def _method_route(self, method: str, rule: str, options: t.Any):
        if 'methods' in options:
            raise RuntimeError('Use the "route" decorator to use the "methods" argument.')

        def decorator(f):
            if isinstance(f, MethodViewType):
                raise RuntimeError(
                    'The route shortcuts cannot be used with "MethodView" classes, '
                    'use the "route" decorator instead.'
                )
            return self.route(rule, methods=[method], **options)(f)
        return decorator

    def get(self, rule: str, **options: t.Any):
        """Shortcut for `app.route()` or `app.route(methods=['GET'])`."""
        return self._method_route('GET', rule, options)

    def post(self, rule: str, **options: t.Any):
        """Shortcut for `app.route(methods=['POST'])`."""
        return self._method_route('POST', rule, options)

    def put(self, rule: str, **options: t.Any):
        """Shortcut for `app.route(methods=['PUT'])`."""
        return self._method_route('PUT', rule, options)

    def patch(self, rule: str, **options: t.Any):
        """Shortcut for `app.route(methods=['PATCH'])`."""
        return self._method_route('PATCH', rule, options)

    def delete(self, rule: str, **options: t.Any):
        """Shortcut for `app.route(methods=['DELETE'])`."""
        return self._method_route('DELETE', rule, options)

    def auth_required(
        self,
        auth: HTTPAuthType,
        role: t.Optional[str] = None,
        roles: t.Optional[list] = None,
        optional: t.Optional[str] = None
    ) -> t.Callable[[DecoratedType], DecoratedType]:
        """Protect a view with provided authentication settings.

        > Be sure to put it under the routes decorators (i.e., `app.route`, `app.get`,
        `app.post`, etc.).

        Examples:

        ```python
        from apiflask import APIFlask, HTTPTokenAuth

        app = APIFlask(__name__)
        auth = HTTPTokenAuth()

        @app.get('/')
        @app.auth_required(auth)
        def hello():
            return 'Hello'!
        ```

        Arguments:
            auth: The `auth` object, an instance of
                [`HTTPBasicAuth`][apiflask.security.HTTPBasicAuth]
                or [`HTTPTokenAuth`][apiflask.security.HTTPTokenAuth].
            role: Deprecated since 1.0, use `roles` instead.
            roles: The selected roles to allow to visit this view, accepts a list of role names.
                See [Flask-HTTPAuth's documentation][_role]{target:_blank} for more details.
                [_role]: https://flask-httpauth.readthedocs.io/en/latest/#user-roles
            optional: Set to `True` to allow the view to execute even the authentication
                information is not included with the request, in which case the attribute
                `auth.current_user` will be `None`.

        *Version changed: 1.0.0*

        - The `role` parameter is deprecated.

        *Version changed: 0.12.0*

        - Move to `APIFlask` and `APIBlueprint` classes.

        *Version changed: 0.4.0*

        - Add parameter `roles`.
        """
        _roles = None
        if role is not None:
            warnings.warn(
                'The `role` parameter is deprecated and will be removed in 1.1, '
                'use `roles` and always pass a list instead.',
                DeprecationWarning,
                stacklevel=3,
            )
            _roles = [role]
        elif roles is not None:
            _roles = roles

        def decorator(f):
            f = _ensure_sync(f)
            _annotate(f, auth=auth, roles=_roles or [])
            return auth.login_required(role=_roles, optional=optional)(f)
        return decorator

    def input(
        self,
        schema: SchemaType,
        location: str = 'json',
        schema_name: t.Optional[str] = None,
        example: t.Optional[t.Any] = None,
        examples: t.Optional[t.Dict[str, t.Any]] = None,
        **kwargs: t.Any
    ) -> t.Callable[[DecoratedType], DecoratedType]:
        """Add input settings for view functions.

        > Be sure to put it under the routes decorators (i.e., `app.route`, `app.get`,
        `app.post`, etc.).

        If the validation passed, the data will inject into view
        function as a positional argument in the form of `dict`. Otherwise,
        an error response with the detail of the validation result will be returned.

        Examples:

        ```python
        from apiflask import APIFlask

        app = APIFlask(__name__)

        @app.get('/')
        @app.input(PetInSchema)
        def hello(parsed_and_validated_input_data):
            print(parsed_and_validated_input_data)
            return 'Hello'!
        ```

        Arguments:
            schema: The marshmallow schema of the input data.
            location: The location of the input data, one of `'json'` (default),
                `'files'`, `'form'`, `'cookies'`, `'headers'`, `'query'`
                (same as `'querystring'`).
            schema_name: The schema name for dict schema, only needed when you pass
                a schema dict (e.g., `{'name': String(required=True)}`) for `json`
                location.
            example: The example data in dict for request body, you should use either
                `example` or `examples`, not both.
            examples: Multiple examples for request body, you should pass a dict
                that contains multiple examples. Example:

                ```python
                {
                    'example foo': {  # example name
                        'summary': 'an example of foo',  # summary field is optional
                        'value': {'name': 'foo', 'id': 1}  # example value
                    },
                    'example bar': {
                        'summary': 'an example of bar',
                        'value': {'name': 'bar', 'id': 2}
                    },
                }
                ```

        *Version changed: 1.0*

        - Ensure only one input body location was used.
        - Add `form_and_files` and `json_or_form` (from webargs) location.
        - Rewrite `files` to act as `form_and_files`.
        - Use correct request content type for `form` and `files`.

        *Version changed: 0.12.0*

        - Move to APIFlask and APIBlueprint classes.

        *Version changed: 0.4.0*

        - Add parameter `examples`.
        """
        if isinstance(schema, ABCMapping):
            schema = _generate_schema_from_mapping(schema, schema_name)
        if isinstance(schema, type):  # pragma: no cover
            schema = schema()

        def decorator(f):
            f = _ensure_sync(f)
            is_body_location = location in BODY_LOCATIONS
            if is_body_location and hasattr(f, '_spec') and 'body' in f._spec:
                raise RuntimeError(
                    'When using the app.input() decorator, you can only declare one request '
                    'body location (one of "json", "form", "files", "form_and_files", '
                    'and "json_or_form").'
                )
            if location not in SUPPORTED_LOCATIONS:
                raise ValueError(
                    'Unknown input location. The supported locations are: "json", "files",'
                    ' "form", "cookies", "headers", "query" (same as "querystring"), "path"'
                    f' (same as "view_args") and "form_and_files". Got "{location}" instead.'
                )
            if location == 'json':
                _annotate(f, body=schema, body_example=example, body_examples=examples)
            elif location == 'form':
                _annotate(
                    f,
                    body=schema,
                    body_example=example,
                    body_examples=examples,
                    content_type='application/x-www-form-urlencoded'
                )
            elif location in ['files', 'form_and_files']:
                _annotate(
                    f,
                    body=schema,
                    body_example=example,
                    body_examples=examples,
                    content_type='multipart/form-data'
                )
            else:
                if not hasattr(f, '_spec') or f._spec.get('args') is None:
                    _annotate(f, args=[])
                # TODO: Support set example for request parameters
                f._spec['args'].append((schema, location))
            return use_args(schema, location=location, **kwargs)(f)
        return decorator

    def output(
        self,
        schema: SchemaType,
        status_code: int = 200,
        description: t.Optional[str] = None,
        schema_name: t.Optional[str] = None,
        example: t.Optional[t.Any] = None,
        examples: t.Optional[t.Dict[str, t.Any]] = None,
        links: t.Optional[t.Dict[str, t.Any]] = None,
    ) -> t.Callable[[DecoratedType], DecoratedType]:
        """Add output settings for view functions.

        > Be sure to put it under the routes decorators (i.e., `app.route`, `app.get`,
        `app.post`, etc.).

        The decorator will format the return value of your view function with
        provided marshmallow schema. You can return a dict or an object (such
        as a model class instance of ORMs). APIFlask will handle the formatting
        and turn your return value into a JSON response.

        P.S. The output data will not be validated; it's a design choice of marshmallow.
        marshmallow 4.0 may be support the output validation.

        Examples:

        ```python
        from apiflask import APIFlask

        app = APIFlask(__name__)

        @app.get('/')
        @app.output(PetOutSchema)
        def hello():
            return the_dict_or_object_match_petout_schema
        ```

        Arguments:
            schema: The schemas of the output data.
            status_code: The status code of the response, defaults to `200`.
            description: The description of the response.
            schema_name: The schema name for dict schema, only needed when you pass
                a schema dict (e.g., `{'name': String()}`).
            example: The example data in dict for response body, you should use either
                `example` or `examples`, not both.
            examples: Multiple examples for response body, you should pass a dict
                that contains multiple examples. Example:

                ```python
                {
                    'example foo': {  # example name
                        'summary': 'an example of foo',  # summary field is optional
                        'value': {'name': 'foo', 'id': 1}  # example value
                    },
                    'example bar': {
                        'summary': 'an example of bar',
                        'value': {'name': 'bar', 'id': 2}
                    },
                }
                ```
            links: The `links` of response. It accepts a dict which maps a link name to
                a link object. Example:

                ```python
                {
                    'getAddressByUserId': {
                        'operationId': 'getUserAddress',
                        'parameters': {
                            'userId': '$request.path.id'
                        }
                    }
                }
                ```

                See the [docs](https://apiflask.com/openapi/#response-links) for more details
                about setting response links.

        *Version changed: 0.12.0*

        - Move to APIFlask and APIBlueprint classes.

        *Version changed: 0.10.0*

        - Add `links` parameter.

        *Version changed: 0.9.0*

        - Add base response customization support.

        *Version changed: 0.6.0*

        - Support decorating async views.

        *Version changed: 0.5.2*

        - Return the `Response` object directly.

        *Version changed: 0.4.0*

        - Add parameter `examples`.
        """
        if schema == {}:
            schema = EmptySchema
        if isinstance(schema, ABCMapping):
            schema = _generate_schema_from_mapping(schema, schema_name)
        if isinstance(schema, type):  # pragma: no cover
            schema = schema()

        if isinstance(schema, EmptySchema):
            status_code = 204

        def decorator(f):
            f = _ensure_sync(f)
            _annotate(f, response={
                'schema': schema,
                'status_code': status_code,
                'description': description,
                'example': example,
                'examples': examples,
                'links': links,
            })

            def _jsonify(
                obj: t.Any,
                many: bool = _sentinel,  # type: ignore
                *args: t.Any,
                **kwargs: t.Any
            ) -> Response:  # pragma: no cover
                """From Flask-Marshmallow, see the NOTICE file for license information."""
                if many is _sentinel:
                    many = schema.many  # type: ignore
                base_schema: OpenAPISchemaType = current_app.config['BASE_RESPONSE_SCHEMA']
                if base_schema is not None and status_code != 204:
                    data_key: str = current_app.config['BASE_RESPONSE_DATA_KEY']
                    if data_key not in obj:
                        raise RuntimeError(
                            f'The data key "{data_key}" is not found in the returned dict.'
                        )
                    obj[data_key] = schema.dump(obj[data_key], many=many)  # type: ignore
                    data = base_schema().dump(obj)  # type: ignore
                else:
                    data = schema.dump(obj, many=many)  # type: ignore
                return jsonify(data, *args, **kwargs)

            @wraps(f)
            def _response(*args: t.Any, **kwargs: t.Any) -> ResponseReturnValueType:
                rv = f(*args, **kwargs)
                if isinstance(rv, Response):
                    return rv
                if not isinstance(rv, tuple):
                    return _jsonify(rv), status_code
                json = _jsonify(rv[0])
                if len(rv) == 2:
                    rv = (json, rv[1]) if isinstance(rv[1], int) else (json, status_code, rv[1])
                elif len(rv) >= 3:
                    rv = (json, rv[1], rv[2])
                else:
                    rv = (json, status_code)
                return rv  # type: ignore
            return _response
        return decorator

    def doc(
        self,
        summary: t.Optional[str] = None,
        description: t.Optional[str] = None,
        tag: t.Optional[str] = None,
        tags: t.Optional[t.List[str]] = None,
        responses: t.Optional[t.Union[t.List[int], t.Dict[int, str]]] = None,
        deprecated: t.Optional[bool] = None,
        hide: t.Optional[bool] = None,
        operation_id: t.Optional[str] = None,
        security: t.Optional[t.Union[str, t.List[t.Union[str, t.Dict[str, list]]]]] = None,
    ) -> t.Callable[[DecoratedType], DecoratedType]:
        """Set up the OpenAPI Spec for view functions.

        > Be sure to put it under the routes decorators (i.e., `app.route`, `app.get`,
        `app.post`, etc.).

        Examples:

        ```python
        from apiflask import APIFlask

        app = APIFlask(__name__)

        @app.get('/')
        @app.doc(summary='Say hello', tags=['Foo'])
        def hello():
            return 'Hello'
        ```

        Arguments:
            summary: The summary of this endpoint. If not set, the name of the view function
                will be used. If your view function is named with `get_pet`, then the summary
                will be "Get Pet". If the view function has a docstring, then the first
                line of the docstring will be used. The precedence will be:

                ```
                @app.doc(summary='blah') > the first line of docstring > the view function name
                ```

            description: The description of this endpoint. If not set, the lines after the empty
                line of the docstring will be used.
            tag: Deprecated since 1.0, use `tags` instead.
            tags: A list of tag names of this endpoint, map the tags you passed in the `app.tags`
                attribute. If `app.tags` is not set, the blueprint name will be used as tag name.
            responses: The other responses for this view function, accepts a dict in a format
                of `{404: 'Not Found'}` or a list of status code (`[404, 418]`). If pass a dict,
                and a response with the same status code is already exist, the existing
                description will be overwritten.
            deprecated: Flag this endpoint as deprecated in API docs.
            hide: Hide this endpoint in API docs.
            operation_id: The `operationId` of this endpoint. Set config `AUTO_OPERATION_ID` to
                `True` to enable the auto-generating of operationId (in the format of
                `{method}_{endpoint}`).
            security: The `security` used for this endpoint. Match the security info specified in
                the `SECURITY_SCHEMES` configuration. If you don't need specify the scopes, just
                pass a security name (equals to `[{'foo': []}]`) or a list of security names (equals
                to `[{'foo': []}, {'bar': []}]`).

        *Version changed: 1.0*

        - Add `security` parameter to support customizing security info.
        - The `role` parameter is deprecated.

        *Version changed: 0.12.0*

        - Move to `APIFlask` and `APIBlueprint` classes.

        *Version changed: 0.10.0*

        - Add parameter `operation_id`.

        *Version changed: 0.5.0*

        - Change the default value of parameters `hide` and `deprecated` from `False` to `None`.

        *Version changed: 0.4.0*

        - Add parameter `tag`.

        *Version changed: 0.3.0*

        - Change the default value of `deprecated` from `None` to `False`.
        - Rename parameter `tags` to `tag`.

        *Version added: 0.2.0*
        """
        _tags = None
        if tag is not None:
            warnings.warn(
                'The `tag` parameter is deprecated and will be removed in 1.1, '
                'use `tags` and always pass a list instead.',
                DeprecationWarning,
                stacklevel=2,
            )
            _tags = [tag]
        elif tags is not None:
            _tags = tags

        def decorator(f):
            f = _ensure_sync(f)
            _annotate(
                f,
                summary=summary,
                description=description,
                tags=_tags,
                responses=responses,
                deprecated=deprecated,
                hide=hide,
                operation_id=operation_id,
                security=security,
            )
            return f
        return decorator

auth_required(self, auth, role=None, roles=None, optional=None)

Protect a view with provided authentication settings.

Be sure to put it under the routes decorators (i.e., app.route, app.get, app.post, etc.).

Examples:

from apiflask import APIFlask, HTTPTokenAuth

app = APIFlask(__name__)
auth = HTTPTokenAuth()

@app.get('/')
@app.auth_required(auth)
def hello():
    return 'Hello'!

Parameters:

Name Type Description Default
auth Union[HTTPBasicAuth, HTTPTokenAuth]

The auth object, an instance of HTTPBasicAuth or HTTPTokenAuth.

required
role Optional[str]

Deprecated since 1.0, use roles instead.

None
roles Optional[list]

The selected roles to allow to visit this view, accepts a list of role names. See Flask-HTTPAuth's documentation for more details.

None
optional Optional[str]

Set to True to allow the view to execute even the authentication information is not included with the request, in which case the attribute auth.current_user will be None.

None

Version changed: 1.0.0

  • The role parameter is deprecated.

Version changed: 0.12.0

  • Move to APIFlask and APIBlueprint classes.

Version changed: 0.4.0

  • Add parameter roles.
Source code in apiflask/scaffold.py
def auth_required(
    self,
    auth: HTTPAuthType,
    role: t.Optional[str] = None,
    roles: t.Optional[list] = None,
    optional: t.Optional[str] = None
) -> t.Callable[[DecoratedType], DecoratedType]:
    """Protect a view with provided authentication settings.

    > Be sure to put it under the routes decorators (i.e., `app.route`, `app.get`,
    `app.post`, etc.).

    Examples:

    ```python
    from apiflask import APIFlask, HTTPTokenAuth

    app = APIFlask(__name__)
    auth = HTTPTokenAuth()

    @app.get('/')
    @app.auth_required(auth)
    def hello():
        return 'Hello'!
    ```

    Arguments:
        auth: The `auth` object, an instance of
            [`HTTPBasicAuth`][apiflask.security.HTTPBasicAuth]
            or [`HTTPTokenAuth`][apiflask.security.HTTPTokenAuth].
        role: Deprecated since 1.0, use `roles` instead.
        roles: The selected roles to allow to visit this view, accepts a list of role names.
            See [Flask-HTTPAuth's documentation][_role]{target:_blank} for more details.
            [_role]: https://flask-httpauth.readthedocs.io/en/latest/#user-roles
        optional: Set to `True` to allow the view to execute even the authentication
            information is not included with the request, in which case the attribute
            `auth.current_user` will be `None`.

    *Version changed: 1.0.0*

    - The `role` parameter is deprecated.

    *Version changed: 0.12.0*

    - Move to `APIFlask` and `APIBlueprint` classes.

    *Version changed: 0.4.0*

    - Add parameter `roles`.
    """
    _roles = None
    if role is not None:
        warnings.warn(
            'The `role` parameter is deprecated and will be removed in 1.1, '
            'use `roles` and always pass a list instead.',
            DeprecationWarning,
            stacklevel=3,
        )
        _roles = [role]
    elif roles is not None:
        _roles = roles

    def decorator(f):
        f = _ensure_sync(f)
        _annotate(f, auth=auth, roles=_roles or [])
        return auth.login_required(role=_roles, optional=optional)(f)
    return decorator

delete(self, rule, **options)

Shortcut for app.route(methods=['DELETE']).

Source code in apiflask/scaffold.py
def delete(self, rule: str, **options: t.Any):
    """Shortcut for `app.route(methods=['DELETE'])`."""
    return self._method_route('DELETE', rule, options)

doc(self, summary=None, description=None, tag=None, tags=None, responses=None, deprecated=None, hide=None, operation_id=None, security=None)

Set up the OpenAPI Spec for view functions.

Be sure to put it under the routes decorators (i.e., app.route, app.get, app.post, etc.).

Examples:

from apiflask import APIFlask

app = APIFlask(__name__)

@app.get('/')
@app.doc(summary='Say hello', tags=['Foo'])
def hello():
    return 'Hello'

Parameters:

Name Type Description Default
summary Optional[str]

The summary of this endpoint. If not set, the name of the view function will be used. If your view function is named with get_pet, then the summary will be "Get Pet". If the view function has a docstring, then the first line of the docstring will be used. The precedence will be:

@app.doc(summary='blah') > the first line of docstring > the view function name
None
description Optional[str]

The description of this endpoint. If not set, the lines after the empty line of the docstring will be used.

None
tag Optional[str]

Deprecated since 1.0, use tags instead.

None
tags Optional[List[str]]

A list of tag names of this endpoint, map the tags you passed in the app.tags attribute. If app.tags is not set, the blueprint name will be used as tag name.

None
responses Union[List[int], Dict[int, str]]

The other responses for this view function, accepts a dict in a format of {404: 'Not Found'} or a list of status code ([404, 418]). If pass a dict, and a response with the same status code is already exist, the existing description will be overwritten.

None
deprecated Optional[bool]

Flag this endpoint as deprecated in API docs.

None
hide Optional[bool]

Hide this endpoint in API docs.

None
operation_id Optional[str]

The operationId of this endpoint. Set config AUTO_OPERATION_ID to True to enable the auto-generating of operationId (in the format of {method}_{endpoint}).

None
security Union[str, List[Union[str, Dict[str, list]]]]

The security used for this endpoint. Match the security info specified in the SECURITY_SCHEMES configuration. If you don't need specify the scopes, just pass a security name (equals to [{'foo': []}]) or a list of security names (equals to [{'foo': []}, {'bar': []}]).

None

Version changed: 1.0

  • Add security parameter to support customizing security info.
  • The role parameter is deprecated.

Version changed: 0.12.0

  • Move to APIFlask and APIBlueprint classes.

Version changed: 0.10.0

  • Add parameter operation_id.

Version changed: 0.5.0

  • Change the default value of parameters hide and deprecated from False to None.

Version changed: 0.4.0

  • Add parameter tag.

Version changed: 0.3.0

  • Change the default value of deprecated from None to False.
  • Rename parameter tags to tag.

Version added: 0.2.0

Source code in apiflask/scaffold.py
def doc(
    self,
    summary: t.Optional[str] = None,
    description: t.Optional[str] = None,
    tag: t.Optional[str] = None,
    tags: t.Optional[t.List[str]] = None,
    responses: t.Optional[t.Union[t.List[int], t.Dict[int, str]]] = None,
    deprecated: t.Optional[bool] = None,
    hide: t.Optional[bool] = None,
    operation_id: t.Optional[str] = None,
    security: t.Optional[t.Union[str, t.List[t.Union[str, t.Dict[str, list]]]]] = None,
) -> t.Callable[[DecoratedType], DecoratedType]:
    """Set up the OpenAPI Spec for view functions.

    > Be sure to put it under the routes decorators (i.e., `app.route`, `app.get`,
    `app.post`, etc.).

    Examples:

    ```python
    from apiflask import APIFlask

    app = APIFlask(__name__)

    @app.get('/')
    @app.doc(summary='Say hello', tags=['Foo'])
    def hello():
        return 'Hello'
    ```

    Arguments:
        summary: The summary of this endpoint. If not set, the name of the view function
            will be used. If your view function is named with `get_pet`, then the summary
            will be "Get Pet". If the view function has a docstring, then the first
            line of the docstring will be used. The precedence will be:

            ```
            @app.doc(summary='blah') > the first line of docstring > the view function name
            ```

        description: The description of this endpoint. If not set, the lines after the empty
            line of the docstring will be used.
        tag: Deprecated since 1.0, use `tags` instead.
        tags: A list of tag names of this endpoint, map the tags you passed in the `app.tags`
            attribute. If `app.tags` is not set, the blueprint name will be used as tag name.
        responses: The other responses for this view function, accepts a dict in a format
            of `{404: 'Not Found'}` or a list of status code (`[404, 418]`). If pass a dict,
            and a response with the same status code is already exist, the existing
            description will be overwritten.
        deprecated: Flag this endpoint as deprecated in API docs.
        hide: Hide this endpoint in API docs.
        operation_id: The `operationId` of this endpoint. Set config `AUTO_OPERATION_ID` to
            `True` to enable the auto-generating of operationId (in the format of
            `{method}_{endpoint}`).
        security: The `security` used for this endpoint. Match the security info specified in
            the `SECURITY_SCHEMES` configuration. If you don't need specify the scopes, just
            pass a security name (equals to `[{'foo': []}]`) or a list of security names (equals
            to `[{'foo': []}, {'bar': []}]`).

    *Version changed: 1.0*

    - Add `security` parameter to support customizing security info.
    - The `role` parameter is deprecated.

    *Version changed: 0.12.0*

    - Move to `APIFlask` and `APIBlueprint` classes.

    *Version changed: 0.10.0*

    - Add parameter `operation_id`.

    *Version changed: 0.5.0*

    - Change the default value of parameters `hide` and `deprecated` from `False` to `None`.

    *Version changed: 0.4.0*

    - Add parameter `tag`.

    *Version changed: 0.3.0*

    - Change the default value of `deprecated` from `None` to `False`.
    - Rename parameter `tags` to `tag`.

    *Version added: 0.2.0*
    """
    _tags = None
    if tag is not None:
        warnings.warn(
            'The `tag` parameter is deprecated and will be removed in 1.1, '
            'use `tags` and always pass a list instead.',
            DeprecationWarning,
            stacklevel=2,
        )
        _tags = [tag]
    elif tags is not None:
        _tags = tags

    def decorator(f):
        f = _ensure_sync(f)
        _annotate(
            f,
            summary=summary,
            description=description,
            tags=_tags,
            responses=responses,
            deprecated=deprecated,
            hide=hide,
            operation_id=operation_id,
            security=security,
        )
        return f
    return decorator

get(self, rule, **options)

Shortcut for app.route() or app.route(methods=['GET']).

Source code in apiflask/scaffold.py
def get(self, rule: str, **options: t.Any):
    """Shortcut for `app.route()` or `app.route(methods=['GET'])`."""
    return self._method_route('GET', rule, options)

input(self, schema, location='json', schema_name=None, example=None, examples=None, **kwargs)

Add input settings for view functions.

Be sure to put it under the routes decorators (i.e., app.route, app.get, app.post, etc.).

If the validation passed, the data will inject into view function as a positional argument in the form of dict. Otherwise, an error response with the detail of the validation result will be returned.

Examples:

from apiflask import APIFlask

app = APIFlask(__name__)

@app.get('/')
@app.input(PetInSchema)
def hello(parsed_and_validated_input_data):
    print(parsed_and_validated_input_data)
    return 'Hello'!

Parameters:

Name Type Description Default
schema Union[Schema, Type[Schema], Dict[str, Union[Field, type]]]

The marshmallow schema of the input data.

required
location str

The location of the input data, one of 'json' (default), 'files', 'form', 'cookies', 'headers', 'query' (same as 'querystring').

'json'
schema_name Optional[str]

The schema name for dict schema, only needed when you pass a schema dict (e.g., {'name': String(required=True)}) for json location.

None
example Optional[Any]

The example data in dict for request body, you should use either example or examples, not both.

None
examples Optional[Dict[str, Any]]

Multiple examples for request body, you should pass a dict that contains multiple examples. Example:

{
    'example foo': {  # example name
        'summary': 'an example of foo',  # summary field is optional
        'value': {'name': 'foo', 'id': 1}  # example value
    },
    'example bar': {
        'summary': 'an example of bar',
        'value': {'name': 'bar', 'id': 2}
    },
}
None

Version changed: 1.0

  • Ensure only one input body location was used.
  • Add form_and_files and json_or_form (from webargs) location.
  • Rewrite files to act as form_and_files.
  • Use correct request content type for form and files.

Version changed: 0.12.0

  • Move to APIFlask and APIBlueprint classes.

Version changed: 0.4.0

  • Add parameter examples.
Source code in apiflask/scaffold.py
def input(
    self,
    schema: SchemaType,
    location: str = 'json',
    schema_name: t.Optional[str] = None,
    example: t.Optional[t.Any] = None,
    examples: t.Optional[t.Dict[str, t.Any]] = None,
    **kwargs: t.Any
) -> t.Callable[[DecoratedType], DecoratedType]:
    """Add input settings for view functions.

    > Be sure to put it under the routes decorators (i.e., `app.route`, `app.get`,
    `app.post`, etc.).

    If the validation passed, the data will inject into view
    function as a positional argument in the form of `dict`. Otherwise,
    an error response with the detail of the validation result will be returned.

    Examples:

    ```python
    from apiflask import APIFlask

    app = APIFlask(__name__)

    @app.get('/')
    @app.input(PetInSchema)
    def hello(parsed_and_validated_input_data):
        print(parsed_and_validated_input_data)
        return 'Hello'!
    ```

    Arguments:
        schema: The marshmallow schema of the input data.
        location: The location of the input data, one of `'json'` (default),
            `'files'`, `'form'`, `'cookies'`, `'headers'`, `'query'`
            (same as `'querystring'`).
        schema_name: The schema name for dict schema, only needed when you pass
            a schema dict (e.g., `{'name': String(required=True)}`) for `json`
            location.
        example: The example data in dict for request body, you should use either
            `example` or `examples`, not both.
        examples: Multiple examples for request body, you should pass a dict
            that contains multiple examples. Example:

            ```python
            {
                'example foo': {  # example name
                    'summary': 'an example of foo',  # summary field is optional
                    'value': {'name': 'foo', 'id': 1}  # example value
                },
                'example bar': {
                    'summary': 'an example of bar',
                    'value': {'name': 'bar', 'id': 2}
                },
            }
            ```

    *Version changed: 1.0*

    - Ensure only one input body location was used.
    - Add `form_and_files` and `json_or_form` (from webargs) location.
    - Rewrite `files` to act as `form_and_files`.
    - Use correct request content type for `form` and `files`.

    *Version changed: 0.12.0*

    - Move to APIFlask and APIBlueprint classes.

    *Version changed: 0.4.0*

    - Add parameter `examples`.
    """
    if isinstance(schema, ABCMapping):
        schema = _generate_schema_from_mapping(schema, schema_name)
    if isinstance(schema, type):  # pragma: no cover
        schema = schema()

    def decorator(f):
        f = _ensure_sync(f)
        is_body_location = location in BODY_LOCATIONS
        if is_body_location and hasattr(f, '_spec') and 'body' in f._spec:
            raise RuntimeError(
                'When using the app.input() decorator, you can only declare one request '
                'body location (one of "json", "form", "files", "form_and_files", '
                'and "json_or_form").'
            )
        if location not in SUPPORTED_LOCATIONS:
            raise ValueError(
                'Unknown input location. The supported locations are: "json", "files",'
                ' "form", "cookies", "headers", "query" (same as "querystring"), "path"'
                f' (same as "view_args") and "form_and_files". Got "{location}" instead.'
            )
        if location == 'json':
            _annotate(f, body=schema, body_example=example, body_examples=examples)
        elif location == 'form':
            _annotate(
                f,
                body=schema,
                body_example=example,
                body_examples=examples,
                content_type='application/x-www-form-urlencoded'
            )
        elif location in ['files', 'form_and_files']:
            _annotate(
                f,
                body=schema,
                body_example=example,
                body_examples=examples,
                content_type='multipart/form-data'
            )
        else:
            if not hasattr(f, '_spec') or f._spec.get('args') is None:
                _annotate(f, args=[])
            # TODO: Support set example for request parameters
            f._spec['args'].append((schema, location))
        return use_args(schema, location=location, **kwargs)(f)
    return decorator

output(self, schema, status_code=200, description=None, schema_name=None, example=None, examples=None, links=None)

Add output settings for view functions.

Be sure to put it under the routes decorators (i.e., app.route, app.get, app.post, etc.).

The decorator will format the return value of your view function with provided marshmallow schema. You can return a dict or an object (such as a model class instance of ORMs). APIFlask will handle the formatting and turn your return value into a JSON response.

P.S. The output data will not be validated; it's a design choice of marshmallow. marshmallow 4.0 may be support the output validation.

Examples:

from apiflask import APIFlask

app = APIFlask(__name__)

@app.get('/')
@app.output(PetOutSchema)
def hello():
    return the_dict_or_object_match_petout_schema

Parameters:

Name Type Description Default
schema Union[Schema, Type[Schema], Dict[str, Union[Field, type]]]

The schemas of the output data.

required
status_code int

The status code of the response, defaults to 200.

200
description Optional[str]

The description of the response.

None
schema_name Optional[str]

The schema name for dict schema, only needed when you pass a schema dict (e.g., {'name': String()}).

None
example Optional[Any]

The example data in dict for response body, you should use either example or examples, not both.

None
examples Optional[Dict[str, Any]]

Multiple examples for response body, you should pass a dict that contains multiple examples. Example:

{
    'example foo': {  # example name
        'summary': 'an example of foo',  # summary field is optional
        'value': {'name': 'foo', 'id': 1}  # example value
    },
    'example bar': {
        'summary': 'an example of bar',
        'value': {'name': 'bar', 'id': 2}
    },
}
None
links Optional[Dict[str, Any]]

The links of response. It accepts a dict which maps a link name to a link object. Example:

{
    'getAddressByUserId': {
        'operationId': 'getUserAddress',
        'parameters': {
            'userId': '$request.path.id'
        }
    }
}

See the docs for more details about setting response links.

None

Version changed: 0.12.0

  • Move to APIFlask and APIBlueprint classes.

Version changed: 0.10.0

  • Add links parameter.

Version changed: 0.9.0

  • Add base response customization support.

Version changed: 0.6.0

  • Support decorating async views.

Version changed: 0.5.2

  • Return the Response object directly.

Version changed: 0.4.0

  • Add parameter examples.
Source code in apiflask/scaffold.py
def output(
    self,
    schema: SchemaType,
    status_code: int = 200,
    description: t.Optional[str] = None,
    schema_name: t.Optional[str] = None,
    example: t.Optional[t.Any] = None,
    examples: t.Optional[t.Dict[str, t.Any]] = None,
    links: t.Optional[t.Dict[str, t.Any]] = None,
) -> t.Callable[[DecoratedType], DecoratedType]:
    """Add output settings for view functions.

    > Be sure to put it under the routes decorators (i.e., `app.route`, `app.get`,
    `app.post`, etc.).

    The decorator will format the return value of your view function with
    provided marshmallow schema. You can return a dict or an object (such
    as a model class instance of ORMs). APIFlask will handle the formatting
    and turn your return value into a JSON response.

    P.S. The output data will not be validated; it's a design choice of marshmallow.
    marshmallow 4.0 may be support the output validation.

    Examples:

    ```python
    from apiflask import APIFlask

    app = APIFlask(__name__)

    @app.get('/')
    @app.output(PetOutSchema)
    def hello():
        return the_dict_or_object_match_petout_schema
    ```

    Arguments:
        schema: The schemas of the output data.
        status_code: The status code of the response, defaults to `200`.
        description: The description of the response.
        schema_name: The schema name for dict schema, only needed when you pass
            a schema dict (e.g., `{'name': String()}`).
        example: The example data in dict for response body, you should use either
            `example` or `examples`, not both.
        examples: Multiple examples for response body, you should pass a dict
            that contains multiple examples. Example:

            ```python
            {
                'example foo': {  # example name
                    'summary': 'an example of foo',  # summary field is optional
                    'value': {'name': 'foo', 'id': 1}  # example value
                },
                'example bar': {
                    'summary': 'an example of bar',
                    'value': {'name': 'bar', 'id': 2}
                },
            }
            ```
        links: The `links` of response. It accepts a dict which maps a link name to
            a link object. Example:

            ```python
            {
                'getAddressByUserId': {
                    'operationId': 'getUserAddress',
                    'parameters': {
                        'userId': '$request.path.id'
                    }
                }
            }
            ```

            See the [docs](https://apiflask.com/openapi/#response-links) for more details
            about setting response links.

    *Version changed: 0.12.0*

    - Move to APIFlask and APIBlueprint classes.

    *Version changed: 0.10.0*

    - Add `links` parameter.

    *Version changed: 0.9.0*

    - Add base response customization support.

    *Version changed: 0.6.0*

    - Support decorating async views.

    *Version changed: 0.5.2*

    - Return the `Response` object directly.

    *Version changed: 0.4.0*

    - Add parameter `examples`.
    """
    if schema == {}:
        schema = EmptySchema
    if isinstance(schema, ABCMapping):
        schema = _generate_schema_from_mapping(schema, schema_name)
    if isinstance(schema, type):  # pragma: no cover
        schema = schema()

    if isinstance(schema, EmptySchema):
        status_code = 204

    def decorator(f):
        f = _ensure_sync(f)
        _annotate(f, response={
            'schema': schema,
            'status_code': status_code,
            'description': description,
            'example': example,
            'examples': examples,
            'links': links,
        })

        def _jsonify(
            obj: t.Any,
            many: bool = _sentinel,  # type: ignore
            *args: t.Any,
            **kwargs: t.Any
        ) -> Response:  # pragma: no cover
            """From Flask-Marshmallow, see the NOTICE file for license information."""
            if many is _sentinel:
                many = schema.many  # type: ignore
            base_schema: OpenAPISchemaType = current_app.config['BASE_RESPONSE_SCHEMA']
            if base_schema is not None and status_code != 204:
                data_key: str = current_app.config['BASE_RESPONSE_DATA_KEY']
                if data_key not in obj:
                    raise RuntimeError(
                        f'The data key "{data_key}" is not found in the returned dict.'
                    )
                obj[data_key] = schema.dump(obj[data_key], many=many)  # type: ignore
                data = base_schema().dump(obj)  # type: ignore
            else:
                data = schema.dump(obj, many=many)  # type: ignore
            return jsonify(data, *args, **kwargs)

        @wraps(f)
        def _response(*args: t.Any, **kwargs: t.Any) -> ResponseReturnValueType:
            rv = f(*args, **kwargs)
            if isinstance(rv, Response):
                return rv
            if not isinstance(rv, tuple):
                return _jsonify(rv), status_code
            json = _jsonify(rv[0])
            if len(rv) == 2:
                rv = (json, rv[1]) if isinstance(rv[1], int) else (json, status_code, rv[1])
            elif len(rv) >= 3:
                rv = (json, rv[1], rv[2])
            else:
                rv = (json, status_code)
            return rv  # type: ignore
        return _response
    return decorator

patch(self, rule, **options)

Shortcut for app.route(methods=['PATCH']).

Source code in apiflask/scaffold.py
def patch(self, rule: str, **options: t.Any):
    """Shortcut for `app.route(methods=['PATCH'])`."""
    return self._method_route('PATCH', rule, options)

post(self, rule, **options)

Shortcut for app.route(methods=['POST']).

Source code in apiflask/scaffold.py
def post(self, rule: str, **options: t.Any):
    """Shortcut for `app.route(methods=['POST'])`."""
    return self._method_route('POST', rule, options)

put(self, rule, **options)

Shortcut for app.route(methods=['PUT']).

Source code in apiflask/scaffold.py
def put(self, rule: str, **options: t.Any):
    """Shortcut for `app.route(methods=['PUT'])`."""
    return self._method_route('PUT', rule, options)