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
list[dict[str, str]] | None
The servers information of the API (openapi.servers), accepts
multiple server dicts. Example value:
This attribute can also be configured from the config with the
SERVERS configuration key. Defaults to None.
tags
TagsType | None
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
dict[str, str] | None
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
dict[str, str | dict] | None
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:
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
dict[str, str] | None
The contact information of the API (openapi.info.contact). Example:
This attribute can also be configured from the config with the
SECURITY_SCHEMES configuration key. Defaults to None.
spec_callback
SpecCallbackType | None
It stores the function object registered by
spec_processor. You can also
pass a callback function to it directly without using spec_processor.
Example:
It stores the function object registered 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:
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 APIFlaskdefschema_name_resolver(schema):name=schema.__class__.__name__# get schema class nameifschema.partial:# add a "Update" suffix for partial schemaname+='Update'returnnameapp.schema_name_resolver=schema_name_resolver
Version changed: 1.0
Add instance attribute security_schemes as an alias of config SECURITY_SCHEMES.
@route_patchclassAPIFlask(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 registered 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 registered 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 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: ignoretags:TagsType|None=ConfigAttribute('TAGS')# type: ignoreservers:list[dict[str,str]]|None=ConfigAttribute('SERVERS')# type: ignoreinfo:dict[str,str|dict]|None=ConfigAttribute('INFO')# type: ignoredescription:str|None=ConfigAttribute('DESCRIPTION')# type: ignorecontact:dict[str,str]|None=ConfigAttribute('CONTACT')# type: ignorelicense:dict[str,str]|None=ConfigAttribute('LICENSE')# type: ignoreexternal_docs:dict[str,str]|None=ConfigAttribute('EXTERNAL_DOCS')# type: ignoreterms_of_service:str|None=ConfigAttribute('TERMS_OF_SERVICE')# type: ignoresecurity_schemes:dict[str,t.Any]|None=ConfigAttribute('SECURITY_SCHEMES')# type: ignoredef__init__(self,import_name:str,title:str='APIFlask',version:str='0.1.0',spec_path:str|None='/openapi.json',docs_path:str|None='/docs',docs_oauth2_redirect_path:str|None='/docs/oauth2-redirect',docs_oauth2_redirect_path_external:bool=False,docs_ui:str='swagger-ui',openapi_blueprint_url_prefix:str|None=None,json_errors:bool=True,enable_openapi:bool=True,spec_plugins:list[BasePlugin]|None=None,static_url_path:str|None=None,static_folder:str='static',static_host:str|None=None,host_matching:bool=False,subdomain_matching:bool=False,template_folder:str='templates',instance_path:str|None=None,instance_relative_config:bool=False,root_path:str|None=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. docs_oauth2_redirect_path_external: If `True`, the `docs_oauth2_redirect_path` will be an external URL. 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. spec_plugins: List of apispec-compatible plugins (subclasses of `apispec.BasePlugin`), defaults to `None`. The `MarshmallowPlugin` for apispec is already included by default, so it doesn't need to be provided here. Other keyword arguments are directly passed to `flask.Flask`. *Version changed: 2.0.0* - Remove the deprecated `redoc_path` parameter. *Version changed: 1.2.0* - Add `spec_plugins` parameter. *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 configself.config.from_object('apiflask.settings')self.title=titleself.version=versionself.spec_path=spec_pathself.docs_ui=docs_uiself.docs_path=docs_pathself.docs_oauth2_redirect_path=docs_oauth2_redirect_pathself.docs_oauth2_redirect_path_external=docs_oauth2_redirect_path_externalself.openapi_blueprint_url_prefix=openapi_blueprint_url_prefixself.enable_openapi=enable_openapiself.json_errors=json_errorsself.spec_callback:SpecCallbackType|None=Noneself.error_callback:ErrorCallbackType=self._error_handlerself.schema_name_resolver=self._schema_name_resolverself.spec_plugins:list[BasePlugin]=spec_pluginsor[]self._spec:dict|str|None=Noneself._auth_blueprints:dict[str,t.Dict[str,t.Any]]={}self._auths:set[HTTPAuthType|MultiAuth]=set()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: ignoredefhandle_http_errors(error:HTTPError)->ResponseReturnValueType:returnself.error_callback(error)ifself.json_errors:self._apply_error_callback_to_werkzeug_errors()def_apply_error_callback_to_werkzeug_errors(self)->None:@self.errorhandler(WerkzeugHTTPException)# type: ignoredefhandle_werkzeug_errors(e:WerkzeugHTTPException)->ResponseReturnValueType:headers=dict(e.get_headers())# remove the original MIME headerdelheaders['Content-Type']error=HTTPError(e.code,message=e.name,headers=headers)returnself.error_callback(error)# TODO: remove this function when we drop the Flask < 2.2 support# List return values are supported in Flask 2.2defmake_response(self,rv)->Response:"""Patch the make_response form Flask to allow returning list as JSON. *Version added: 1.1.0* """ifisinstance(rv,list):rv=jsonify(rv)elifisinstance(rv,tuple)andisinstance(rv[0],list):rv=(jsonify(rv[0]),*rv[1:])returnsuper().make_response(rv)@staticmethoddef_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 response body. """body={'detail':error.detail,'message':error.message,**error.extra_data}returnbody,error.status_code,error.headersdeferror_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_execution)` 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 422 (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 Werkzeug 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. """self.error_callback=self.ensure_sync(f)self._apply_error_callback_to_werkzeug_errors()returnfdef_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 and API docs. *Version changed: 2.3.3* - Add 'docs_oauth2_redirect_path_external' parameter to support absolute redirect url. *Version changed: 2.1.0* - Inject the OpenAPI endpoints decorators. *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)ifself.spec_path:@bp.route(self.spec_path)@self._apply_decorators(config_name='SPEC_DECORATORS')defspec():ifself.config['SPEC_FORMAT']=='json':response=jsonify(self._get_spec('json'))response.mimetype=self.config['JSON_SPEC_MIMETYPE']returnresponsereturn(self._get_spec('yaml'),200,{'Content-Type':self.config['YAML_SPEC_MIMETYPE']},)ifself.docs_path:ifself.docs_uinotinui_templates:valid_values=list(ui_templates.keys())raiseValueError(f'Invalid docs_ui value, expected one of {valid_values!r}, 'f'got {self.docs_ui!r}.')@bp.route(self.docs_path)@self._apply_decorators(config_name='DOCS_DECORATORS')defdocs():docs_oauth2_redirect_path=Noneifself.docs_ui=='swagger-ui':ifself.docs_oauth2_redirect_path_external:docs_oauth2_redirect_path=url_for('openapi.swagger_ui_oauth_redirect',_external=True)else:docs_oauth2_redirect_path=self.docs_oauth2_redirect_pathreturnrender_template_string(ui_templates[self.docs_ui],title=self.title,version=self.version,oauth2_redirect_path=docs_oauth2_redirect_path,)ifself.docs_ui=='swagger-ui':ifself.docs_oauth2_redirect_path:@bp.route(self.docs_oauth2_redirect_path)@self._apply_decorators(config_name='SWAGGER_UI_OAUTH_REDIRECT_DECORATORS')defswagger_ui_oauth_redirect()->str:returnrender_template_string(swagger_ui_oauth2_redirect_template)ifself.enable_openapiand(self.spec_pathorself.docs_path):self.register_blueprint(bp)def_get_spec(self,spec_format:str|None=None,force_update:bool=False)->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 true, 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. *Version changed: 1.3.0* - Add the `SPEC_PROCESSOR_PASS_OBJECT` config to control the argument type when calling the spec processor. """ifspec_formatisNone:spec_format=self.config['SPEC_FORMAT']ifself._specisNoneorforce_update:spec_object:APISpec=self._generate_spec()ifself.spec_callback:ifself.config['SPEC_PROCESSOR_PASS_OBJECT']:self._spec=self.spec_callback(spec_object# type: ignore).to_dict()else:self._spec=self.spec_callback(spec_object.to_dict())else:self._spec=spec_object.to_dict()ifspec_formatin['yml','yaml']:fromapispec.yaml_utilsimportdict_to_yamlself._spec=dict_to_yaml(self._spec)# type: ignore# sync local specifself.config['SYNC_LOCAL_SPEC']:spec_path=self.config['LOCAL_SPEC_PATH']ifspec_pathisNone:raiseTypeError('The spec path (LOCAL_SPEC_PATH) should be a valid path string.')spec:strifspec_format=='json':spec=json.dumps(self._spec,indent=self.config['LOCAL_SPEC_JSON_INDENT'])else:spec=str(self._spec)withopen(spec_path,'w')asf:f.write(spec)returnself._spec# type: ignoredefspec_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. """self.spec_callback=self.ensure_sync(f)returnf@propertydefspec(self)->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. """returnself._get_spec(force_update=True)@staticmethoddef_schema_name_resolver(schema:type[Schema])->str:"""Default schema name resolver."""adapter=registry.create_adapter(schema)returnadapter.get_schema_name()def_make_info(self)->dict:"""Make OpenAPI info object."""info:dictifself.info:info=self.infoelse:info={}ifself.contact:info['contact']=self.contactifself.license:info['license']=self.licenseifself.terms_of_service:info['termsOfService']=self.terms_of_serviceifself.description:info['description']=self.descriptionreturninfodef_make_tags(self)->list[dict[str,t.Any]]:"""Make OpenAPI tags object."""tags:TagsType|None=self.tagsiftagsisnotNone:# convert simple tags list into standard OpenAPI tagsifisinstance(tags[0],str):forindex,tag_nameinenumerate(tags):tags[index]={'name':tag_name}# type: ignoreelse:tags:list[dict[str,t.Any]]=[]# type: ignoreifself.config['AUTO_TAGS']:# auto-generate tags from blueprintsforblueprint_name,blueprintinself.blueprints.items():if(blueprint_name=='openapi'ornothasattr(blueprint,'enable_openapi')ornotblueprint.enable_openapi):# type: ignorecontinuetag:dict[str,t.Any]=get_tag(blueprint,blueprint_name)# type: ignoretags.append(tag)# type: ignorereturntags# type: ignoredef_collect_security_info(self)->t.Tuple[list[str],list[HTTPAuthType]]:"""Detect `auth_required` on blueprint before_request functions and view functions."""# security schemesauth_names:set[str]=set()def_register_base_auth(auth:HTTPAuthType)->None:ifnotisinstance(auth,SecurityScheme):raiseTypeError('Unknown authentication scheme.')ifauth.nameinauth_names:warnings.warn(f"The auth scheme name '{auth.name}' has existed, "'so it will be overwritten.',stacklevel=2,)self._auths.add(auth)auth_names.add(auth.name)def_update_auth_info(auth:HTTPAuthType|MultiAuth)->None:ifisinstance(auth,MultiAuth):self._auths.add(auth)forbase_authinauth._auths:_register_base_auth(base_auth)return_register_base_auth(auth)# collect auth info on blueprint before_request functionsforblueprint_name,funcsinself.before_request_funcs.items():# skip app-level before_request functions (blueprint_name is None)ifblueprint_nameisNoneornotself.blueprints[blueprint_name].enable_openapi:# type: ignorecontinueforfinfuncs:ifhasattr(f,'_spec'):# pragma: no coverauth=f._spec.get('auth')# type: ignoreifauthisnotNoneandauthnotinself._auths:self._auth_blueprints[blueprint_name]={'auth':auth,'roles':f._spec.get('roles'),# type: ignore}_update_auth_info(auth)# collect auth info on view functionsforruleinself.url_map.iter_rules():view_func:ViewFuncType=self.view_functions[rule.endpoint]# type: ignoreifhasattr(view_func,'_spec'):auth=view_func._spec.get('auth')ifauthisnotNoneandauthnotinself._auths:_update_auth_info(auth)# method viewsifhasattr(view_func,'_method_spec'):formethod_specinview_func._method_spec.values():auth=method_spec.get('auth')ifauthisnotNoneandauthnotinself._auths:_update_auth_info(auth)# only base authentications have a schema, multiple authentication consists of base authentications # noqa: E501base_auths=[authforauthinself._authsifisinstance(auth,_AuthBase)]return[security_schema.nameforsecurity_schemainbase_auths],[security_schemaforsecurity_schemainbase_auths]def_generate_spec(self)->APISpec:"""Generate the spec, return an instance of `apispec.APISpec`. *Version changed: 1.3.0* - Support setting custom response content type. *Version changed: 1.2.1* - Set default `servers` value. *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. """# Track registered schema classes to avoid duplicates# Maps schema class id to registered nameregistered_schema_classes:dict[int,str]={}kwargs:dict={}ifself.servers:kwargs['servers']=self.serverselse:ifself.config['AUTO_SERVERS']andhas_request_context():kwargs['servers']=[{'url':request.url_root}]ifself.external_docs:kwargs['externalDocs']=self.external_docs# Keep marshmallow plugin for backwards compatibilitytry:self._ma_plugin:MarshmallowPlugin=MarshmallowPlugin(schema_name_resolver=self.schema_name_resolver# type: ignore)spec_plugins:list[BasePlugin]=[self._ma_plugin,*self.spec_plugins]exceptImportError:# If marshmallow is not available, just use custom pluginsself._ma_plugin=None# type: ignorespec_plugins=self.spec_pluginsspec:APISpec=APISpec(title=self.title,version=self.version,openapi_version=self.config['OPENAPI_VERSION'],plugins=spec_plugins,info=self._make_info(),tags=self._make_tags(),**kwargs,)# configure flask-marshmallow URL types if marshmallow plugin is availableifself._ma_pluginisnotNone:# configure flask-marshmallow URL typesself._ma_plugin.converter.field_mapping[fields.URLFor]=('string','url')# type: ignoreself._ma_plugin.converter.field_mapping[fields.AbsoluteURLFor]=(# type: ignore'string','url',)ifsqlaisnotNone:# pragma: no coverself._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)ifself.config['SECURITY_SCHEMES']isnotNone:security_schemes.update(self.config['SECURITY_SCHEMES'])forname,schemeinsecurity_schemes.items():spec.components.security_scheme(name,scheme)# pathspaths:dict[str,dict[str,t.Any]]={}rules:list[t.Any]=sorted(list(self.url_map.iter_rules()),key=lambdarule:len(rule.rule))forruleinrules:operations:dict[str,t.Any]={}view_func:ViewFuncType=self.view_functions[rule.endpoint]# type: ignore# skip endpoints from openapi blueprint and the built-in static endpointifrule.endpointindefault_bypassed_endpoints:continueblueprint_name:str|None=None# type: ignoreif'.'inrule.endpoint:blueprint_name:str=rule.endpoint.rsplit('.',1)[0]# type: ignoreblueprint=self.blueprints.get(blueprint_name)# type: ignoreifblueprintisNone:# just a normal view with dots in its endpoint, reset blueprint_nameblueprint_name=Noneelse:if(rule.endpoint==(f'{blueprint_name}.static')ornothasattr(blueprint,'enable_openapi')ornotblueprint.enable_openapi):# type: ignorecontinue# add a default 200 response for bare viewsifnothasattr(view_func,'_spec'):ifnotinspect.ismethod(view_func)andself.config['AUTO_200_RESPONSE']:# type: ignoreview_func._spec={'response':default_response}else:continue# pragma: no cover# method viewsifhasattr(view_func,'_method_spec'):skip=Trueformethod,method_specinview_func._method_spec.items():ifmethod_spec.get('no_spec'):ifself.config['AUTO_200_RESPONSE']:view_func._method_spec[method]['response']=default_responseskip=Falseelse:skip=Falseifskip:continue# skip views flagged with @app.doc(hide=True)ifview_func._spec.get('hide'):continue# operation tagsoperation_tags:list[str]|None=Noneifview_func._spec.get('tags'):operation_tags=view_func._spec.get('tags')else:# use blueprint name as tagifself.tagsisNoneandself.config['AUTO_TAGS']andblueprint_nameisnotNone:blueprint=self.blueprints[blueprint_name]operation_tags=get_operation_tags(blueprint,blueprint_name)# type: ignoreformethodin['GET','POST','PUT','PATCH','DELETE']:ifmethodnotinrule.methods:continue# method viewsifhasattr(view_func,'_method_spec'):ifmethodnotinview_func._method_spec:continue# pragma: no coverview_func._spec=view_func._method_spec[method]ifview_func._spec.get('no_spec')andnotself.config['AUTO_200_RESPONSE']:continueif(view_func._spec.get('generated_summary')andnotself.config['AUTO_OPERATION_SUMMARY']):view_func._spec['summary']=''if(view_func._spec.get('generated_description')andnotself.config['AUTO_OPERATION_DESCRIPTION']):view_func._spec['description']=''ifview_func._spec.get('hide'):continueifview_func._spec.get('tags'):operation_tags=view_func._spec.get('tags')else:if(self.tagsisNoneandself.config['AUTO_TAGS']andblueprint_nameisnotNone):blueprint=self.blueprints[blueprint_name]operation_tags=get_operation_tags(blueprint,blueprint_name)# type: ignore# operation parameters# Process parameters using schema adapters to handle both marshmallow and Pydanticparameters=[]forschema,locationinview_func._spec.get('args',[]):try:# Use schema_to_parameters for proper handling of different schema typesschema_params=openapi_helper.schema_to_parameters(schema,location=location)parameters.extend(schema_params)exceptException:# Fallback to original behavior for unknown schema typesparameters.append({'in':location,'schema':schema})operation:dict[str,t.Any]={'parameters':parameters,'responses':{},}ifoperation_tags:operation['tags']=operation_tags# summaryifview_func._spec.get('summary'):operation['summary']=view_func._spec.get('summary')else:# auto-generate summary from dotstring or view function nameifself.config['AUTO_OPERATION_SUMMARY']:operation['summary']=get_path_summary(view_func)# type: ignore# descriptionifview_func._spec.get('description'):operation['description']=view_func._spec.get('description')else:# auto-generate description from dotstringifself.config['AUTO_OPERATION_DESCRIPTION']:docs=[line.strip()forlinein(view_func.__doc__or'').strip().split('\n')]iflen(docs)>1:# use the remain lines of docstring as descriptionoperation['description']='\n'.join(docs[1:]).strip()# deprecatedifview_func._spec.get('deprecated'):operation['deprecated']=view_func._spec.get('deprecated')# operationIdoperation_id=view_func._spec.get('operation_id')ifoperation_idisNone:ifself.config['AUTO_OPERATION_ID']:operation['operationId']=(f"{method.lower()}_{rule.endpoint.replace('.','_')}")else:operation['operationId']=operation_id# responsesifview_func._spec.get('response'):schema=view_func._spec.get('response')['schema']# Check if the adapter was created with many=True (for list types)schema_adapter_many=view_func._spec.get('response').get('schema_adapter_many',False)status_code:str=str(view_func._spec.get('response')['status_code'])description:str=(view_func._spec.get('response')['description']orself.config['SUCCESS_DESCRIPTION'])example=view_func._spec.get('response')['example']examples=view_func._spec.get('response')['examples']links=view_func._spec.get('response')['links']content_type=view_func._spec.get('response')['content_type']headers=view_func._spec.get('response')['headers']self._add_response(spec,registered_schema_classes,operation,status_code,schema,description,example=example,examples=examples,links=links,content_type=content_type,headers_schema=headers,schema_adapter_many=schema_adapter_many,)else:# add a default 200 response for views without using @app.output# or @app.doc(responses={...})ifnotview_func._spec.get('responses')andself.config['AUTO_200_RESPONSE']:self._add_response(spec,registered_schema_classes,operation,'200',{},self.config['SUCCESS_DESCRIPTION'],)# add validation error responseifself.config['AUTO_VALIDATION_ERROR_RESPONSE']and(view_func._spec.get('body')orview_func._spec.get('args')):status_code:str=str(# type: ignoreself.config['VALIDATION_ERROR_STATUS_CODE'])description:str=self.config[# type: ignore'VALIDATION_ERROR_DESCRIPTION']schema:SchemaType=self.config['VALIDATION_ERROR_SCHEMA']# type: ignoreself._add_response_with_schema(spec,registered_schema_classes,operation,status_code,schema,'ValidationError',description,)# add authentication error responsehas_bp_level_auth=(blueprint_nameisnotNoneandblueprint_nameinself._auth_blueprints)view_func_auth=view_func._spec.get('auth')custom_security=view_func._spec.get('security')operation_extensions=view_func._spec.get('extensions')ifself.config['AUTO_AUTH_ERROR_RESPONSE']and(has_bp_level_authorview_func_authorcustom_security):status_code:str=str(# type: ignoreself.config['AUTH_ERROR_STATUS_CODE'])description:str=self.config['AUTH_ERROR_DESCRIPTION']# type: ignoreschema:SchemaType=self.config['HTTP_ERROR_SCHEMA']# type: ignoreself._add_response_with_schema(spec,registered_schema_classes,operation,status_code,schema,'HTTPError',description,)# add 404 error responseifself.config['AUTO_404_RESPONSE']andrule.arguments:description:str=self.config['NOT_FOUND_DESCRIPTION']# type: ignoreschema:SchemaType=self.config['HTTP_ERROR_SCHEMA']# type: ignoreself._add_response_with_schema(spec,registered_schema_classes,operation,'404',schema,'HTTPError',description,)ifview_func._spec.get('responses'):responses:ResponsesType=view_func._spec.get('responses')# turn status_code list to dict {status_code: reason_phrase}ifisinstance(responses,list):responses:dict[int,str]={}# type: ignoreforstatus_codeinview_func._spec.get('responses'):responses[# type: ignorestatus_code]=get_reason_phrase(int(status_code),'')forstatus_code,valueinresponses.items():# type: ignorestatus_code:str=str(status_code)# type: ignore# custom complete response specifisinstance(value,dict):existing_response=operation['responses'].setdefault(status_code,{})existing_response_content=existing_response.setdefault('content',{})existing_response_content.update(value.get('content',{}))if(new_description:=value.get('description'))isnotNone:existing_response['description']=new_descriptioncontinueelse:description=value# overwrite existing response descriptionifstatus_codeinoperation['responses']:ifnotisinstance(view_func._spec.get('responses'),list):# pragma: no coveroperation['responses'][status_code]['description']=descriptioncontinue# add error response schema for error responsesifstatus_code.startswith('4')orstatus_code.startswith('5'):schema:SchemaType=self.config['HTTP_ERROR_SCHEMA']# type: ignoreself._add_response_with_schema(spec,registered_schema_classes,operation,status_code,schema,'HTTPError',description,)else:# add default response for other responsesself._add_response(spec,registered_schema_classes,operation,status_code,{},description,)# requestBodyifview_func._spec.get('body'):content_types=view_func._spec.get('content_type')ifnotisinstance(content_types,list):content_types=[content_types]operation['requestBody']={'content':{}}forcontent_typeincontent_types:body_schema_obj=view_func._spec['body']# Handle body schema registration and referencingtry:# Check if this is a schema object (marshmallow/pydantic schema)# Plain dicts have __class__.__name__ == 'dict'is_schema_obj=(hasattr(body_schema_obj,'__class__')andhasattr(body_schema_obj.__class__,'__name__')andbody_schema_obj.__class__.__name__!='dict')# Skip if schema is already a reference (dict with $ref key)ifisinstance(body_schema_obj,dict)and'$ref'inbody_schema_obj:body_schema=body_schema_objelifis_schema_obj:# Register schema and get referencebody_schema=self._register_schema_and_get_ref(spec,registered_schema_classes,body_schema_obj)else:# Fallback to inline schema for plain dicts or other typesbody_schema=body_schema_objexceptException:# If anything fails, fall back to passing schema as-isbody_schema=body_schema_objoperation['requestBody']['content'][content_type]={'schema':body_schema,}ifview_func._spec.get('body_example'):example=view_func._spec.get('body_example')operation['requestBody']['content'][content_type]['example']=exampleifview_func._spec.get('body_examples'):examples=view_func._spec.get('body_examples')operation['requestBody']['content'][content_type]['examples']=examples# securityifcustom_security:# custom security# TODO: validate the security name and the formatoperation['security']=[]operation_security=custom_securityifisinstance(operation_security,str):# 'A' -> [{'A': []}]operation['security']=[{operation_security:[]}]elifisinstance(operation_security,list):# ['A', 'B'] -> [{'A': []}, {'B': []}]ifisinstance(operation_security[0],str):operation['security']=[{name:[]}fornameinoperation_security]else:operation['security']=operation_securityelse:raiseValueError('The operation security must be a string or a list.')else:ifhas_bp_level_auth:bp_auth_info=self._auth_blueprints[blueprint_name]# type: ignoreoperation['security']=[{security[bp_auth_info['auth']]:bp_auth_info['roles']}]# view-wide authifview_func_auth:ifisinstance(view_func_auth,MultiAuth):operation['security']=[{base_auth.name:view_func._spec['roles']}forbase_authinview_func_auth._auths]else:operation['security']=[{security[view_func_auth]:view_func._spec['roles']}]operations[method.lower()]=operationifoperation_extensions:forextension,valueinoperation_extensions.items():operation[extension]=value# parameterspath_arguments:t.Iterable=re.findall(r'<(([^<:]+:)?([^>]+))>',rule.rule)ifpath_argumentsandnot(hasattr(view_func,'_spec')andview_func._spec.get('omit_default_path_parameters',False)):arguments:list[dict[str,str]]=[]for_,argument_type,argument_nameinpath_arguments:argument=get_argument(argument_type,argument_name)arguments.append(argument)for_,operationinoperations.items():operation['parameters']=arguments+operation['parameters']path:str=re.sub(r'<([^<:]+:)?','{',rule.rule).replace('>','}')ifpathnotinpaths:paths[path]=operationselse:paths[path].update(operations)forpath,operationsinpaths.items():# sort by method before adding them to the specsorted_operations:dict[str,t.Any]={}formethodin['get','post','put','patch','delete']:ifmethodinoperations:sorted_operations[method]=operations[method]spec.path(path=path,operations=sorted_operations)returnspecdef_apply_decorators(self,config_name:str):"""Apply the decorators to the OpenAPI endpoints at runtime. Arguments: config_name: The config name to get the list of decorators. """defdecorator(f):@wraps(f)defwrapper(*args,**kwargs):decorated_func=fdecorators=self.config[config_name]ifdecorators:fordecoratorindecorators:decorated_func=decorator(decorated_func)returndecorated_func(*args,**kwargs)returnwrapperreturndecoratordef_register_schema_and_get_ref(self,spec:APISpec,registered_schema_classes:dict[int,str],schema_obj:t.Any,)->dict[str,str]:"""Register a schema and return its reference. Arguments: spec: The APISpec object registered_schema_classes: Dictionary tracking registered schema classes schema_obj: The schema object to register Returns: A dictionary with $ref key pointing to the registered schema """# For classes (like Pydantic models), use the class itself# For instances (like Marshmallow schemas), use the classifisinstance(schema_obj,type):schema_class_id=id(schema_obj)else:schema_class_id=id(schema_obj.__class__)ifschema_class_idinregistered_schema_classesandnot(hasattr(schema_obj,'partial')andschema_obj.partial):# type: ignore# Reuse the name from the first registrationschema_name=registered_schema_classes[schema_class_id]else:# Get the schema name for registrationschema_name=self.schema_name_resolver(schema_obj)# Handle name conflicts with different schema classesifschema_nameinspec.components.schemas:schema_name=get_unique_schema_name(spec,schema_name)# Register schema - convert to OpenAPI dict for non-marshmallow schemastry:# Try to detect if it's a marshmallow schemaadapter=registry.create_adapter(schema_obj)ifadapter.schema_type=='marshmallow':# For marshmallow, pass the schema object to MarshmallowPluginspec.components.schema(schema_name,schema=schema_obj)else:# For other schema types (Pydantic, etc), convert to dict firstschema_dict=openapi_helper.schema_to_json_schema(schema_obj)# Extract and register nested $defs (for Pydantic)nested_defs=extract_pydantic_defs(schema_dict,schema_name)fornested_name,nested_schemainnested_defs.items():ifnested_namenotinspec.components.schemas:spec.components.schema(nested_name,nested_schema)# Register the main schemaspec.components.schema(schema_name,schema_dict)exceptException:# Fallback: try to register as-isspec.components.schema(schema_name,schema=schema_obj)# Track this schema classregistered_schema_classes[schema_class_id]=schema_name# Return referencereturn{'$ref':f'#/components/schemas/{schema_name}'}def_add_response(self,spec:APISpec,registered_schema_classes:dict[int,str],operation:dict,status_code:str,schema:SchemaType|dict,description:str,example:t.Any|None=None,examples:dict[str,t.Any]|None=None,links:dict[str,t.Any]|None=None,content_type:str|None='application/json',headers_schema:SchemaType|None=None,schema_adapter_many:bool=False,)->None:"""Add response to operation. *Version changed: 2.1.0* - Add parameter `headers_schema`. *Version changed: 1.3.0* - Add parameter `content_type`. *Version changed: 0.10.0* - Add `links` parameter. """# Register schema if it's a schema object (not FileSchema, EmptySchema, or plain dict)ifstatus_code!='204'andschema:is_schema_obj=(hasattr(schema,'__class__')andhasattr(schema.__class__,'__name__')andschema.__class__.__name__!='dict'andnotisinstance(schema,(FileSchema,EmptySchema))andnot(isinstance(schema,dict)and'$ref'inschema))ifis_schema_obj:# Register schema and get referenceschema=self._register_schema_and_get_ref(spec,registered_schema_classes,schema)# Wrap schema in array type if many=Trueifschema_adapter_manyandisinstance(schema,dict)and'$ref'inschema:schema={'type':'array','items':schema}base_schema:OpenAPISchemaType|None=self.config['BASE_RESPONSE_SCHEMA']data_key:str=self.config['BASE_RESPONSE_DATA_KEY']ifbase_schemaisnotNone:base_schema_spec:dict[str,t.Any]ifisinstance(base_schema,type):# Convert schema class to instance, then to full JSON schema# Use schema_to_json_schema to get complete schema with propertiesbase_schema_spec=openapi_helper.schema_to_json_schema(base_schema)elifisinstance(base_schema,dict):base_schema_spec=base_schemaelse:raiseTypeError(_bad_schema_message)ifdata_keynotinbase_schema_spec['properties']:# type: ignoreraiseRuntimeError(f'The data key {data_key!r} is not found in the base response schema spec.')base_schema_spec['properties'][data_key]=schema# type: ignoreschema=base_schema_specoperation['responses'][status_code]={}ifstatus_code!='204':ifisinstance(schema,FileSchema):schema={'type':schema.type,'format':schema.format}elifisinstance(schema,EmptySchema):schema={}operation['responses'][status_code]['content']={content_type:{'schema':schema}}operation['responses'][status_code]['description']=descriptionifexampleisnotNone:operation['responses'][status_code]['content'][content_type]['example']=exampleifexamplesisnotNone:operation['responses'][status_code]['content'][content_type]['examples']=examplesiflinksisnotNone:operation['responses'][status_code]['links']=linksifheaders_schemaisnotNone:# Use openapi_helper to convert headers schema to parametersheader_params=openapi_helper.schema_to_parameters(headers_schema,location='headers')headers={header['name']:headerforheaderinheader_params}forheaderinheaders.values():header.pop('in',None)header.pop('name',None)operation['responses'][status_code]['headers']=headersdef_add_response_with_schema(self,spec:APISpec,registered_schema_classes:dict[int,str],operation:dict,status_code:str,schema:OpenAPISchemaType,schema_name:str,description:str,)->None:"""Add response with given schema to operation."""ifisinstance(schema,type):schema=schema()self._add_response(spec,registered_schema_classes,operation,status_code,schema,description)elifisinstance(schema,dict):ifschema_namenotinspec.components.schemas:spec.components.schema(schema_name,schema)schema_ref={'$ref':f'#/components/schemas/{schema_name}'}self._add_response(spec,registered_schema_classes,operation,status_code,schema_ref,description)else:raiseTypeError(_bad_schema_message)
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 | None
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 | None
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 | None
The path to Swagger UI OAuth redirect.
'/docs/oauth2-redirect'
docs_oauth2_redirect_path_external
bool
If True, the docs_oauth2_redirect_path
will be an external URL.
False
openapi_blueprint_url_prefix
str | None
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
spec_plugins
list[BasePlugin] | None
List of apispec-compatible plugins (subclasses of apispec.BasePlugin),
defaults to None. The MarshmallowPlugin for apispec is already included
by default, so it doesn't need to be provided here.
None
Other keyword arguments are directly passed to flask.Flask.
def__init__(self,import_name:str,title:str='APIFlask',version:str='0.1.0',spec_path:str|None='/openapi.json',docs_path:str|None='/docs',docs_oauth2_redirect_path:str|None='/docs/oauth2-redirect',docs_oauth2_redirect_path_external:bool=False,docs_ui:str='swagger-ui',openapi_blueprint_url_prefix:str|None=None,json_errors:bool=True,enable_openapi:bool=True,spec_plugins:list[BasePlugin]|None=None,static_url_path:str|None=None,static_folder:str='static',static_host:str|None=None,host_matching:bool=False,subdomain_matching:bool=False,template_folder:str='templates',instance_path:str|None=None,instance_relative_config:bool=False,root_path:str|None=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. docs_oauth2_redirect_path_external: If `True`, the `docs_oauth2_redirect_path` will be an external URL. 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. spec_plugins: List of apispec-compatible plugins (subclasses of `apispec.BasePlugin`), defaults to `None`. The `MarshmallowPlugin` for apispec is already included by default, so it doesn't need to be provided here. Other keyword arguments are directly passed to `flask.Flask`. *Version changed: 2.0.0* - Remove the deprecated `redoc_path` parameter. *Version changed: 1.2.0* - Add `spec_plugins` parameter. *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 configself.config.from_object('apiflask.settings')self.title=titleself.version=versionself.spec_path=spec_pathself.docs_ui=docs_uiself.docs_path=docs_pathself.docs_oauth2_redirect_path=docs_oauth2_redirect_pathself.docs_oauth2_redirect_path_external=docs_oauth2_redirect_path_externalself.openapi_blueprint_url_prefix=openapi_blueprint_url_prefixself.enable_openapi=enable_openapiself.json_errors=json_errorsself.spec_callback:SpecCallbackType|None=Noneself.error_callback:ErrorCallbackType=self._error_handlerself.schema_name_resolver=self._schema_name_resolverself.spec_plugins:list[BasePlugin]=spec_pluginsor[]self._spec:dict|str|None=Noneself._auth_blueprints:dict[str,t.Dict[str,t.Any]]={}self._auths:set[HTTPAuthType|MultiAuth]=set()self._register_openapi_blueprint()self._register_error_handlers()
You can still register a specific error handler for a specific error code
or exception with the app.errorhandler(code_or_execution) 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.
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
422 (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 Werkzeug 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:
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:
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.
deferror_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_execution)` 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 422 (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 Werkzeug 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. """self.error_callback=self.ensure_sync(f)self._apply_error_callback_to_werkzeug_errors()returnf
Patch the make_response form Flask to allow returning list as JSON.
Version added: 1.1.0
Source code in apiflask/app.py
402403404405406407408409410
defmake_response(self,rv)->Response:"""Patch the make_response form Flask to allow returning list as JSON. *Version added: 1.1.0* """ifisinstance(rv,list):rv=jsonify(rv)elifisinstance(rv,tuple)andisinstance(rv[0],list):rv=(jsonify(rv[0]),*rv[1:])returnsuper().make_response(rv)
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.
defspec_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. """self.spec_callback=self.ensure_sync(f)returnf
classAPIScaffold:"""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)->t.Callable[[T_route],T_route]:if'methods'inoptions:raiseRuntimeError('Use the "route" decorator to use the "methods" argument.')defdecorator(f):ifisinstance(f,type(MethodView)):raiseRuntimeError('The route shortcuts cannot be used with "MethodView" classes, ''use the "route" decorator instead.')returnself.route(rule,methods=[method],**options)(f)returndecoratordefget(self,rule:str,**options:t.Any)->t.Callable[[T_route],T_route]:"""Shortcut for `app.route()` or `app.route(methods=['GET'])`."""returnself._method_route('GET',rule,options)defpost(self,rule:str,**options:t.Any)->t.Callable[[T_route],T_route]:"""Shortcut for `app.route(methods=['POST'])`."""returnself._method_route('POST',rule,options)defput(self,rule:str,**options:t.Any)->t.Callable[[T_route],T_route]:"""Shortcut for `app.route(methods=['PUT'])`."""returnself._method_route('PUT',rule,options)defpatch(self,rule:str,**options:t.Any)->t.Callable[[T_route],T_route]:"""Shortcut for `app.route(methods=['PATCH'])`."""returnself._method_route('PATCH',rule,options)defdelete(self,rule:str,**options:t.Any)->t.Callable[[T_route],T_route]:"""Shortcut for `app.route(methods=['DELETE'])`."""returnself._method_route('DELETE',rule,options)defauth_required(self,auth:HTTPAuthType,roles:list|None=None,optional:str|None=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] or [`HTTPAPIKeyAuth`][apiflask.security.HTTPAPIKeyAuth]. 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: 2.0.0* - Remove the deprecated `role` parameter. *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`. """defdecorator(f):f=_ensure_sync(f)_annotate(f,auth=auth,roles=rolesor[])returnauth.login_required(role=roles,optional=optional)(f)returndecoratordefinput(self,schema:SchemaType,location:str='json',arg_name:str|None=None,schema_name:str|None=None,example:t.Any|None=None,examples:dict[str,t.Any]|None=None,validation:bool=True,**kwargs:t.Any,)->t.Callable[[DecoratedType],DecoratedType]:"""Add input settings for view functions. If the validation passed, the data will be injected into the view function as a keyword argument in the form of `dict` and named `{location}_data`. Otherwise, an error response with the detail of the validation result will be returned. > 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.input(PetIn, location='json') def hello(json_data): print(json_data) return 'Hello'! ``` Arguments: schema: The marshmallow schema or Pydantic model of the input data. location: The location of the input data, one of `'json'` (default), `'files'`, `'form'`, `'cookies'`, `'headers'`, `'query'` (same as `'querystring'`). arg_name: The name of the argument passed to the view function, defaults to `{location}_data`. schema_name: The schema name for dict schema, only needed when you pass a marshmallow 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} }, } ``` validation: Flag to allow disabling of validation on input. Default to `True`. *Version changed: 2.2.2 - Add parameter `validation` to allow disabling of validation on input. *Version changed: 2.0.0* - Always pass parsed data to view function as a keyword argument. The argument name will be in the form of `{location}_data`. *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`. """defdecorator(f):f=_ensure_sync(f)is_body_location=locationinBODY_LOCATIONSifis_body_locationandhasattr(f,'_spec')and'body'inf._spec:raiseRuntimeError('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").')# Create schema adapter and use the instantiated schema for spec annotation# This ensures the marshmallow plugin receives schema instancesadapter=registry.create_adapter(schema,schema_name=schema_name)annotation_schema=adapter.schemaiflocation=='json':_annotate(f,body=annotation_schema,body_example=example,body_examples=examples,content_type='application/json',)eliflocation=='form':_annotate(f,body=annotation_schema,body_example=example,body_examples=examples,content_type='application/x-www-form-urlencoded',)eliflocationin['files','form_and_files']:_annotate(f,body=annotation_schema,body_example=example,body_examples=examples,content_type='multipart/form-data',)eliflocation=='json_or_form':_annotate(f,body=annotation_schema,body_example=example,body_examples=examples,content_type=['application/x-www-form-urlencoded','application/json'],)else:ifnothasattr(f,'_spec')orf._spec.get('args')isNone:_annotate(f,args=[])iflocationin['path','view_args']:_annotate(f,omit_default_path_parameters=True)# TODO: Support set example for request parametersf._spec['args'].append((annotation_schema,location))arg_name_val=arg_nameorf'{location}_data'# For marshmallow schemas, use the original webargs approach for compatibilityifadapter.schema_type=='marshmallow':from.schema_adapters.marshmallowimportparserifnotvalidation:@wraps(f)defwrapper(*args:t.Any,**kwargs:t.Any):location_data=parser.load_location_data(schema=annotation_schema,req=flask_request,location=location)kwargs[arg_name_val]=location_datareturnf(*args,**kwargs)returnwrapperreturnparser.use_args(annotation_schema,location=location,arg_name=arg_name_val,**kwargs)(f)# For other schema types (Pydantic, etc.), use the adapter systemelse:@wraps(f)defwrapper(*args:t.Any,**kwargs:t.Any):location_data=adapter.validate_input(flask_request,location,**kwargs)kwargs[arg_name_val]=location_datareturnf(*args,**kwargs)returnwrapperreturndecoratordefoutput(self,schema:SchemaType,status_code:int=200,description:str|None=None,schema_name:str|None=None,example:t.Any|None=None,examples:dict[str,t.Any]|None=None,links:dict[str,t.Any]|None=None,content_type:str|None='application/json',headers:SchemaType|None=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(PetOut) def hello(): return the_dict_or_object_match_petout_schema ``` Arguments: schema: The marshmallow schema or Pydantic model 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. content_type: The content/media type of the response. It defaults to `application/json`. headers: The schemas of the headers. *Version changed: 2.1.0* - Add parameter `headers`. *Version changed: 2.0.0* - Don't change the status code to 204 for EmptySchema. *Version changed: 1.3.0* - Add parameter `content_type`. *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`. """body_schema_adapter=registry.create_adapter(schema,schema_name=schema_name)body_schema=body_schema_adapter.schemaheaders_schema=NoneifheadersisnotNone:headers_schema_adapter=registry.create_adapter(headers,schema_name=None)headers_schema=headers_schema_adapter.schemadefdecorator(f):f=_ensure_sync(f)_annotate(f,response={'schema':body_schema,'schema_adapter_many':body_schema_adapter.many,# Store the many flag'status_code':status_code,'description':description,'example':example,'examples':examples,'links':links,'content_type':content_type,'headers':headers_schema,},)def_jsonify(obj:t.Any,many:bool=_sentinel,# type: ignore*args:t.Any,**kwargs:t.Any,)->Response:# pragma: no cover"""Serialize output using schema adapters."""ifisinstance(body_schema,FileSchema):returnobj# type: ignore# Handle many parameterifmanyis_sentinel:# Check adapter's many flag first (for list[Model] syntax)# Then check schema's many attribute (for marshmallow compatibility)many=getattr(body_schema_adapter,'many',False)orgetattr(schema,'many',False)# type: ignorebase_schema:OpenAPISchemaType=current_app.config['BASE_RESPONSE_SCHEMA']ifbase_schemaisnotNoneandstatus_code!=204:data_key:str=current_app.config['BASE_RESPONSE_DATA_KEY']ifisinstance(obj,dict):ifdata_keynotinobj:raiseRuntimeError(f'The data key {data_key!r} is not found in the returned dict.')# Serialize the data partobj[data_key]=body_schema_adapter.serialize_output(obj[data_key],many=many)else:ifnothasattr(obj,data_key):raiseRuntimeError(f'The data key {data_key!r} is not found in the returned object.')# Serialize the data partdata_value=getattr(obj,data_key)serialized_data=body_schema_adapter.serialize_output(data_value,many=many)setattr(obj,data_key,serialized_data)base_schema_adapter=registry.create_adapter(base_schema)data=base_schema_adapter.serialize_output(obj)# type: ignoreelse:data=body_schema_adapter.serialize_output(obj,many=many)# type: ignorereturnjsonify(data,*args,**kwargs)@wraps(f)def_response(*args:t.Any,**kwargs:t.Any)->ResponseReturnValueType:rv=f(*args,**kwargs)ifisinstance(rv,Response):returnrvifnotisinstance(rv,tuple):return_jsonify(rv),status_codejson=_jsonify(rv[0])iflen(rv)==2:rv=(json,rv[1])ifisinstance(rv[1],int)else(json,status_code,rv[1])eliflen(rv)>=3:rv=(json,rv[1],rv[2])else:rv=(json,status_code)returnrv# type: ignorereturn_responsereturndecoratordefdoc(self,summary:str|None=None,description:str|None=None,tags:list[str]|None=None,responses:ResponsesType|None=None,deprecated:bool|None=None,hide:bool|None=None,operation_id:str|None=None,security:str|list[str|dict[str,list]]|None=None,extensions:dict[str,t.Any]|None=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. 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 list of status codes (`[404, 418]`) or a dict in a format of either `{404: 'Not Found'}` or `{404: {'description': 'Not Found', 'content': {'application/json': {'schema': FooSchema}}}}`. If a dict is passed and a response with the same status code is already present, the existing data 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': []}]`). extensions: The spec extensions of this endpoint (OpenAPI operation object). The fields in this extensions dict should start with "x-" prefix. See more details in the [Specification Extensions](https://spec.openapis.org/oas/v3.1.0#specification-extensions) chapter of OpenAPI docs. *Version changed: 2.2.0* - Add `extensions` parameter to support setting spec extensions. *Version changed: 2.0.0* - Remove the deprecated `tag` parameter. - Expand `responses` to support additional structure and parameters. *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* """defdecorator(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,extensions=extensions,)returnfreturndecorator
The auth object, an instance of
HTTPBasicAuth
or HTTPTokenAuth
or [HTTPAPIKeyAuth][apiflask.security.HTTPAPIKeyAuth].
required
roles
list | None
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
str | None
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.
defauth_required(self,auth:HTTPAuthType,roles:list|None=None,optional:str|None=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] or [`HTTPAPIKeyAuth`][apiflask.security.HTTPAPIKeyAuth]. 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: 2.0.0* - Remove the deprecated `role` parameter. *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`. """defdecorator(f):f=_ensure_sync(f)_annotate(f,auth=auth,roles=rolesor[])returnauth.login_required(role=roles,optional=optional)(f)returndecorator
defdelete(self,rule:str,**options:t.Any)->t.Callable[[T_route],T_route]:"""Shortcut for `app.route(methods=['DELETE'])`."""returnself._method_route('DELETE',rule,options)
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
str | None
The description of this endpoint. If not set, the lines after the empty
line of the docstring will be used.
None
tags
list[str] | None
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
ResponsesType | None
The other responses for this view function, accepts a list of status codes
([404, 418]) or a dict in a format of either {404: 'Not Found'} or
{404: {'description': 'Not Found', 'content': {'application/json':
{'schema': FooSchema}}}}. If a dict is passed and a response with the same status
code is already present, the existing data will be overwritten.
None
deprecated
bool | None
Flag this endpoint as deprecated in API docs.
None
hide
bool | None
Hide this endpoint in API docs.
None
operation_id
str | None
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
str | list[str | dict[str, list]] | None
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
extensions
dict[str, Any] | None
The spec extensions of this endpoint (OpenAPI operation object). The fields
in this extensions dict should start with "x-" prefix. See more details in the
Specification Extensions
chapter of OpenAPI docs.
None
Version changed: 2.2.0
Add extensions parameter to support setting spec extensions.
Version changed: 2.0.0
Remove the deprecated tag parameter.
Expand responses to support additional structure and parameters.
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.
defdoc(self,summary:str|None=None,description:str|None=None,tags:list[str]|None=None,responses:ResponsesType|None=None,deprecated:bool|None=None,hide:bool|None=None,operation_id:str|None=None,security:str|list[str|dict[str,list]]|None=None,extensions:dict[str,t.Any]|None=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. 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 list of status codes (`[404, 418]`) or a dict in a format of either `{404: 'Not Found'}` or `{404: {'description': 'Not Found', 'content': {'application/json': {'schema': FooSchema}}}}`. If a dict is passed and a response with the same status code is already present, the existing data 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': []}]`). extensions: The spec extensions of this endpoint (OpenAPI operation object). The fields in this extensions dict should start with "x-" prefix. See more details in the [Specification Extensions](https://spec.openapis.org/oas/v3.1.0#specification-extensions) chapter of OpenAPI docs. *Version changed: 2.2.0* - Add `extensions` parameter to support setting spec extensions. *Version changed: 2.0.0* - Remove the deprecated `tag` parameter. - Expand `responses` to support additional structure and parameters. *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* """defdecorator(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,extensions=extensions,)returnfreturndecorator
Shortcut for app.route() or app.route(methods=['GET']).
Source code in apiflask/scaffold.py
737475
defget(self,rule:str,**options:t.Any)->t.Callable[[T_route],T_route]:"""Shortcut for `app.route()` or `app.route(methods=['GET'])`."""returnself._method_route('GET',rule,options)
If the validation passed, the data will be injected into the view
function as a keyword argument in the form of dict and named {location}_data.
Otherwise, an error response with the detail of the validation result will be
returned.
Be sure to put it under the routes decorators (i.e., app.route, app.get,
app.post, etc.).
The marshmallow schema or Pydantic model 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'
arg_name
str | None
The name of the argument passed to the view function,
defaults to {location}_data.
None
schema_name
str | None
The schema name for dict schema, only needed when you pass
a marshmallow schema dict (e.g., {'name': String(required=True)}) for json
location.
None
example
Any | None
The example data in dict for request body, you should use either
example or examples, not both.
None
examples
dict[str, Any] | None
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
validation
bool
Flag to allow disabling of validation on input. Default to True.
True
*Version changed: 2.2.2
Add parameter validation to allow disabling of validation on input.
Version changed: 2.0.0
Always pass parsed data to view function as a keyword argument.
The argument name will be in the form of {location}_data.
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.
definput(self,schema:SchemaType,location:str='json',arg_name:str|None=None,schema_name:str|None=None,example:t.Any|None=None,examples:dict[str,t.Any]|None=None,validation:bool=True,**kwargs:t.Any,)->t.Callable[[DecoratedType],DecoratedType]:"""Add input settings for view functions. If the validation passed, the data will be injected into the view function as a keyword argument in the form of `dict` and named `{location}_data`. Otherwise, an error response with the detail of the validation result will be returned. > 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.input(PetIn, location='json') def hello(json_data): print(json_data) return 'Hello'! ``` Arguments: schema: The marshmallow schema or Pydantic model of the input data. location: The location of the input data, one of `'json'` (default), `'files'`, `'form'`, `'cookies'`, `'headers'`, `'query'` (same as `'querystring'`). arg_name: The name of the argument passed to the view function, defaults to `{location}_data`. schema_name: The schema name for dict schema, only needed when you pass a marshmallow 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} }, } ``` validation: Flag to allow disabling of validation on input. Default to `True`. *Version changed: 2.2.2 - Add parameter `validation` to allow disabling of validation on input. *Version changed: 2.0.0* - Always pass parsed data to view function as a keyword argument. The argument name will be in the form of `{location}_data`. *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`. """defdecorator(f):f=_ensure_sync(f)is_body_location=locationinBODY_LOCATIONSifis_body_locationandhasattr(f,'_spec')and'body'inf._spec:raiseRuntimeError('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").')# Create schema adapter and use the instantiated schema for spec annotation# This ensures the marshmallow plugin receives schema instancesadapter=registry.create_adapter(schema,schema_name=schema_name)annotation_schema=adapter.schemaiflocation=='json':_annotate(f,body=annotation_schema,body_example=example,body_examples=examples,content_type='application/json',)eliflocation=='form':_annotate(f,body=annotation_schema,body_example=example,body_examples=examples,content_type='application/x-www-form-urlencoded',)eliflocationin['files','form_and_files']:_annotate(f,body=annotation_schema,body_example=example,body_examples=examples,content_type='multipart/form-data',)eliflocation=='json_or_form':_annotate(f,body=annotation_schema,body_example=example,body_examples=examples,content_type=['application/x-www-form-urlencoded','application/json'],)else:ifnothasattr(f,'_spec')orf._spec.get('args')isNone:_annotate(f,args=[])iflocationin['path','view_args']:_annotate(f,omit_default_path_parameters=True)# TODO: Support set example for request parametersf._spec['args'].append((annotation_schema,location))arg_name_val=arg_nameorf'{location}_data'# For marshmallow schemas, use the original webargs approach for compatibilityifadapter.schema_type=='marshmallow':from.schema_adapters.marshmallowimportparserifnotvalidation:@wraps(f)defwrapper(*args:t.Any,**kwargs:t.Any):location_data=parser.load_location_data(schema=annotation_schema,req=flask_request,location=location)kwargs[arg_name_val]=location_datareturnf(*args,**kwargs)returnwrapperreturnparser.use_args(annotation_schema,location=location,arg_name=arg_name_val,**kwargs)(f)# For other schema types (Pydantic, etc.), use the adapter systemelse:@wraps(f)defwrapper(*args:t.Any,**kwargs:t.Any):location_data=adapter.validate_input(flask_request,location,**kwargs)kwargs[arg_name_val]=location_datareturnf(*args,**kwargs)returnwrapperreturndecorator
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.
The marshmallow schema or Pydantic model of the output data.
required
status_code
int
The status code of the response, defaults to 200.
200
description
str | None
The description of the response.
None
schema_name
str | None
The schema name for dict schema, only needed when you pass
a schema dict (e.g., {'name': String()}).
None
example
Any | None
The example data in dict for response body, you should use either
example or examples, not both.
None
examples
dict[str, Any] | None
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
dict[str, Any] | None
The links of response. It accepts a dict which maps a link name to
a link object. Example:
defoutput(self,schema:SchemaType,status_code:int=200,description:str|None=None,schema_name:str|None=None,example:t.Any|None=None,examples:dict[str,t.Any]|None=None,links:dict[str,t.Any]|None=None,content_type:str|None='application/json',headers:SchemaType|None=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(PetOut) def hello(): return the_dict_or_object_match_petout_schema ``` Arguments: schema: The marshmallow schema or Pydantic model 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. content_type: The content/media type of the response. It defaults to `application/json`. headers: The schemas of the headers. *Version changed: 2.1.0* - Add parameter `headers`. *Version changed: 2.0.0* - Don't change the status code to 204 for EmptySchema. *Version changed: 1.3.0* - Add parameter `content_type`. *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`. """body_schema_adapter=registry.create_adapter(schema,schema_name=schema_name)body_schema=body_schema_adapter.schemaheaders_schema=NoneifheadersisnotNone:headers_schema_adapter=registry.create_adapter(headers,schema_name=None)headers_schema=headers_schema_adapter.schemadefdecorator(f):f=_ensure_sync(f)_annotate(f,response={'schema':body_schema,'schema_adapter_many':body_schema_adapter.many,# Store the many flag'status_code':status_code,'description':description,'example':example,'examples':examples,'links':links,'content_type':content_type,'headers':headers_schema,},)def_jsonify(obj:t.Any,many:bool=_sentinel,# type: ignore*args:t.Any,**kwargs:t.Any,)->Response:# pragma: no cover"""Serialize output using schema adapters."""ifisinstance(body_schema,FileSchema):returnobj# type: ignore# Handle many parameterifmanyis_sentinel:# Check adapter's many flag first (for list[Model] syntax)# Then check schema's many attribute (for marshmallow compatibility)many=getattr(body_schema_adapter,'many',False)orgetattr(schema,'many',False)# type: ignorebase_schema:OpenAPISchemaType=current_app.config['BASE_RESPONSE_SCHEMA']ifbase_schemaisnotNoneandstatus_code!=204:data_key:str=current_app.config['BASE_RESPONSE_DATA_KEY']ifisinstance(obj,dict):ifdata_keynotinobj:raiseRuntimeError(f'The data key {data_key!r} is not found in the returned dict.')# Serialize the data partobj[data_key]=body_schema_adapter.serialize_output(obj[data_key],many=many)else:ifnothasattr(obj,data_key):raiseRuntimeError(f'The data key {data_key!r} is not found in the returned object.')# Serialize the data partdata_value=getattr(obj,data_key)serialized_data=body_schema_adapter.serialize_output(data_value,many=many)setattr(obj,data_key,serialized_data)base_schema_adapter=registry.create_adapter(base_schema)data=base_schema_adapter.serialize_output(obj)# type: ignoreelse:data=body_schema_adapter.serialize_output(obj,many=many)# type: ignorereturnjsonify(data,*args,**kwargs)@wraps(f)def_response(*args:t.Any,**kwargs:t.Any)->ResponseReturnValueType:rv=f(*args,**kwargs)ifisinstance(rv,Response):returnrvifnotisinstance(rv,tuple):return_jsonify(rv),status_codejson=_jsonify(rv[0])iflen(rv)==2:rv=(json,rv[1])ifisinstance(rv[1],int)else(json,status_code,rv[1])eliflen(rv)>=3:rv=(json,rv[1],rv[2])else:rv=(json,status_code)returnrv# type: ignorereturn_responsereturndecorator
defpatch(self,rule:str,**options:t.Any)->t.Callable[[T_route],T_route]:"""Shortcut for `app.route(methods=['PATCH'])`."""returnself._method_route('PATCH',rule,options)
defpost(self,rule:str,**options:t.Any)->t.Callable[[T_route],T_route]:"""Shortcut for `app.route(methods=['POST'])`."""returnself._method_route('POST',rule,options)
defput(self,rule:str,**options:t.Any)->t.Callable[[T_route],T_route]:"""Shortcut for `app.route(methods=['PUT'])`."""returnself._method_route('PUT',rule,options)