Skip to content

Commit 0988375

Browse files
committed
Update Inputfilter to allow decorator field definition
model Update Inputfilter to allow decorator field definition Doc
1 parent eac3a46 commit 0988375

File tree

13 files changed

+1773
-83
lines changed

13 files changed

+1773
-83
lines changed

README.md

Lines changed: 30 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -68,46 +68,42 @@ A more detailed guide can be found [in the docs](https://leandercs.github.io/fla
6868

6969
```python
7070
from flask_inputfilter import InputFilter
71+
from flask_inputfilter.declarative import field, _conditions
7172
from flask_inputfilter.conditions import ExactlyOneOfCondition
7273
from flask_inputfilter.enums import RegexEnum
7374
from flask_inputfilter.filters import StringTrimFilter, ToIntegerFilter, ToNullFilter
7475
from flask_inputfilter.validators import IsIntegerValidator, IsStringValidator, RegexValidator
7576

7677
class UpdateZipcodeInputFilter(InputFilter):
77-
def __init__(self):
78-
super().__init__()
79-
80-
self.add(
81-
'id',
82-
required=True,
83-
filters=[ToIntegerFilter(), ToNullFilter()],
84-
validators=[
85-
IsIntegerValidator()
86-
]
87-
)
88-
89-
self.add(
90-
'zipcode',
91-
filters=[StringTrimFilter()],
92-
validators=[
93-
RegexValidator(
94-
RegexEnum.POSTAL_CODE.value,
95-
'The zipcode is not in the correct format.'
96-
)
97-
]
98-
)
99-
100-
self.add(
101-
'city',
102-
filters=[StringTrimFilter()],
103-
validators=[
104-
IsStringValidator()
105-
]
106-
)
107-
108-
self.add_condition(
109-
ExactlyOneOfCondition(['zipcode', 'city'])
110-
)
78+
79+
id: int = field(
80+
required=True,
81+
filters=[ToIntegerFilter(), ToNullFilter()],
82+
validators=[
83+
IsIntegerValidator()
84+
]
85+
)
86+
87+
zipcode: str = field(
88+
filters=[StringTrimFilter()],
89+
validators=[
90+
RegexValidator(
91+
RegexEnum.POSTAL_CODE.value,
92+
'The zipcode is not in the correct format.'
93+
)
94+
]
95+
)
96+
97+
city: str = field(
98+
filters=[StringTrimFilter()],
99+
validators=[
100+
IsStringValidator()
101+
]
102+
)
103+
104+
_conditions(
105+
ExactlyOneOfCondition(['zipcode', 'city'])
106+
)
111107
```
112108

113109

Update.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
1. Add py.typed marker for better IDE support
2+
3+
- Create a py.typed file to indicate the package is type-annotated
4+
- This enables better autocomplete and type checking in IDEs
5+
6+
5. Add common validation presets
7+
8+
- Email, URL, phone number, credit card validators
9+
- Common field combinations (e.g., address, user profile)
10+
11+
6. Improve error messages
12+
13+
- Add field path in nested validations
14+
- Support for i18n/localization
15+
- Better error aggregation for multiple fields
16+
17+
7. Add async support
18+
19+
- Support async validators for external API calls
20+
- Async filter application for IO-bound operations
21+
22+
8. Create CLI tool for code generation
23+
24+
- Generate InputFilter classes from JSON schema
25+
- Generate from OpenAPI specifications
26+
- Interactive filter builder
27+
28+
9. Add middleware integration helpers
29+
30+
- Direct integration with Flask-RESTful
31+
- Support for Flask-RESTX/Flask-Smorest
32+
- Marshmallow compatibility layer
33+
34+
10. Improve documentation
35+
36+
- Add more real-world examples
37+
- Create a cookbook section
38+
- Add migration guide from other validation libraries
39+
40+
41+
Validatoren erhalten richtige Fehler-Typen von Fehlern,
42+
die auftreten, damit man pro Fehlertyp eine Fehlermeldung schreiben kann
Lines changed: 26 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from enum import Enum
22

33
from flask_inputfilter import InputFilter
4+
from flask_inputfilter.declarative import field
45
from flask_inputfilter.filters import ToFloatFilter
56
from flask_inputfilter.validators import (
67
ArrayElementValidator,
@@ -21,31 +22,28 @@ class Tags(Enum):
2122

2223

2324
class ProductInputFilter(InputFilter):
24-
def __init__(self):
25-
super().__init__()
26-
27-
self.add(
28-
"name",
29-
required=True,
30-
validators=[
31-
IsStringValidator(),
32-
],
33-
)
34-
35-
self.add(
36-
"price",
37-
required=True,
38-
filters=[ToFloatFilter()],
39-
validators=[
40-
IsFloatValidator(),
41-
],
42-
)
43-
44-
self.add(
45-
"tags",
46-
required=False,
47-
validators=[
48-
IsArrayValidator(),
49-
ArrayElementValidator(InEnumValidator(Tags)),
50-
],
51-
)
25+
26+
name: str = field(
27+
required=True,
28+
validators=[
29+
IsStringValidator()
30+
]
31+
)
32+
33+
price: float = field(
34+
required=True,
35+
filters=[
36+
ToFloatFilter()
37+
],
38+
validators=[
39+
IsFloatValidator()
40+
]
41+
)
42+
43+
tags: list = field(
44+
required=False,
45+
validators=[
46+
IsArrayValidator(),
47+
ArrayElementValidator(InEnumValidator(Tags))
48+
]
49+
)
Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from dataclasses import dataclass
22

33
from flask_inputfilter import InputFilter
4+
from flask_inputfilter.declarative import field
45
from flask_inputfilter.validators import (
56
IsDataclassValidator,
67
IsStringValidator,
@@ -22,19 +23,24 @@ class Address:
2223

2324

2425
class ProfileInputFilter(InputFilter):
25-
def __init__(self):
26-
super().__init__()
27-
28-
self.add(
29-
"user",
30-
required=True,
31-
validators=[IsDataclassValidator(dataclass_type=User)],
32-
)
33-
34-
self.add(
35-
"address",
36-
required=True,
37-
validators=[IsDataclassValidator(dataclass_type=Address)],
38-
)
39-
40-
self.add("phone", required=False, validators=[IsStringValidator()])
26+
27+
user: User = field(
28+
required=True,
29+
validators=[
30+
IsDataclassValidator(dataclass_type=User)
31+
]
32+
)
33+
34+
address: Address = field(
35+
required=True,
36+
validators=[
37+
IsDataclassValidator(dataclass_type=Address)
38+
]
39+
)
40+
41+
phone: str = field(
42+
required=False,
43+
validators=[
44+
IsStringValidator()
45+
]
46+
)
Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,34 @@
11
from flask_inputfilter import InputFilter
2+
from flask_inputfilter.declarative import field
23
from flask_inputfilter.validators import IsIntegerValidator, IsStringValidator
34

5+
@dataclass
6+
class User:
7+
name: str
8+
age: int
9+
email: str
410

511
class UserInputFilter(InputFilter):
6-
def __init__(self):
7-
super().__init__()
812

9-
self.add("name", required=True, validators=[IsStringValidator()])
13+
_model = User
1014

11-
self.add("age", required=True, validators=[IsIntegerValidator()])
15+
name: str = field(
16+
required=True,
17+
validators=[
18+
IsStringValidator()
19+
]
20+
)
1221

13-
self.add("email", required=True, validators=[IsStringValidator()])
22+
age: int = field(
23+
required=True,
24+
validators=[
25+
IsIntegerValidator()
26+
]
27+
)
28+
29+
email: str = field(
30+
required=True,
31+
validators=[
32+
IsStringValidator()
33+
]
34+
)
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from .factory_functions import field
2+
from .field_descriptor import FieldDescriptor
3+
4+
__all__ = [
5+
"FieldDescriptor",
6+
"field",
7+
]
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
from __future__ import annotations
2+
3+
from typing import TYPE_CHECKING, Any, Optional, Union
4+
5+
from .field_descriptor import FieldDescriptor
6+
7+
if TYPE_CHECKING:
8+
from flask_inputfilter.models import (
9+
BaseFilter,
10+
BaseValidator,
11+
ExternalApiConfig,
12+
)
13+
14+
15+
def field(
16+
required: bool = False,
17+
default: Any = None,
18+
fallback: Any = None,
19+
filters: Optional[list[BaseFilter]] = None,
20+
validators: Optional[list[BaseValidator]] = None,
21+
steps: Optional[list[Union[BaseFilter, BaseValidator]]] = None,
22+
external_api: Optional[ExternalApiConfig] = None,
23+
copy: Optional[str] = None,
24+
) -> FieldDescriptor:
25+
"""
26+
Create a field descriptor for declarative field definition.
27+
28+
This function creates a FieldDescriptor that can be used as a class
29+
attribute to define input filter fields declaratively.
30+
31+
**Parameters:**
32+
33+
- **required** (*bool*): Whether the field is required. Default: False.
34+
- **default** (*Any*): The default value of the field. Default: None.
35+
- **fallback** (*Any*): The fallback value of the field, if
36+
validations fail or field is None, although it is required. Default: None.
37+
- **filters** (*Optional[list[BaseFilter]]*): The filters to apply to
38+
the field value. Default: None.
39+
- **validators** (*Optional[list[BaseValidator]]*): The validators to
40+
apply to the field value. Default: None.
41+
- **steps** (*Optional[list[Union[BaseFilter, BaseValidator]]]*): Allows
42+
to apply multiple filters and validators in a specific order. Default: None.
43+
- **external_api** (*Optional[ExternalApiConfig]*): Configuration for an
44+
external API call. Default: None.
45+
- **copy** (*Optional[str]*): The name of the field to copy the value
46+
from. Default: None.
47+
48+
**Returns:**
49+
50+
A field descriptor configured with the given parameters.
51+
52+
**Example:**
53+
54+
.. code-block:: python
55+
56+
from flask_inputfilter import InputFilter
57+
from flask_inputfilter.declarative import field
58+
from flask_inputfilter.validators import IsStringValidator
59+
60+
class UserInputFilter(InputFilter):
61+
name: str = field(required=True, validators=[IsStringValidator()])
62+
age: int = field(required=True, default=18)
63+
"""
64+
return FieldDescriptor(
65+
required=required,
66+
default=default,
67+
fallback=fallback,
68+
filters=filters,
69+
validators=validators,
70+
steps=steps,
71+
external_api=external_api,
72+
copy=copy,
73+
)

0 commit comments

Comments
 (0)