Skip to content

Commit 6b8b904

Browse files
committed
Initial Modular Response initialization
Signed-off-by: Mihai Damaschin <[email protected]>
1 parent 49b1c38 commit 6b8b904

File tree

8 files changed

+81
-34
lines changed

8 files changed

+81
-34
lines changed

flask_openapi3/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,6 @@
4545
from .models.server import Server
4646
from .models.server import ServerVariable
4747
from .models.tag import Tag
48-
from .models.validation_error import UnprocessableEntity
48+
from .models.validation_error import ErrorResponseModel
4949
from .openapi import OpenAPI
5050
from .view import APIView

flask_openapi3/blueprint.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,8 @@ def _do_decorator(
113113
servers: Optional[List[Server]] = None,
114114
openapi_extensions: Optional[Dict[str, Any]] = None,
115115
doc_ui: bool = True,
116-
method: str = HTTPMethod.GET
116+
method: str = HTTPMethod.GET,
117+
validation_error_status: str = "422"
117118
) -> Tuple[Type[BaseModel], Type[BaseModel], Type[BaseModel], Type[BaseModel], Type[BaseModel], Type[BaseModel]]:
118119
"""
119120
Collects OpenAPI specification information for Flask routes and view functions.
@@ -136,6 +137,8 @@ def _do_decorator(
136137
openapi_extensions: Allows extensions to the OpenAPI Schema.
137138
doc_ui: Add openapi document UI (swagger, rapidoc and redoc).
138139
Defaults to True.
140+
validation_error_status: HTTP Status of the response given when a validation error is detected by pydantic
141+
Defaults to 422
139142
"""
140143
if self.doc_ui is True and doc_ui is True:
141144
if responses is None:
@@ -178,7 +181,8 @@ def _do_decorator(
178181
operation=operation
179182
)
180183
# Parse response
181-
get_responses(combine_responses, extra_responses, self.components_schemas, operation)
184+
get_responses(combine_responses, extra_responses, self.components_schemas, operation,
185+
validation_error_status)
182186
# Convert route parameter format from /pet/<petId> to /pet/{petId}
183187
uri = re.sub(r"<([^<:]+:)?", "{", rule).replace(">", "}")
184188
trail_slash = uri.endswith("/")

flask_openapi3/models/validation_error.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from pydantic import BaseModel, Field
77

88

9-
class UnprocessableEntity(BaseModel):
9+
class ErrorResponseModel(BaseModel):
1010
# More information: https://pydantic-docs.helpmanual.io/usage/models/#error-handling
1111
loc: Optional[List[str]] = Field(None, title="Location", description="the error's location as a list. ")
1212
msg: Optional[str] = Field(None, title="Message", description="a computer-readable identifier of the error type.")

