everysk-lib 1.10.2__cp312-cp312-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- everysk/__init__.py +30 -0
- everysk/_version.py +683 -0
- everysk/api/__init__.py +61 -0
- everysk/api/api_requestor.py +167 -0
- everysk/api/api_resources/__init__.py +23 -0
- everysk/api/api_resources/api_resource.py +371 -0
- everysk/api/api_resources/calculation.py +779 -0
- everysk/api/api_resources/custom_index.py +42 -0
- everysk/api/api_resources/datastore.py +81 -0
- everysk/api/api_resources/file.py +42 -0
- everysk/api/api_resources/market_data.py +223 -0
- everysk/api/api_resources/parser.py +66 -0
- everysk/api/api_resources/portfolio.py +43 -0
- everysk/api/api_resources/private_security.py +42 -0
- everysk/api/api_resources/report.py +65 -0
- everysk/api/api_resources/report_template.py +39 -0
- everysk/api/api_resources/tests.py +115 -0
- everysk/api/api_resources/worker_execution.py +64 -0
- everysk/api/api_resources/workflow.py +65 -0
- everysk/api/api_resources/workflow_execution.py +93 -0
- everysk/api/api_resources/workspace.py +42 -0
- everysk/api/http_client.py +63 -0
- everysk/api/tests.py +32 -0
- everysk/api/utils.py +262 -0
- everysk/config.py +451 -0
- everysk/core/_tests/serialize/test_json.py +336 -0
- everysk/core/_tests/serialize/test_orjson.py +295 -0
- everysk/core/_tests/serialize/test_pickle.py +48 -0
- everysk/core/cloud_function/main.py +78 -0
- everysk/core/cloud_function/tests.py +86 -0
- everysk/core/compress.py +245 -0
- everysk/core/datetime/__init__.py +12 -0
- everysk/core/datetime/calendar.py +144 -0
- everysk/core/datetime/date.py +424 -0
- everysk/core/datetime/date_expression.py +299 -0
- everysk/core/datetime/date_mixin.py +1475 -0
- everysk/core/datetime/date_settings.py +30 -0
- everysk/core/datetime/datetime.py +713 -0
- everysk/core/exceptions.py +435 -0
- everysk/core/fields.py +1176 -0
- everysk/core/firestore.py +555 -0
- everysk/core/fixtures/_settings.py +29 -0
- everysk/core/fixtures/other/_settings.py +18 -0
- everysk/core/fixtures/user_agents.json +88 -0
- everysk/core/http.py +691 -0
- everysk/core/lists.py +92 -0
- everysk/core/log.py +709 -0
- everysk/core/number.py +37 -0
- everysk/core/object.py +1469 -0
- everysk/core/redis.py +1021 -0
- everysk/core/retry.py +51 -0
- everysk/core/serialize.py +674 -0
- everysk/core/sftp.py +414 -0
- everysk/core/signing.py +53 -0
- everysk/core/slack.py +127 -0
- everysk/core/string.py +199 -0
- everysk/core/tests.py +240 -0
- everysk/core/threads.py +199 -0
- everysk/core/undefined.py +70 -0
- everysk/core/unittests.py +73 -0
- everysk/core/workers.py +241 -0
- everysk/sdk/__init__.py +23 -0
- everysk/sdk/base.py +98 -0
- everysk/sdk/brutils/cnpj.py +391 -0
- everysk/sdk/brutils/cnpj_pd.py +129 -0
- everysk/sdk/engines/__init__.py +26 -0
- everysk/sdk/engines/cache.py +185 -0
- everysk/sdk/engines/compliance.py +37 -0
- everysk/sdk/engines/cryptography.py +69 -0
- everysk/sdk/engines/expression.cp312-win_amd64.pyd +0 -0
- everysk/sdk/engines/expression.pyi +55 -0
- everysk/sdk/engines/helpers.cp312-win_amd64.pyd +0 -0
- everysk/sdk/engines/helpers.pyi +26 -0
- everysk/sdk/engines/lock.py +120 -0
- everysk/sdk/engines/market_data.py +244 -0
- everysk/sdk/engines/settings.py +19 -0
- everysk/sdk/entities/__init__.py +23 -0
- everysk/sdk/entities/base.py +784 -0
- everysk/sdk/entities/base_list.py +131 -0
- everysk/sdk/entities/custom_index/base.py +209 -0
- everysk/sdk/entities/custom_index/settings.py +29 -0
- everysk/sdk/entities/datastore/base.py +160 -0
- everysk/sdk/entities/datastore/settings.py +17 -0
- everysk/sdk/entities/fields.py +375 -0
- everysk/sdk/entities/file/base.py +215 -0
- everysk/sdk/entities/file/settings.py +63 -0
- everysk/sdk/entities/portfolio/base.py +248 -0
- everysk/sdk/entities/portfolio/securities.py +241 -0
- everysk/sdk/entities/portfolio/security.py +580 -0
- everysk/sdk/entities/portfolio/settings.py +97 -0
- everysk/sdk/entities/private_security/base.py +226 -0
- everysk/sdk/entities/private_security/settings.py +17 -0
- everysk/sdk/entities/query.py +603 -0
- everysk/sdk/entities/report/base.py +214 -0
- everysk/sdk/entities/report/settings.py +23 -0
- everysk/sdk/entities/script.py +310 -0
- everysk/sdk/entities/secrets/base.py +128 -0
- everysk/sdk/entities/secrets/script.py +119 -0
- everysk/sdk/entities/secrets/settings.py +17 -0
- everysk/sdk/entities/settings.py +48 -0
- everysk/sdk/entities/tags.py +174 -0
- everysk/sdk/entities/worker_execution/base.py +307 -0
- everysk/sdk/entities/worker_execution/settings.py +63 -0
- everysk/sdk/entities/workflow_execution/base.py +113 -0
- everysk/sdk/entities/workflow_execution/settings.py +32 -0
- everysk/sdk/entities/workspace/base.py +99 -0
- everysk/sdk/entities/workspace/settings.py +27 -0
- everysk/sdk/settings.py +67 -0
- everysk/sdk/tests.py +105 -0
- everysk/sdk/worker_base.py +47 -0
- everysk/server/__init__.py +9 -0
- everysk/server/applications.py +63 -0
- everysk/server/endpoints.py +516 -0
- everysk/server/example_api.py +69 -0
- everysk/server/middlewares.py +80 -0
- everysk/server/requests.py +62 -0
- everysk/server/responses.py +119 -0
- everysk/server/routing.py +64 -0
- everysk/server/settings.py +36 -0
- everysk/server/tests.py +36 -0
- everysk/settings.py +98 -0
- everysk/sql/__init__.py +9 -0
- everysk/sql/connection.py +232 -0
- everysk/sql/model.py +376 -0
- everysk/sql/query.py +417 -0
- everysk/sql/row_factory.py +63 -0
- everysk/sql/settings.py +49 -0
- everysk/sql/utils.py +129 -0
- everysk/tests.py +23 -0
- everysk/utils.py +81 -0
- everysk/version.py +15 -0
- everysk_lib-1.10.2.dist-info/.gitignore +5 -0
- everysk_lib-1.10.2.dist-info/METADATA +326 -0
- everysk_lib-1.10.2.dist-info/RECORD +137 -0
- everysk_lib-1.10.2.dist-info/WHEEL +5 -0
- everysk_lib-1.10.2.dist-info/licenses/LICENSE.txt +9 -0
- everysk_lib-1.10.2.dist-info/top_level.txt +2 -0
everysk/core/fields.py
ADDED
|
@@ -0,0 +1,1176 @@
|
|
|
1
|
+
###############################################################################
|
|
2
|
+
#
|
|
3
|
+
# (C) Copyright 2023 EVERYSK TECHNOLOGIES
|
|
4
|
+
#
|
|
5
|
+
# This is an unpublished work containing confidential and proprietary
|
|
6
|
+
# information of EVERYSK TECHNOLOGIES. Disclosure, use, or reproduction
|
|
7
|
+
# without authorization of EVERYSK TECHNOLOGIES is prohibited.
|
|
8
|
+
#
|
|
9
|
+
###############################################################################
|
|
10
|
+
import re
|
|
11
|
+
from collections.abc import Callable, Iterator
|
|
12
|
+
from sys import maxsize as max_int
|
|
13
|
+
from types import UnionType
|
|
14
|
+
from typing import Any
|
|
15
|
+
from urllib.parse import urlparse
|
|
16
|
+
|
|
17
|
+
from everysk.core.datetime import Date, DateTime
|
|
18
|
+
from everysk.core.exceptions import FieldValueError, ReadonlyError
|
|
19
|
+
from everysk.core.object import BaseDict, BaseField
|
|
20
|
+
from everysk.utils import bool_convert
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
###############################################################################
|
|
24
|
+
# Private Functions Implementation
|
|
25
|
+
###############################################################################
|
|
26
|
+
def _do_nothing(*args, **kwargs) -> None:
|
|
27
|
+
# pylint: disable=unused-argument
|
|
28
|
+
raise ReadonlyError('This field value cannot be changed.')
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _min_max_validate(min_value: Any, max_value: Any, value: Any, attr_name: str) -> None:
|
|
32
|
+
"""
|
|
33
|
+
Helper function to validate if value is between min and max for fields.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
min_value (Any): The min value to be checked.
|
|
37
|
+
max_value (Any): The max value to be checked.
|
|
38
|
+
value (Any): The value used to validate.
|
|
39
|
+
attr_name (str): The name of the attribute to be used on errors.
|
|
40
|
+
|
|
41
|
+
Raises:
|
|
42
|
+
FieldValueError: If the value is not between min and max.
|
|
43
|
+
|
|
44
|
+
Example:
|
|
45
|
+
>>> _min_max_validate(0, 10, 5, "my_field")
|
|
46
|
+
# No exception is raised
|
|
47
|
+
|
|
48
|
+
>>> _min_max_validate(0, 10, 15, "my_field")
|
|
49
|
+
# FieldValueError is raised with the message:
|
|
50
|
+
# "The value '15' for field 'my_field' must be between 0 and 10."
|
|
51
|
+
"""
|
|
52
|
+
if value is not None and value is not Undefined:
|
|
53
|
+
min_check = False
|
|
54
|
+
max_check = False
|
|
55
|
+
|
|
56
|
+
# Check if min_value and max_value are callable
|
|
57
|
+
min_value = min_value if not callable(min_value) else min_value()
|
|
58
|
+
max_value = max_value if not callable(max_value) else max_value()
|
|
59
|
+
|
|
60
|
+
if min_value is not None and min_value is not Undefined:
|
|
61
|
+
min_check = value < min_value
|
|
62
|
+
|
|
63
|
+
if max_value is not None and max_value is not Undefined:
|
|
64
|
+
max_check = value > max_value
|
|
65
|
+
|
|
66
|
+
if min_check or max_check:
|
|
67
|
+
msg = f"The value '{value}' for field '{attr_name}' must be between {min_value} and {max_value}."
|
|
68
|
+
raise FieldValueError(msg)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
###############################################################################
|
|
72
|
+
# Field Class Implementation
|
|
73
|
+
###############################################################################
|
|
74
|
+
class Field(BaseField):
|
|
75
|
+
attr_type: Any = Any
|
|
76
|
+
choices: set = None
|
|
77
|
+
|
|
78
|
+
def __new__(cls, *args, **kwargs) -> Any:
|
|
79
|
+
"""
|
|
80
|
+
For the VSCode autocomplete works correctly, we need to say what is the
|
|
81
|
+
type that the method __new__ returns
|
|
82
|
+
"""
|
|
83
|
+
return super().__new__(cls)
|
|
84
|
+
|
|
85
|
+
def __init__(
|
|
86
|
+
self,
|
|
87
|
+
default: Any = None,
|
|
88
|
+
*,
|
|
89
|
+
required: bool = False,
|
|
90
|
+
readonly: bool = False,
|
|
91
|
+
required_lazy: bool = False,
|
|
92
|
+
empty_is_none: bool = False,
|
|
93
|
+
choices: set | None = None,
|
|
94
|
+
**kwargs,
|
|
95
|
+
) -> None:
|
|
96
|
+
super().__init__(
|
|
97
|
+
attr_type=self.attr_type,
|
|
98
|
+
default=default,
|
|
99
|
+
required=required,
|
|
100
|
+
readonly=readonly,
|
|
101
|
+
required_lazy=required_lazy,
|
|
102
|
+
empty_is_none=empty_is_none,
|
|
103
|
+
choices=choices,
|
|
104
|
+
**kwargs,
|
|
105
|
+
)
|
|
106
|
+
if self.default not in (None, Undefined) and self._has_invalid_choices(self.default):
|
|
107
|
+
msg = f"The default value '{self.default}' must be in this list {self.choices}."
|
|
108
|
+
raise FieldValueError(msg)
|
|
109
|
+
|
|
110
|
+
def _get_choices(self) -> set:
|
|
111
|
+
"""Returns the list of choices."""
|
|
112
|
+
return self.choices or set()
|
|
113
|
+
|
|
114
|
+
def _has_invalid_choices(self, value: Any) -> bool:
|
|
115
|
+
"""
|
|
116
|
+
Validates whether the given value is within the allowed choices.
|
|
117
|
+
|
|
118
|
+
If `self.attr_type` is a list, tuple, or set, it checks whether all elements in `value`
|
|
119
|
+
exist in `self.choices`. If `value` is a single item, it verifies its presence in `self.choices`.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
value (Any): The value or collection of values to validate.
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
bool: True if the value is invalid (not in `self.choices`), otherwise False.
|
|
126
|
+
"""
|
|
127
|
+
choices = self._get_choices()
|
|
128
|
+
# If we don't have a list of choices we don't need to check
|
|
129
|
+
if not choices:
|
|
130
|
+
return False
|
|
131
|
+
|
|
132
|
+
raise_value_error = False
|
|
133
|
+
if self.attr_type in {list, tuple, set}:
|
|
134
|
+
if set(value).difference(choices):
|
|
135
|
+
raise_value_error = True
|
|
136
|
+
|
|
137
|
+
elif value not in choices:
|
|
138
|
+
raise_value_error = True
|
|
139
|
+
|
|
140
|
+
return raise_value_error
|
|
141
|
+
|
|
142
|
+
def validate(self, attr_name: str, value: Any, attr_type: type | UnionType | None = None) -> None:
|
|
143
|
+
"""
|
|
144
|
+
Validates the value against the field's type and choices.
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
attr_name (str): The name of the attribute being validated.
|
|
148
|
+
value (Any): The value to validate.
|
|
149
|
+
attr_type (type | UnionType | None, optional): The expected type of the attribute. Defaults to None.
|
|
150
|
+
|
|
151
|
+
Raises:
|
|
152
|
+
FieldValueError: If the value is not valid according to the field's type and choices.
|
|
153
|
+
"""
|
|
154
|
+
# Execute the normal validations
|
|
155
|
+
super().validate(attr_name, value, attr_type)
|
|
156
|
+
|
|
157
|
+
# Then we check if the value is in the choices
|
|
158
|
+
if value not in (None, Undefined) and self._has_invalid_choices(value):
|
|
159
|
+
msg = f"The value '{value}' for field '{attr_name}' must be in this list {self.choices}."
|
|
160
|
+
raise FieldValueError(msg)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
###############################################################################
|
|
164
|
+
# BoolField Class Implementation
|
|
165
|
+
###############################################################################
|
|
166
|
+
class BoolField(Field):
|
|
167
|
+
attr_type: bool = bool
|
|
168
|
+
|
|
169
|
+
def __new__(cls, *args, **kwargs) -> bool:
|
|
170
|
+
# This signature is used to change the default autocomplete for this class
|
|
171
|
+
return super().__new__(cls, *args, **kwargs)
|
|
172
|
+
|
|
173
|
+
def clean_value(self, value: Any) -> Any:
|
|
174
|
+
"""
|
|
175
|
+
Converts the given value to a boolean if possible using the 'convert_boolean' function.
|
|
176
|
+
(Check the function's documentation for more information)
|
|
177
|
+
|
|
178
|
+
Args:
|
|
179
|
+
value (Boolean): The value to be converted. Can be of any type
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
Any: The value converted to its boolean corresponding
|
|
183
|
+
|
|
184
|
+
Example:
|
|
185
|
+
>>> from everysk.core.fields import BoolField
|
|
186
|
+
>>> bool_field = BoolField()
|
|
187
|
+
>>> bool_field.clean_value("y")
|
|
188
|
+
>>> True
|
|
189
|
+
|
|
190
|
+
>>> bool_field.clean_value("n")
|
|
191
|
+
>>> False
|
|
192
|
+
|
|
193
|
+
>>> bool_field.clean_value("a")
|
|
194
|
+
>>> ValueError: Invalid truth value 'a'
|
|
195
|
+
"""
|
|
196
|
+
# https://docs.python.org/3/distutils/apiref.html#distutils.util.strtobool
|
|
197
|
+
# The module distutils is deprecated, then we put the function code here
|
|
198
|
+
if value is not None and value is not Undefined:
|
|
199
|
+
value = bool_convert(value)
|
|
200
|
+
|
|
201
|
+
return super().clean_value(value)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
###############################################################################
|
|
205
|
+
# DateField Class Implementation
|
|
206
|
+
###############################################################################
|
|
207
|
+
class DateField(Field):
|
|
208
|
+
attr_type: Date = Date
|
|
209
|
+
min_date: Date | Callable = None
|
|
210
|
+
max_date: Date | Callable = None
|
|
211
|
+
|
|
212
|
+
def __new__(cls, *args, **kwargs) -> Date:
|
|
213
|
+
# This signature is used to change the default autocomplete for this class
|
|
214
|
+
return super().__new__(cls, *args, **kwargs)
|
|
215
|
+
|
|
216
|
+
def __init__(
|
|
217
|
+
self,
|
|
218
|
+
default: Any = None,
|
|
219
|
+
*,
|
|
220
|
+
min_date: Date = None,
|
|
221
|
+
max_date: Date = None,
|
|
222
|
+
required: bool = False,
|
|
223
|
+
readonly: bool = False,
|
|
224
|
+
required_lazy: bool = False,
|
|
225
|
+
empty_is_none: bool = False,
|
|
226
|
+
choices: set | None = None,
|
|
227
|
+
**kwargs,
|
|
228
|
+
) -> None:
|
|
229
|
+
super().__init__(
|
|
230
|
+
default=default,
|
|
231
|
+
min_date=min_date,
|
|
232
|
+
max_date=max_date,
|
|
233
|
+
required=required,
|
|
234
|
+
readonly=readonly,
|
|
235
|
+
required_lazy=required_lazy,
|
|
236
|
+
empty_is_none=empty_is_none,
|
|
237
|
+
choices=choices,
|
|
238
|
+
**kwargs,
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
def clean_value(self, value: Any) -> Any:
|
|
242
|
+
"""
|
|
243
|
+
Method simple converts Date strings to Date object in the Everysk format
|
|
244
|
+
|
|
245
|
+
Args:
|
|
246
|
+
value (Any): Desired date string to be converted
|
|
247
|
+
|
|
248
|
+
Returns:
|
|
249
|
+
A new Date object representing the converted date
|
|
250
|
+
|
|
251
|
+
Raises:
|
|
252
|
+
ValueError: If the string is not represented in either
|
|
253
|
+
ISO format ("YYYY-MM-DD") or Everysk format ("YYYYMMDD").
|
|
254
|
+
|
|
255
|
+
Example:
|
|
256
|
+
>>> from everysk.core.fields import DateField
|
|
257
|
+
>>> date_field = DateField()
|
|
258
|
+
>>> date_field.clean_value("20140314")
|
|
259
|
+
Date(2014, 3, 14)
|
|
260
|
+
>>> date_field.clean_value("2014-03-14")
|
|
261
|
+
Date(2014, 3, 14)
|
|
262
|
+
"""
|
|
263
|
+
if isinstance(value, str):
|
|
264
|
+
if '-' in value:
|
|
265
|
+
value = Date.fromisoformat(value)
|
|
266
|
+
else:
|
|
267
|
+
# Everysk format
|
|
268
|
+
value = Date.strptime(value, '%Y%m%d')
|
|
269
|
+
|
|
270
|
+
return super().clean_value(value)
|
|
271
|
+
|
|
272
|
+
def validate(self, attr_name: str, value: Any, attr_type: type | UnionType | None = None) -> None:
|
|
273
|
+
"""
|
|
274
|
+
Checks if value is greater than min and lower than max including both values.
|
|
275
|
+
|
|
276
|
+
Args:
|
|
277
|
+
attr_name (str): Name of the attribute used for error checking
|
|
278
|
+
value (Any): Value used for validation
|
|
279
|
+
attr_type (type | UnionType, optional): Type of the field we are trying to validate. Defaults to None.
|
|
280
|
+
|
|
281
|
+
Returns:
|
|
282
|
+
Either the value is between min_date and max_date
|
|
283
|
+
|
|
284
|
+
Raises:
|
|
285
|
+
FieldValueError: If the value is not between min_date and max_date
|
|
286
|
+
|
|
287
|
+
Example:
|
|
288
|
+
>>> from everysk.core.fields import DateField
|
|
289
|
+
>>> from everysk.core.datetime import Date
|
|
290
|
+
>>> date_field = DateField(min_date=Date(2023, 1, 1), max_date=Date(2023, 12, 31))
|
|
291
|
+
|
|
292
|
+
>>> try:
|
|
293
|
+
>>> ... date_field.validate("example_date", Date(2023, 6, 15))
|
|
294
|
+
>>> ... print("June 15, 2023, is a valid date")
|
|
295
|
+
>>> except Exception as e:
|
|
296
|
+
>>> ... print(f"Validation error: {e}")
|
|
297
|
+
>>> June 15, 2023, is a valid date
|
|
298
|
+
|
|
299
|
+
>>> try:
|
|
300
|
+
>>> ... date_field.validate("example_date", Date(2022, 12, 31))
|
|
301
|
+
>>> ... print("December 31, 2022, is a valid date")
|
|
302
|
+
>>> except Exception as e:
|
|
303
|
+
>>> ... print(f"validation error: {e}")
|
|
304
|
+
>>> Validation error: The value '2022-12-31' for field 'example_date' must be between 2023-01-01 and 2023-12-31.
|
|
305
|
+
""" # noqa: E501
|
|
306
|
+
_min_max_validate(self.min_date, self.max_date, value, attr_name)
|
|
307
|
+
return super().validate(attr_name, value, attr_type)
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
###############################################################################
|
|
311
|
+
# DateTimeField Class Implementation
|
|
312
|
+
###############################################################################
|
|
313
|
+
class DateTimeField(Field):
|
|
314
|
+
attr_type: DateTime = DateTime
|
|
315
|
+
min_date: DateTime | Callable = None
|
|
316
|
+
max_date: DateTime | Callable = None
|
|
317
|
+
force_time: str = None
|
|
318
|
+
|
|
319
|
+
def __new__(cls, *args, **kwargs) -> DateTime:
|
|
320
|
+
# This signature is used to change the default autocomplete for this class
|
|
321
|
+
return super().__new__(cls, *args, **kwargs)
|
|
322
|
+
|
|
323
|
+
def __init__(
|
|
324
|
+
self,
|
|
325
|
+
default: Any = None,
|
|
326
|
+
*,
|
|
327
|
+
min_date: DateTime = None,
|
|
328
|
+
max_date: DateTime = None,
|
|
329
|
+
force_time: str = 'FIRST_MINUTE',
|
|
330
|
+
required: bool = False,
|
|
331
|
+
readonly: bool = False,
|
|
332
|
+
required_lazy: bool = False,
|
|
333
|
+
empty_is_none: bool = False,
|
|
334
|
+
choices: set | None = None,
|
|
335
|
+
**kwargs,
|
|
336
|
+
) -> None:
|
|
337
|
+
super().__init__(
|
|
338
|
+
default=default,
|
|
339
|
+
min_date=min_date,
|
|
340
|
+
max_date=max_date,
|
|
341
|
+
force_time=force_time,
|
|
342
|
+
required=required,
|
|
343
|
+
readonly=readonly,
|
|
344
|
+
required_lazy=required_lazy,
|
|
345
|
+
empty_is_none=empty_is_none,
|
|
346
|
+
choices=choices,
|
|
347
|
+
**kwargs,
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
def clean_value(self, value: Any) -> Any:
|
|
351
|
+
"""
|
|
352
|
+
Converts a DateTime string to a DateTime object using the 'fromisoformat' function.
|
|
353
|
+
(check 'fromisoformat' function for more information)
|
|
354
|
+
|
|
355
|
+
Args:
|
|
356
|
+
value (Any): desired DateTime string to be converted
|
|
357
|
+
|
|
358
|
+
Returns:
|
|
359
|
+
Any: a new DateTime object formatted
|
|
360
|
+
|
|
361
|
+
Raises:
|
|
362
|
+
ValueError: if the input is not formatted in the ISO format
|
|
363
|
+
|
|
364
|
+
Example:
|
|
365
|
+
>>> from everysk.core.fields import DateTimeField
|
|
366
|
+
>>> date_time_field = DateTimeField()
|
|
367
|
+
|
|
368
|
+
>>> date_time_field.clean_value("2023-03-15")
|
|
369
|
+
>>> DateTime(2023, 3, 15, 0, 0, tzinfo=zoneinfo.ZoneInfo(key='UTC'))
|
|
370
|
+
|
|
371
|
+
>>> date_time_field.clean_value("2023-03-15T14:30:00")
|
|
372
|
+
>>> DateTime(2023, 3, 15, 14, 30, tzinfo=zoneinfo.ZoneInfo(key='UTC'))
|
|
373
|
+
|
|
374
|
+
>>> date_time_field.clean_value("14-03-2023")
|
|
375
|
+
>>> ValueError: time data "14-03-2023" does not match format: "%Y-%m-%d"
|
|
376
|
+
"""
|
|
377
|
+
# Convert DateTime strings to DateTime object
|
|
378
|
+
if isinstance(value, str):
|
|
379
|
+
if ':' not in value:
|
|
380
|
+
value: DateTime = DateTime.fromisoformat(value).force_time(self.force_time)
|
|
381
|
+
else:
|
|
382
|
+
value: DateTime = DateTime.fromisoformat(value)
|
|
383
|
+
elif Date.is_date(value):
|
|
384
|
+
value: DateTime = DateTime.fromisoformat(value.isoformat())
|
|
385
|
+
|
|
386
|
+
return super().clean_value(value)
|
|
387
|
+
|
|
388
|
+
def validate(self, attr_name: str, value: Any, attr_type: type | UnionType | None = None) -> None:
|
|
389
|
+
"""
|
|
390
|
+
Checks is the value passed is in the correct range between min and max,
|
|
391
|
+
as well as required, required_lazy, and attribute_type validations.
|
|
392
|
+
|
|
393
|
+
Args:
|
|
394
|
+
attr_name (str): The name of the attribute being validated, used for identification in error messages.
|
|
395
|
+
value (Any): The value to be validated for the specified attribute.
|
|
396
|
+
attr_type (type | UnionType, optional):
|
|
397
|
+
The expected type of the attribute. If provided, the value is checked to ensure it is of this type.
|
|
398
|
+
Defaults to None.
|
|
399
|
+
|
|
400
|
+
Raises:
|
|
401
|
+
FieldValueError: if the value provided is not between min and max values
|
|
402
|
+
|
|
403
|
+
Example:
|
|
404
|
+
>>> from everysk.core.datetime import Date
|
|
405
|
+
>>> from everysk.core.fields import DateTimeField
|
|
406
|
+
>>> from everysk.core.datetime import DateTime
|
|
407
|
+
>>> date_time_field = DateTimeField(min_date=DateTime(2023, 1, 1), max_date=DateTime(2023, 12, 31))
|
|
408
|
+
|
|
409
|
+
>>> try:
|
|
410
|
+
>>> ... date_time_field.validate("test_field", DateTime(2023, 6, 15))
|
|
411
|
+
>>> ... print("Validation successful")
|
|
412
|
+
>>> except FieldValueError as e:
|
|
413
|
+
>>> ... print(f"Validation error: {e}")
|
|
414
|
+
"""
|
|
415
|
+
_min_max_validate(self.min_date, self.max_date, value, attr_name)
|
|
416
|
+
return super().validate(attr_name, value, attr_type)
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
###############################################################################
|
|
420
|
+
# DictField Class Implementation
|
|
421
|
+
###############################################################################
|
|
422
|
+
class DictField(Field):
|
|
423
|
+
# Field that supports a dictionary and BaseDict as a value.
|
|
424
|
+
attr_type: dict | BaseDict = dict | BaseDict
|
|
425
|
+
|
|
426
|
+
class ReadonlyDict(dict):
|
|
427
|
+
__setitem__ = _do_nothing
|
|
428
|
+
__delitem__ = _do_nothing
|
|
429
|
+
pop = _do_nothing
|
|
430
|
+
popitem = _do_nothing
|
|
431
|
+
clear = _do_nothing
|
|
432
|
+
update = _do_nothing
|
|
433
|
+
setdefault = _do_nothing
|
|
434
|
+
|
|
435
|
+
def __new__(cls, *args, **kwargs) -> dict | BaseDict:
|
|
436
|
+
# This signature is used to change the default autocomplete for this class
|
|
437
|
+
return super().__new__(cls, *args, **kwargs)
|
|
438
|
+
|
|
439
|
+
def __init__(
|
|
440
|
+
self,
|
|
441
|
+
default: Any = None,
|
|
442
|
+
*,
|
|
443
|
+
required: bool = False,
|
|
444
|
+
readonly: bool = False,
|
|
445
|
+
required_lazy: bool = False,
|
|
446
|
+
empty_is_none: bool = False,
|
|
447
|
+
**kwargs,
|
|
448
|
+
) -> None:
|
|
449
|
+
# When the field is readonly, we need to change the content to.
|
|
450
|
+
if readonly and default:
|
|
451
|
+
default = self.ReadonlyDict(default)
|
|
452
|
+
|
|
453
|
+
super().__init__(
|
|
454
|
+
default=default,
|
|
455
|
+
required=required,
|
|
456
|
+
readonly=readonly,
|
|
457
|
+
required_lazy=required_lazy,
|
|
458
|
+
empty_is_none=empty_is_none,
|
|
459
|
+
**kwargs,
|
|
460
|
+
)
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
###############################################################################
|
|
464
|
+
# EmailField Class Implementation
|
|
465
|
+
###############################################################################
|
|
466
|
+
class EmailField(Field):
|
|
467
|
+
attr_type: str = str
|
|
468
|
+
|
|
469
|
+
def __new__(cls, *args, **kwargs) -> str:
|
|
470
|
+
# This signature is used to change the default autocomplete for this class
|
|
471
|
+
return super().__new__(cls, *args, **kwargs)
|
|
472
|
+
|
|
473
|
+
def validate(self, attr_name: str, value: Any, attr_type: type | UnionType | None = None) -> None:
|
|
474
|
+
"""
|
|
475
|
+
Validates if the value is an e-mail address.
|
|
476
|
+
To validate we check the existence of the '@' character and the length of the string.
|
|
477
|
+
|
|
478
|
+
Args:
|
|
479
|
+
attr_name (str): The name of the attribute being validated, used for identification in error messages.
|
|
480
|
+
value (Any): The value to be validated for the specified attribute.
|
|
481
|
+
attr_type (type | UnionType, optional):
|
|
482
|
+
The expected type of the attribute. If provided, the value is checked to ensure it is of this type.
|
|
483
|
+
Defaults to None.
|
|
484
|
+
|
|
485
|
+
Raises:
|
|
486
|
+
FieldValueError: If the value is not an e-mail address.
|
|
487
|
+
"""
|
|
488
|
+
# The maximum length of an email is 320 characters per RFC 3696 section 3,
|
|
489
|
+
# and at least 3 digits a@b
|
|
490
|
+
msg = f'Key {attr_name} must be an e-mail.'
|
|
491
|
+
if (
|
|
492
|
+
value is not None
|
|
493
|
+
and value is not Undefined
|
|
494
|
+
and isinstance(value, str)
|
|
495
|
+
and ('@' not in value or len(value) > 320 or len(value) < 3) # noqa: PLR2004
|
|
496
|
+
):
|
|
497
|
+
raise FieldValueError(msg)
|
|
498
|
+
|
|
499
|
+
try:
|
|
500
|
+
# Get the cases where we could have more than one @ in the string
|
|
501
|
+
user, domain = value.split('@')
|
|
502
|
+
except ValueError as error:
|
|
503
|
+
raise FieldValueError(msg) from error
|
|
504
|
+
|
|
505
|
+
return super().validate(attr_name, value, attr_type)
|
|
506
|
+
|
|
507
|
+
|
|
508
|
+
###############################################################################
|
|
509
|
+
# FloatField Class Implementation
|
|
510
|
+
###############################################################################
|
|
511
|
+
class FloatField(Field):
|
|
512
|
+
attr_type: float = float
|
|
513
|
+
min_size: float = None
|
|
514
|
+
max_size: float = None
|
|
515
|
+
|
|
516
|
+
def __new__(cls, *args, **kwargs) -> float:
|
|
517
|
+
# This signature is used to change the default autocomplete for this class
|
|
518
|
+
return super().__new__(cls, *args, **kwargs)
|
|
519
|
+
|
|
520
|
+
def __init__(
|
|
521
|
+
self,
|
|
522
|
+
default: Any = None,
|
|
523
|
+
*,
|
|
524
|
+
min_size: float = float('-inf'),
|
|
525
|
+
max_size: float = float('inf'),
|
|
526
|
+
required: bool = False,
|
|
527
|
+
readonly: bool = False,
|
|
528
|
+
required_lazy: bool = False,
|
|
529
|
+
empty_is_none: bool = False,
|
|
530
|
+
choices: set | None = None,
|
|
531
|
+
**kwargs,
|
|
532
|
+
) -> None:
|
|
533
|
+
super().__init__(
|
|
534
|
+
default=default,
|
|
535
|
+
required=required,
|
|
536
|
+
readonly=readonly,
|
|
537
|
+
min_size=min_size,
|
|
538
|
+
max_size=max_size,
|
|
539
|
+
required_lazy=required_lazy,
|
|
540
|
+
empty_is_none=empty_is_none,
|
|
541
|
+
choices=choices,
|
|
542
|
+
**kwargs,
|
|
543
|
+
)
|
|
544
|
+
|
|
545
|
+
def clean_value(self, value: Any) -> Any:
|
|
546
|
+
"""
|
|
547
|
+
Convert a float string to a float object
|
|
548
|
+
|
|
549
|
+
Args:
|
|
550
|
+
value (Any): Value desired for conversion to float
|
|
551
|
+
|
|
552
|
+
Returns:
|
|
553
|
+
Float: if value is able to be converted to float object
|
|
554
|
+
|
|
555
|
+
Raises:
|
|
556
|
+
ValueError: If value could not be converted to a float
|
|
557
|
+
|
|
558
|
+
Example:
|
|
559
|
+
>>> from everysk.core.fields import FloatField
|
|
560
|
+
>>> float_field = FloatField()
|
|
561
|
+
|
|
562
|
+
>>> float_field.clean_value("3.14")
|
|
563
|
+
>>> 3.14
|
|
564
|
+
|
|
565
|
+
>>> float_field.clean_value(123)
|
|
566
|
+
>>> 123.0
|
|
567
|
+
|
|
568
|
+
>>> float_field.clean_value('abc')
|
|
569
|
+
>>> ValueError: could not convert string to float: 'abc'
|
|
570
|
+
"""
|
|
571
|
+
# Convert Float strings to float object
|
|
572
|
+
if isinstance(value, (int, str)):
|
|
573
|
+
value = float(value)
|
|
574
|
+
|
|
575
|
+
return super().clean_value(value)
|
|
576
|
+
|
|
577
|
+
def validate(self, attr_name: str, value: Any, attr_type: type | UnionType | None = None) -> None:
|
|
578
|
+
"""
|
|
579
|
+
Checks if value is greater than min and lower than max including both values.
|
|
580
|
+
|
|
581
|
+
Args:
|
|
582
|
+
attr_name (str): The name of the attribute being validated, used for identification in error messages.
|
|
583
|
+
value (Any): The value to be validated for the specified attribute.
|
|
584
|
+
attr_type (type | UnionType, optional):
|
|
585
|
+
The expected type of the attribute. If provided, the value is checked to ensure it is of this type.
|
|
586
|
+
Defaults to None.
|
|
587
|
+
|
|
588
|
+
Raises:
|
|
589
|
+
FieldValueError: If value is not between min and max.
|
|
590
|
+
|
|
591
|
+
Example:
|
|
592
|
+
>>> from everysk.core.fields import FloatField
|
|
593
|
+
>>> from everysk.core.exceptions import FieldValueError
|
|
594
|
+
>>> float_field = FloatField(min_size=0.0, max_size=100.0)
|
|
595
|
+
|
|
596
|
+
>>> try:
|
|
597
|
+
>>> ... float_field.validate("test_field", 50.0)
|
|
598
|
+
>>> ... print("Validation successful: 50.0 is within the range.")
|
|
599
|
+
>>> except FieldValueError as e:
|
|
600
|
+
>>> ... print(f"Validation error: {e}")
|
|
601
|
+
>>> Validation successful: 50.0 is within the range.
|
|
602
|
+
|
|
603
|
+
>>> try:
|
|
604
|
+
>>> ... float_field.validate("test_field", -10.0)
|
|
605
|
+
>>> ... print("Validation successful: -10.0 is within the range.")
|
|
606
|
+
>>> except FieldValueError as e:
|
|
607
|
+
>>> ... print(f"Validation error: {e}")
|
|
608
|
+
>>> Validation error: The value '-10.0' for field 'test_field' is not within the allowed range.
|
|
609
|
+
"""
|
|
610
|
+
_min_max_validate(min_value=self.min_size, max_value=self.max_size, value=value, attr_name=attr_name)
|
|
611
|
+
return super().validate(attr_name, value, attr_type)
|
|
612
|
+
|
|
613
|
+
|
|
614
|
+
###############################################################################
|
|
615
|
+
# IntField Class Implementation
|
|
616
|
+
###############################################################################
|
|
617
|
+
class IntField(Field):
|
|
618
|
+
attr_type: int = int
|
|
619
|
+
min_size: int = None
|
|
620
|
+
max_size: int = None
|
|
621
|
+
|
|
622
|
+
def __new__(cls, *args, **kwargs) -> int:
|
|
623
|
+
# This signature is used to change the default autocomplete for this class
|
|
624
|
+
return super().__new__(cls, *args, **kwargs)
|
|
625
|
+
|
|
626
|
+
def __init__(
|
|
627
|
+
self,
|
|
628
|
+
default: Any = None,
|
|
629
|
+
*,
|
|
630
|
+
min_size: int = -max_int,
|
|
631
|
+
max_size: int = max_int,
|
|
632
|
+
required: bool = False,
|
|
633
|
+
readonly: bool = False,
|
|
634
|
+
required_lazy: bool = False,
|
|
635
|
+
empty_is_none: bool = False,
|
|
636
|
+
choices: set | None = None,
|
|
637
|
+
**kwargs,
|
|
638
|
+
) -> None:
|
|
639
|
+
super().__init__(
|
|
640
|
+
default=default,
|
|
641
|
+
required=required,
|
|
642
|
+
readonly=readonly,
|
|
643
|
+
min_size=min_size,
|
|
644
|
+
max_size=max_size,
|
|
645
|
+
required_lazy=required_lazy,
|
|
646
|
+
empty_is_none=empty_is_none,
|
|
647
|
+
choices=choices,
|
|
648
|
+
**kwargs,
|
|
649
|
+
)
|
|
650
|
+
|
|
651
|
+
def clean_value(self, value: Any) -> Any:
|
|
652
|
+
"""
|
|
653
|
+
Converts a value to an integer if it can be converted
|
|
654
|
+
|
|
655
|
+
Args:
|
|
656
|
+
value (Any): Value to convert
|
|
657
|
+
|
|
658
|
+
Returns:
|
|
659
|
+
Int: value converted to an integer
|
|
660
|
+
|
|
661
|
+
Raises:
|
|
662
|
+
ValueError: if the string cannot be converted to an integer
|
|
663
|
+
|
|
664
|
+
Example:
|
|
665
|
+
>>> from everysk.core.fields import IntField
|
|
666
|
+
>>> int_field = IntField()
|
|
667
|
+
|
|
668
|
+
>>> int_field.clean_value("123")
|
|
669
|
+
>>> 123
|
|
670
|
+
|
|
671
|
+
>>> int_field.clean_value("abc")
|
|
672
|
+
>>> ValueError: invalid literal for int() with base 10: 'abc'
|
|
673
|
+
"""
|
|
674
|
+
# Convert Int strings to int object
|
|
675
|
+
if isinstance(value, str):
|
|
676
|
+
value = int(value)
|
|
677
|
+
|
|
678
|
+
return super().clean_value(value)
|
|
679
|
+
|
|
680
|
+
def validate(self, attr_name: str, value: Any, attr_type: type | UnionType | None = None) -> None:
|
|
681
|
+
"""
|
|
682
|
+
Checks if value is greater than min and lower than max including both values.
|
|
683
|
+
|
|
684
|
+
Args:
|
|
685
|
+
attr_name (str): The name of the attribute being validated, used for identification in error messages.
|
|
686
|
+
value (Any): The value to be validated for the specified attribute.
|
|
687
|
+
attr_type (type | UnionType, optional):
|
|
688
|
+
The expected type of the attribute. If provided, the value is checked to ensure it is of this type.
|
|
689
|
+
Defaults to None.
|
|
690
|
+
|
|
691
|
+
Raises:
|
|
692
|
+
FieldValueError: If value is not between min_size and max.
|
|
693
|
+
|
|
694
|
+
Example:
|
|
695
|
+
>>> from everysk.core.fields import IntField
|
|
696
|
+
>>> from everysk.core.exceptions import FieldValueError
|
|
697
|
+
>>> int_field = IntField(min_size=1, max_size=10)
|
|
698
|
+
|
|
699
|
+
>>> try:
|
|
700
|
+
>>> ... int_field.validate("test_field", 5)
|
|
701
|
+
>>> ... print("Validation successful")
|
|
702
|
+
>>> except FieldValueError as e:
|
|
703
|
+
>>> ... print(f"Validation error: {e}")
|
|
704
|
+
>>> Validation successful
|
|
705
|
+
|
|
706
|
+
>>> try:
|
|
707
|
+
>>> ... int_field.validate("test_field", -1)
|
|
708
|
+
>>> ... print("validation successful")
|
|
709
|
+
>>> except FieldValueError as e:
|
|
710
|
+
>>> ...print(f"Validation error: {e}")
|
|
711
|
+
>>> Validation error: The value '-1' for field 'test_field' must be between 1 and 10.
|
|
712
|
+
"""
|
|
713
|
+
_min_max_validate(min_value=self.min_size, max_value=self.max_size, value=value, attr_name=attr_name)
|
|
714
|
+
return super().validate(attr_name, value, attr_type)
|
|
715
|
+
|
|
716
|
+
|
|
717
|
+
###############################################################################
|
|
718
|
+
# IteratorField Class Implementation
|
|
719
|
+
###############################################################################
|
|
720
|
+
class IteratorField(Field):
|
|
721
|
+
attr_type: Iterator = Iterator
|
|
722
|
+
|
|
723
|
+
def __new__(cls, *args, **kwargs) -> Iterator:
|
|
724
|
+
# This signature is used to change the default autocomplete for this class
|
|
725
|
+
return super().__new__(cls, *args, **kwargs)
|
|
726
|
+
|
|
727
|
+
def clean_value(self, value: Any) -> Any:
|
|
728
|
+
"""
|
|
729
|
+
Cleans the given value before storing it.
|
|
730
|
+
|
|
731
|
+
Args:
|
|
732
|
+
value (Any): The value to be cleaned.
|
|
733
|
+
|
|
734
|
+
Returns:
|
|
735
|
+
Any: The cleaned value.
|
|
736
|
+
|
|
737
|
+
"""
|
|
738
|
+
# Convert List/Str to iterators
|
|
739
|
+
if isinstance(value, (list, str)):
|
|
740
|
+
value = iter(value)
|
|
741
|
+
|
|
742
|
+
return super().clean_value(value)
|
|
743
|
+
|
|
744
|
+
|
|
745
|
+
###############################################################################
|
|
746
|
+
# ListField
|
|
747
|
+
###############################################################################
|
|
748
|
+
class ListField(Field):
|
|
749
|
+
"""
|
|
750
|
+
https://stackoverflow.com/questions/855191/how-big-can-a-python-list-get#comment112727918_15739630
|
|
751
|
+
so on 64 systems is maxsize / 8
|
|
752
|
+
"""
|
|
753
|
+
|
|
754
|
+
attr_type: list = list
|
|
755
|
+
min_size: int = None
|
|
756
|
+
max_size: int = None
|
|
757
|
+
separator: str = ','
|
|
758
|
+
|
|
759
|
+
class ReadonlyList(list):
|
|
760
|
+
__setitem__ = _do_nothing
|
|
761
|
+
__delitem__ = _do_nothing
|
|
762
|
+
append = _do_nothing
|
|
763
|
+
clear = _do_nothing
|
|
764
|
+
extend = _do_nothing
|
|
765
|
+
insert = _do_nothing
|
|
766
|
+
pop = _do_nothing
|
|
767
|
+
remove = _do_nothing
|
|
768
|
+
reverse = _do_nothing
|
|
769
|
+
sort = _do_nothing
|
|
770
|
+
|
|
771
|
+
def __new__(cls, *args, **kwargs) -> list:
|
|
772
|
+
# This signature is used to change the default autocomplete for this class
|
|
773
|
+
return super().__new__(cls, *args, **kwargs)
|
|
774
|
+
|
|
775
|
+
def __init__(
|
|
776
|
+
self,
|
|
777
|
+
default: Any = None,
|
|
778
|
+
*,
|
|
779
|
+
min_size: int = 0,
|
|
780
|
+
max_size: int = max_int / 8,
|
|
781
|
+
readonly: bool = False,
|
|
782
|
+
required: bool = False,
|
|
783
|
+
required_lazy: bool = False,
|
|
784
|
+
separator: str = ',',
|
|
785
|
+
empty_is_none: bool = False,
|
|
786
|
+
**kwargs,
|
|
787
|
+
) -> None:
|
|
788
|
+
if min_size < 0:
|
|
789
|
+
raise FieldValueError('List min_size cloud not be a negative number.')
|
|
790
|
+
|
|
791
|
+
# When the field is readonly, we need to change the content to.
|
|
792
|
+
if readonly and (default is not None or default is not Undefined):
|
|
793
|
+
default = self.ReadonlyList(default)
|
|
794
|
+
|
|
795
|
+
super().__init__(
|
|
796
|
+
default=default,
|
|
797
|
+
min_size=min_size,
|
|
798
|
+
max_size=max_size,
|
|
799
|
+
readonly=readonly,
|
|
800
|
+
required=required,
|
|
801
|
+
required_lazy=required_lazy,
|
|
802
|
+
separator=separator,
|
|
803
|
+
empty_is_none=empty_is_none,
|
|
804
|
+
**kwargs,
|
|
805
|
+
)
|
|
806
|
+
|
|
807
|
+
def clean_value(self, value: Any) -> Any:
|
|
808
|
+
"""
|
|
809
|
+
Clean the value before storing it.
|
|
810
|
+
This method is used to ensure that the value is in the correct format before storing it.
|
|
811
|
+
This method checks if the value is a string and converts it into a list if necessary.
|
|
812
|
+
It then calls the parent class's clean_value method to perform additional cleaning.
|
|
813
|
+
|
|
814
|
+
Args:
|
|
815
|
+
value (Any): The value to be cleaned.
|
|
816
|
+
|
|
817
|
+
Returns:
|
|
818
|
+
Any: The cleaned value.
|
|
819
|
+
|
|
820
|
+
Example:
|
|
821
|
+
>>> field = Field()
|
|
822
|
+
>>> field.clean_value("example")
|
|
823
|
+
['example']
|
|
824
|
+
"""
|
|
825
|
+
if isinstance(value, str):
|
|
826
|
+
# When we receive environment vars or HTTP query params they are always strings with ',' as separator
|
|
827
|
+
value = [item.strip() for item in value.split(self.separator)]
|
|
828
|
+
|
|
829
|
+
return super().clean_value(value)
|
|
830
|
+
|
|
831
|
+
def validate(self, attr_name: str, value: Any, attr_type: type | UnionType | None = None) -> None:
|
|
832
|
+
"""
|
|
833
|
+
Checks if value is greater than min_size and lower than max_size including both values.
|
|
834
|
+
|
|
835
|
+
Args:
|
|
836
|
+
attr_name (str): The name of the attribute being validated, used for identification in error messages.
|
|
837
|
+
value (Any): The value to be validated for the specified attribute.
|
|
838
|
+
attr_type (type | UnionType, optional):
|
|
839
|
+
The expected type of the attribute. If provided, the value is checked to ensure it is of this type.
|
|
840
|
+
Defaults to None.
|
|
841
|
+
|
|
842
|
+
Raises:
|
|
843
|
+
FieldValueError: If value is not list instance or if is not between min_size and max_size
|
|
844
|
+
"""
|
|
845
|
+
if value is not None and value is not Undefined:
|
|
846
|
+
if not isinstance(value, list):
|
|
847
|
+
msg = f"The '{attr_name}' value must be a list."
|
|
848
|
+
raise FieldValueError(msg)
|
|
849
|
+
if not self.min_size <= len(value) <= self.max_size:
|
|
850
|
+
msg = f"The attribute '{attr_name}' is not within the specified list range. min_size: {self.min_size} max_size: {self.max_size}" # noqa: E501
|
|
851
|
+
raise FieldValueError(msg)
|
|
852
|
+
|
|
853
|
+
super().validate(attr_name, value, attr_type)
|
|
854
|
+
|
|
855
|
+
|
|
856
|
+
###############################################################################
|
|
857
|
+
# SetField Class Implementation
|
|
858
|
+
###############################################################################
|
|
859
|
+
class SetField(Field):
|
|
860
|
+
attr_type: set = set
|
|
861
|
+
min_size: int = None
|
|
862
|
+
max_size: int = None
|
|
863
|
+
|
|
864
|
+
class ReadonlySet(set):
|
|
865
|
+
__setitem__ = _do_nothing
|
|
866
|
+
__delitem__ = _do_nothing
|
|
867
|
+
add = _do_nothing
|
|
868
|
+
clear = _do_nothing
|
|
869
|
+
discard = _do_nothing
|
|
870
|
+
pop = _do_nothing
|
|
871
|
+
remove = _do_nothing
|
|
872
|
+
update = _do_nothing
|
|
873
|
+
|
|
874
|
+
def __new__(cls, *args, **kwargs) -> set:
|
|
875
|
+
# This signature is used to change the default autocomplete for this class
|
|
876
|
+
return super().__new__(cls, *args, **kwargs)
|
|
877
|
+
|
|
878
|
+
def __init__(
|
|
879
|
+
self,
|
|
880
|
+
default: Any = None,
|
|
881
|
+
*,
|
|
882
|
+
min_size: int = 0,
|
|
883
|
+
max_size: int = max_int / 8,
|
|
884
|
+
readonly: bool = False,
|
|
885
|
+
required: bool = False,
|
|
886
|
+
required_lazy: bool = False,
|
|
887
|
+
empty_is_none: bool = False,
|
|
888
|
+
choices: set | None = None,
|
|
889
|
+
**kwargs,
|
|
890
|
+
) -> None:
|
|
891
|
+
if min_size < 0:
|
|
892
|
+
raise FieldValueError('Set min_size cloud not be a negative number.')
|
|
893
|
+
|
|
894
|
+
# When the field is readonly, we need to change the content to.
|
|
895
|
+
if readonly and (default is not None or default is not Undefined):
|
|
896
|
+
default = self.ReadonlySet(default)
|
|
897
|
+
|
|
898
|
+
super().__init__(
|
|
899
|
+
default=default,
|
|
900
|
+
min_size=min_size,
|
|
901
|
+
max_size=max_size,
|
|
902
|
+
readonly=readonly,
|
|
903
|
+
required=required,
|
|
904
|
+
required_lazy=required_lazy,
|
|
905
|
+
empty_is_none=empty_is_none,
|
|
906
|
+
choices=choices,
|
|
907
|
+
**kwargs,
|
|
908
|
+
)
|
|
909
|
+
|
|
910
|
+
def validate(self, attr_name: str, value: Any, attr_type: type | UnionType | None = None) -> None:
|
|
911
|
+
"""
|
|
912
|
+
Checks if value is greater than min_size and lower than max_size including both values.
|
|
913
|
+
|
|
914
|
+
Args:
|
|
915
|
+
attr_name (str): The name of the attribute being validated, used for identification in error messages.
|
|
916
|
+
value (Any): The value to be validated for the specified attribute.
|
|
917
|
+
attr_type (type | UnionType, optional):
|
|
918
|
+
The expected type of the attribute. If provided, the value is checked to ensure it is of this type.
|
|
919
|
+
Defaults to None.
|
|
920
|
+
|
|
921
|
+
Raises:
|
|
922
|
+
FieldValueError: If value is not set instance.
|
|
923
|
+
FieldValueError: If value is not between min_size and max_size.
|
|
924
|
+
"""
|
|
925
|
+
super().validate(attr_name, value, attr_type)
|
|
926
|
+
|
|
927
|
+
if value is not None and value is not Undefined and not self.min_size <= len(value) <= self.max_size:
|
|
928
|
+
msg = f"The attribute '{attr_name}' is not within the specified set range. min_size: {self.min_size} max_size: {self.max_size}" # noqa: E501
|
|
929
|
+
raise FieldValueError(msg)
|
|
930
|
+
|
|
931
|
+
|
|
932
|
+
###############################################################################
|
|
933
|
+
# StrField Class Implementation
|
|
934
|
+
###############################################################################
|
|
935
|
+
class StrField(Field):
|
|
936
|
+
"""
|
|
937
|
+
Represents a string field in an entity.
|
|
938
|
+
This field type is used to store string values and provides validation
|
|
939
|
+
for string attributes based on specified constraints such as minimum and maximum size
|
|
940
|
+
and regular expression pattern matching.
|
|
941
|
+
|
|
942
|
+
Args:
|
|
943
|
+
default (Any, optional): The default value for the field. Defaults to None.
|
|
944
|
+
min_size (int, optional): The minimum allowed length for the string. Defaults to 0.
|
|
945
|
+
max_size (int, optional): The maximum allowed length for the string. Defaults to the maximum integer value.
|
|
946
|
+
regex (str, optional): The regular expression pattern to match against the string value. Defaults to None.
|
|
947
|
+
readonly (bool, optional): Whether the field is read-only. Defaults to False.
|
|
948
|
+
required (bool, optional): Whether the field is required. Defaults to False.
|
|
949
|
+
required_lazy (bool, optional): Whether the field is lazily required. Defaults to False.
|
|
950
|
+
empty_is_none (bool, optional): Whether an empty string should be treated as None. Defaults to False.
|
|
951
|
+
|
|
952
|
+
Raises:
|
|
953
|
+
FieldValueError: If the minimum size is negative
|
|
954
|
+
"""
|
|
955
|
+
|
|
956
|
+
attr_type: str = str
|
|
957
|
+
min_size: int = None
|
|
958
|
+
max_size: int = None
|
|
959
|
+
regex: re.Pattern = None
|
|
960
|
+
|
|
961
|
+
def __new__(cls, *args, **kwargs) -> str:
|
|
962
|
+
# This signature is used to change the default autocomplete for this class
|
|
963
|
+
return super().__new__(cls, *args, **kwargs)
|
|
964
|
+
|
|
965
|
+
def __init__(
|
|
966
|
+
self,
|
|
967
|
+
default: Any = None,
|
|
968
|
+
*,
|
|
969
|
+
min_size: int = 0,
|
|
970
|
+
max_size: int = max_int,
|
|
971
|
+
regex: str | None = None,
|
|
972
|
+
readonly: bool = False,
|
|
973
|
+
required: bool = False,
|
|
974
|
+
required_lazy: bool = False,
|
|
975
|
+
empty_is_none: bool = False,
|
|
976
|
+
choices: set | None = None,
|
|
977
|
+
**kwargs,
|
|
978
|
+
) -> None:
|
|
979
|
+
if min_size < 0:
|
|
980
|
+
msg = 'String min_size cloud not be a negative number.'
|
|
981
|
+
raise FieldValueError(msg)
|
|
982
|
+
|
|
983
|
+
super().__init__(
|
|
984
|
+
default=default,
|
|
985
|
+
regex=regex,
|
|
986
|
+
min_size=min_size,
|
|
987
|
+
max_size=max_size,
|
|
988
|
+
readonly=readonly,
|
|
989
|
+
required=required,
|
|
990
|
+
required_lazy=required_lazy,
|
|
991
|
+
empty_is_none=empty_is_none,
|
|
992
|
+
choices=choices,
|
|
993
|
+
**kwargs,
|
|
994
|
+
)
|
|
995
|
+
|
|
996
|
+
def validate(self, attr_name: str, value: Any, attr_type: type | UnionType | None = None) -> None:
|
|
997
|
+
"""
|
|
998
|
+
Validates a string for its size and regex if they are specified.
|
|
999
|
+
|
|
1000
|
+
Args:
|
|
1001
|
+
attr_name (str): The name of the attribute being validated, used for identification in error messages.
|
|
1002
|
+
value (Any): The value to be validated for the specified attribute.
|
|
1003
|
+
attr_type (type | UnionType, optional):
|
|
1004
|
+
The expected type of the attribute. If provided, the value is checked to ensure it is of this type.
|
|
1005
|
+
Defaults to None.
|
|
1006
|
+
|
|
1007
|
+
Raises:
|
|
1008
|
+
FieldValueError: If the value does not match a regex, is not the
|
|
1009
|
+
correct type, or is an invalid length.
|
|
1010
|
+
"""
|
|
1011
|
+
# First let all default checks be done.
|
|
1012
|
+
super().validate(attr_name, value, attr_type)
|
|
1013
|
+
|
|
1014
|
+
if self.regex and value and value is not Undefined and not self.regex.match(value):
|
|
1015
|
+
msg = f"The value '{value}' for field '{attr_name}' must match with this regex: {self.regex.pattern}."
|
|
1016
|
+
raise FieldValueError(msg)
|
|
1017
|
+
|
|
1018
|
+
# Then we check for the size.
|
|
1019
|
+
try:
|
|
1020
|
+
if value is not None and value is not Undefined:
|
|
1021
|
+
_min_max_validate(
|
|
1022
|
+
min_value=self.min_size, max_value=self.max_size, value=len(value), attr_name=attr_name
|
|
1023
|
+
)
|
|
1024
|
+
except FieldValueError as error:
|
|
1025
|
+
msg = f"The length '{len(value)}' for attribute '{attr_name}' must be between '{self.min_size}' and '{self.max_size}'." # noqa: E501
|
|
1026
|
+
raise FieldValueError(msg) from error
|
|
1027
|
+
|
|
1028
|
+
|
|
1029
|
+
# This field is legacy, the choice is already implemented in all fields.
|
|
1030
|
+
# So we just inherit from StrField to keep compatibility.
|
|
1031
|
+
class ChoiceField(StrField):
|
|
1032
|
+
pass
|
|
1033
|
+
|
|
1034
|
+
|
|
1035
|
+
###############################################################################
|
|
1036
|
+
# RegexField Class Implementation
|
|
1037
|
+
###############################################################################
|
|
1038
|
+
class RegexField(Field):
|
|
1039
|
+
attr_type: re.Pattern = re.Pattern
|
|
1040
|
+
|
|
1041
|
+
def __new__(cls, *args, **kwargs) -> re.Pattern:
|
|
1042
|
+
# This signature is used to change the default autocomplete for this class
|
|
1043
|
+
return super().__new__(cls, *args, **kwargs)
|
|
1044
|
+
|
|
1045
|
+
def __init__(
|
|
1046
|
+
self,
|
|
1047
|
+
default: Any = None,
|
|
1048
|
+
*,
|
|
1049
|
+
readonly: bool = False,
|
|
1050
|
+
required: bool = False,
|
|
1051
|
+
required_lazy: bool = False,
|
|
1052
|
+
empty_is_none: bool = False,
|
|
1053
|
+
choices: set | None = None,
|
|
1054
|
+
**kwargs,
|
|
1055
|
+
) -> None:
|
|
1056
|
+
super().__init__(
|
|
1057
|
+
default=default,
|
|
1058
|
+
readonly=readonly,
|
|
1059
|
+
required=required,
|
|
1060
|
+
required_lazy=required_lazy,
|
|
1061
|
+
empty_is_none=empty_is_none,
|
|
1062
|
+
choices=choices,
|
|
1063
|
+
**kwargs,
|
|
1064
|
+
)
|
|
1065
|
+
|
|
1066
|
+
def clean_value(self, value: Any) -> Any:
|
|
1067
|
+
"""
|
|
1068
|
+
Clean the value by compiling it into a regular expression pattern.
|
|
1069
|
+
|
|
1070
|
+
This method takes a value and compiles it into a regular expression pattern if it is a string.
|
|
1071
|
+
If the value is equal to the default value, it updates the default value with the compiled pattern.
|
|
1072
|
+
It then calls the parent class's clean_value method to perform any additional cleaning.
|
|
1073
|
+
|
|
1074
|
+
Args:
|
|
1075
|
+
value (Any): The value to be cleaned.
|
|
1076
|
+
|
|
1077
|
+
Returns:
|
|
1078
|
+
Any: The cleaned value.
|
|
1079
|
+
|
|
1080
|
+
Example:
|
|
1081
|
+
>>> field = RegexField(default='[0-9]+')
|
|
1082
|
+
>>> field.clean_value('123')
|
|
1083
|
+
re.compile('[0-9]+')
|
|
1084
|
+
"""
|
|
1085
|
+
if isinstance(value, str):
|
|
1086
|
+
value = re.compile(value)
|
|
1087
|
+
|
|
1088
|
+
return super().clean_value(value)
|
|
1089
|
+
|
|
1090
|
+
|
|
1091
|
+
###############################################################################
|
|
1092
|
+
# TupleField Class Implementation
|
|
1093
|
+
###############################################################################
|
|
1094
|
+
class TupleField(Field):
|
|
1095
|
+
attr_type: tuple = tuple
|
|
1096
|
+
|
|
1097
|
+
def __new__(cls, *args, **kwargs) -> tuple:
|
|
1098
|
+
# This signature is used to change the default autocomplete for this class
|
|
1099
|
+
return super().__new__(cls, *args, **kwargs)
|
|
1100
|
+
|
|
1101
|
+
|
|
1102
|
+
###############################################################################
|
|
1103
|
+
# URLField Class Implementation
|
|
1104
|
+
###############################################################################
|
|
1105
|
+
class URLField(Field):
|
|
1106
|
+
attr_type: str = str
|
|
1107
|
+
supported_schemes: set = ('http', 'https', 'ftp', 'ftps', 'git')
|
|
1108
|
+
|
|
1109
|
+
def __new__(cls, *args, **kwargs) -> str:
|
|
1110
|
+
# This signature is used to change the default autocomplete for this class
|
|
1111
|
+
return super().__new__(cls, *args, **kwargs)
|
|
1112
|
+
|
|
1113
|
+
def validate(self, attr_name: str, value: Any, attr_type: type | UnionType | None = None) -> None:
|
|
1114
|
+
"""
|
|
1115
|
+
Validates a URL string to ensure it is a valid URL.
|
|
1116
|
+
We check the protocol HTTP, HTTPS, FTP, FTPS, the domain format and size and the port number.
|
|
1117
|
+
|
|
1118
|
+
Args:
|
|
1119
|
+
attr_name (str): The name of the attribute being validated, used for identification in error messages.
|
|
1120
|
+
value (Any): The value to be validated for the specified attribute.
|
|
1121
|
+
attr_type (type | UnionType, optional):
|
|
1122
|
+
The expected type of the attribute. If provided, the value is checked to ensure it is of this type.
|
|
1123
|
+
Defaults to None.
|
|
1124
|
+
|
|
1125
|
+
Raises:
|
|
1126
|
+
FieldValueError: If the url is invalid.
|
|
1127
|
+
"""
|
|
1128
|
+
if isinstance(value, str):
|
|
1129
|
+
# https://github.com/django/django/blob/main/django/core/validators.py
|
|
1130
|
+
ul = '\u00a1-\uffff' # Unicode letters range (must not be a raw string).
|
|
1131
|
+
|
|
1132
|
+
# IP patterns
|
|
1133
|
+
ipv4_re = (
|
|
1134
|
+
r'(?:0|25[0-5]|2[0-4][0-9]|1[0-9]?[0-9]?|[1-9][0-9]?)'
|
|
1135
|
+
r'(?:\.(?:0|25[0-5]|2[0-4][0-9]|1[0-9]?[0-9]?|[1-9][0-9]?)){3}'
|
|
1136
|
+
)
|
|
1137
|
+
ipv6_re = r'\[[0-9a-f:.]+\]' # (simple regex, validated later)
|
|
1138
|
+
|
|
1139
|
+
# Host patterns
|
|
1140
|
+
hostname_re = r'[a-z' + ul + r'0-9](?:[a-z' + ul + r'0-9-]{0,61}[a-z' + ul + r'0-9])?'
|
|
1141
|
+
|
|
1142
|
+
# Max length for domain name labels is 63 characters per RFC 1034 sec. 3.1
|
|
1143
|
+
domain_re = r'(?:\.(?!-)[a-z' + ul + r'0-9-]{1,63}(?<!-))*'
|
|
1144
|
+
tld_re = (
|
|
1145
|
+
r'\.' # dot
|
|
1146
|
+
r'(?!-)' # can't start with a dash
|
|
1147
|
+
r'(?:[a-z' + ul + '-]{2,63}' # domain label
|
|
1148
|
+
r'|xn--[a-z0-9]{1,59})' # or punycode label
|
|
1149
|
+
r'(?<!-)' # can't end with a dash
|
|
1150
|
+
r'\.?' # may have a trailing dot
|
|
1151
|
+
)
|
|
1152
|
+
host_re = '(' + hostname_re + domain_re + tld_re + '|localhost)'
|
|
1153
|
+
|
|
1154
|
+
regex = re.compile(
|
|
1155
|
+
r'^(?:[a-z0-9.+-]*)://' # scheme is validated separately
|
|
1156
|
+
r'(?:[^\s:@/]+(?::[^\s:@/]*)?@)?' # user:pass authentication
|
|
1157
|
+
r'(?:' + ipv4_re + '|' + ipv6_re + '|' + host_re + ')'
|
|
1158
|
+
r'(?::[0-9]{1,5})?' # port
|
|
1159
|
+
r'(?:[/?#][^\s]*)?' # resource path
|
|
1160
|
+
r'\Z',
|
|
1161
|
+
re.IGNORECASE,
|
|
1162
|
+
)
|
|
1163
|
+
msg = f'Key {attr_name} must be an URL.'
|
|
1164
|
+
if not regex.match(value):
|
|
1165
|
+
raise FieldValueError(msg)
|
|
1166
|
+
|
|
1167
|
+
# The scheme needs a separate check
|
|
1168
|
+
try:
|
|
1169
|
+
result = urlparse(value)
|
|
1170
|
+
if result.scheme not in self.supported_schemes:
|
|
1171
|
+
raise FieldValueError(msg)
|
|
1172
|
+
|
|
1173
|
+
except ValueError:
|
|
1174
|
+
raise FieldValueError(msg) from ValueError
|
|
1175
|
+
|
|
1176
|
+
return super().validate(attr_name, value, attr_type)
|