Skip to content

Commit aa8e17a

Browse files
committed
Optimize InputFilter
1 parent 048c588 commit aa8e17a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+852
-698
lines changed

flask_inputfilter/_input_filter.pyx

Lines changed: 77 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ cdef class InputFilter:
8080
all required conditions; otherwise, returns False.
8181
"""
8282
try:
83-
self.validateData()
83+
self.validate_data()
8484

8585
except ValidationError as e:
8686
self.errors = e.args[0]
@@ -99,13 +99,7 @@ cdef class InputFilter:
9999
cls
100100
101101
Returns:
102-
Callable[
103-
[Any],
104-
Callable[
105-
[tuple[Any, ...], Dict[str, Any]],
106-
Union[Response, tuple[Any, Dict[str, Any]]],
107-
],
108-
]
102+
Callable
109103
"""
110104
def decorator(
111105
f,
@@ -140,14 +134,22 @@ cdef class InputFilter:
140134
if not any(request_method == method for method in input_filter.methods):
141135
return Response(status=405, response="Method Not Allowed")
142136

143-
data = request.json if request.is_json else request.args
137+
if request.is_json:
138+
data = request.get_json(cache=True)
139+
if not isinstance(data, dict):
140+
data = {}
141+
else:
142+
data = dict(request.args)
144143

145144
try:
146-
kwargs = kwargs or {}
145+
if kwargs:
146+
data.update(kwargs)
147147

148-
input_filter.data = {**data, **kwargs}
148+
input_filter.data = data
149+
input_filter.validated_data = {}
150+
input_filter.errors = {}
149151

150-
g.validated_data = input_filter.validateData()
152+
g.validated_data = input_filter.validate_data()
151153

152154
except ValidationError as e:
153155
return Response(
@@ -157,7 +159,7 @@ cdef class InputFilter:
157159
)
158160

159161
except Exception:
160-
logging.getLogger(__name__).exception(
162+
logging.exception(
161163
"An unexpected exception occurred while "
162164
"validating input data.",
163165
)
@@ -205,59 +207,89 @@ cdef class InputFilter:
205207
"""
206208
data = data or self.data
207209
cdef dict errors = {}
208-
cdef bint required
210+
cdef dict validated_data = {}
211+
209212
cdef object default
210213
cdef object fallback
211214
cdef list filters
212215
cdef list validators
213216
cdef object external_api
214217
cdef str copy
215218

216-
for field_name, field_info in self.fields.items():
217-
value = data.get(field_name)
218-
219-
required = field_info.required
220-
default = field_info.default
221-
fallback = field_info.fallback
222-
filters = field_info.filters + self.global_filters
223-
validators = field_info.validators + self.global_validators
224-
steps = field_info.steps
225-
external_api = field_info.external_api
226-
copy = field_info.copy
219+
cdef list global_filters = self.global_filters
220+
cdef list global_validators = self.global_validators
221+
cdef bint has_global_filters = bool(global_filters)
222+
cdef bint has_global_validators = bool(global_validators)
227223

224+
for field_name, field_info in self.fields.items():
228225
try:
229-
if copy:
230-
value = self.validated_data.get(copy)
231-
232-
if external_api:
226+
if field_info.copy:
227+
value = validated_data.get(field_info.copy)
228+
elif field_info.external_api:
233229
value = ExternalApiMixin.call_external_api(
234-
external_api, fallback, self.validated_data
230+
field_info.external_api,
231+
field_info.fallback,
232+
validated_data,
235233
)
234+
else:
235+
value = data.get(field_name)
236+
237+
if field_info.filters or has_global_filters:
238+
filters = field_info.filters
239+
if has_global_filters:
240+
filters = filters + global_filters
241+
value = FieldMixin.apply_filters(filters, value)
242+
243+
if field_info.validators or has_global_validators:
244+
validators = field_info.validators
245+
if has_global_validators:
246+
validators = validators + global_validators
247+
result = FieldMixin.validate_field(
248+
validators, field_info.fallback, value
249+
)
250+
if result is not None:
251+
value = result
236252

237-
value = FieldMixin.apply_filters(filters, value)
238-
value = FieldMixin.validate_field(validators, fallback, value) or value
239-
value = FieldMixin.apply_steps(steps, fallback, value) or value
240-
value = FieldMixin.check_for_required(
241-
field_name, required, default, fallback, value
242-
)
243-
244-
self.validated_data[field_name] = value
253+
if field_info.steps:
254+
result = FieldMixin.apply_steps(
255+
field_info.steps, field_info.fallback, value
256+
)
257+
if result is not None:
258+
value = result
259+
260+
if value is None:
261+
if field_info.required:
262+
if field_info.fallback is not None:
263+
value = field_info.fallback
264+
elif field_info.default is not None:
265+
value = field_info.default
266+
else:
267+
raise ValidationError(
268+
f"Field '{field_name}' is required."
269+
)
270+
elif field_info.default is not None:
271+
value = field_info.default
272+
273+
validated_data[field_name] = value
245274

246275
except ValidationError as e:
247276
errors[field_name] = str(e)
248277

249-
try:
250-
FieldMixin.check_conditions(self.conditions, self.validated_data)
251-
except ValidationError as e:
252-
errors["_condition"] = str(e)
278+
if self.conditions:
279+
try:
280+
FieldMixin.check_conditions(self.conditions, validated_data)
281+
except ValidationError as e:
282+
errors["_condition"] = str(e)
253283

254284
if errors:
255285
raise ValidationError(errors)
256286

287+
self.validated_data = validated_data
288+
257289
if self.model_class is not None:
258-
return self.serialize()
290+
return self.model_class(**validated_data)
259291

260-
return self.validated_data
292+
return validated_data
261293

262294
cpdef void addCondition(self, condition: BaseCondition):
263295
warnings.warn(
@@ -826,7 +858,7 @@ cdef class InputFilter:
826858
"Can only merge with another InputFilter instance."
827859
)
828860

829-
for key, new_field in other.getInputs().items():
861+
for key, new_field in other.get_inputs().items():
830862
self.fields[key] = new_field
831863

832864
self.conditions += other.conditions

flask_inputfilter/conditions/temporal_order_condition.py

Lines changed: 3 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
from __future__ import annotations
22

3-
from datetime import date, datetime
43
from typing import Any, Dict
54

65
from flask_inputfilter.conditions import BaseCondition
7-
from flask_inputfilter.exceptions import ValidationError
6+
from flask_inputfilter.helpers import parse_date
87

98

109
class TemporalOrderCondition(BaseCondition):
@@ -56,26 +55,6 @@ def __init__(
5655
self.larger_date_field = larger_date_field
5756

5857
def check(self, data: Dict[str, Any]) -> bool:
59-
smaller_date = self._parse_date(data.get(self.smaller_date_field))
60-
larger_date = self._parse_date(data.get(self.larger_date_field))
61-
62-
return smaller_date < larger_date
63-
64-
@staticmethod
65-
def _parse_date(value: Any) -> datetime:
66-
if isinstance(value, datetime):
67-
return value
68-
69-
elif isinstance(value, date):
70-
return datetime.combine(value, datetime.min.time())
71-
72-
elif isinstance(value, str):
73-
try:
74-
return datetime.fromisoformat(value)
75-
76-
except ValueError:
77-
raise ValidationError(f"Invalid date format: {value}")
78-
79-
raise ValidationError(
80-
f"Unsupported type for date parsing: {type(value)}"
58+
return parse_date(data.get(self.smaller_date_field)) < parse_date(
59+
data.get(self.larger_date_field)
8160
)

flask_inputfilter/filters/array_element_filter.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,12 @@ def apply(self, value: Any) -> List[Any]:
4949

5050
result = []
5151
for element in value:
52-
if isinstance(self.element_filter, BaseFilter):
52+
if hasattr(self.element_filter, "apply"):
5353
result.append(self.element_filter.apply(element))
5454
continue
5555

5656
elif isinstance(self.element_filter, list) and all(
57-
isinstance(v, BaseFilter) for v in self.element_filter
57+
hasattr(v, "apply") for v in self.element_filter
5858
):
5959
for filter_instance in self.element_filter:
6060
element = filter_instance.apply(element)

flask_inputfilter/filters/base_64_image_downscale_filter.py

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,12 @@ class Base64ImageDownscaleFilter(BaseFilter):
1616
1717
**Parameters:**
1818
19-
- **size** (*int*, default: ``1024 * 1024``): A rough pixel count used to
20-
compute default dimensions.
21-
- **width** (*Optional[int]*): The target width. If not provided, it is
22-
calculated as ``sqrt(size)``.
23-
- **height** (*Optional[int]*): The target height. If not provided, it is
24-
calculated as ``sqrt(size)``.
19+
- **size** (*Optional[int]*, default: ``1024 * 1024``): A rough pixel
20+
count used to compute default dimensions.
21+
- **width** (*Optional[int]*, default: ``size**0.5``): The target width.
22+
If not provided, it is calculated as ``sqrt(size)``.
23+
- **height** (*Optional[int]*, default: ``size**0.5``): The target height.
24+
If not provided, it is calculated as ``sqrt(size)``.
2525
- **proportionally** (*bool*, default: ``True``): Determines if the image
2626
should be scaled proportionally. If ``False``, the image is
2727
forcefully resized to the specified width and height.
@@ -46,17 +46,18 @@ def __init__(self):
4646
])
4747
"""
4848

49-
__slots__ = ("size", "width", "height", "proportionally")
49+
__slots__ = ("width", "height", "proportionally")
5050

5151
def __init__(
5252
self,
53-
size: int = 1024 * 1024,
53+
size: Optional[int] = None,
5454
width: Optional[int] = None,
5555
height: Optional[int] = None,
5656
proportionally: bool = True,
5757
) -> None:
58-
self.width = int(width or size**0.5)
59-
self.height = int(height or size**0.5)
58+
size = size if size else 1024 * 1024
59+
self.width = int(width if width else size**0.5)
60+
self.height = int(height if height else size**0.5)
6061
self.proportionally = proportionally
6162

6263
def apply(self, value: Any) -> Any:

flask_inputfilter/filters/base_64_image_resize_filter.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import base64
44
import io
5-
from typing import Any
5+
from typing import Any, Optional
66

77
from PIL import Image
88

@@ -56,12 +56,12 @@ def __init__(self):
5656
def __init__(
5757
self,
5858
max_size: int = 4 * 1024 * 1024,
59-
format: ImageFormatEnum = ImageFormatEnum.JPEG,
59+
format: Optional[ImageFormatEnum] = None,
6060
preserve_icc_profile: bool = False,
6161
preserve_metadata: bool = False,
6262
) -> None:
6363
self.max_size = max_size
64-
self.format = format
64+
self.format = format if format else ImageFormatEnum.JPEG
6565
self.preserve_metadata = preserve_metadata
6666
self.preserve_icc_profile = preserve_icc_profile
6767

flask_inputfilter/filters/string_remove_emojis_filter.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ def __init__(self):
3939
])
4040
"""
4141

42+
__slots__ = ()
43+
4244
def apply(self, value: Any) -> Union[Optional[str], Any]:
4345
if not isinstance(value, str):
4446
return value

flask_inputfilter/filters/string_slugify_filter.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ def __init__(self):
3030
])
3131
"""
3232

33+
__slots__ = ()
34+
3335
def apply(self, value: Any) -> Union[Optional[str], Any]:
3436
if not isinstance(value, str):
3537
return value

flask_inputfilter/filters/string_trim_filter.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,5 +27,7 @@ def __init__(self):
2727
])
2828
"""
2929

30+
__slots__ = ()
31+
3032
def apply(self, value: Any) -> Union[str, Any]:
3133
return value.strip() if isinstance(value, str) else value

flask_inputfilter/filters/to_alpha_numeric_filter.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ def __init__(self):
2929
])
3030
"""
3131

32+
__slots__ = ()
33+
3234
def apply(self, value: Any) -> Union[Optional[str], Any]:
3335
if not isinstance(value, str):
3436
return value

flask_inputfilter/filters/to_boolean_filter.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,5 +27,7 @@ def __init__(self):
2727
])
2828
"""
2929

30+
__slots__ = ()
31+
3032
def apply(self, value: Any) -> Union[Optional[bool], Any]:
3133
return bool(value)

0 commit comments

Comments
 (0)