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.
Files changed (137) hide show
  1. everysk/__init__.py +30 -0
  2. everysk/_version.py +683 -0
  3. everysk/api/__init__.py +61 -0
  4. everysk/api/api_requestor.py +167 -0
  5. everysk/api/api_resources/__init__.py +23 -0
  6. everysk/api/api_resources/api_resource.py +371 -0
  7. everysk/api/api_resources/calculation.py +779 -0
  8. everysk/api/api_resources/custom_index.py +42 -0
  9. everysk/api/api_resources/datastore.py +81 -0
  10. everysk/api/api_resources/file.py +42 -0
  11. everysk/api/api_resources/market_data.py +223 -0
  12. everysk/api/api_resources/parser.py +66 -0
  13. everysk/api/api_resources/portfolio.py +43 -0
  14. everysk/api/api_resources/private_security.py +42 -0
  15. everysk/api/api_resources/report.py +65 -0
  16. everysk/api/api_resources/report_template.py +39 -0
  17. everysk/api/api_resources/tests.py +115 -0
  18. everysk/api/api_resources/worker_execution.py +64 -0
  19. everysk/api/api_resources/workflow.py +65 -0
  20. everysk/api/api_resources/workflow_execution.py +93 -0
  21. everysk/api/api_resources/workspace.py +42 -0
  22. everysk/api/http_client.py +63 -0
  23. everysk/api/tests.py +32 -0
  24. everysk/api/utils.py +262 -0
  25. everysk/config.py +451 -0
  26. everysk/core/_tests/serialize/test_json.py +336 -0
  27. everysk/core/_tests/serialize/test_orjson.py +295 -0
  28. everysk/core/_tests/serialize/test_pickle.py +48 -0
  29. everysk/core/cloud_function/main.py +78 -0
  30. everysk/core/cloud_function/tests.py +86 -0
  31. everysk/core/compress.py +245 -0
  32. everysk/core/datetime/__init__.py +12 -0
  33. everysk/core/datetime/calendar.py +144 -0
  34. everysk/core/datetime/date.py +424 -0
  35. everysk/core/datetime/date_expression.py +299 -0
  36. everysk/core/datetime/date_mixin.py +1475 -0
  37. everysk/core/datetime/date_settings.py +30 -0
  38. everysk/core/datetime/datetime.py +713 -0
  39. everysk/core/exceptions.py +435 -0
  40. everysk/core/fields.py +1176 -0
  41. everysk/core/firestore.py +555 -0
  42. everysk/core/fixtures/_settings.py +29 -0
  43. everysk/core/fixtures/other/_settings.py +18 -0
  44. everysk/core/fixtures/user_agents.json +88 -0
  45. everysk/core/http.py +691 -0
  46. everysk/core/lists.py +92 -0
  47. everysk/core/log.py +709 -0
  48. everysk/core/number.py +37 -0
  49. everysk/core/object.py +1469 -0
  50. everysk/core/redis.py +1021 -0
  51. everysk/core/retry.py +51 -0
  52. everysk/core/serialize.py +674 -0
  53. everysk/core/sftp.py +414 -0
  54. everysk/core/signing.py +53 -0
  55. everysk/core/slack.py +127 -0
  56. everysk/core/string.py +199 -0
  57. everysk/core/tests.py +240 -0
  58. everysk/core/threads.py +199 -0
  59. everysk/core/undefined.py +70 -0
  60. everysk/core/unittests.py +73 -0
  61. everysk/core/workers.py +241 -0
  62. everysk/sdk/__init__.py +23 -0
  63. everysk/sdk/base.py +98 -0
  64. everysk/sdk/brutils/cnpj.py +391 -0
  65. everysk/sdk/brutils/cnpj_pd.py +129 -0
  66. everysk/sdk/engines/__init__.py +26 -0
  67. everysk/sdk/engines/cache.py +185 -0
  68. everysk/sdk/engines/compliance.py +37 -0
  69. everysk/sdk/engines/cryptography.py +69 -0
  70. everysk/sdk/engines/expression.cp312-win_amd64.pyd +0 -0
  71. everysk/sdk/engines/expression.pyi +55 -0
  72. everysk/sdk/engines/helpers.cp312-win_amd64.pyd +0 -0
  73. everysk/sdk/engines/helpers.pyi +26 -0
  74. everysk/sdk/engines/lock.py +120 -0
  75. everysk/sdk/engines/market_data.py +244 -0
  76. everysk/sdk/engines/settings.py +19 -0
  77. everysk/sdk/entities/__init__.py +23 -0
  78. everysk/sdk/entities/base.py +784 -0
  79. everysk/sdk/entities/base_list.py +131 -0
  80. everysk/sdk/entities/custom_index/base.py +209 -0
  81. everysk/sdk/entities/custom_index/settings.py +29 -0
  82. everysk/sdk/entities/datastore/base.py +160 -0
  83. everysk/sdk/entities/datastore/settings.py +17 -0
  84. everysk/sdk/entities/fields.py +375 -0
  85. everysk/sdk/entities/file/base.py +215 -0
  86. everysk/sdk/entities/file/settings.py +63 -0
  87. everysk/sdk/entities/portfolio/base.py +248 -0
  88. everysk/sdk/entities/portfolio/securities.py +241 -0
  89. everysk/sdk/entities/portfolio/security.py +580 -0
  90. everysk/sdk/entities/portfolio/settings.py +97 -0
  91. everysk/sdk/entities/private_security/base.py +226 -0
  92. everysk/sdk/entities/private_security/settings.py +17 -0
  93. everysk/sdk/entities/query.py +603 -0
  94. everysk/sdk/entities/report/base.py +214 -0
  95. everysk/sdk/entities/report/settings.py +23 -0
  96. everysk/sdk/entities/script.py +310 -0
  97. everysk/sdk/entities/secrets/base.py +128 -0
  98. everysk/sdk/entities/secrets/script.py +119 -0
  99. everysk/sdk/entities/secrets/settings.py +17 -0
  100. everysk/sdk/entities/settings.py +48 -0
  101. everysk/sdk/entities/tags.py +174 -0
  102. everysk/sdk/entities/worker_execution/base.py +307 -0
  103. everysk/sdk/entities/worker_execution/settings.py +63 -0
  104. everysk/sdk/entities/workflow_execution/base.py +113 -0
  105. everysk/sdk/entities/workflow_execution/settings.py +32 -0
  106. everysk/sdk/entities/workspace/base.py +99 -0
  107. everysk/sdk/entities/workspace/settings.py +27 -0
  108. everysk/sdk/settings.py +67 -0
  109. everysk/sdk/tests.py +105 -0
  110. everysk/sdk/worker_base.py +47 -0
  111. everysk/server/__init__.py +9 -0
  112. everysk/server/applications.py +63 -0
  113. everysk/server/endpoints.py +516 -0
  114. everysk/server/example_api.py +69 -0
  115. everysk/server/middlewares.py +80 -0
  116. everysk/server/requests.py +62 -0
  117. everysk/server/responses.py +119 -0
  118. everysk/server/routing.py +64 -0
  119. everysk/server/settings.py +36 -0
  120. everysk/server/tests.py +36 -0
  121. everysk/settings.py +98 -0
  122. everysk/sql/__init__.py +9 -0
  123. everysk/sql/connection.py +232 -0
  124. everysk/sql/model.py +376 -0
  125. everysk/sql/query.py +417 -0
  126. everysk/sql/row_factory.py +63 -0
  127. everysk/sql/settings.py +49 -0
  128. everysk/sql/utils.py +129 -0
  129. everysk/tests.py +23 -0
  130. everysk/utils.py +81 -0
  131. everysk/version.py +15 -0
  132. everysk_lib-1.10.2.dist-info/.gitignore +5 -0
  133. everysk_lib-1.10.2.dist-info/METADATA +326 -0
  134. everysk_lib-1.10.2.dist-info/RECORD +137 -0
  135. everysk_lib-1.10.2.dist-info/WHEEL +5 -0
  136. everysk_lib-1.10.2.dist-info/licenses/LICENSE.txt +9 -0
  137. 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)