flask_openapi3/openapi.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ def __init__(
4545
external_docs: Optional[ExternalDocumentation] = None,
4646
operation_id_callback: Callable = get_operation_id_for_path,
4747
openapi_extensions: Optional[Dict[str, Any]] = None,
48+
validation_error_status: Optional[str] = "422",
4849
**kwargs: Any
4950
) -> None:
5051
"""
@@ -77,6 +78,8 @@ def __init__(
7778
Default to `get_operation_id_for_path` from utils.
7879
openapi_extensions: Extensions to the OpenAPI Schema.
7980
See https://spec.openapis.org/oas/v3.0.3#specification-extensions.
81+
validation_error_status: HTTP Status of the response given when a validation error is detected by pydantic.
82+
Defaults to 422
8083
**kwargs: Additional kwargs to be passed to Flask.
8184
"""
8285
super(OpenAPI, self).__init__(import_name, **kwargs)
@@ -120,6 +123,9 @@ def __init__(
120123
# Set OpenAPI extensions
121124
self.openapi_extensions = openapi_extensions or dict()
122125

126+
# Set HTTP Response of validation errors within OpenAPI
127+
self.validation_error_status = validation_error_status
128+
123129
# Initialize the OpenAPI documentation UI
124130
if doc_ui:
125131
self._init_doc()
@@ -294,7 +300,8 @@ def _do_decorator(
294300
servers: Optional[List[Server]] = None,
295301
openapi_extensions: Optional[Dict[str, Any]] = None,
296302
doc_ui: bool = True,
297-
method: str = HTTPMethod.GET
303+
method: str = HTTPMethod.GET,
304+
validation_error_status: str = "422"
298305
) -> Tuple[Type[BaseModel], Type[BaseModel], Type[BaseModel], Type[BaseModel], Type[BaseModel], Type[BaseModel]]:
299306
"""
300307
Collects OpenAPI specification information for Flask routes and view functions.
@@ -317,6 +324,8 @@ def _do_decorator(
317324
openapi_extensions: Allows extensions to the OpenAPI Schema.
318325
doc_ui: Add OpenAPI document UI (swagger, rapidoc, and redoc). Defaults to True.
319326
method: HTTP method for the operation. Defaults to GET.
327+
validation_error_status: HTTP Status of the response given when a validation error is detected by pydantic
328+
Defaults to 422
320329
"""
321330
if doc_ui is True:
322331
if responses is None:
@@ -358,7 +367,8 @@ def _do_decorator(
358367
operation=operation
359368
)
360369
# Parse response
361-
get_responses(combine_responses, extra_responses, self.components_schemas, operation)
370+
get_responses(combine_responses, extra_responses, self.components_schemas, operation,
371+
validation_error_status)
362372
# Convert route parameter format from /pet/<petId> to /pet/{petId}
363373
uri = re.sub(r"<([^<:]+:)?", "{", rule).replace(">", "}")
364374
# Parse method

flask_openapi3/request.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,8 @@ def _do_request(
9393
query: Optional[Type[BaseModel]] = None,
9494
form: Optional[Type[BaseModel]] = None,
9595
body: Optional[Type[BaseModel]] = None,
96-
path_kwargs: Optional[Dict[Any, Any]] = None
96+
validation_error_status: str = "422",
97+
path_kwargs: Optional[Dict[Any, Any]] = None,
9798
) -> Union[Response, Dict]:
9899
"""
99100
Validate requests and responses.
@@ -105,6 +106,7 @@ def _do_request(
105106
query: Query model.
106107
form: Form model.
107108
body: Body model.
109+
validation_error_status: HTTP Status of the response upon validation failure
108110
path_kwargs: Path parameters.
109111
110112
Returns:
@@ -135,7 +137,7 @@ def _do_request(
135137
# Create a JSON response with validation error details
136138
response = make_response(e.json())
137139
response.headers["Content-Type"] = "application/json"
138-
response.status_code = 422
140+
response.status_code = int(validation_error_status)
139141
return response
140142

141143
return func_kwargs

flask_openapi3/scaffold.py

Lines changed: 40 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,8 @@ def _do_decorator(
5555
servers: Optional[List[Server]] = None,
5656
openapi_extensions: Optional[Dict[str, Any]] = None,
5757
doc_ui: bool = True,
58-
method: str = HTTPMethod.GET
58+
method: str = HTTPMethod.GET,
59+
validation_error_status: str = "422"
5960
) -> Tuple[Type[BaseModel], Type[BaseModel], Type[BaseModel], Type[BaseModel], Type[BaseModel], Type[BaseModel]]:
6061
raise NotImplementedError
6162

@@ -71,8 +72,9 @@ def create_view_func(
7172
query,
7273
form,
7374
body,
75+
validation_error_status="422",
7476
view_class=None,
75-
view_kwargs=None
77+
view_kwargs=None,
7678
):
7779
is_coroutine_function = iscoroutinefunction(func)
7880
if is_coroutine_function:
@@ -85,10 +87,11 @@ async def view_func(**kwargs) -> Response:
8587
query=query,
8688
form=form,
8789
body=body,
90+
validation_error_status=validation_error_status,
8891
path_kwargs=kwargs
8992
)
9093
if isinstance(func_kwargs, Response):
91-
# 422
94+
# validation_error_status from OpenAPI Initialization
9295
return func_kwargs
9396
# handle async request
9497
if view_class:
@@ -112,10 +115,11 @@ def view_func(**kwargs) -> Response:
112115
query=query,
113116
form=form,
114117
body=body,
115-
path_kwargs=kwargs
118+
path_kwargs=kwargs,
119+
validation_error_status=validation_error_status
116120
)
117121
if isinstance(result, Response):
118-
# 422
122+
# validation_error_status from OpenAPI initialization
119123
return result
120124
# handle request
121125
if view_class:
@@ -153,6 +157,7 @@ def get(
153157
servers: Optional[List[Server]] = None,
154158
openapi_extensions: Optional[Dict[str, Any]] = None,
155159
doc_ui: bool = True,
160+
validation_error_status: str = "422",
156161
**options: Any
157162
) -> Callable:
158163
"""
@@ -176,6 +181,8 @@ def get(
176181
openapi_extensions: Allows extensions to the OpenAPI Schema.
177182
doc_ui: Add openapi document UI (swagger, rapidoc and redoc).
178183
Default to True.
184+
validation_error_status: HTTP Status of the response given when a validation error is detected by pydantic.
185+
Defaults to 422
179186
"""
180187

181188
if extra_form is not None:
@@ -210,10 +217,11 @@ def decorator(func) -> Callable:
210217
servers=servers,
211218
openapi_extensions=openapi_extensions,
212219
doc_ui=doc_ui,
213-
method=HTTPMethod.GET
220+
method=HTTPMethod.GET,
221+
validation_error_status=validation_error_status
214222
)
215223

216-
view_func = self.create_view_func(func, header, cookie, path, query, form, body)
224+
view_func = self.create_view_func(func, header, cookie, path, query, form, body, validation_error_status)
217225
options.update({"methods": [HTTPMethod.GET]})
218226
self.add_url_rule(rule, view_func=view_func, **options)
219227

@@ -239,6 +247,7 @@ def post(
239247
servers: Optional[List[Server]] = None,
240248
openapi_extensions: Optional[Dict[str, Any]] = None,
241249
doc_ui: bool = True,
250+
validation_error_status: str = "422",
242251
**options: Any
243252
) -> Callable:
244253
"""
@@ -261,6 +270,8 @@ def post(
261270
servers: An alternative server array to service this operation.
262271
openapi_extensions: Allows extensions to the OpenAPI Schema.
263272
doc_ui: Declares this operation to be shown.
273+
validation_error_status: HTTP Status of the response given when a validation error is detected by pydantic.
274+
Defaults to 422
264275
"""
265276
if extra_form is not None:
266277
warnings.warn(
@@ -294,10 +305,11 @@ def decorator(func) -> Callable:
294305
servers=servers,
295306
openapi_extensions=openapi_extensions,
296307
doc_ui=doc_ui,
297-
method=HTTPMethod.POST
308+
method=HTTPMethod.POST,
309+
validation_error_status=validation_error_status
298310
)
299311

300-
view_func = self.create_view_func(func, header, cookie, path, query, form, body)
312+
view_func = self.create_view_func(func, header, cookie, path, query, form, body, validation_error_status)
301313
options.update({"methods": [HTTPMethod.POST]})
302314
self.add_url_rule(rule, view_func=view_func, **options)
303315

@@ -323,6 +335,7 @@ def put(
323335
servers: Optional[List[Server]] = None,
324336
openapi_extensions: Optional[Dict[str, Any]] = None,
325337
doc_ui: bool = True,
338+
validation_error_status: str = "422",
326339
**options: Any
327340
) -> Callable:
328341
"""
@@ -345,6 +358,8 @@ def put(
345358
servers: An alternative server array to service this operation.
346359
openapi_extensions: Allows extensions to the OpenAPI Schema.
347360
doc_ui: Declares this operation to be shown.
361+
validation_error_status: HTTP Status of the response given when a validation error is detected by pydantic.
362+
Defaults to 422
348363
"""
349364
if extra_form is not None:
350365
warnings.warn(
@@ -378,10 +393,11 @@ def decorator(func) -> Callable:
378393
servers=servers,
379394
openapi_extensions=openapi_extensions,
380395
doc_ui=doc_ui,
381-
method=HTTPMethod.PUT
396+
method=HTTPMethod.PUT,
397+
validation_error_status=validation_error_status
382398
)
383399

384-
view_func = self.create_view_func(func, header, cookie, path, query, form, body)
400+
view_func = self.create_view_func(func, header, cookie, path, query, form, body, validation_error_status)
385401
options.update({"methods": [HTTPMethod.PUT]})
386402
self.add_url_rule(rule, view_func=view_func, **options)
387403

@@ -406,6 +422,7 @@ def delete(
406422
security: Optional[List[Dict[str, List[Any]]]] = None,
407423
servers: Optional[List[Server]] = None,
408424
openapi_extensions: Optional[Dict[str, Any]] = None,
425+
validation_error_status: str = "422",
409426
doc_ui: bool = True,
410427
**options: Any
411428
) -> Callable:
@@ -429,6 +446,8 @@ def delete(
429446
servers: An alternative server array to service this operation.
430447
openapi_extensions: Allows extensions to the OpenAPI Schema.
431448
doc_ui: Declares this operation to be shown.
449+
validation_error_status: HTTP Status of the response given when a validation error is detected by pydantic.
450+
Defaults to 422
432451
"""
433452
if extra_form is not None:
434453
warnings.warn(
@@ -462,10 +481,11 @@ def decorator(func) -> Callable:
462481
servers=servers,
463482
openapi_extensions=openapi_extensions,
464483
doc_ui=doc_ui,
465-
method=HTTPMethod.DELETE
484+
method=HTTPMethod.DELETE,
485+
validation_error_status=validation_error_status
466486
)
467487

468-
view_func = self.create_view_func(func, header, cookie, path, query, form, body)
488+
view_func = self.create_view_func(func, header, cookie, path, query, form, body, validation_error_status)
469489
options.update({"methods": [HTTPMethod.DELETE]})
470490
self.add_url_rule(rule, view_func=view_func, **options)
471491

@@ -491,6 +511,7 @@ def patch(
491511
servers: Optional[List[Server]] = None,
492512
openapi_extensions: Optional[Dict[str, Any]] = None,
493513
doc_ui: bool = True,
514+
validation_error_status: str = "422",
494515
**options: Any
495516
) -> Callable:
496517
"""
@@ -513,6 +534,8 @@ def patch(
513534
servers: An alternative server array to service this operation.
514535
openapi_extensions: Allows extensions to the OpenAPI Schema.
515536
doc_ui: Declares this operation to be shown.
537+
validation_error_status: HTTP Status of the response given when a validation error is detected by pydantic.
538+
Defaults to 422
516539
"""
517540
if extra_form is not None:
518541
warnings.warn(
@@ -546,10 +569,12 @@ def decorator(func) -> Callable:
546569
servers=servers,
547570
openapi_extensions=openapi_extensions,
548571
doc_ui=doc_ui,
549-
method=HTTPMethod.PATCH
572+
method=HTTPMethod.PATCH,
573+
validation_error_status=validation_error_status
550574
)
551575

552-
view_func = self.create_view_func(func, header, cookie, path, query, form, body)
576+
view_func = self.create_view_func(func, header, cookie, path, query, form, body,
577+
validation_error_status=validation_error_status)
553578
options.update({"methods": [HTTPMethod.PATCH]})
554579
self.add_url_rule(rule, view_func=view_func, **options)
555580

flask_openapi3/utils.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from .models.common import Schema, MediaType, Encoding, ExtraRequestBody
1414
from .models.path import Operation, RequestBody, PathItem, Response
1515
from .models.path import ParameterInType, Parameter
16-
from .models.validation_error import UnprocessableEntity
16+
from .models.validation_error import ErrorResponseModel
1717

1818

1919
def get_operation(
@@ -283,30 +283,31 @@ def get_responses(
283283
responses: Optional[Dict[str, Union[Type[BaseModel], Dict[Any, Any], None]]],
284284
extra_responses: Dict[str, dict],
285285
components_schemas: dict,
286-
operation: Operation
286+
operation: Operation,
287+
validation_error_status: str = "422"
287288
) -> None:
288289
if responses is None:
289290
responses = {}
290291
_responses = {}
291292
_schemas = {}
292-
if not responses.get("422"):
293-
# Handle response 422 for Unprocessable Entity
294-
_responses["422"] = Response(
295-
description=HTTP_STATUS["422"],
293+
if not responses.get(validation_error_status):
294+
# Handle response for validation modelling errors
295+
_responses[validation_error_status] = Response(
296+
description=HTTP_STATUS[validation_error_status],
296297
content={
297298
"application/json": MediaType(
298299
**{
299300
"schema": Schema(
300301
**{
301302
"type": "array",
302-
"items": {"$ref": f"{OPENAPI3_REF_PREFIX}/{UnprocessableEntity.__name__}"}
303+
"items": {"$ref": f"{OPENAPI3_REF_PREFIX}/{ErrorResponseModel.__name__}"}
303304
}
304305
)
305306
}
306307
)
307308
}
308309
)
309-
_schemas[UnprocessableEntity.__name__] = Schema(**UnprocessableEntity.schema())
310+
_schemas[ErrorResponseModel.__name__] = Schema(**ErrorResponseModel.schema())
310311
for key, response in responses.items():
311312
if response is None:
312313
# If the response is None, it means HTTP status code "204" (No Content)

0 commit comments

Comments
 (0)