Skip to content

Commit e431a4c

Browse files
authored
Merge pull request #63 from LeanderCS/implement-decorator-field-definition
Implement decorator field definition
2 parents eac3a46 + 8d03748 commit e431a4c

File tree

97 files changed

+2752
-693
lines changed

Some content is hidden

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

97 files changed

+2752
-693
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
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

docs/source/changelog.rst

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,73 @@ Changelog
33

44
All notable changes to this project will be documented in this file.
55

6+
7+
[0.7.0] - 2025-09-25
8+
--------------------
9+
10+
Added
11+
^^^^^
12+
- Added that fields, conditons, global filters and global validators can be
13+
added as decorator and do not require a self.add, self.add_condition,
14+
self.add_global_filter or self.add_global_validator call in the __init__.
15+
16+
``self.add`` => ``field``
17+
18+
``self.add_condition`` => ``_conditions``
19+
20+
``self.add_global_filter`` => ``_global_filters``
21+
22+
``self.add_global_validator`` => ``_global_validators``
23+
24+
``self.add_model`` => ``_model``
25+
26+
**Before**:
27+
.. code-block:: python
28+
29+
class UpdateZipcodeInputFilter(InputFilter):
30+
def __init__(self):
31+
super().__init__()
32+
self.add(
33+
'id',
34+
required=True,
35+
filters=[ToIntegerFilter(), ToNullFilter()],
36+
validators=[
37+
IsIntegerValidator()
38+
]
39+
)
40+
41+
self.add_condition(ExactlyOneOfCondition(['zipcode', 'city']))
42+
43+
self.add_global_filter(StringTrimFilter())
44+
45+
self.add_global_validator(IsStringValidator())
46+
47+
self.set_model(UserModel)
48+
49+
**After**:
50+
.. code-block:: python
51+
52+
class UpdateZipcodeInputFilter(InputFilter):
53+
id: int = field(
54+
required=True,
55+
filters=[ToIntegerFilter(), ToNullFilter()],
56+
validators=[IsIntegerValidator()]
57+
)
58+
59+
_conditions = [ExactlyOneOfCondition(['zipcode', 'city'])]
60+
61+
_global_filters = [StringTrimFilter()]
62+
63+
_global_validators = [IsStringValidator()]
64+
65+
_model = UserModel
66+
67+
The Change is fully backward compatible, but the new way is more readable
68+
and maintainable.
69+
70+
You can also mix both ways inside a single InputFilter.
71+
72+
673
[0.6.3] - 2025-09-24
774
--------------------
875

docs/source/guides/frontend_validation.rst

Lines changed: 15 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -18,28 +18,21 @@ Example implementation
1818
app = Flask(__name__)
1919
2020
class UpdateZipcodeInputFilter(InputFilter):
21-
def __init__(self):
22-
super().__init__()
23-
24-
self.add(
25-
'id',
26-
required=True,
27-
filters=[ToIntegerFilter(), ToNullFilter()],
28-
validators=[
29-
IsIntegerValidator()
30-
]
31-
)
32-
33-
self.add(
34-
'zipcode',
35-
filters=[StringTrimFilter()],
36-
validators=[
37-
RegexValidator(
38-
pattern=RegexEnum.POSTAL_CODE.value,
39-
error_message='The zipcode is not in the correct format.'
40-
)
41-
]
42-
)
21+
id: int = field(
22+
required=True,
23+
filters=[ToIntegerFilter(), ToNullFilter()],
24+
validators=[IsIntegerValidator()]
25+
)
26+
27+
zipcode: str = field(
28+
filters=[StringTrimFilter()],
29+
validators=[
30+
RegexValidator(
31+
pattern=RegexEnum.POSTAL_CODE.value,
32+
error_message='The zipcode is not in the correct format.'
33+
)
34+
]
35+
)
4336
4437
@app.route('/form-update-zipcode', methods=['POST'])
4538
@UpdateZipcodeInputFilter.validate()

docs/source/index.rst

