Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/connexion/apps/flask.py: 8%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""
2This module defines a FlaskApp, a Connexion application to wrap a Flask application.
3"""
5import functools
6import pathlib
7import typing as t
9import flask
10import starlette.exceptions
11import werkzeug.exceptions
12from a2wsgi import WSGIMiddleware
13from flask import Response as FlaskResponse
14from starlette.types import Receive, Scope, Send
16from connexion.apps.abstract import AbstractApp
17from connexion.decorators import FlaskDecorator
18from connexion.exceptions import ResolverError
19from connexion.frameworks import flask as flask_utils
20from connexion.jsonifier import Jsonifier
21from connexion.lifecycle import ConnexionRequest, ConnexionResponse
22from connexion.middleware.abstract import AbstractRoutingAPI, SpecMiddleware
23from connexion.middleware.lifespan import Lifespan
24from connexion.operations import AbstractOperation
25from connexion.options import SwaggerUIOptions
26from connexion.resolver import Resolver
27from connexion.types import MaybeAwaitable, WSGIApp
28from connexion.uri_parsing import AbstractURIParser
31class FlaskOperation:
32 def __init__(
33 self,
34 fn: t.Callable,
35 jsonifier: Jsonifier,
36 operation_id: str,
37 pythonic_params: bool,
38 ) -> None:
39 self._fn = fn
40 self.jsonifier = jsonifier
41 self.operation_id = operation_id
42 self.pythonic_params = pythonic_params
43 functools.update_wrapper(self, fn)
45 @classmethod
46 def from_operation(
47 cls,
48 operation: AbstractOperation,
49 *,
50 pythonic_params: bool,
51 jsonifier: Jsonifier,
52 ) -> "FlaskOperation":
53 return cls(
54 fn=operation.function,
55 jsonifier=jsonifier,
56 operation_id=operation.operation_id,
57 pythonic_params=pythonic_params,
58 )
60 @property
61 def fn(self) -> t.Callable:
62 decorator = FlaskDecorator(
63 pythonic_params=self.pythonic_params,
64 jsonifier=self.jsonifier,
65 )
66 return decorator(self._fn)
68 def __call__(self, *args, **kwargs) -> FlaskResponse:
69 return self.fn(*args, **kwargs)
72class FlaskApi(AbstractRoutingAPI):
73 def __init__(
74 self, *args, jsonifier: t.Optional[Jsonifier] = None, **kwargs
75 ) -> None:
76 self.jsonifier = jsonifier or Jsonifier(flask.json, indent=2)
77 super().__init__(*args, **kwargs)
79 def _set_base_path(self, base_path: t.Optional[str] = None) -> None:
80 super()._set_base_path(base_path)
81 self._set_blueprint()
83 def _set_blueprint(self):
84 endpoint = flask_utils.flaskify_endpoint(self.base_path) or "/"
85 self.blueprint = flask.Blueprint(
86 endpoint,
87 __name__,
88 url_prefix=self.base_path,
89 )
91 def _add_resolver_error_handler(self, method: str, path: str, err: ResolverError):
92 pass
94 def make_operation(self, operation):
95 return FlaskOperation.from_operation(
96 operation, pythonic_params=self.pythonic_params, jsonifier=self.jsonifier
97 )
99 @staticmethod
100 def _framework_path_and_name(
101 operation: AbstractOperation, path: str
102 ) -> t.Tuple[str, str]:
103 flask_path = flask_utils.flaskify_path(
104 path, operation.get_path_parameter_types()
105 )
106 endpoint_name = flask_utils.flaskify_endpoint(
107 operation.operation_id, operation.randomize_endpoint
108 )
109 return flask_path, endpoint_name
111 def _add_operation_internal(
112 self,
113 method: str,
114 path: str,
115 operation: t.Callable,
116 name: t.Optional[str] = None,
117 ) -> None:
118 self.blueprint.add_url_rule(path, name, operation, methods=[method])
120 def add_url_rule(
121 self,
122 rule,
123 endpoint: t.Optional[str] = None,
124 view_func: t.Optional[t.Callable] = None,
125 **options,
126 ):
127 return self.blueprint.add_url_rule(rule, endpoint, view_func, **options)
130class FlaskASGIApp(SpecMiddleware):
131 def __init__(self, import_name, server_args: dict, **kwargs):
132 self.app = flask.Flask(import_name, **server_args)
133 self.app.json = flask_utils.FlaskJSONProvider(self.app)
134 self.app.url_map.converters["float"] = flask_utils.NumberConverter
135 self.app.url_map.converters["int"] = flask_utils.IntegerConverter
137 # Propagate Errors so we can handle them in the middleware
138 self.app.config["PROPAGATE_EXCEPTIONS"] = True
139 self.app.config["TRAP_BAD_REQUEST_ERRORS"] = True
140 self.app.config["TRAP_HTTP_EXCEPTIONS"] = True
142 self.asgi_app = WSGIMiddleware(self.app.wsgi_app)
144 def add_api(self, specification, *, name: t.Optional[str] = None, **kwargs):
145 api = FlaskApi(specification, **kwargs)
147 if name is not None:
148 self.app.register_blueprint(api.blueprint, name=name)
149 else:
150 self.app.register_blueprint(api.blueprint)
152 return api
154 def add_url_rule(
155 self,
156 rule,
157 endpoint: t.Optional[str] = None,
158 view_func: t.Optional[t.Callable] = None,
159 **options,
160 ):
161 return self.app.add_url_rule(rule, endpoint, view_func, **options)
163 async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
164 return await self.asgi_app(scope, receive, send)
167class FlaskApp(AbstractApp):
168 """Connexion Application based on ConnexionMiddleware wrapping a Flask application."""
170 _middleware_app: FlaskASGIApp
172 def __init__(
173 self,
174 import_name: str,
175 *,
176 lifespan: t.Optional[Lifespan] = None,
177 middlewares: t.Optional[list] = None,
178 server_args: t.Optional[dict] = None,
179 specification_dir: t.Union[pathlib.Path, str] = "",
180 arguments: t.Optional[dict] = None,
181 auth_all_paths: t.Optional[bool] = None,
182 jsonifier: t.Optional[Jsonifier] = None,
183 pythonic_params: t.Optional[bool] = None,
184 resolver: t.Optional[t.Union[Resolver, t.Callable]] = None,
185 resolver_error: t.Optional[int] = None,
186 strict_validation: t.Optional[bool] = None,
187 swagger_ui_options: t.Optional[SwaggerUIOptions] = None,
188 uri_parser_class: t.Optional[AbstractURIParser] = None,
189 validate_responses: t.Optional[bool] = None,
190 validator_map: t.Optional[dict] = None,
191 security_map: t.Optional[dict] = None,
192 ):
193 """
194 :param import_name: The name of the package or module that this object belongs to. If you
195 are using a single module, __name__ is always the correct value. If you however are
196 using a package, it’s usually recommended to hardcode the name of your package there.
197 :param lifespan: A lifespan context function, which can be used to perform startup and
198 shutdown tasks.
199 :param middlewares: The list of middlewares to wrap around the application. Defaults to
200 :obj:`middleware.main.ConnexionMiddleware.default_middlewares`
201 :param server_args: Arguments to pass to the Flask application.
202 :param specification_dir: The directory holding the specification(s). The provided path
203 should either be absolute or relative to the root path of the application. Defaults to
204 the root path.
205 :param arguments: Arguments to substitute the specification using Jinja.
206 :param auth_all_paths: whether to authenticate all paths not defined in the specification.
207 Defaults to False.
208 :param jsonifier: Custom jsonifier to overwrite json encoding for json responses.
209 :param pythonic_params: When True, CamelCase parameters are converted to snake_case and an
210 underscore is appended to any shadowed built-ins. Defaults to False.
211 :param resolver: Callable that maps operationId to a function or instance of
212 :class:`resolver.Resolver`.
213 :param resolver_error: Error code to return for operations for which the operationId could
214 not be resolved. If no error code is provided, the application will fail when trying to
215 start.
216 :param strict_validation: When True, extra form or query parameters not defined in the
217 specification result in a validation error. Defaults to False.
218 :param swagger_ui_options: Instance of :class:`options.SwaggerUIOptions` with
219 configuration options for the swagger ui.
220 :param uri_parser_class: Class to use for uri parsing. See :mod:`uri_parsing`.
221 :param validate_responses: Whether to validate responses against the specification. This has
222 an impact on performance. Defaults to False.
223 :param validator_map: A dictionary of validators to use. Defaults to
224 :obj:`validators.VALIDATOR_MAP`.
225 :param security_map: A dictionary of security handlers to use. Defaults to
226 :obj:`security.SECURITY_HANDLERS`
227 """
228 self._middleware_app = FlaskASGIApp(import_name, server_args or {})
230 super().__init__(
231 import_name,
232 lifespan=lifespan,
233 middlewares=middlewares,
234 specification_dir=specification_dir,
235 arguments=arguments,
236 auth_all_paths=auth_all_paths,
237 jsonifier=jsonifier,
238 pythonic_params=pythonic_params,
239 resolver=resolver,
240 resolver_error=resolver_error,
241 strict_validation=strict_validation,
242 swagger_ui_options=swagger_ui_options,
243 uri_parser_class=uri_parser_class,
244 validate_responses=validate_responses,
245 validator_map=validator_map,
246 security_map=security_map,
247 )
249 self.app = self._middleware_app.app
250 self.app.register_error_handler(
251 werkzeug.exceptions.HTTPException, self._http_exception
252 )
254 def _http_exception(self, exc: werkzeug.exceptions.HTTPException):
255 """Reraise werkzeug HTTPExceptions as starlette HTTPExceptions"""
256 raise starlette.exceptions.HTTPException(exc.code, detail=exc.description)
258 def add_url_rule(
259 self,
260 rule,
261 endpoint: t.Optional[str] = None,
262 view_func: t.Optional[t.Callable] = None,
263 **options,
264 ):
265 self._middleware_app.add_url_rule(
266 rule, endpoint=endpoint, view_func=view_func, **options
267 )
269 def add_error_handler(
270 self,
271 code_or_exception: t.Union[int, t.Type[Exception]],
272 function: t.Callable[
273 [ConnexionRequest, Exception], MaybeAwaitable[ConnexionResponse]
274 ],
275 ) -> None:
276 self.middleware.add_error_handler(code_or_exception, function)
278 def add_wsgi_middleware(
279 self, middleware: t.Type[WSGIApp], **options: t.Any
280 ) -> None:
281 """Wrap the underlying Flask application with a WSGI middleware. Note that it will only be
282 called at the end of the middleware stack. Middleware that needs to act sooner, needs to
283 be added as ASGI middleware instead.
285 Adding multiple middleware using this method wraps each middleware around the previous one.
287 :param middleware: Middleware class to add
288 :param options: Options to pass to the middleware_class on initialization
289 """
290 self._middleware_app.asgi_app.app = middleware(
291 self._middleware_app.asgi_app.app, **options # type: ignore
292 )