everysk-lib 1.10.3__cp311-cp311-win_amd64.whl → 1.11.0__cp311-cp311-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/core/_tests/mapping/__init__.py +9 -0
- everysk/core/_tests/mapping/base.py +306 -0
- everysk/core/_tests/mapping/fields.py +1154 -0
- everysk/core/datetime/datetime.py +1 -1
- everysk/core/http.py +6 -6
- everysk/core/mapping/__init__.py +29 -0
- everysk/core/mapping/base.py +214 -0
- everysk/core/mapping/fields.py +1133 -0
- everysk/core/mapping/metaclass.py +101 -0
- everysk/core/object.py +132 -124
- everysk/core/tests.py +19 -0
- everysk/sdk/engines/expression.cp311-win_amd64.pyd +0 -0
- everysk/sdk/engines/helpers.cp311-win_amd64.pyd +0 -0
- everysk/sdk/entities/script.py +0 -1
- everysk/sql/connection.py +24 -0
- {everysk_lib-1.10.3.dist-info → everysk_lib-1.11.0.dist-info}/METADATA +2 -2
- {everysk_lib-1.10.3.dist-info → everysk_lib-1.11.0.dist-info}/RECORD +21 -14
- {everysk_lib-1.10.3.dist-info → everysk_lib-1.11.0.dist-info}/.gitignore +0 -0
- {everysk_lib-1.10.3.dist-info → everysk_lib-1.11.0.dist-info}/WHEEL +0 -0
- {everysk_lib-1.10.3.dist-info → everysk_lib-1.11.0.dist-info}/licenses/LICENSE.txt +0 -0
- {everysk_lib-1.10.3.dist-info → everysk_lib-1.11.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,1154 @@
|
|
|
1
|
+
###############################################################################
|
|
2
|
+
#
|
|
3
|
+
# (C) Copyright 2025 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
|
+
# ruff: noqa: E501, ERA001, RUF012
|
|
11
|
+
|
|
12
|
+
import re
|
|
13
|
+
from collections.abc import Iterator
|
|
14
|
+
from sys import maxsize
|
|
15
|
+
from zoneinfo import ZoneInfo
|
|
16
|
+
|
|
17
|
+
from everysk.core.datetime import Date, DateTime
|
|
18
|
+
from everysk.core.exceptions import ReadonlyError, RequiredError
|
|
19
|
+
from everysk.core.mapping import fields
|
|
20
|
+
from everysk.core.mapping.base import BaseMapping
|
|
21
|
+
from everysk.core.unittests import TestCase, mock
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class BaseMappingFieldTestCase(TestCase):
|
|
25
|
+
def test_init_field_type(self) -> None:
|
|
26
|
+
field = fields.BaseMappingField(field_type=int)
|
|
27
|
+
self.assertEqual(field.field_type, int)
|
|
28
|
+
|
|
29
|
+
def test_init_field_name(self) -> None:
|
|
30
|
+
field = fields.BaseMappingField(field_name='test_field')
|
|
31
|
+
self.assertEqual(field.field_name, 'test_field')
|
|
32
|
+
|
|
33
|
+
def test_init_choices(self) -> None:
|
|
34
|
+
field = fields.BaseMappingField()
|
|
35
|
+
self.assertSetEqual(field.choices, set())
|
|
36
|
+
field = fields.BaseMappingField(choices={1, 2, 3})
|
|
37
|
+
self.assertSetEqual(field.choices, {1, 2, 3})
|
|
38
|
+
|
|
39
|
+
def test_init_default(self) -> None:
|
|
40
|
+
field = fields.BaseMappingField(default=10)
|
|
41
|
+
self.assertEqual(field.default, 10)
|
|
42
|
+
|
|
43
|
+
@mock.patch.object(fields.BaseMappingField, 'clean_value', return_value='cleaned_value')
|
|
44
|
+
def test_init_default_cleaned(self, clean_value: mock.MagicMock) -> None:
|
|
45
|
+
field = fields.BaseMappingField(default='raw_value', field_type=str)
|
|
46
|
+
self.assertEqual(field.default, 'cleaned_value')
|
|
47
|
+
clean_value.assert_called_once_with('raw_value')
|
|
48
|
+
|
|
49
|
+
@mock.patch.object(fields.BaseMappingField, 'validate', return_value='validated_value')
|
|
50
|
+
def test_init_default_validated(self, validate: mock.MagicMock) -> None:
|
|
51
|
+
field = fields.BaseMappingField(default='raw_value', field_type=str)
|
|
52
|
+
self.assertEqual(field.default, 'raw_value')
|
|
53
|
+
validate.assert_called_once_with('raw_value')
|
|
54
|
+
|
|
55
|
+
def test_init_default_empty_dict(self) -> None:
|
|
56
|
+
with self.assertRaises(ValueError) as context:
|
|
57
|
+
_ = fields.BaseMappingField(default={})
|
|
58
|
+
self.assertEqual(
|
|
59
|
+
str(context.exception), 'Default value cannot be a dict, list, or set unless the field is readonly.'
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
def test_init_default_empty_list(self) -> None:
|
|
63
|
+
with self.assertRaises(ValueError) as context:
|
|
64
|
+
_ = fields.BaseMappingField(default=[])
|
|
65
|
+
self.assertEqual(
|
|
66
|
+
str(context.exception), 'Default value cannot be a dict, list, or set unless the field is readonly.'
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
def test_init_default_empty_set(self) -> None:
|
|
70
|
+
with self.assertRaises(ValueError) as context:
|
|
71
|
+
_ = fields.BaseMappingField(default=set())
|
|
72
|
+
self.assertEqual(
|
|
73
|
+
str(context.exception), 'Default value cannot be a dict, list, or set unless the field is readonly.'
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
def test_init_readonly(self) -> None:
|
|
77
|
+
field = fields.BaseMappingField()
|
|
78
|
+
self.assertFalse(field.readonly)
|
|
79
|
+
field = fields.BaseMappingField(readonly=True)
|
|
80
|
+
self.assertTrue(field.readonly)
|
|
81
|
+
|
|
82
|
+
def test_init_required(self) -> None:
|
|
83
|
+
field = fields.BaseMappingField()
|
|
84
|
+
self.assertFalse(field.required)
|
|
85
|
+
field = fields.BaseMappingField(required=True)
|
|
86
|
+
self.assertTrue(field.required)
|
|
87
|
+
|
|
88
|
+
def test_init_extra_keys(self) -> None:
|
|
89
|
+
field = fields.BaseMappingField(extra_key1='value1', extra_key2='value2')
|
|
90
|
+
self.assertEqual(field.extra_key1, 'value1')
|
|
91
|
+
self.assertEqual(field.extra_key2, 'value2')
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class BaseMappingObjectTestCase:
|
|
95
|
+
## Class used to test object functionality with different fields
|
|
96
|
+
## use it as inheritance on every field test case and set the attributes below
|
|
97
|
+
# field: fields.BaseMappingField = fields.BaseMappingField # noqa:
|
|
98
|
+
# choices: set = {'default', 'updated'}
|
|
99
|
+
# value_invalid_type: int = 123
|
|
100
|
+
# value_init: str = 'default'
|
|
101
|
+
# value_update: str = 'updated'
|
|
102
|
+
|
|
103
|
+
def test_init_without_value(self) -> None:
|
|
104
|
+
class TestField(BaseMapping):
|
|
105
|
+
__validate_fields__: bool = True
|
|
106
|
+
f1 = self.field()
|
|
107
|
+
|
|
108
|
+
obj = TestField()
|
|
109
|
+
self.assertEqual(obj.f1, Undefined)
|
|
110
|
+
self.assertEqual(obj['f1'], Undefined)
|
|
111
|
+
|
|
112
|
+
def test_choices(self) -> None:
|
|
113
|
+
if self.choices is None:
|
|
114
|
+
return
|
|
115
|
+
|
|
116
|
+
class TestField(BaseMapping):
|
|
117
|
+
__validate_fields__: bool = True
|
|
118
|
+
f1 = self.field(choices=self.choices)
|
|
119
|
+
|
|
120
|
+
obj = TestField(f1=self.value_init)
|
|
121
|
+
obj.f1 = self.value_update
|
|
122
|
+
self.assertEqual(obj.f1, self.value_update)
|
|
123
|
+
self.assertEqual(obj['f1'], self.value_update)
|
|
124
|
+
|
|
125
|
+
def test_instance(self) -> None:
|
|
126
|
+
class TestField(BaseMapping):
|
|
127
|
+
__validate_fields__: bool = True
|
|
128
|
+
f1 = self.field()
|
|
129
|
+
|
|
130
|
+
TestField(f1=self.value_init)
|
|
131
|
+
|
|
132
|
+
def test_instance_invalid_type(self) -> None:
|
|
133
|
+
class TestField(BaseMapping):
|
|
134
|
+
__validate_fields__: bool = True
|
|
135
|
+
f1 = self.field()
|
|
136
|
+
|
|
137
|
+
with self.assertRaises(TypeError) as context:
|
|
138
|
+
TestField(f1=self.value_invalid_type)
|
|
139
|
+
|
|
140
|
+
self.assertEqual(
|
|
141
|
+
str(context.exception),
|
|
142
|
+
f'Field f1 must be of type {self.field.field_type.__name__}, got {type(self.value_invalid_type).__name__}.',
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
def test_invalid_choice(self) -> None:
|
|
146
|
+
if self.choices is None:
|
|
147
|
+
return
|
|
148
|
+
|
|
149
|
+
class TestField(BaseMapping):
|
|
150
|
+
__validate_fields__: bool = True
|
|
151
|
+
f1 = self.field(choices=self.choices)
|
|
152
|
+
|
|
153
|
+
with self.assertRaises(ValueError) as context:
|
|
154
|
+
_ = TestField(f1='invalid_choice')
|
|
155
|
+
self.assertEqual(
|
|
156
|
+
str(context.exception), f"The value 'invalid_choice' for field 'f1' must be in this list {self.choices}."
|
|
157
|
+
)
|
|
158
|
+
obj = TestField()
|
|
159
|
+
with self.assertRaises(ValueError) as context:
|
|
160
|
+
obj.f1 = 'invalid_choice'
|
|
161
|
+
self.assertEqual(
|
|
162
|
+
str(context.exception), f"The value 'invalid_choice' for field 'f1' must be in this list {self.choices}."
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
with self.assertRaises(ValueError) as context:
|
|
166
|
+
obj['f1'] = 'invalid_choice'
|
|
167
|
+
self.assertEqual(
|
|
168
|
+
str(context.exception), f"The value 'invalid_choice' for field 'f1' must be in this list {self.choices}."
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
def test_readonly_field(self) -> None:
|
|
172
|
+
class TestField(BaseMapping):
|
|
173
|
+
__validate_fields__: bool = True
|
|
174
|
+
f1 = self.field(readonly=True)
|
|
175
|
+
|
|
176
|
+
with self.assertRaises(ReadonlyError) as context:
|
|
177
|
+
obj = TestField(f1=self.value_init)
|
|
178
|
+
self.assertEqual(str(context.exception), 'Field f1 is readonly.')
|
|
179
|
+
|
|
180
|
+
obj = TestField()
|
|
181
|
+
with self.assertRaises(ReadonlyError) as context:
|
|
182
|
+
obj.f1 = self.value_update
|
|
183
|
+
self.assertEqual(str(context.exception), 'Field f1 is readonly.')
|
|
184
|
+
|
|
185
|
+
with self.assertRaises(ReadonlyError) as context:
|
|
186
|
+
obj['f1'] = self.value_update
|
|
187
|
+
self.assertEqual(str(context.exception), 'Field f1 is readonly.')
|
|
188
|
+
|
|
189
|
+
def test_required_field(self) -> None:
|
|
190
|
+
class TestField(BaseMapping):
|
|
191
|
+
__validate_fields__: bool = True
|
|
192
|
+
f1 = self.field(required=True)
|
|
193
|
+
|
|
194
|
+
with self.assertRaises(RequiredError) as context:
|
|
195
|
+
_ = TestField()
|
|
196
|
+
self.assertEqual(str(context.exception), 'Field f1 is required.')
|
|
197
|
+
|
|
198
|
+
def test_value_init(self) -> None:
|
|
199
|
+
class TestField(BaseMapping):
|
|
200
|
+
__validate_fields__: bool = True
|
|
201
|
+
f1 = self.field()
|
|
202
|
+
|
|
203
|
+
obj = TestField(f1=self.value_init)
|
|
204
|
+
self.assertEqual(obj.f1, self.value_init)
|
|
205
|
+
self.assertEqual(obj['f1'], self.value_init)
|
|
206
|
+
|
|
207
|
+
def test_value_update_attribute(self) -> None:
|
|
208
|
+
class TestField(BaseMapping):
|
|
209
|
+
__validate_fields__: bool = True
|
|
210
|
+
f1 = self.field()
|
|
211
|
+
|
|
212
|
+
obj = TestField(f1=self.value_init)
|
|
213
|
+
obj.f1 = self.value_update
|
|
214
|
+
self.assertEqual(obj.f1, self.value_update)
|
|
215
|
+
self.assertEqual(obj['f1'], self.value_update)
|
|
216
|
+
|
|
217
|
+
def test_value_update_key(self) -> None:
|
|
218
|
+
class TestField(BaseMapping):
|
|
219
|
+
__validate_fields__: bool = True
|
|
220
|
+
f1 = self.field()
|
|
221
|
+
|
|
222
|
+
obj = TestField(f1=self.value_init)
|
|
223
|
+
obj['f1'] = self.value_update
|
|
224
|
+
self.assertEqual(obj.f1, self.value_update)
|
|
225
|
+
self.assertEqual(obj['f1'], self.value_update)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
class BoolFieldTestCase(TestCase, BaseMappingObjectTestCase):
|
|
229
|
+
field: fields.BoolField = fields.BoolField
|
|
230
|
+
choices: set = {True, False}
|
|
231
|
+
value_invalid_type: int = 123
|
|
232
|
+
value_init: bool = True
|
|
233
|
+
value_update: bool = False
|
|
234
|
+
|
|
235
|
+
def test_attr_type(self) -> None:
|
|
236
|
+
field = fields.BoolField()
|
|
237
|
+
self.assertEqual(field.field_type, bool)
|
|
238
|
+
|
|
239
|
+
def test_clean_value_true(self) -> None:
|
|
240
|
+
field = fields.BoolField()
|
|
241
|
+
self.assertTrue(field.clean_value(1))
|
|
242
|
+
self.assertTrue(field.clean_value(True)) # noqa: FBT003
|
|
243
|
+
self.assertTrue(field.clean_value('y'))
|
|
244
|
+
self.assertTrue(field.clean_value('yes'))
|
|
245
|
+
self.assertTrue(field.clean_value('on'))
|
|
246
|
+
|
|
247
|
+
def test_clean_value_false(self) -> None:
|
|
248
|
+
field = fields.BoolField()
|
|
249
|
+
self.assertFalse(field.clean_value(0))
|
|
250
|
+
self.assertFalse(field.clean_value(False)) # noqa: FBT003
|
|
251
|
+
self.assertFalse(field.clean_value('n'))
|
|
252
|
+
self.assertFalse(field.clean_value('no'))
|
|
253
|
+
self.assertFalse(field.clean_value('off'))
|
|
254
|
+
|
|
255
|
+
def test_invalid_clean_value(self) -> None:
|
|
256
|
+
field = fields.BoolField()
|
|
257
|
+
self.assertEqual(field.clean_value('invalid'), 'invalid')
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
class ChoiceFieldTestCase(TestCase, BaseMappingObjectTestCase):
|
|
261
|
+
field: fields.ChoiceField = fields.ChoiceField
|
|
262
|
+
choices: set = {'a', 'b', 'c'}
|
|
263
|
+
value_invalid_type: int = 123
|
|
264
|
+
value_init: str = 'a'
|
|
265
|
+
value_update: str = 'b'
|
|
266
|
+
|
|
267
|
+
def test_attr_type(self) -> None:
|
|
268
|
+
field = fields.StrField()
|
|
269
|
+
self.assertEqual(field.field_type, str)
|
|
270
|
+
|
|
271
|
+
def test_min_size_default_value(self) -> None:
|
|
272
|
+
field = fields.StrField()
|
|
273
|
+
self.assertEqual(field.min_size, 0)
|
|
274
|
+
|
|
275
|
+
def test_max_size_default_value(self) -> None:
|
|
276
|
+
field = fields.StrField()
|
|
277
|
+
self.assertEqual(field.max_size, maxsize)
|
|
278
|
+
|
|
279
|
+
def test_validate_min_size(self) -> None:
|
|
280
|
+
field = fields.StrField(min_size=2)
|
|
281
|
+
with self.assertRaises(ValueError) as context:
|
|
282
|
+
field.validate('a')
|
|
283
|
+
self.assertEqual(str(context.exception), "The size for field 'None' must be greater than or equal to 2.")
|
|
284
|
+
|
|
285
|
+
def test_validate_max_size(self) -> None:
|
|
286
|
+
field = fields.StrField(max_size=2)
|
|
287
|
+
with self.assertRaises(ValueError) as context:
|
|
288
|
+
field.validate('abc')
|
|
289
|
+
self.assertEqual(str(context.exception), "The size for field 'None' must be less than or equal to 2.")
|
|
290
|
+
|
|
291
|
+
def test_validate_regex(self) -> None:
|
|
292
|
+
field = fields.StrField(regex=r'^[a-z]+$')
|
|
293
|
+
with self.assertRaises(ValueError) as context:
|
|
294
|
+
field.validate('abc123')
|
|
295
|
+
self.assertEqual(
|
|
296
|
+
str(context.exception), "The value 'abc123' for field 'None' must match with this regex: ^[a-z]+$"
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
class DateFieldTestCase(TestCase, BaseMappingObjectTestCase):
|
|
301
|
+
field: fields.DateField = fields.DateField
|
|
302
|
+
choices: set = {Date(2026, 1, 1), Date(2026, 1, 2)}
|
|
303
|
+
value_invalid_type: int = 123
|
|
304
|
+
value_init: Date = Date(2026, 1, 1)
|
|
305
|
+
value_update: Date = Date(2026, 1, 2)
|
|
306
|
+
|
|
307
|
+
def test_attr_type(self) -> None:
|
|
308
|
+
field = fields.DateField()
|
|
309
|
+
self.assertEqual(field.field_type, Date)
|
|
310
|
+
|
|
311
|
+
def test_clean_value_str_isoformat(self) -> None:
|
|
312
|
+
field = fields.DateField()
|
|
313
|
+
self.assertEqual(field.clean_value('2026-01-01'), Date(2026, 1, 1))
|
|
314
|
+
|
|
315
|
+
def test_clean_value_str_everysk(self) -> None:
|
|
316
|
+
field = fields.DateField()
|
|
317
|
+
self.assertEqual(field.clean_value('20260101'), Date(2026, 1, 1))
|
|
318
|
+
|
|
319
|
+
def test_clean_value_date(self) -> None:
|
|
320
|
+
field = fields.DateField()
|
|
321
|
+
self.assertEqual(field.clean_value(Date(2026, 1, 1)), Date(2026, 1, 1))
|
|
322
|
+
|
|
323
|
+
def test_invalid_isoformat(self) -> None:
|
|
324
|
+
field = fields.DateField()
|
|
325
|
+
with self.assertRaises(ValueError) as context:
|
|
326
|
+
field.clean_value('01-01-2026')
|
|
327
|
+
self.assertEqual(str(context.exception), "Invalid isoformat string: '01-01-2026'")
|
|
328
|
+
|
|
329
|
+
def test_invalid_everysk_format(self) -> None:
|
|
330
|
+
field = fields.DateField()
|
|
331
|
+
self.assertEqual(field.clean_value('2026/01/01'), '2026/01/01')
|
|
332
|
+
|
|
333
|
+
def test_min_date_validation(self) -> None:
|
|
334
|
+
field = fields.DateField(min_date=Date(2026, 1, 1))
|
|
335
|
+
with self.assertRaises(ValueError) as context:
|
|
336
|
+
field.validate(Date(2025, 12, 31))
|
|
337
|
+
self.assertEqual(
|
|
338
|
+
str(context.exception),
|
|
339
|
+
"The value '2025-12-31' for field 'None' must be greater than or equal to 2026-01-01.",
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
def test_max_date_validation(self) -> None:
|
|
343
|
+
field = fields.DateField(max_date=Date(2026, 12, 31))
|
|
344
|
+
with self.assertRaises(ValueError) as context:
|
|
345
|
+
field.validate(Date(2027, 1, 1))
|
|
346
|
+
self.assertEqual(
|
|
347
|
+
str(context.exception), "The value '2027-01-01' for field 'None' must be less than or equal to 2026-12-31."
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
class DateTimeFieldTestCase(TestCase, BaseMappingObjectTestCase):
|
|
352
|
+
field: fields.DateTimeField = fields.DateTimeField
|
|
353
|
+
choices: set = {DateTime(2026, 1, 1), DateTime(2026, 1, 2)}
|
|
354
|
+
value_invalid_type: int = 123
|
|
355
|
+
value_init: DateTime = DateTime(2026, 1, 1)
|
|
356
|
+
value_update: DateTime = DateTime(2026, 1, 2)
|
|
357
|
+
|
|
358
|
+
def test_attr_type(self) -> None:
|
|
359
|
+
field = fields.DateTimeField()
|
|
360
|
+
self.assertEqual(field.field_type, DateTime)
|
|
361
|
+
|
|
362
|
+
def test_clean_value_str_isoformat(self) -> None:
|
|
363
|
+
field = fields.DateTimeField()
|
|
364
|
+
self.assertEqual(field.clean_value('2026-01-01T12:30:45'), DateTime(2026, 1, 1, 12, 30, 45))
|
|
365
|
+
|
|
366
|
+
def test_clean_value_str_isoformat_tz(self) -> None:
|
|
367
|
+
field = fields.DateTimeField()
|
|
368
|
+
self.assertEqual(
|
|
369
|
+
field.clean_value('2026-01-01T12:30:45+00:00'), DateTime(2026, 1, 1, 12, 30, 45, tzinfo=ZoneInfo('UTC'))
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
def test_clean_value_str_everysk(self) -> None:
|
|
373
|
+
field = fields.DateTimeField()
|
|
374
|
+
self.assertEqual(field.clean_value('2026-01-01 13:00:00'), DateTime(2026, 1, 1, 13, 0, 0))
|
|
375
|
+
|
|
376
|
+
def test_clean_value_str_everysk_tz(self) -> None:
|
|
377
|
+
field = fields.DateTimeField()
|
|
378
|
+
self.assertEqual(
|
|
379
|
+
field.clean_value('2026-01-01 13:00:00+00:00'), DateTime(2026, 1, 1, 13, 0, 0, tzinfo=ZoneInfo('UTC'))
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
def test_clean_value_date(self) -> None:
|
|
383
|
+
field = fields.DateTimeField()
|
|
384
|
+
self.assertEqual(field.clean_value(Date(2026, 1, 1)), DateTime(2026, 1, 1, 0, 0, 0))
|
|
385
|
+
|
|
386
|
+
def test_clean_value_datetime(self) -> None:
|
|
387
|
+
field = fields.DateTimeField()
|
|
388
|
+
self.assertEqual(field.clean_value(DateTime(2026, 1, 1, 12, 30, 45)), DateTime(2026, 1, 1, 12, 30, 45))
|
|
389
|
+
|
|
390
|
+
def test_force_time(self) -> None:
|
|
391
|
+
# Default is None so time is not changed
|
|
392
|
+
field = fields.DateTimeField()
|
|
393
|
+
self.assertEqual(field.clean_value(DateTime(2026, 1, 1, 12, 30, 45)), DateTime(2026, 1, 1, 12, 30, 45))
|
|
394
|
+
self.assertEqual(field.clean_value('2026-01-01T13:00:00'), DateTime(2026, 1, 1, 13, 0, 0))
|
|
395
|
+
self.assertEqual(field.clean_value('2026-01-01 13:00:00'), DateTime(2026, 1, 1, 13, 0, 0))
|
|
396
|
+
self.assertEqual(field.clean_value('2026-01-01'), DateTime(2026, 1, 1, 0, 0, 0))
|
|
397
|
+
|
|
398
|
+
def test_force_time_first_minute(self) -> None:
|
|
399
|
+
field = fields.DateTimeField(force_time='FIRST_MINUTE')
|
|
400
|
+
self.assertEqual(field.clean_value(DateTime(2026, 1, 1, 8, 15, 0)), DateTime(2026, 1, 1, 0, 0, 0))
|
|
401
|
+
self.assertEqual(field.clean_value('2026-01-01T13:00:00'), DateTime(2026, 1, 1, 0, 0, 0))
|
|
402
|
+
self.assertEqual(field.clean_value('2026-01-01 13:00:00'), DateTime(2026, 1, 1, 0, 0, 0))
|
|
403
|
+
self.assertEqual(field.clean_value('2026-01-01'), DateTime(2026, 1, 1, 0, 0, 0))
|
|
404
|
+
|
|
405
|
+
def test_force_time_last_minute(self) -> None:
|
|
406
|
+
field = fields.DateTimeField(force_time='LAST_MINUTE')
|
|
407
|
+
self.assertEqual(field.clean_value(DateTime(2026, 1, 1, 8, 15, 0)), DateTime(2026, 1, 1, 23, 59, 59, 999999))
|
|
408
|
+
self.assertEqual(field.clean_value('2026-01-01T13:00:00'), DateTime(2026, 1, 1, 23, 59, 59, 999999))
|
|
409
|
+
self.assertEqual(field.clean_value('2026-01-01 13:00:00'), DateTime(2026, 1, 1, 23, 59, 59, 999999))
|
|
410
|
+
self.assertEqual(field.clean_value('2026-01-01'), DateTime(2026, 1, 1, 23, 59, 59, 999999))
|
|
411
|
+
|
|
412
|
+
def test_force_time_midday(self) -> None:
|
|
413
|
+
field = fields.DateTimeField(force_time='MIDDAY')
|
|
414
|
+
self.assertEqual(field.clean_value(DateTime(2026, 1, 1, 8, 15, 0)), DateTime(2026, 1, 1, 12, 0, 0))
|
|
415
|
+
self.assertEqual(field.clean_value('2026-01-01T13:00:00'), DateTime(2026, 1, 1, 12, 0, 0))
|
|
416
|
+
self.assertEqual(field.clean_value('2026-01-01 13:00:00'), DateTime(2026, 1, 1, 12, 0, 0))
|
|
417
|
+
self.assertEqual(field.clean_value('2026-01-01'), DateTime(2026, 1, 1, 12, 0, 0))
|
|
418
|
+
|
|
419
|
+
def test_force_time_now(self) -> None:
|
|
420
|
+
fixed_now = DateTime(2026, 1, 1, 15, 45, 30)
|
|
421
|
+
|
|
422
|
+
with mock.patch('everysk.core.mapping.fields.DateTime.now', return_value=fixed_now):
|
|
423
|
+
field = fields.DateTimeField(force_time='NOW')
|
|
424
|
+
self.assertEqual(field.clean_value(DateTime(2026, 1, 1, 8, 15, 0)), fixed_now)
|
|
425
|
+
self.assertEqual(field.clean_value('2026-01-01T13:00:00'), fixed_now)
|
|
426
|
+
self.assertEqual(field.clean_value('2026-01-01 13:00:00'), fixed_now)
|
|
427
|
+
self.assertEqual(field.clean_value('2026-01-01'), fixed_now)
|
|
428
|
+
|
|
429
|
+
def test_invalid_isoformat(self) -> None:
|
|
430
|
+
field = fields.DateTimeField()
|
|
431
|
+
self.assertEqual(field.clean_value('01-01-2026 12:30:45'), '01-01-2026 12:30:45')
|
|
432
|
+
|
|
433
|
+
def test_invalid_everysk_format(self) -> None:
|
|
434
|
+
field = fields.DateTimeField()
|
|
435
|
+
self.assertEqual(field.clean_value('2026/01/01 12:30:45'), '2026/01/01 12:30:45')
|
|
436
|
+
|
|
437
|
+
def test_min_date_validation(self) -> None:
|
|
438
|
+
field = fields.DateTimeField(min_date=DateTime(2026, 1, 1, 0, 0, 0))
|
|
439
|
+
with self.assertRaises(ValueError) as context:
|
|
440
|
+
field.validate(DateTime(2025, 12, 31, 23, 59, 59))
|
|
441
|
+
self.assertEqual(
|
|
442
|
+
str(context.exception),
|
|
443
|
+
"The value '2025-12-31 23:59:59+00:00' for field 'None' must be greater than or equal to 2026-01-01 00:00:00+00:00.",
|
|
444
|
+
)
|
|
445
|
+
|
|
446
|
+
def test_max_date_validation(self) -> None:
|
|
447
|
+
field = fields.DateTimeField(max_date=DateTime(2026, 12, 31, 23, 59, 59))
|
|
448
|
+
with self.assertRaises(ValueError) as context:
|
|
449
|
+
field.validate(DateTime(2027, 1, 1, 0, 0, 0))
|
|
450
|
+
self.assertEqual(
|
|
451
|
+
str(context.exception),
|
|
452
|
+
"The value '2027-01-01 00:00:00+00:00' for field 'None' must be less than or equal to 2026-12-31 23:59:59+00:00.",
|
|
453
|
+
)
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
class DictFieldTestCase(TestCase, BaseMappingObjectTestCase):
|
|
457
|
+
field: fields.DictField = fields.DictField
|
|
458
|
+
choices: set = None
|
|
459
|
+
value_invalid_type: int = 123
|
|
460
|
+
value_init: dict = {'a': 1}
|
|
461
|
+
value_update: dict = {'b': 2}
|
|
462
|
+
|
|
463
|
+
def setUp(self) -> None:
|
|
464
|
+
self._field = fields.DictField(readonly=True)
|
|
465
|
+
self._value = self._field.clean_value({'key': 'value'})
|
|
466
|
+
|
|
467
|
+
def test_attr_type(self) -> None:
|
|
468
|
+
field = fields.DictField()
|
|
469
|
+
self.assertEqual(field.field_type, dict)
|
|
470
|
+
|
|
471
|
+
def test_clean_value_readonly(self) -> None:
|
|
472
|
+
self.assertEqual(self._value, {'key': 'value'})
|
|
473
|
+
self.assertIsInstance(self._value, fields.DictField.ReadonlyDict)
|
|
474
|
+
|
|
475
|
+
def test_clean_value_readonly_clear(self) -> None:
|
|
476
|
+
with self.assertRaises(ReadonlyError):
|
|
477
|
+
self._value.clear()
|
|
478
|
+
|
|
479
|
+
def test_clean_value_readonly_del(self) -> None:
|
|
480
|
+
with self.assertRaises(ReadonlyError):
|
|
481
|
+
del self._value['key']
|
|
482
|
+
|
|
483
|
+
def test_clean_value_readonly_pop(self) -> None:
|
|
484
|
+
with self.assertRaises(ReadonlyError):
|
|
485
|
+
self._value.pop('key')
|
|
486
|
+
|
|
487
|
+
def test_clean_value_readonly_popitem(self) -> None:
|
|
488
|
+
with self.assertRaises(ReadonlyError):
|
|
489
|
+
self._value.popitem()
|
|
490
|
+
|
|
491
|
+
def test_clean_value_readonly_set(self) -> None:
|
|
492
|
+
with self.assertRaises(ReadonlyError):
|
|
493
|
+
self._value['key'] = 'new_value'
|
|
494
|
+
|
|
495
|
+
def test_clean_value_readonly_setdefault(self) -> None:
|
|
496
|
+
with self.assertRaises(ReadonlyError):
|
|
497
|
+
self._value.setdefault('key', 'new_value')
|
|
498
|
+
|
|
499
|
+
def test_clean_value_readonly_update(self) -> None:
|
|
500
|
+
with self.assertRaises(ReadonlyError):
|
|
501
|
+
self._value.update({'new_key': 'new_value'})
|
|
502
|
+
|
|
503
|
+
|
|
504
|
+
class EmailFieldTestCase(TestCase, BaseMappingObjectTestCase):
|
|
505
|
+
field: fields.EmailField = fields.EmailField
|
|
506
|
+
choices: set = {'a@b.c', 'd@e.f'}
|
|
507
|
+
value_invalid_type: int = 123
|
|
508
|
+
value_init: str = 'a@b.c'
|
|
509
|
+
value_update: str = 'd@e.f'
|
|
510
|
+
|
|
511
|
+
def test_attr_type(self) -> None:
|
|
512
|
+
field = fields.EmailField()
|
|
513
|
+
self.assertEqual(field.field_type, str)
|
|
514
|
+
|
|
515
|
+
def test_validate_valid_email(self) -> None:
|
|
516
|
+
field = fields.EmailField()
|
|
517
|
+
field.validate('fscariott@everysk.com')
|
|
518
|
+
|
|
519
|
+
def test_validate_invalid_email(self) -> None:
|
|
520
|
+
field = fields.EmailField()
|
|
521
|
+
with self.assertRaises(ValueError) as context:
|
|
522
|
+
field.validate('invalid-email')
|
|
523
|
+
self.assertEqual(str(context.exception), 'Key None must be an e-mail.')
|
|
524
|
+
|
|
525
|
+
def test_validate_invalid_email_max_size(self) -> None:
|
|
526
|
+
field = fields.EmailField()
|
|
527
|
+
long_email = 'a' * 315 + '@everysk.com' # total length = 321
|
|
528
|
+
with self.assertRaises(ValueError) as context:
|
|
529
|
+
field.validate(long_email)
|
|
530
|
+
self.assertEqual(str(context.exception), "The size for field 'None' must be less than or equal to 320.")
|
|
531
|
+
|
|
532
|
+
def test_validate_invalid_email_min_size(self) -> None:
|
|
533
|
+
field = fields.EmailField()
|
|
534
|
+
short_email = 'a@b' # total length = 3
|
|
535
|
+
with self.assertRaises(ValueError) as context:
|
|
536
|
+
field.validate(short_email)
|
|
537
|
+
self.assertEqual(str(context.exception), "The size for field 'None' must be greater than or equal to 5.")
|
|
538
|
+
|
|
539
|
+
def test_validate_invalid_email_more_at(self) -> None:
|
|
540
|
+
field = fields.EmailField()
|
|
541
|
+
with self.assertRaises(ValueError) as context:
|
|
542
|
+
field.validate('fscariott@everysk.com@teste')
|
|
543
|
+
self.assertEqual(str(context.exception), 'Key None must be an e-mail.')
|
|
544
|
+
|
|
545
|
+
|
|
546
|
+
class FloatFieldTestCase(TestCase, BaseMappingObjectTestCase):
|
|
547
|
+
field: fields.FloatField = fields.FloatField
|
|
548
|
+
choices: set = {1.23, 4.56}
|
|
549
|
+
value_invalid_type: str = 'invalid'
|
|
550
|
+
value_init: float = 1.23
|
|
551
|
+
value_update: float = 4.56
|
|
552
|
+
|
|
553
|
+
def test_attr_type(self) -> None:
|
|
554
|
+
field = fields.FloatField()
|
|
555
|
+
self.assertEqual(field.field_type, float)
|
|
556
|
+
|
|
557
|
+
def test_clean_value_int(self) -> None:
|
|
558
|
+
field = fields.FloatField()
|
|
559
|
+
self.assertEqual(field.clean_value(123), 123.0)
|
|
560
|
+
|
|
561
|
+
def test_clean_value_str(self) -> None:
|
|
562
|
+
field = fields.FloatField()
|
|
563
|
+
self.assertEqual(field.clean_value('123.45'), 123.45)
|
|
564
|
+
|
|
565
|
+
def test_clean_value_invalid_str(self) -> None:
|
|
566
|
+
field = fields.FloatField()
|
|
567
|
+
self.assertEqual(field.clean_value('invalid'), 'invalid')
|
|
568
|
+
|
|
569
|
+
def test_validate_max_value(self) -> None:
|
|
570
|
+
field = fields.FloatField(max_value=100.0)
|
|
571
|
+
with self.assertRaises(ValueError) as context:
|
|
572
|
+
field.validate(150.0)
|
|
573
|
+
self.assertEqual(
|
|
574
|
+
str(context.exception), "The value '150.0' for field 'None' must be less than or equal to 100.0."
|
|
575
|
+
)
|
|
576
|
+
|
|
577
|
+
def test_validate_min_value(self) -> None:
|
|
578
|
+
field = fields.FloatField(min_value=10.0)
|
|
579
|
+
with self.assertRaises(ValueError) as context:
|
|
580
|
+
field.validate(5.0)
|
|
581
|
+
self.assertEqual(
|
|
582
|
+
str(context.exception), "The value '5.0' for field 'None' must be greater than or equal to 10.0."
|
|
583
|
+
)
|
|
584
|
+
|
|
585
|
+
|
|
586
|
+
class IntFieldTestCase(TestCase, BaseMappingObjectTestCase):
|
|
587
|
+
field: fields.IntField = fields.IntField
|
|
588
|
+
choices: set = {1, 2}
|
|
589
|
+
value_invalid_type: str = 'invalid'
|
|
590
|
+
value_init: int = 1
|
|
591
|
+
value_update: int = 2
|
|
592
|
+
|
|
593
|
+
def test_attr_type(self) -> None:
|
|
594
|
+
field = fields.IntField()
|
|
595
|
+
self.assertEqual(field.field_type, int)
|
|
596
|
+
|
|
597
|
+
def test_clean_value_str(self) -> None:
|
|
598
|
+
field = fields.IntField()
|
|
599
|
+
self.assertEqual(field.clean_value('123'), 123)
|
|
600
|
+
|
|
601
|
+
def test_clean_value_invalid_str(self) -> None:
|
|
602
|
+
field = fields.IntField()
|
|
603
|
+
self.assertEqual(field.clean_value('invalid'), 'invalid')
|
|
604
|
+
|
|
605
|
+
def test_validate_max_value(self) -> None:
|
|
606
|
+
field = fields.IntField(max_value=100)
|
|
607
|
+
with self.assertRaises(ValueError) as context:
|
|
608
|
+
field.validate(150)
|
|
609
|
+
self.assertEqual(str(context.exception), "The value '150' for field 'None' must be less than or equal to 100.")
|
|
610
|
+
|
|
611
|
+
def test_validate_min_value(self) -> None:
|
|
612
|
+
field = fields.IntField(min_value=10)
|
|
613
|
+
with self.assertRaises(ValueError) as context:
|
|
614
|
+
field.validate(5)
|
|
615
|
+
self.assertEqual(str(context.exception), "The value '5' for field 'None' must be greater than or equal to 10.")
|
|
616
|
+
|
|
617
|
+
|
|
618
|
+
class IteratorFieldTestCase(TestCase, BaseMappingObjectTestCase):
|
|
619
|
+
field: fields.IteratorField = fields.IteratorField
|
|
620
|
+
choices: set = None
|
|
621
|
+
value_invalid_type: str = 'invalid'
|
|
622
|
+
value_init: Iterator = iter([1])
|
|
623
|
+
value_update: Iterator = iter([2])
|
|
624
|
+
|
|
625
|
+
def test_attr_type(self) -> None:
|
|
626
|
+
field = fields.IteratorField()
|
|
627
|
+
self.assertEqual(field.field_type, Iterator)
|
|
628
|
+
|
|
629
|
+
def test_clean_value_list(self) -> None:
|
|
630
|
+
field = fields.IteratorField()
|
|
631
|
+
value = field.clean_value([1, 2, 3])
|
|
632
|
+
self.assertIsInstance(value, Iterator)
|
|
633
|
+
self.assertListEqual(list(value), [1, 2, 3])
|
|
634
|
+
|
|
635
|
+
def test_clean_value_set(self) -> None:
|
|
636
|
+
field = fields.IteratorField()
|
|
637
|
+
value = field.clean_value({3, 1, 2})
|
|
638
|
+
self.assertIsInstance(value, Iterator)
|
|
639
|
+
self.assertListEqual(sorted(value), [1, 2, 3])
|
|
640
|
+
|
|
641
|
+
def test_clean_value_str(self) -> None:
|
|
642
|
+
field = fields.IteratorField()
|
|
643
|
+
value = field.clean_value('a,b,c')
|
|
644
|
+
self.assertIsInstance(value, Iterator)
|
|
645
|
+
self.assertListEqual(list(value), ['a', 'b', 'c'])
|
|
646
|
+
|
|
647
|
+
def test_clean_value_str_separator(self) -> None:
|
|
648
|
+
field = fields.IteratorField(separator=';')
|
|
649
|
+
value = field.clean_value('a;b;c')
|
|
650
|
+
self.assertIsInstance(value, Iterator)
|
|
651
|
+
self.assertListEqual(list(value), ['a', 'b', 'c'])
|
|
652
|
+
|
|
653
|
+
def test_clean_value_tuple(self) -> None:
|
|
654
|
+
field = fields.IteratorField()
|
|
655
|
+
value = field.clean_value((1, 2, 3))
|
|
656
|
+
self.assertIsInstance(value, Iterator)
|
|
657
|
+
self.assertListEqual(list(value), [1, 2, 3])
|
|
658
|
+
|
|
659
|
+
|
|
660
|
+
class ListFieldTestCase(TestCase, BaseMappingObjectTestCase):
|
|
661
|
+
field: fields.ListField = fields.ListField
|
|
662
|
+
choices: set = {1, 2}
|
|
663
|
+
value_invalid_type: str = 'invalid'
|
|
664
|
+
value_init: list = [1]
|
|
665
|
+
value_update: list = [2]
|
|
666
|
+
|
|
667
|
+
def test_attr_type(self) -> None:
|
|
668
|
+
field = fields.ListField()
|
|
669
|
+
self.assertEqual(field.field_type, list)
|
|
670
|
+
|
|
671
|
+
def test_clean_value_readonly(self) -> None:
|
|
672
|
+
field = fields.ListField(readonly=True)
|
|
673
|
+
value = field.clean_value([1, 2, 3])
|
|
674
|
+
self.assertIsInstance(value, fields.ListField.ReadonlyList)
|
|
675
|
+
self.assertListEqual(value, [1, 2, 3])
|
|
676
|
+
|
|
677
|
+
def test_clean_value_readonly_append(self) -> None:
|
|
678
|
+
field = fields.ListField(readonly=True)
|
|
679
|
+
value = field.clean_value([1, 2, 3])
|
|
680
|
+
with self.assertRaises(fields.ReadonlyError):
|
|
681
|
+
value.append(4)
|
|
682
|
+
|
|
683
|
+
def test_clean_value_readonly_clear(self) -> None:
|
|
684
|
+
field = fields.ListField(readonly=True)
|
|
685
|
+
value = field.clean_value([1, 2, 3])
|
|
686
|
+
with self.assertRaises(fields.ReadonlyError):
|
|
687
|
+
value.clear()
|
|
688
|
+
|
|
689
|
+
def test_clean_value_readonly_extend(self) -> None:
|
|
690
|
+
field = fields.ListField(readonly=True)
|
|
691
|
+
value = field.clean_value([1, 2, 3])
|
|
692
|
+
with self.assertRaises(fields.ReadonlyError):
|
|
693
|
+
value.extend([4, 5])
|
|
694
|
+
|
|
695
|
+
def test_clean_value_readonly_insert(self) -> None:
|
|
696
|
+
field = fields.ListField(readonly=True)
|
|
697
|
+
value = field.clean_value([1, 2, 3])
|
|
698
|
+
with self.assertRaises(fields.ReadonlyError):
|
|
699
|
+
value.insert(1, 4)
|
|
700
|
+
|
|
701
|
+
def test_clean_value_readonly_pop(self) -> None:
|
|
702
|
+
field = fields.ListField(readonly=True)
|
|
703
|
+
value = field.clean_value([1, 2, 3])
|
|
704
|
+
with self.assertRaises(fields.ReadonlyError):
|
|
705
|
+
value.pop()
|
|
706
|
+
|
|
707
|
+
def test_clean_value_readonly_remove(self) -> None:
|
|
708
|
+
field = fields.ListField(readonly=True)
|
|
709
|
+
value = field.clean_value([1, 2, 3])
|
|
710
|
+
with self.assertRaises(fields.ReadonlyError):
|
|
711
|
+
value.remove(2)
|
|
712
|
+
|
|
713
|
+
def test_clean_value_readonly_reverse(self) -> None:
|
|
714
|
+
field = fields.ListField(readonly=True)
|
|
715
|
+
value = field.clean_value([1, 2, 3])
|
|
716
|
+
with self.assertRaises(fields.ReadonlyError):
|
|
717
|
+
value.reverse()
|
|
718
|
+
|
|
719
|
+
def test_clean_value_readonly_sort(self) -> None:
|
|
720
|
+
field = fields.ListField(readonly=True)
|
|
721
|
+
value = field.clean_value([1, 2, 3])
|
|
722
|
+
with self.assertRaises(fields.ReadonlyError):
|
|
723
|
+
value.sort()
|
|
724
|
+
|
|
725
|
+
def test_clean_value_set(self) -> None:
|
|
726
|
+
field = fields.ListField()
|
|
727
|
+
value = field.clean_value({3, 1, 2})
|
|
728
|
+
self.assertIsInstance(value, list)
|
|
729
|
+
self.assertListEqual(sorted(value), [1, 2, 3])
|
|
730
|
+
|
|
731
|
+
def test_clean_value_str(self) -> None:
|
|
732
|
+
field = fields.ListField()
|
|
733
|
+
value = field.clean_value('a,b,c')
|
|
734
|
+
self.assertIsInstance(value, list)
|
|
735
|
+
self.assertListEqual(value, ['a', 'b', 'c'])
|
|
736
|
+
|
|
737
|
+
def test_clean_value_str_separator(self) -> None:
|
|
738
|
+
field = fields.ListField(separator=';')
|
|
739
|
+
value = field.clean_value('a;b;c')
|
|
740
|
+
self.assertIsInstance(value, list)
|
|
741
|
+
self.assertListEqual(value, ['a', 'b', 'c'])
|
|
742
|
+
|
|
743
|
+
def test_clean_value_tuple(self) -> None:
|
|
744
|
+
field = fields.ListField()
|
|
745
|
+
value = field.clean_value((1, 2, 3))
|
|
746
|
+
self.assertIsInstance(value, list)
|
|
747
|
+
self.assertListEqual(value, [1, 2, 3])
|
|
748
|
+
|
|
749
|
+
def test_invalid_choice_list(self) -> None:
|
|
750
|
+
class TestField(BaseMapping):
|
|
751
|
+
__validate_fields__: bool = True
|
|
752
|
+
f1 = self.field(choices=self.choices)
|
|
753
|
+
|
|
754
|
+
with self.assertRaises(ValueError) as context:
|
|
755
|
+
_ = TestField(f1=[3])
|
|
756
|
+
self.assertEqual(str(context.exception), f"The value '[3]' for field 'f1' must be in this list {self.choices}.")
|
|
757
|
+
obj = TestField()
|
|
758
|
+
with self.assertRaises(ValueError) as context:
|
|
759
|
+
obj.f1 = [3]
|
|
760
|
+
self.assertEqual(str(context.exception), f"The value '[3]' for field 'f1' must be in this list {self.choices}.")
|
|
761
|
+
|
|
762
|
+
with self.assertRaises(ValueError) as context:
|
|
763
|
+
obj['f1'] = [3]
|
|
764
|
+
self.assertEqual(str(context.exception), f"The value '[3]' for field 'f1' must be in this list {self.choices}.")
|
|
765
|
+
|
|
766
|
+
def test_invalid_choice_list_values(self) -> None:
|
|
767
|
+
class TestField(BaseMapping):
|
|
768
|
+
__validate_fields__: bool = True
|
|
769
|
+
f1 = self.field(choices=self.choices)
|
|
770
|
+
|
|
771
|
+
with self.assertRaises(ValueError) as context:
|
|
772
|
+
_ = TestField(f1=[1, 3])
|
|
773
|
+
self.assertEqual(
|
|
774
|
+
str(context.exception), f"The value '[1, 3]' for field 'f1' must be in this list {self.choices}."
|
|
775
|
+
)
|
|
776
|
+
obj = TestField()
|
|
777
|
+
with self.assertRaises(ValueError) as context:
|
|
778
|
+
obj.f1 = [1, 3]
|
|
779
|
+
self.assertEqual(
|
|
780
|
+
str(context.exception), f"The value '[1, 3]' for field 'f1' must be in this list {self.choices}."
|
|
781
|
+
)
|
|
782
|
+
|
|
783
|
+
with self.assertRaises(ValueError) as context:
|
|
784
|
+
obj['f1'] = [1, 3]
|
|
785
|
+
self.assertEqual(
|
|
786
|
+
str(context.exception), f"The value '[1, 3]' for field 'f1' must be in this list {self.choices}."
|
|
787
|
+
)
|
|
788
|
+
|
|
789
|
+
def test_max_size_default_value(self) -> None:
|
|
790
|
+
field = fields.ListField()
|
|
791
|
+
self.assertEqual(field.max_size, maxsize // 8)
|
|
792
|
+
|
|
793
|
+
def test_min_size_default_value(self) -> None:
|
|
794
|
+
field = fields.ListField()
|
|
795
|
+
self.assertEqual(field.min_size, 0)
|
|
796
|
+
|
|
797
|
+
def test_validate_max_size(self) -> None:
|
|
798
|
+
field = fields.ListField(max_size=2)
|
|
799
|
+
with self.assertRaises(ValueError) as context:
|
|
800
|
+
field.validate([1, 2, 3])
|
|
801
|
+
self.assertEqual(str(context.exception), "The size for field 'None' must be less than or equal to 2.")
|
|
802
|
+
|
|
803
|
+
def test_validate_min_size(self) -> None:
|
|
804
|
+
field = fields.ListField(min_size=2)
|
|
805
|
+
with self.assertRaises(ValueError) as context:
|
|
806
|
+
field.validate([1])
|
|
807
|
+
self.assertEqual(str(context.exception), "The size for field 'None' must be greater than or equal to 2.")
|
|
808
|
+
|
|
809
|
+
|
|
810
|
+
class SetFieldTestCase(TestCase, BaseMappingObjectTestCase):
|
|
811
|
+
field: fields.SetField = fields.SetField
|
|
812
|
+
choices: set = {1, 2}
|
|
813
|
+
value_invalid_type: str = 'invalid'
|
|
814
|
+
value_init: set = {1}
|
|
815
|
+
value_update: set = {2}
|
|
816
|
+
|
|
817
|
+
def test_attr_type(self) -> None:
|
|
818
|
+
field = fields.SetField()
|
|
819
|
+
self.assertEqual(field.field_type, set)
|
|
820
|
+
|
|
821
|
+
def test_clean_value_list(self) -> None:
|
|
822
|
+
field = fields.SetField()
|
|
823
|
+
value = field.clean_value([1, 2, 2, 3])
|
|
824
|
+
self.assertIsInstance(value, set)
|
|
825
|
+
self.assertSetEqual(value, {1, 2, 3})
|
|
826
|
+
|
|
827
|
+
def test_clean_value_readonly(self) -> None:
|
|
828
|
+
field = fields.SetField(readonly=True)
|
|
829
|
+
value = field.clean_value({1, 2, 3})
|
|
830
|
+
self.assertIsInstance(value, fields.SetField.ReadonlySet)
|
|
831
|
+
self.assertSetEqual(value, {1, 2, 3})
|
|
832
|
+
|
|
833
|
+
def test_clean_value_readonly_add(self) -> None:
|
|
834
|
+
field = fields.SetField(readonly=True)
|
|
835
|
+
value = field.clean_value({1, 2, 3})
|
|
836
|
+
with self.assertRaises(fields.ReadonlyError):
|
|
837
|
+
value.add(4)
|
|
838
|
+
|
|
839
|
+
def test_clean_value_readonly_clear(self) -> None:
|
|
840
|
+
field = fields.SetField(readonly=True)
|
|
841
|
+
value = field.clean_value({1, 2, 3})
|
|
842
|
+
with self.assertRaises(fields.ReadonlyError):
|
|
843
|
+
value.clear()
|
|
844
|
+
|
|
845
|
+
def test_clean_value_readonly_discard(self) -> None:
|
|
846
|
+
field = fields.SetField(readonly=True)
|
|
847
|
+
value = field.clean_value({1, 2, 3})
|
|
848
|
+
with self.assertRaises(fields.ReadonlyError):
|
|
849
|
+
value.discard(2)
|
|
850
|
+
|
|
851
|
+
def test_clean_value_readonly_pop(self) -> None:
|
|
852
|
+
field = fields.SetField(readonly=True)
|
|
853
|
+
value = field.clean_value({1, 2, 3})
|
|
854
|
+
with self.assertRaises(fields.ReadonlyError):
|
|
855
|
+
value.pop()
|
|
856
|
+
|
|
857
|
+
def test_clean_value_readonly_remove(self) -> None:
|
|
858
|
+
field = fields.SetField(readonly=True)
|
|
859
|
+
value = field.clean_value({1, 2, 3})
|
|
860
|
+
with self.assertRaises(fields.ReadonlyError):
|
|
861
|
+
value.remove(2)
|
|
862
|
+
|
|
863
|
+
def test_clean_value_readonly_update(self) -> None:
|
|
864
|
+
field = fields.SetField(readonly=True)
|
|
865
|
+
value = field.clean_value({1, 2, 3})
|
|
866
|
+
with self.assertRaises(fields.ReadonlyError):
|
|
867
|
+
value.update({4})
|
|
868
|
+
|
|
869
|
+
def test_clean_value_str(self) -> None:
|
|
870
|
+
field = fields.SetField()
|
|
871
|
+
value = field.clean_value('a,b,a,c')
|
|
872
|
+
self.assertIsInstance(value, set)
|
|
873
|
+
self.assertSetEqual(value, {'a', 'b', 'c'})
|
|
874
|
+
|
|
875
|
+
def test_clean_value_str_separator(self) -> None:
|
|
876
|
+
field = fields.SetField(separator=';')
|
|
877
|
+
value = field.clean_value('a;b;a;c')
|
|
878
|
+
self.assertIsInstance(value, set)
|
|
879
|
+
self.assertSetEqual(value, {'a', 'b', 'c'})
|
|
880
|
+
|
|
881
|
+
def test_clean_value_tuple(self) -> None:
|
|
882
|
+
field = fields.SetField()
|
|
883
|
+
value = field.clean_value((1, 2, 2, 3))
|
|
884
|
+
self.assertIsInstance(value, set)
|
|
885
|
+
self.assertSetEqual(value, {1, 2, 3})
|
|
886
|
+
|
|
887
|
+
def test_invalid_choice_set(self) -> None:
|
|
888
|
+
class TestField(BaseMapping):
|
|
889
|
+
__validate_fields__: bool = True
|
|
890
|
+
f1 = self.field(choices=self.choices)
|
|
891
|
+
|
|
892
|
+
with self.assertRaises(ValueError) as context:
|
|
893
|
+
_ = TestField(f1={3})
|
|
894
|
+
self.assertEqual(
|
|
895
|
+
str(context.exception), f"The value '{{3}}' for field 'f1' must be in this list {self.choices}."
|
|
896
|
+
)
|
|
897
|
+
obj = TestField()
|
|
898
|
+
with self.assertRaises(ValueError) as context:
|
|
899
|
+
obj.f1 = {3}
|
|
900
|
+
self.assertEqual(
|
|
901
|
+
str(context.exception), f"The value '{{3}}' for field 'f1' must be in this list {self.choices}."
|
|
902
|
+
)
|
|
903
|
+
|
|
904
|
+
with self.assertRaises(ValueError) as context:
|
|
905
|
+
obj['f1'] = {3}
|
|
906
|
+
self.assertEqual(
|
|
907
|
+
str(context.exception), f"The value '{{3}}' for field 'f1' must be in this list {self.choices}."
|
|
908
|
+
)
|
|
909
|
+
|
|
910
|
+
def test_invalid_choice_set_values(self) -> None:
|
|
911
|
+
class TestField(BaseMapping):
|
|
912
|
+
__validate_fields__: bool = True
|
|
913
|
+
f1 = self.field(choices=self.choices)
|
|
914
|
+
|
|
915
|
+
with self.assertRaises(ValueError) as context:
|
|
916
|
+
_ = TestField(f1={1, 3})
|
|
917
|
+
self.assertEqual(
|
|
918
|
+
str(context.exception), f"The value '{{1, 3}}' for field 'f1' must be in this list {self.choices}."
|
|
919
|
+
)
|
|
920
|
+
obj = TestField()
|
|
921
|
+
with self.assertRaises(ValueError) as context:
|
|
922
|
+
obj.f1 = {1, 3}
|
|
923
|
+
self.assertEqual(
|
|
924
|
+
str(context.exception), f"The value '{{1, 3}}' for field 'f1' must be in this list {self.choices}."
|
|
925
|
+
)
|
|
926
|
+
|
|
927
|
+
with self.assertRaises(ValueError) as context:
|
|
928
|
+
obj['f1'] = {1, 3}
|
|
929
|
+
self.assertEqual(
|
|
930
|
+
str(context.exception), f"The value '{{1, 3}}' for field 'f1' must be in this list {self.choices}."
|
|
931
|
+
)
|
|
932
|
+
|
|
933
|
+
def test_max_size_default_value(self) -> None:
|
|
934
|
+
field = fields.SetField()
|
|
935
|
+
self.assertEqual(field.max_size, maxsize // 8)
|
|
936
|
+
|
|
937
|
+
def test_min_size_default_value(self) -> None:
|
|
938
|
+
field = fields.SetField()
|
|
939
|
+
self.assertEqual(field.min_size, 0)
|
|
940
|
+
|
|
941
|
+
def test_validate_min_size(self) -> None:
|
|
942
|
+
field = fields.SetField(min_size=2)
|
|
943
|
+
with self.assertRaises(ValueError) as context:
|
|
944
|
+
field.validate({1})
|
|
945
|
+
self.assertEqual(str(context.exception), "The size for field 'None' must be greater than or equal to 2.")
|
|
946
|
+
|
|
947
|
+
def test_validate_max_size(self) -> None:
|
|
948
|
+
field = fields.SetField(max_size=2)
|
|
949
|
+
with self.assertRaises(ValueError) as context:
|
|
950
|
+
field.validate({1, 2, 3})
|
|
951
|
+
self.assertEqual(str(context.exception), "The size for field 'None' must be less than or equal to 2.")
|
|
952
|
+
|
|
953
|
+
|
|
954
|
+
class StrFieldTestCase(TestCase, BaseMappingObjectTestCase):
|
|
955
|
+
field: fields.StrField = fields.StrField
|
|
956
|
+
choices: set = {'a', 'b', 'c'}
|
|
957
|
+
value_invalid_type: int = 123
|
|
958
|
+
value_init: str = 'a'
|
|
959
|
+
value_update: str = 'b'
|
|
960
|
+
|
|
961
|
+
def test_attr_type(self) -> None:
|
|
962
|
+
field = fields.StrField()
|
|
963
|
+
self.assertEqual(field.field_type, str)
|
|
964
|
+
|
|
965
|
+
def test_min_size_default_value(self) -> None:
|
|
966
|
+
field = fields.StrField()
|
|
967
|
+
self.assertEqual(field.min_size, 0)
|
|
968
|
+
|
|
969
|
+
def test_max_size_default_value(self) -> None:
|
|
970
|
+
field = fields.StrField()
|
|
971
|
+
self.assertEqual(field.max_size, maxsize)
|
|
972
|
+
|
|
973
|
+
def test_validate_min_size(self) -> None:
|
|
974
|
+
field = fields.StrField(min_size=2)
|
|
975
|
+
with self.assertRaises(ValueError) as context:
|
|
976
|
+
field.validate('a')
|
|
977
|
+
self.assertEqual(str(context.exception), "The size for field 'None' must be greater than or equal to 2.")
|
|
978
|
+
|
|
979
|
+
def test_validate_max_size(self) -> None:
|
|
980
|
+
field = fields.StrField(max_size=2)
|
|
981
|
+
with self.assertRaises(ValueError) as context:
|
|
982
|
+
field.validate('abc')
|
|
983
|
+
self.assertEqual(str(context.exception), "The size for field 'None' must be less than or equal to 2.")
|
|
984
|
+
|
|
985
|
+
def test_validate_regex(self) -> None:
|
|
986
|
+
field = fields.StrField(regex=r'^[a-z]+$')
|
|
987
|
+
with self.assertRaises(ValueError) as context:
|
|
988
|
+
field.validate('abc123')
|
|
989
|
+
self.assertEqual(
|
|
990
|
+
str(context.exception), "The value 'abc123' for field 'None' must match with this regex: ^[a-z]+$"
|
|
991
|
+
)
|
|
992
|
+
|
|
993
|
+
|
|
994
|
+
class RegexFieldTestCase(TestCase, BaseMappingObjectTestCase):
|
|
995
|
+
field: fields.RegexField = fields.RegexField
|
|
996
|
+
choices: set = None
|
|
997
|
+
value_invalid_type: int = 123
|
|
998
|
+
value_init: re.Pattern = re.compile('a')
|
|
999
|
+
value_update: re.Pattern = re.compile('b')
|
|
1000
|
+
|
|
1001
|
+
def test_attr_type(self) -> None:
|
|
1002
|
+
field = fields.RegexField()
|
|
1003
|
+
self.assertEqual(field.field_type, re.Pattern)
|
|
1004
|
+
|
|
1005
|
+
def test_clean_value(self) -> None:
|
|
1006
|
+
field = fields.RegexField()
|
|
1007
|
+
value = field.clean_value(r'^[a-z]+$')
|
|
1008
|
+
self.assertIsInstance(value, re.Pattern)
|
|
1009
|
+
self.assertEqual(value.pattern, r'^[a-z]+$')
|
|
1010
|
+
|
|
1011
|
+
|
|
1012
|
+
class TupleFieldTestCase(TestCase, BaseMappingObjectTestCase):
|
|
1013
|
+
field: fields.TupleField = fields.TupleField
|
|
1014
|
+
choices: set = (1, 2)
|
|
1015
|
+
value_invalid_type: int = 123
|
|
1016
|
+
value_init: tuple = (1,)
|
|
1017
|
+
value_update: tuple = (2,)
|
|
1018
|
+
|
|
1019
|
+
def test_attr_type(self) -> None:
|
|
1020
|
+
field = fields.TupleField()
|
|
1021
|
+
self.assertEqual(field.field_type, tuple)
|
|
1022
|
+
|
|
1023
|
+
def test_clean_value_list(self) -> None:
|
|
1024
|
+
field = fields.TupleField()
|
|
1025
|
+
value = field.clean_value([1, 2, 3])
|
|
1026
|
+
self.assertIsInstance(value, tuple)
|
|
1027
|
+
self.assertTupleEqual(value, (1, 2, 3))
|
|
1028
|
+
|
|
1029
|
+
def test_clean_value_set(self) -> None:
|
|
1030
|
+
field = fields.TupleField()
|
|
1031
|
+
value = field.clean_value({3, 1, 2})
|
|
1032
|
+
self.assertIsInstance(value, tuple)
|
|
1033
|
+
self.assertTupleEqual(tuple(sorted(value)), (1, 2, 3))
|
|
1034
|
+
|
|
1035
|
+
def test_clean_value_str(self) -> None:
|
|
1036
|
+
field = fields.TupleField()
|
|
1037
|
+
value = field.clean_value('a,b,c')
|
|
1038
|
+
self.assertIsInstance(value, tuple)
|
|
1039
|
+
self.assertTupleEqual(value, ('a', 'b', 'c'))
|
|
1040
|
+
|
|
1041
|
+
def test_clean_value_str_separator(self) -> None:
|
|
1042
|
+
field = fields.TupleField(separator=';')
|
|
1043
|
+
value = field.clean_value('a;b;c')
|
|
1044
|
+
self.assertIsInstance(value, tuple)
|
|
1045
|
+
self.assertTupleEqual(value, ('a', 'b', 'c'))
|
|
1046
|
+
|
|
1047
|
+
def test_invalid_choice_tuple(self) -> None:
|
|
1048
|
+
class TestField(BaseMapping):
|
|
1049
|
+
__validate_fields__: bool = True
|
|
1050
|
+
f1 = self.field(choices=self.choices)
|
|
1051
|
+
|
|
1052
|
+
with self.assertRaises(ValueError) as context:
|
|
1053
|
+
_ = TestField(f1=(3,))
|
|
1054
|
+
self.assertEqual(
|
|
1055
|
+
str(context.exception), f"The value '(3,)' for field 'f1' must be in this list {self.choices}."
|
|
1056
|
+
)
|
|
1057
|
+
obj = TestField()
|
|
1058
|
+
with self.assertRaises(ValueError) as context:
|
|
1059
|
+
obj.f1 = (3,)
|
|
1060
|
+
self.assertEqual(
|
|
1061
|
+
str(context.exception), f"The value '(3,)' for field 'f1' must be in this list {self.choices}."
|
|
1062
|
+
)
|
|
1063
|
+
|
|
1064
|
+
with self.assertRaises(ValueError) as context:
|
|
1065
|
+
obj['f1'] = (3,)
|
|
1066
|
+
self.assertEqual(
|
|
1067
|
+
str(context.exception), f"The value '(3,)' for field 'f1' must be in this list {self.choices}."
|
|
1068
|
+
)
|
|
1069
|
+
|
|
1070
|
+
def test_invalid_choice_tuple_values(self) -> None:
|
|
1071
|
+
class TestField(BaseMapping):
|
|
1072
|
+
__validate_fields__: bool = True
|
|
1073
|
+
f1 = self.field(choices=self.choices)
|
|
1074
|
+
|
|
1075
|
+
with self.assertRaises(ValueError) as context:
|
|
1076
|
+
_ = TestField(f1=(1, 3))
|
|
1077
|
+
self.assertEqual(
|
|
1078
|
+
str(context.exception), f"The value '(1, 3)' for field 'f1' must be in this list {self.choices}."
|
|
1079
|
+
)
|
|
1080
|
+
obj = TestField()
|
|
1081
|
+
with self.assertRaises(ValueError) as context:
|
|
1082
|
+
obj.f1 = (1, 3)
|
|
1083
|
+
self.assertEqual(
|
|
1084
|
+
str(context.exception), f"The value '(1, 3)' for field 'f1' must be in this list {self.choices}."
|
|
1085
|
+
)
|
|
1086
|
+
|
|
1087
|
+
with self.assertRaises(ValueError) as context:
|
|
1088
|
+
obj['f1'] = (1, 3)
|
|
1089
|
+
self.assertEqual(
|
|
1090
|
+
str(context.exception), f"The value '(1, 3)' for field 'f1' must be in this list {self.choices}."
|
|
1091
|
+
)
|
|
1092
|
+
|
|
1093
|
+
def test_min_size_default_value(self) -> None:
|
|
1094
|
+
field = fields.TupleField()
|
|
1095
|
+
self.assertEqual(field.min_size, 0)
|
|
1096
|
+
|
|
1097
|
+
def test_max_size_default_value(self) -> None:
|
|
1098
|
+
field = fields.TupleField()
|
|
1099
|
+
self.assertEqual(field.max_size, maxsize // 8)
|
|
1100
|
+
|
|
1101
|
+
def test_validate_min_size(self) -> None:
|
|
1102
|
+
field = fields.TupleField(min_size=2)
|
|
1103
|
+
with self.assertRaises(ValueError) as context:
|
|
1104
|
+
field.validate((1,))
|
|
1105
|
+
self.assertEqual(str(context.exception), "The size for field 'None' must be greater than or equal to 2.")
|
|
1106
|
+
|
|
1107
|
+
def test_validate_max_size(self) -> None:
|
|
1108
|
+
field = fields.TupleField(max_size=2)
|
|
1109
|
+
with self.assertRaises(ValueError) as context:
|
|
1110
|
+
field.validate((1, 2, 3))
|
|
1111
|
+
self.assertEqual(str(context.exception), "The size for field 'None' must be less than or equal to 2.")
|
|
1112
|
+
|
|
1113
|
+
|
|
1114
|
+
class URLFieldTestCase(TestCase, BaseMappingObjectTestCase):
|
|
1115
|
+
field: fields.URLField = fields.URLField
|
|
1116
|
+
choices: set = {'http://example.com', 'https://example.com'}
|
|
1117
|
+
value_invalid_type: int = 123
|
|
1118
|
+
value_init: str = 'http://example.com'
|
|
1119
|
+
value_update: str = 'https://example.com'
|
|
1120
|
+
|
|
1121
|
+
def test_attr_type(self) -> None:
|
|
1122
|
+
field = fields.URLField()
|
|
1123
|
+
self.assertEqual(field.field_type, str)
|
|
1124
|
+
|
|
1125
|
+
def test_validate_http_url(self) -> None:
|
|
1126
|
+
field = fields.URLField()
|
|
1127
|
+
field.validate('http://exampe.com/path?var=1&var=2')
|
|
1128
|
+
|
|
1129
|
+
def test_validate_https_url(self) -> None:
|
|
1130
|
+
field = fields.URLField()
|
|
1131
|
+
field.validate('https://exampe.com/path?var=1&var=2')
|
|
1132
|
+
|
|
1133
|
+
def test_validate_localhost_url(self) -> None:
|
|
1134
|
+
field = fields.URLField()
|
|
1135
|
+
field.validate('http://localhost:8080/path?var=1&var=2')
|
|
1136
|
+
|
|
1137
|
+
def test_validate_invalid_scheme(self) -> None:
|
|
1138
|
+
field = fields.URLField()
|
|
1139
|
+
with self.assertRaises(ValueError) as context:
|
|
1140
|
+
field.validate('tp://example.com/path?var=1&var=2')
|
|
1141
|
+
self.assertEqual(str(context.exception), 'Key None must be an URL.')
|
|
1142
|
+
|
|
1143
|
+
def test_validate_invalid_domain(self) -> None:
|
|
1144
|
+
field = fields.URLField()
|
|
1145
|
+
with self.assertRaises(ValueError) as context:
|
|
1146
|
+
field.validate('http://example com/path?var=1&var=2')
|
|
1147
|
+
self.assertEqual(str(context.exception), 'Key None must be an URL.')
|
|
1148
|
+
|
|
1149
|
+
def test_validate_invalid_query(self) -> None:
|
|
1150
|
+
field = fields.URLField()
|
|
1151
|
+
with self.assertRaises(ValueError) as context:
|
|
1152
|
+
field.validate('http://localhost:8080/path?var=1 var=2')
|
|
1153
|
+
|
|
1154
|
+
self.assertEqual(str(context.exception), 'Key None must be an URL.')
|