Lines changed: 22 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -56,40 +56,28 @@ Definition
5656
.. code-block:: python
5757
5858
class UpdateZipcodeInputFilter(InputFilter):
59-
def __init__(self):
60-
super().__init__()
61-
62-
self.add(
63-
'id',
64-
required=True,
65-
filters=[ToIntegerFilter(), ToNullFilter()],
66-
validators=[
67-
IsIntegerValidator()
68-
]
69-
)
70-
71-
self.add(
72-
'zipcode',
73-
filters=[StringTrimFilter()],
74-
validators=[
75-
RegexValidator(
76-
RegexEnum.POSTAL_CODE.value,
77-
'The zipcode is not in the correct format.'
78-
)
79-
]
80-
)
81-
82-
self.add(
83-
'city',
84-
filters=[StringTrimFilter()],
85-
validators=[
86-
IsStringValidator()
87-
]
88-
)
89-
90-
self.add_condition(
91-
ExactlyOneOfCondition(['zipcode', 'city'])
92-
)
59+
id: int = field(
60+
required=True,
61+
filters=[ToIntegerFilter(), ToNullFilter()],
62+
validators=[IsIntegerValidator()]
63+
)
64+
65+
zipcode: str = field(
66+
filters=[StringTrimFilter()],
67+
validators=[
68+
RegexValidator(
69+
RegexEnum.POSTAL_CODE.value,
70+
'The zipcode is not in the correct format.'
71+
)
72+
]
73+
)
74+
75+
city: str = field(
76+
filters=[StringTrimFilter()],
77+
validators=[IsStringValidator()]
78+
)
79+
80+
_conditions = [ExactlyOneOfCondition(['zipcode', 'city'])]
9381
9482
Usage
9583
^^^^^

docs/source/options/condition.rst

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,32 +6,25 @@ Condition
66
Overview
77
--------
88

9-
Conditions are added using the ``add_condition`` method. They evaluate the combined input data, ensuring that inter-field dependencies and relationships (such as equality, ordering, or presence) meet predefined rules.
9+
Conditions are added using the ``_conditions`` class attribute. They evaluate the combined input data, ensuring that inter-field dependencies and relationships (such as equality, ordering, or presence) meet predefined rules.
1010

1111
Example
1212
-------
1313

1414
.. code-block:: python
1515
1616
class TestInputFilter(InputFilter):
17-
def __init__(self):
18-
super().__init__()
17+
username: str = field(
18+
filters=[StringTrimFilter()],
19+
validators=[IsStringValidator()]
20+
)
1921
20-
self.add(
21-
'username',
22-
filters=[StringTrimFilter()],
23-
validators=[IsStringValidator()]
24-
)
22+
name: str = field(
23+
filters=[StringTrimFilter()],
24+
validators=[IsStringValidator()]
25+
)
2526
26-
self.add(
27-
'name',
28-
filters=[StringTrimFilter()],
29-
validators=[IsStringValidator()]
30-
)
31-
32-
self.add_condition(
33-
OneOfCondition(['id', 'name'])
34-
)
27+
_conditions = [OneOfCondition(['id', 'name'])]
3528
3629
Available Conditions
3730
--------------------

docs/source/options/copy.rst

Lines changed: 25 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ Copy functionality
44
Overview
55
--------
66

7-
The copy functionality is configured via the ``copy`` parameter in the ``add`` method.
7+
The copy functionality is configured via the ``copy`` parameter in the ``field`` function.
88
This parameter accepts a string with the name the value should be copied from.
99

1010
.. note::
@@ -20,21 +20,14 @@ Basic Copy Integration
2020
.. code-block:: python
2121
2222
class MyInputFilter(InputFilter):
23-
def __init__(self):
24-
super().__init__()
25-
26-
self.add(
27-
"username",
28-
validator=[
29-
IsStringValidator()
30-
]
31-
)
32-
33-
self.add(
34-
"escapedUsername",
35-
copy="username"
36-
filters=[StringSlugifyFilter()]
37-
)
23+
username: str = field(
24+
validators=[IsStringValidator()]
25+
)
26+
27+
escapedUsername: str = field(
28+
copy="username",
29+
filters=[StringSlugifyFilter()]
30+
)
3831
3932
# Example usage
4033
# Body: {"username": "Very Important User"}
@@ -53,30 +46,22 @@ The coping can also be used as a chain.
5346
.. code-block:: python
5447
5548
class MyInputFilter(InputFilter):
56-
def __init__(self):
57-
super().__init__()
58-
59-
self.add(
60-
"username"
61-
)
62-
63-
self.add(
64-
"escapedUsername",
65-
copy="username"
66-
filters=[StringSlugifyFilter()]
67-
)
68-
69-
self.add(
70-
"upperEscapedUsername",
71-
copy="escapedUsername"
72-
filters=[ToUpperFilter()]
73-
)
74-
75-
self.add(
76-
"lowerEscapedUsername",
77-
copy="escapedUsername"
78-
filters=[ToLowerFilter()]
79-
)
49+
username: str = field()
50+
51+
escapedUsername: str = field(
52+
copy="username",
53+
filters=[StringSlugifyFilter()]
54+
)
55+
56+
upperEscapedUsername: str = field(
57+
copy="escapedUsername",
58+
filters=[ToUpperFilter()]
59+
)
60+
61+
lowerEscapedUsername: str = field(
62+
copy="escapedUsername",
63+
filters=[ToLowerFilter()]
64+
)
8065
8166
# Example usage
8267
# Body: {"username": "Very Important User"}

0 commit comments

Comments
 (0)