Coverage for pyfields/tests/test_core.py: 91%
442 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-11-06 16:35 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-11-06 16:35 +0000
1# Authors: Sylvain Marie <sylvain.marie@se.com>
2#
3# Copyright (c) Schneider Electric Industries, 2019. All right reserved.
5# Authors: Sylvain Marie <sylvain.marie@se.com>
6#
7import pickle
8import sys
9from collections import OrderedDict
11import pytest
13from valid8 import ValidationError, ValidationFailure
14from valid8.base import InvalidValue
15from valid8.validation_lib import non_empty, Empty
17from pyfields import field, MandatoryFieldInitError, UnsupportedOnNativeFieldError, \
18 copy_value, copy_field, Converter, Field, ConversionError, ReadOnlyFieldError, FieldTypeError, make_init
21@pytest.mark.parametrize('write_before_reading', [False, True], ids="write_before_reading={}".format)
22@pytest.mark.parametrize('type_', ['default_factory', 'default', 'mandatory'], ids="type_={}".format)
23@pytest.mark.parametrize('read_only', [False, True], ids="read_only={}".format)
24def test_field(write_before_reading, type_, read_only):
25 """Checks that field works as expected"""
27 if type_ == 'default_factory':
28 class Tweety(object):
29 afraid = field(default_factory=lambda obj: False, read_only=read_only)
30 elif type_ == 'default':
31 class Tweety(object):
32 afraid = field(default=False, read_only=read_only)
33 elif type_ == 'mandatory': 33 ↛ 37line 33 didn't jump to line 37, because the condition on line 33 was never false
34 class Tweety(object):
35 afraid = field(read_only=read_only)
36 else:
37 raise ValueError()
39 # instantiate
40 t = Tweety()
42 written = False
44 # (1) write
45 if write_before_reading:
46 t.afraid = True
47 written = True
49 # (2) read
50 if not write_before_reading and type_ == 'mandatory':
51 # mandatory value not already overridden
52 with pytest.raises(MandatoryFieldInitError):
53 print(t.afraid)
54 else:
55 # either default value (False) or already-written value (True)
56 assert t.afraid is write_before_reading
57 written = True # because reading a non-mandatory field sets it to default
59 # (3) write (possibly again) and check
60 if not (read_only and written):
61 t.afraid = True
62 assert t.afraid is True
63 if not read_only:
64 t.afraid = False
65 assert t.afraid is False
66 written = True
68 # if read only, check exception on second write
69 if read_only:
70 # make sure that now the value has been
71 assert written
73 with pytest.raises(ReadOnlyFieldError) as exc_info:
74 t.afraid = False
75 qualname = Tweety.afraid.qualname
76 assert str(exc_info.value) == "Read-only field '%s' has already been " \
77 "initialized on instance %s and cannot be modified anymore." % (qualname, t)
80def test_slots():
81 """tests that fields are replaced with descriptor fields automatically when used on a class with `__slots__`"""
82 class WithSlots(object):
83 __slots__ = ('_a',)
84 a = field()
86 if sys.version_info < (3, 0): 86 ↛ 88line 86 didn't jump to line 88, because the condition on line 86 was never true
87 # qualname does not exist, we use str(cls)
88 a_fixed_name = "pyfields.tests.test_core.WithSlots.a"
89 else:
90 a_fixed_name = "test_slots.<locals>.WithSlots.a"
92 a_unknown_name = "<unknown_cls>.None"
94 if sys.version_info >= (3, 6): 94 ↛ 99line 94 didn't jump to line 99, because the condition on line 94 was never false
95 # change is done immediately
96 assert repr(WithSlots.__dict__['a']) == "<DescriptorField: %s>" % a_fixed_name
97 else:
98 # change will be done after first access
99 assert repr(WithSlots.__dict__['a']) == "<NativeField: %s>" % a_unknown_name
101 w = WithSlots()
103 if sys.version_info < (3, 6): 103 ↛ 105line 103 didn't jump to line 105, because the condition on line 103 was never true
104 # Really not ideal you have to do something
105 try:
106 w.a
107 except:
108 pass
110 w.a = 1
111 assert w.a == 1
113 assert repr(WithSlots.a) == "<DescriptorField: %s>" % a_fixed_name
116def test_slots2():
117 class WithSlots(object):
118 __slots__ = ('__dict__',)
119 a = field()
121 if sys.version_info >= (3, 6): 121 ↛ 124line 121 didn't jump to line 124, because the condition on line 121 was never false
122 a_name = "test_slots2.<locals>.WithSlots.a"
123 else:
124 a_name = "<unknown_cls>.None"
126 assert repr(WithSlots.__dict__['a']) == "<NativeField: %s>" % a_name
129def test_default_factory():
130 """"""
131 class Foo(object):
132 a = field(default_factory=copy_value([]))
133 b = field(default_factory=copy_field('z'))
134 c = field()
136 @c.default_factory
137 def c_default(self):
138 return self.a + ['yes']
140 z = field()
142 f = Foo()
143 g = Foo()
144 assert f.a == []
145 assert g.a == []
146 g.a.append(1)
147 assert g.a == [1]
148 assert f.a == []
149 # we can not initialize b since it requires a copy of uninitialized z
150 with pytest.raises(MandatoryFieldInitError) as exc_info:
151 print(f.b)
152 assert str(exc_info.value).startswith("Mandatory field 'z' has not been initialized yet")
153 # if we initialize z we can now safely make a copy
154 f.z = 12
155 assert f.b == 12
156 f.z += 1
157 assert f.z == 13
158 assert f.b == 12
160 assert g.c == [1, 'yes']
163def test_type():
164 """ Tests that when `type_hint` is provided and `validate_type` is explicitly set, it works as expected """
166 class Foo(object):
167 f = field(type_hint=str, check_type=True)
169 o = Foo()
170 o.f = 'hello'
171 with pytest.raises(FieldTypeError) as exc_info:
172 o.f = 1
174 if sys.version_info < (3, 0): 174 ↛ 175line 174 didn't jump to line 175, because the condition on line 174 was never true
175 qualname = 'pyfields.tests.test_core.Foo.f'
176 else:
177 qualname = 'test_type.<locals>.Foo.f'
178 assert str(exc_info.value) == "Invalid value type provided for '%s'. " \
179 "Value should be of type %s. " \
180 "Instead, received a 'int': 1" % (qualname, str)
183def test_type_multiple_tuple():
184 """ Tests that when `type_hint` is provided and `validate_type` is explicitly set, it works as expected """
186 class Foo(object):
187 f = field(type_hint=(str, int), check_type=True)
189 o = Foo()
190 o.f = 'hello'
191 o.f = 1
192 with pytest.raises(FieldTypeError) as exc_info:
193 o.f = 1.1
195 # msg = Value type should be one of ('str', 'int')
196 msg = "Value type should be one of (%s, %s)" % (str, int)
197 if sys.version_info < (3, 0): 197 ↛ 198line 197 didn't jump to line 198, because the condition on line 197 was never true
198 qualname = 'pyfields.tests.test_core.Foo.f'
199 else:
200 qualname = 'test_type_multiple_tuple.<locals>.Foo.f'
201 assert str(exc_info.value) == "Invalid value type provided for '%s'. " \
202 "%s. " \
203 "Instead, received a 'float': 1.1" % (qualname, msg)
206try:
207 from typing import Optional
208 typing_present = True
209except ImportError:
210 typing_present = False
213@pytest.mark.skipif(not typing_present, reason="typing module is not present")
214def test_type_multiple_typing():
215 """ Tests that when `type_hint` is provided and `validate_type` is explicitly set, it works as expected """
217 from typing import Union
219 class Foo(object):
220 f = field(type_hint=Union[int, str], check_type=True)
222 o = Foo()
223 o.f = 'hello'
224 o.f = 1
225 with pytest.raises(FieldTypeError) as exc_info:
226 o.f = 1.1
228 if sys.version_info < (3, 0): 228 ↛ 229line 228 didn't jump to line 229, because the condition on line 228 was never true
229 qualname = 'pyfields.tests.test_core.Foo.f'
230 else:
231 qualname = 'test_type_multiple_typing.<locals>.Foo.f'
232 assert str(exc_info.value) == "Invalid value type provided for '%s'. " \
233 "Value should be of type typing.Union[int, str]. " \
234 "Instead, received a 'float': 1.1" % qualname
237@pytest.mark.skipif(sys.version_info < (3, 6), reason="class member annotations are not allowed before python 3.6")
238def test_type_from_pep484_annotations():
239 # import the class to use
240 from ._test_py36 import _test_class_annotations
241 Foo = _test_class_annotations()
243 # create an instance
244 foo = Foo()
246 # test that the field that is non-native has type checking active
247 foo.field_with_validate_type = 2
248 with pytest.raises(TypeError) as exc_info:
249 foo.field_with_validate_type = 'hello'
250 assert str(exc_info.value).startswith("Invalid value type provided for ")
252 # by default the type is not checked
253 foo.field_with_defaults = 'hello'
256@pytest.mark.parametrize("case_nb", [1, 2, 3, 4, 5], ids="case_nb={}".format)
257def test_field_validators(case_nb):
258 """ tests that `validators` functionality works correctly with several flavours of definition."""
260 # class EmptyError(ValidationError):
261 # help_msg = "h should be non empty"
263 class EmptyFailure(ValidationFailure, ValueError):
264 """ Custom ValidationFailure raised by non_empty """
265 help_msg = 'len(x) > 0 does not hold for x={wrong_value}'
267 class Foo2(object):
268 # one single validator
269 f = field(default="hey", type_hint=str, validators=non_empty)
271 # one single validator in a list
272 g = field(type_hint=str, validators=[non_empty])
274 # one single validator accepting three arguments (obj, field, val)
275 gg = field(type_hint=str, validators=lambda obj, field, val: obj.f in val)
277 # several validators in a dict. keys and values can contain elements of definition in any order
278 h = field(type_hint=str, validators=OrderedDict([("h should be non empty", (non_empty, EmptyFailure)),
279 ("h should contain field f", (lambda obj, val: obj.f in val)),
280 ("h should contain 'a'", (lambda val: 'a' in val))]))
282 if sys.version_info < (3, 0): 282 ↛ 284line 282 didn't jump to line 284, because the condition on line 282 was never true
283 # qualname does not exist, we use str(cls)
284 c_name = "pyfields.tests.test_core.Foo2"
285 else:
286 c_name = "test_field_validators.<locals>.Foo2"
288 # the object that we'll use
289 o = Foo2()
291 if case_nb == 1:
292 o.f = 'hey'
293 with pytest.raises(ValidationError) as exc_info:
294 o.f = ''
295 str(exc_info.value)
296 assert isinstance(exc_info.value.failure, Empty)
297 assert str(exc_info.value) == "Error validating [%s.f='']. " \
298 "Empty: len(x) > 0 does not hold for x=. Wrong value: ''." % c_name
300 elif case_nb == 2:
301 o.g = 'hey'
302 with pytest.raises(ValidationError) as exc_info:
303 o.g = ''
304 str(exc_info.value)
305 assert isinstance(exc_info.value.failure, Empty)
306 assert str(exc_info.value) == "Error validating [%s.g='']. " \
307 "Empty: len(x) > 0 does not hold for x=. Wrong value: ''." % c_name
309 elif case_nb == 3:
310 o.gg = 'heyho'
311 with pytest.raises(ValidationError) as exc_info:
312 o.gg = 'ho' # does not contain field f ('hey')
313 str(exc_info.value)
314 assert isinstance(exc_info.value.failure, InvalidValue)
315 assert exc_info.value.failure.validation_outcome is False
316 assert str(exc_info.value) == "Error validating [%s.gg=ho]. " \
317 "InvalidValue: Function [<lambda>] returned [False] for value 'ho'." % c_name
319 elif case_nb in (4, 5): 319 ↛ exitline 319 didn't return from function 'test_field_validators', because the condition on line 319 was never false
320 if case_nb == 4:
321 # override the definition for Foo2.h
322 # several validators in a list. Tuples should start with the function
323 Foo2.h = field(name='h', type_hint=str, validators=[(non_empty, "h should be non empty", EmptyFailure),
324 non_empty,
325 (lambda obj, val: obj.f in val, "h should contain field f"),
326 (lambda val: 'a' in val, "h should contain 'a'"),
327 (non_empty, EmptyFailure),
328 ])
330 # o.h should be a non-empty string containing 'a' and containing o.f
331 with pytest.raises(ValidationError) as exc_info:
332 o.h = '' # empty
333 str(exc_info.value)
334 assert isinstance(exc_info.value.failure.__cause__, EmptyFailure)
335 assert str(exc_info.value.failure.__cause__) == "h should be non empty. " \
336 "Function [non_empty] raised " \
337 "Empty: len(x) > 0 does not hold for x=. Wrong value: ''."
339 with pytest.raises(ValidationError) as exc_info:
340 o.h = 'hey' # does not contain 'a'
341 assert isinstance(exc_info.value.failure.__cause__, InvalidValue)
342 assert exc_info.value.failure.__cause__.validation_outcome is False
343 assert str(exc_info.value.failure.__cause__) == "h should contain 'a'. " \
344 "Function [<lambda>] returned [False] for value 'hey'."
346 with pytest.raises(ValidationError) as exc_info:
347 o.h = 'a' # does not contain field f ('hey')
348 assert isinstance(exc_info.value.failure.__cause__, InvalidValue)
349 assert exc_info.value.failure.__cause__.validation_outcome is False
350 assert str(exc_info.value.failure.__cause__) == "h should contain field f. " \
351 "Function [<lambda>] returned [False] for value 'a'."
352 o.h = 'hey ya'
355@pytest.mark.parametrize("explicit", [False, True], ids="explicit={}".format)
356def test_field_validators_decorator(explicit):
357 """Tests that the @<field>.decorator works correctly"""
359 if explicit:
360 native = False
361 else:
362 native = None
363 if sys.version_info < (3, 6): 363 ↛ 364line 363 didn't jump to line 364, because the condition on line 363 was never true
364 with pytest.raises(UnsupportedOnNativeFieldError):
365 class Foo(object):
366 f = field(native=native)
367 @f.validator
368 def validate_f(self, val):
369 return val % 3 == 0
370 return
372 class Foo(object):
373 f = field(native=native)
375 @f.validator
376 def f_should_be_a_multiple_of_3(self, f_val):
377 return f_val % 3 == 0
379 @f.validator(help_msg="not a large enough value")
380 def f_should_be_larger_than_g(self, f_val):
381 return f_val > self.g
383 f_field = Foo.f
384 assert len(f_field.root_validator.base_validation_funcs) == 2
385 foo = Foo()
386 foo.g = 0
387 with pytest.raises(ValidationError) as exc_info:
388 foo.f = 2
389 # assert str(exc_info.value) == "Error validating [%s=2]. " \
390 # "InvalidValue: Function [f_should_be_a_multiple_of_3] returned [False] for value 2." \
391 # % Foo.f.qualname
392 assert str(exc_info.value) == "Error validating [%s=2]. At least one validation function failed for value 2. " \
393 "Successes: ['f_should_be_larger_than_g'] / " \
394 "Failures: {'f_should_be_a_multiple_of_3': 'Returned False.'}." \
395 % Foo.f.qualname
396 foo.f = 3
397 foo.g = 3
398 with pytest.raises(ValidationError) as exc_info:
399 foo.f = 3
400 assert str(exc_info.value) == "Error validating [%s=3]. At least one validation function failed for value 3. " \
401 "Successes: ['f_should_be_a_multiple_of_3'] / " \
402 "Failures: {'f_should_be_larger_than_g': " \
403 "'InvalidValue: not a large enough value. Returned False.'}." \
404 % Foo.f.qualname
407def test_validator_not_compliant_with_native_field():
408 """tests that `native=True` can not be set when a validator is provided"""
409 with pytest.raises(UnsupportedOnNativeFieldError):
410 class Foo(object):
411 f = field(validators=lambda x: True, native=True) 411 ↛ exitline 411 didn't run the lambda on line 411
414@pytest.mark.parametrize("explicit", [False, True], ids="explicit={}".format)
415def test_field_converters_decorator(explicit):
416 """Tests that the @<field>.converter works correctly"""
418 if explicit:
419 native = False
420 else:
421 native = None
422 if sys.version_info < (3, 6): 422 ↛ 423line 422 didn't jump to line 423, because the condition on line 422 was never true
423 with pytest.raises(UnsupportedOnNativeFieldError):
424 class Foo(object):
425 f = field(native=native)
426 @f.converter
427 def validate_f(self, val):
428 return val % 3 == 0
429 return
431 class Foo(object):
432 f = field(native=native)
434 @f.converter(accepts=str)
435 def f_from_str(self, f_val):
436 # make sure the filter has worked
437 assert isinstance(f_val, str)
438 return int(f_val)
440 @f.converter
441 def f_from_anything(self, f_val):
442 if isinstance(f_val, int):
443 # of course we would not do that in real life but this is a test that exceptions are supported
444 raise Exception("no need to convert! already an int")
445 return int(f_val) + 1
447 f_field = Foo.f
448 assert len(f_field.converters) == 2
449 foo = Foo()
450 foo.f = 0 # uses no converter at all
451 assert foo.f == 0
452 foo.f = '2' # uses the first converter
453 assert foo.f == 2
454 foo.f = 2.1 # uses the second converter
455 assert foo.f == 3
458def test_converter_not_compliant_with_native_field():
459 """tests that `native=True` can not be set when a validator is provided"""
460 with pytest.raises(UnsupportedOnNativeFieldError):
461 class Foo(object):
462 f = field(converters=lambda x: x, native=True) 462 ↛ exitline 462 didn't run the lambda on line 462
465@pytest.mark.parametrize("validator_return_none", [False, True], ids="validator_return_none={}".format)
466@pytest.mark.parametrize("nbargs", [1, 2, 3], ids="nbargs={}".format)
467@pytest.mark.parametrize("format", ['single_converter', 'single_fun',
468 '(v_fun, c_fun)', '(v_type, c_fun)', '(joker, c_fun)', '(None, c_fun)',
469 '{v_fun: c_fun}', '{v_type: c_fun}', '{joker: c_fun}', '{None: c_fun}'],
470 ids="format={}".format)
471def test_converters(format, nbargs, validator_return_none):
472 """Various tests about converters definition format"""
474 from mini_lambda import x
476 if nbargs == 1:
477 def parse_nb(x):
478 return int(x)
480 def valid_str(x):
481 if validator_return_none:
482 return None if isinstance(x, str) else False
483 return isinstance(x, str)
484 elif nbargs == 2:
485 def parse_nb(obj, x):
486 assert obj.__class__.__name__ == 'Foo'
487 return int(x)
489 def valid_str(obj, x):
490 assert obj.__class__.__name__ == 'Foo'
491 if validator_return_none:
492 return None if isinstance(x, str) else False
493 return isinstance(x, str)
494 elif nbargs == 3: 494 ↛ 507line 494 didn't jump to line 507, because the condition on line 494 was never false
495 def parse_nb(obj, field, x):
496 assert obj.__class__.__name__ == 'Foo'
497 assert isinstance(field, Field)
498 return int(x)
500 def valid_str(obj, field, x):
501 assert obj.__class__.__name__ == 'Foo'
502 assert isinstance(field, Field)
503 if validator_return_none:
504 return None if isinstance(x, str) else False
505 return isinstance(x, str)
506 else:
507 raise ValueError(nbargs)
509 if format == 'single_converter':
510 class ParseNb(Converter):
511 def convert(self, obj, field, x):
512 if nbargs == 1:
513 return parse_nb(x)
514 elif nbargs == 2:
515 return parse_nb(obj, x)
516 elif nbargs == 3: 516 ↛ exitline 516 didn't return from function 'convert', because the condition on line 516 was never false
517 return parse_nb(obj, field, x)
518 convs = ParseNb()
519 accepts_int = True
520 c_name = "ParseNb"
522 elif format == 'single_fun':
523 convs = parse_nb
524 accepts_int = True
525 c_name = 'parse_nb'
527 elif format == '(v_fun, c_fun)':
528 convs = (valid_str, parse_nb)
529 accepts_int = False
530 c_error_details = "Acceptance test: REJECTED (returned False)"
531 c_name = 'parse_nb'
533 elif format == '(v_type, c_fun)':
534 convs = (str, parse_nb)
535 accepts_int = False
536 c_error_details = "Acceptance test: ERROR [HasWrongType] Value should be an instance of %s. " \
537 "Wrong value: 1." % str
538 c_name = 'parse_nb'
540 elif format == '(joker, c_fun)':
541 convs = ('*', parse_nb)
542 accepts_int = True
543 c_name = 'parse_nb'
545 elif format == '(None, c_fun)':
546 convs = (None, parse_nb)
547 accepts_int = True
548 c_name = 'parse_nb'
550 elif format == '{v_fun: c_fun}':
551 convs = {valid_str: parse_nb}
552 accepts_int = False
553 c_error_details = "Acceptance test: REJECTED (returned False)"
554 c_name = 'parse_nb'
556 elif format == '{v_type: c_fun}':
557 convs = {str: parse_nb}
558 accepts_int = False
559 c_error_details = "Acceptance test: ERROR [HasWrongType] Value should be an instance of %s. " \
560 "Wrong value: 1." % str
561 c_name = 'parse_nb'
563 elif format == '{joker: c_fun}':
564 convs = {'*': parse_nb}
565 accepts_int = True
566 c_name = 'parse_nb'
568 elif format == '{None: c_fun}': 568 ↛ 574line 568 didn't jump to line 574, because the condition on line 568 was never false
569 convs = {None: parse_nb}
570 accepts_int = True
571 c_name = 'parse_nb'
573 else:
574 raise ValueError(format)
576 class Foo(object):
577 f = field(converters=convs, validators=[x % 3 == 0])
579 o = Foo()
580 f_field = Foo.f
581 f_converters = f_field.converters
582 assert len(f_converters) == 1 and isinstance(f_converters[0], Converter)
583 o.f = 3
584 o.f = '6'
585 assert o.f == 6
586 with pytest.raises(ValueError) as exc_info:
587 o.f = '5'
588 if sys.version_info < (3, 0): 588 ↛ 589line 588 didn't jump to line 589, because the condition on line 588 was never true
589 qualname = "pyfields.tests.test_core.Foo.f" # qualname does not exist, we use str(cls)
590 else:
591 qualname = "test_converters.<locals>.Foo.f"
592 assert str(exc_info.value) == "Error validating [%s=5]. " \
593 "InvalidValue: Function [x %% 3 == 0] returned [False] for value 5." % qualname
595 if accepts_int:
596 if nbargs == 1:
597 converted_value, details = f_field.trace_convert(1)
598 else:
599 # we have to provide the object, as it is used in our converter
600 converted_value, details = f_field.trace_convert(1, obj=o)
602 assert converted_value == 1
603 assert str(details) == """Value 1 successfully converted to 1 using converter '%s', after the following attempts:
604 - Converter '%s': Acceptance test: SUCCESS (returned None). Conversion: SUCCESS -> 1
605""" % (c_name, c_name)
606 else:
607 with pytest.raises(ConversionError) as exc_info:
608 if nbargs == 1:
609 converted_value, details = f_field.trace_convert(1)
610 else:
611 # we have to provide the object, as it is used in our converter
612 converted_value, details = f_field.trace_convert(1, obj=o)
613 assert str(exc_info.value) == """Unable to convert value 1. Results:
614 - Converter '%s': %s
615""" % (c_name, c_error_details)
618def test_inheritance():
619 """Makes sure that fields from parent classes are automatically fixed on old python versions.
620 See https://github.com/smarie/python-pyfields/issues/41
621 """
623 class A(object):
624 a = field(default='hello')
626 class B(A):
627 pass
629 b = B()
630 assert b.a == 'hello' # first access should have fixed the field name
632 # make sure that for all python versions (especially 2 and 3.5) the name is now ok.
633 assert A.__dict__['a'].name == 'a'
636class Foo(object):
637 a = field(type_hint=int, default=0, check_type=True)
638 b = field(type_hint=int, validators={'is positive': lambda x: x > 0})
639 c = field(default_factory=copy_field(a))
640 __init__ = make_init()
643def test_pickle():
644 """ Tests that pickle actually works """
646 f = Foo(b=1)
647 serialized = pickle.dumps(f)
648 g = pickle.loads(serialized)
649 assert vars(g) == vars(f)
652@pytest.mark.parametrize("check_type", [False, True], ids="check_type={}".format)
653@pytest.mark.parametrize("default_flavor", ["simple", "copy_value", "factory_function"], ids="use_factory={}".format)
654def test_default_validated(default_flavor, check_type):
655 """ Tests that the default value of a DescriptorField is validated """
657 # --- How the default value is created
658 def make_def_kwargs():
659 if default_flavor == "simple":
660 def_kwargs = dict(default=0)
661 elif default_flavor == "copy_value":
662 def_kwargs = dict(default_factory=copy_value(0))
663 elif default_flavor == "factory_function": 663 ↛ 669line 663 didn't jump to line 669, because the condition on line 663 was never false
664 def custom_factory(obj):
665 # important note: this could be something dependent
666 return 0
667 def_kwargs = dict(default_factory=custom_factory)
668 else:
669 raise ValueError(default_flavor)
670 return def_kwargs
672 def_kwargs = make_def_kwargs()
674 # --- validation can be a type check or a validator
675 if check_type:
676 validator = None
677 else:
678 # a validator that validates the same thing than the type hint
679 def validator(x):
680 return isinstance(x, str)
682 # nominal: the converter is used correctly on the default value so this is ok
683 class Foo(object):
684 bar = field(type_hint=str, check_type=check_type, validators=validator, converters=str, **def_kwargs)
686 # default value check
687 bar_field = Foo.__dict__['bar']
688 if default_flavor == "simple":
689 assert bar_field.default == 0
690 assert bar_field._default_is_safe is False
691 elif default_flavor == "copy_value":
692 assert bar_field.default.get_copied_value() == 0
693 assert bar_field._default_is_safe is False
694 elif default_flavor == "factory_function": 694 ↛ 698line 694 didn't jump to line 698, because the condition on line 694 was never false
695 assert bar_field._default_is_safe is None
697 # instance creation and default value access
698 f = Foo()
699 assert f.bar == '0'
701 # we can check that the default value is modified
702 if default_flavor == "simple":
703 assert bar_field.default == '0'
704 assert bar_field._default_is_safe is True
705 elif default_flavor == "copy_value":
706 assert bar_field.default.get_copied_value() == '0'
707 assert bar_field._default_is_safe is True
708 elif default_flavor == "factory_function": 708 ↛ 712line 708 didn't jump to line 712, because the condition on line 708 was never false
709 assert bar_field._default_is_safe is None
711 # make sure it works fine several times :)
712 del f.bar
713 assert f.bar == '0'
714 g = Foo()
715 assert g.bar == '0'
717 # no converter: does not work, the default value is not valid
718 def_kwargs = make_def_kwargs()
719 class Foo(object):
720 bar = field(type_hint=str, check_type=check_type, validators=validator, **def_kwargs)
722 f = Foo()
723 with pytest.raises(FieldTypeError if check_type else ValidationError):
724 f.bar