Skip to content

Commit 03fc480

Browse files
authored
add serializated arguments and parameters to request object. (#32)
* add serialized arguments and parameters to request object.
1 parent 7049ca0 commit 03fc480

File tree

7 files changed

+81
-73
lines changed

7 files changed

+81
-73
lines changed

CHANGES.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
## Version 0.17.0
2+
3+
* Authorization via HTTPBasicAuth and Bearer token supported.
4+
* Add append serialized path-parameters, arguments, json, cookie and headers to object of request
5+
Flask.
6+
17
## Version 0.16.1
28

39
* Fix the display of the specification described in several files in Swagger UI.

README.md

Lines changed: 35 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ Flask extension for using "specification first" and "API-first" principles.
66

77
- [Flask-First](#flask-first)
88
- [Features](#features)
9-
- [Limitations](#limitations)
109
- [Installation](#installation)
1110
- [Settings](#settings)
11+
- [Tools](#tools)
1212
- [Data types](#data-types)
1313
- [Examples](#examples)
1414
- [Simple example](#simple-example)
@@ -21,23 +21,16 @@ Flask extension for using "specification first" and "API-first" principles.
2121
## Features
2222

2323
* `Application Factory` supported.
24-
* Validating and serializing headers of request from `request.headers` to `request.first_headers`.
25-
* Validating and serializing path parameters of request from `request.view_args`
26-
to `request.first_view_args`.
27-
* Validating and serializing arguments of request from `request.args` to `request.first_args`.
28-
* Validating and serializing cookies of request from `request.cookies` to `request.first_cookies`.
29-
* Validating and serializing JSON of request from `request.json` to `request.first_json`.
24+
* Validating and serializing headers of request.
25+
* Validating and serializing path parameters of request.
26+
* Validating and serializing arguments of request.
27+
* Validating and serializing cookies of request.
28+
* Validating and serializing JSON of request.
3029
* Validating JSON from response for debugging.
3130
* Provides a Swagger UI.
3231
* Support OpenAPI version 3.1.0.
3332
* Support specification from multiple file.
3433

35-
### Limitations
36-
37-
Will be added in future releases.
38-
39-
* Authorization not supported.
40-
4134
## Installation
4235

4336
Recommended using the latest version of Python. Flask-First supports Python 3.9 and newer.
@@ -53,6 +46,23 @@ $ pip install -U flask_first
5346
`FIRST_RESPONSE_VALIDATION` - Default: `False`. Enabling response body validation. Useful when
5447
developing. Must be disabled in a production environment.
5548

49+
## Tools
50+
51+
Possible to get data from path-parameters, arguments, JSON, cookies and headers in serialized form.
52+
Use flask-first object attached to the query.
53+
54+
```python
55+
from flask import request
56+
57+
58+
def route_func():
59+
path_parameters = request.extensions['first']['views']
60+
args = request.extensions['first']['args']
61+
json = request.extensions['first']['json']
62+
cookies = request.extensions['first']['cookies']
63+
headers = request.extensions['first']['headers']
64+
```
65+
5666
## Data types
5767

5868
Supported formats for string type field:
@@ -203,18 +213,18 @@ paths:
203213
options:
204214
summary: CORS support
205215
responses:
206-
200:
207-
headers:
208-
Access-Control-Allow-Origin:
209-
schema:
210-
type: string
211-
Access-Control-Allow-Methods:
212-
schema:
213-
type: string
214-
Access-Control-Allow-Headers:
215-
schema:
216-
type: string
217-
content: { }
216+
200:
217+
headers:
218+
Access-Control-Allow-Origin:
219+
schema:
220+
type: string
221+
Access-Control-Allow-Methods:
222+
schema:
223+
type: string
224+
Access-Control-Allow-Headers:
225+
schema:
226+
type: string
227+
content: { }
218228
```
219229

220230
## Additional documentation

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ classifiers = [
1313
"Programming Language :: Python :: 3"
1414
]
1515
dependencies = [
16-
'Flask>=2.0.2',
16+
'Flask>=2.0.3',
1717
'PyYAML>=6.0.1',
1818
'openapi-spec-validator>=0.5.0',
1919
'marshmallow>=3.14.1'
@@ -23,7 +23,7 @@ license = {file = "LICENSE"}
2323
name = "Flask-First"
2424
readme = "README.md"
2525
requires-python = ">=3.9"
26-
version = "0.16.1"
26+
version = "0.17.0"
2727

2828
[project.optional-dependencies]
2929
dev = [

src/flask_first/__init__.py

Lines changed: 32 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
import re
22
from pathlib import Path
3-
from typing import Any
43

54
import marshmallow
65
from flask import Flask
76
from flask import Request
87
from flask import request
98
from flask import Response
109
from marshmallow.exceptions import ValidationError
11-
from werkzeug.datastructures import Headers
1210
from werkzeug.datastructures import MultiDict
1311

1412
from .first import RequestSerializer
@@ -77,30 +75,27 @@ def _route_registration_in_flask(self, func: callable) -> None:
7775

7876
self._mapped_routes_from_spec.append(rule)
7977

80-
def _extract_data_from_request(
81-
self, request_obj: Request
82-
) -> tuple[Any, str or None, Headers, dict[str, Any] or None, dict, dict, Any or None]:
83-
method = request_obj.method.lower()
78+
@staticmethod
79+
def _extract_method_from_request(request_obj: Request) -> str:
80+
return request_obj.method.lower()
8481

82+
@staticmethod
83+
def _extract_route_from_request(request_obj: Request) -> str:
8584
if request_obj.url_rule is not None:
8685
route = request_obj.url_rule.rule
8786
else:
8887
route = request_obj.url_rule
8988

90-
headers = request_obj.headers
91-
92-
view_args = request_obj.view_args
93-
94-
args = self._resolved_params(request_obj.args)
95-
96-
cookies = self._resolved_params(request_obj.cookies)
89+
return route
9790

91+
@staticmethod
92+
def _extract_json_from_request(request_obj: Request) -> dict or None:
9893
if request_obj.is_json:
9994
json = request_obj.get_json()
10095
else:
10196
json = None
10297

103-
return method, route, headers, view_args, args, cookies, json
98+
return json
10499

105100
@staticmethod
106101
def _resolved_params(payload: MultiDict) -> dict:
@@ -139,31 +134,29 @@ def add_request_validating() -> None:
139134
if request.method in ('OPTIONS',):
140135
return
141136

142-
(
143-
method,
144-
route,
145-
headers,
146-
view_args,
147-
args,
148-
cookies,
149-
json,
150-
) = self._extract_data_from_request(request)
151-
137+
route = self._extract_route_from_request(request)
152138
if route not in self._mapped_routes_from_spec:
153139
return
154140

155141
route_as_in_spec = self.route_to_openapi_format(route)
156142

143+
method = self._extract_method_from_request(request)
157144
params_schemas = self.spec.serialized_spec['paths'][route_as_in_spec][method].get(
158145
'parameters'
159146
)
147+
args = self._resolved_params(request.args)
160148
if params_schemas:
161149
args_schema = params_schemas.get('args')
162150
if args_schema:
163151
schema_fields = args_schema().fields
164152
args = self._arg_to_list(args, schema_fields)
165153

166-
rv = RequestSerializer(
154+
headers = request.headers
155+
view_args = request.view_args
156+
cookies = self._resolved_params(request.cookies)
157+
json = self._extract_json_from_request(request)
158+
159+
request_serializer = RequestSerializer(
167160
self.spec,
168161
method,
169162
route_as_in_spec,
@@ -173,20 +166,22 @@ def add_request_validating() -> None:
173166
params=args,
174167
json=json,
175168
)
176-
rv.validate()
177-
178-
request.first_headers = rv.serialized_headers
179-
request.first_view_args = rv.serialized_path_params
180-
request.first_args = rv.serialized_params
181-
request.first_cookies = rv.serialized_cookies
182-
request.first_json = rv.serialized_json
169+
request_serializer.validate()
170+
171+
request.extensions = {
172+
'first': {
173+
'headers': request_serializer.serialized_headers,
174+
'view_args': request_serializer.serialized_path_params,
175+
'args': request_serializer.serialized_params,
176+
'cookies': request_serializer.serialized_cookies,
177+
'json': request_serializer.serialized_json,
178+
}
179+
}
183180

184181
def _register_response_validation(self) -> None:
185182
@self.app.after_request
186183
def add_response_validating(response: Response) -> Response:
187-
method, route, _, _, _, _, json = self._extract_data_from_request(request)
188-
json = response.get_json()
189-
184+
route = self._extract_route_from_request(request)
190185
if route not in self._mapped_routes_from_spec:
191186
return response
192187

@@ -199,6 +194,7 @@ def add_response_validating(response: Response) -> Response:
199194
f'Route <{e.args[0]}> not defined in specification.'
200195
)
201196

197+
method = self._extract_method_from_request(request)
202198
try:
203199
method_schema: dict = route_schema[method]
204200
except KeyError as e:
@@ -226,6 +222,7 @@ def add_response_validating(response: Response) -> Response:
226222
)
227223

228224
if response_content_type == 'application/json':
225+
json = response.get_json()
229226
json_schema = content[response.content_type]['schema']
230227
try:
231228
if isinstance(json, list):

src/flask_first/first/specification.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,10 +90,6 @@ def resolve(self) -> Mapping[Hashable, Any]:
9090
return self._resolving(schema, self.abs_path)
9191

9292

93-
class Serializer:
94-
pass
95-
96-
9793
class Specification:
9894
def __init__(self, path: Path or str, experimental_validator: bool = False):
9995
self.path = path

tests/test_args.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ def mini_endpoint() -> dict:
2626

2727
def test_specification__required_args(fx_create_app):
2828
def mini_endpoint(required_path) -> dict:
29-
required_arg = request.first_args['required_arg']
29+
required_arg = request.extensions['first']['args']['required_arg']
3030

3131
return {'required_path': required_path, 'required_arg': required_arg}
3232

tests/test_flask_first.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -90,9 +90,9 @@ def items_list() -> Response:
9090
def test_specification__args(fx_app, fx_client):
9191
def items_args() -> dict:
9292
return {
93-
'page': request.first_args['page'],
94-
'per_page': request.first_args['per_page'],
95-
'page_list': request.first_args['page_list'],
93+
'page': request.extensions['first']['args']['page'],
94+
'per_page': request.extensions['first']['args']['per_page'],
95+
'page_list': request.extensions['first']['args']['page_list'],
9696
}
9797

9898
fx_app.extensions['first'].add_view_func(items_args)
@@ -316,7 +316,7 @@ def mini_endpoint(uuid: str) -> dict:
316316

317317
def test_specification__params__format():
318318
def mini_endpoint(uuid_from_path: str, datetime_from_path: str) -> dict:
319-
args = request.first_args
319+
args = request.extensions['first']['args']
320320
uuid_from_query = args['uuid_from_query']
321321
datetime_from_query = args['datetime_from_query']
322322

@@ -360,8 +360,7 @@ def create_app():
360360

361361
def test_specification__param_as_list():
362362
def mini_endpoint() -> dict:
363-
args = request.first_args
364-
param_as_list = args['param_as_list']
363+
param_as_list = request.extensions['first']['args']['param_as_list']
365364

366365
assert isinstance(param_as_list, list)
367366
assert isinstance(param_as_list[0], uuid.UUID)

0 commit comments

Comments
 (0)