From e34b2f629aceef554a800d3de892aeff173d133a Mon Sep 17 00:00:00 2001 From: Franklin Koch Date: Tue, 11 Sep 2018 23:33:32 -0600 Subject: [PATCH 01/17] Propagate validation errors when instance properties are set from dicts --- properties/base/instance.py | 12 +++++++++--- properties/basic.py | 6 +++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/properties/base/instance.py b/properties/base/instance.py index fc58de9..52bf20d 100644 --- a/properties/base/instance.py +++ b/properties/base/instance.py @@ -7,7 +7,7 @@ import json from warnings import warn -from six import PY2 +from six import PY2, text_type from .base import HasProperties, equal from .. import basic @@ -101,8 +101,14 @@ def validate(self, instance, value): if isinstance(value, dict): return self.instance_class(**value) return self.instance_class(value) - except (ValueError, KeyError, TypeError): - self.error(instance, value) + except (ValueError, KeyError, TypeError) as err: + if hasattr(err, 'error_tuples'): + extra = '({})'.format(' & '.join( + err_tup.message for err_tup in err.error_tuples + )) + else: + extra = '' + self.error(instance, value, extra=extra) def assert_valid(self, instance, value=None): """Checks if valid, including HasProperty instances pass validation""" diff --git a/properties/basic.py b/properties/basic.py index 8fc19d3..31739eb 100644 --- a/properties/basic.py +++ b/properties/basic.py @@ -340,13 +340,13 @@ def error(self, instance, value, error_class=None, extra=''): cls=instance.__class__.__name__, ) message = ( - '{prefix} must be {info}. A value of {val!r} {vtype!r} was ' - 'specified. {extra}'.format( + '{prefix} must be {info}. An invalid value of {val!r} {vtype!r} ' + 'was specified.{extra}'.format( prefix=prefix, info=self.info or 'corrected', val=value, vtype=type(value), - extra=extra, + extra=' {}'.format(extra) if extra else '', ) ) if issubclass(error_class, ValidationError): From 7665f3da60c24ea58b0759bdb411e2108c62fc2a Mon Sep 17 00:00:00 2001 From: Franklin Koch Date: Wed, 28 Nov 2018 01:41:52 -0700 Subject: [PATCH 02/17] Print truncated representation of bad values in error messages --- properties/basic.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/properties/basic.py b/properties/basic.py index 31739eb..8d2388d 100644 --- a/properties/basic.py +++ b/properties/basic.py @@ -339,12 +339,17 @@ def error(self, instance, value, error_class=None, extra=''): prefix = prefix + ' of a {cls} instance'.format( cls=instance.__class__.__name__, ) + print_value = repr(value) + if len(print_value) > 107: + print_value = '{} ... {}'.format( + print_value[:50], print_value[-50:] + ) message = ( - '{prefix} must be {info}. An invalid value of {val!r} {vtype!r} ' - 'was specified.{extra}'.format( + '{prefix} must be {info}. An invalid value of {val} {vtype} was ' + 'specified. {extra}'.format( prefix=prefix, info=self.info or 'corrected', - val=value, + val=print_value, vtype=type(value), extra=' {}'.format(extra) if extra else '', ) From acf37d1dd9c8931861085aad7fc78c71df3c67f3 Mon Sep 17 00:00:00 2001 From: Franklin Koch Date: Wed, 28 Nov 2018 02:24:05 -0700 Subject: [PATCH 03/17] Add extra info to most error messages --- properties/base/containers.py | 24 +++++++++++++------ properties/base/instance.py | 4 ++-- properties/base/union.py | 39 ++++++++++++++++++++----------- properties/basic.py | 44 +++++++++++++++++++++++++---------- properties/extras/web.py | 2 +- properties/math.py | 37 +++++++++++++++++++++-------- 6 files changed, 105 insertions(+), 45 deletions(-) diff --git a/properties/base/containers.py b/properties/base/containers.py index af1aed1..f6b0d44 100644 --- a/properties/base/containers.py +++ b/properties/base/containers.py @@ -265,7 +265,7 @@ def validate(self, instance, value): pointers. """ if not self.coerce and not isinstance(value, self._class_container): - self.error(instance, value) + self.error(instance, value, extra='This is an incorrect type.') if self.coerce and not isinstance(value, CONTAINERS): value = [value] if not isinstance(value, self._class_container): @@ -289,10 +289,16 @@ def assert_valid(self, instance, value=None): value = instance._get(self.name) if value is None: return True - if self.min_length is not None and len(value) < self.min_length: - self.error(instance, value) - if self.max_length is not None and len(value) > self.max_length: - self.error(instance, value) + if ( + (self.min_length is not None and len(value) < self.min_length) + or + (self.max_length is not None and len(value) > self.max_length) + ): + self.error( + instance=instance, + value=value, + extra='(Length is {})'.format(len(value)), + ) for val in value: if not self.prop.assert_valid(instance, val): return False @@ -563,12 +569,16 @@ def info(self): def validate(self, instance, value): if not self.coerce and not isinstance(value, self._class_container): - self.error(instance, value) + self.error(instance, value, extra='This is an incorrect type.') if self.coerce: try: value = self._class_container(value) except TypeError: - self.error(instance, value) + self.error( + instance=instance, + value=value, + extra='Cannot coerce to the correct type', + ) out = value.__class__() for key, val in iteritems(value): if self.key_prop: diff --git a/properties/base/instance.py b/properties/base/instance.py index 52bf20d..0204b5d 100644 --- a/properties/base/instance.py +++ b/properties/base/instance.py @@ -9,7 +9,7 @@ from six import PY2, text_type -from .base import HasProperties, equal +from ..base import GENERIC_ERRORS, HasProperties, equal from .. import basic from .. import utils @@ -101,7 +101,7 @@ def validate(self, instance, value): if isinstance(value, dict): return self.instance_class(**value) return self.instance_class(value) - except (ValueError, KeyError, TypeError) as err: + except GENERIC_ERRORS as err: if hasattr(err, 'error_tuples'): extra = '({})'.format(' & '.join( err_tup.message for err_tup in err.error_tuples diff --git a/properties/base/union.py b/properties/base/union.py index abc1057..d38435f 100644 --- a/properties/base/union.py +++ b/properties/base/union.py @@ -162,12 +162,22 @@ def _unused_default_warning(self): def validate(self, instance, value): """Check if value is a valid type of one of the Union props""" + error_messages = [] for prop in self.props: try: return prop.validate(instance, value) - except GENERIC_ERRORS: - continue - self.error(instance, value) + except GENERIC_ERRORS as err: + if hasattr(err, 'error_tuples'): + error_messages += [ + err_tup.message for err_tup in err.error_tuples + ] + if error_messages: + extra = 'Possible explanation:' + for message in error_messages: + extra += '\n - {}'.format(message) + else: + extra = '' + self.error(instance, value, extra=extra) def assert_valid(self, instance, value=None): """Check if the Union has a valid value""" @@ -178,19 +188,22 @@ def assert_valid(self, instance, value=None): value = instance._get(self.name) if value is None: return True + error_messages = [] for prop in self.props: try: return prop.assert_valid(instance, value) - except GENERIC_ERRORS: - continue - message = ( - 'The "{name}" property of a {cls} instance has not been set ' - 'correctly'.format( - name=self.name, - cls=instance.__class__.__name__ - ) - ) - raise utils.ValidationError(message, 'invalid', self.name, instance) + except GENERIC_ERRORS as err: + if hasattr(err, 'error_tuples'): + error_messages += [ + err_tup.message for err_tup in err.error_tuples + ] + if error_messages: + extra = 'Possible explanation:' + for message in error_messages: + extra += '\n - {}'.format(message) + else: + extra = '' + self.error(instance, value, extra=extra) def serialize(self, value, **kwargs): """Return a serialized value diff --git a/properties/basic.py b/properties/basic.py index 8d2388d..7ea9446 100644 --- a/properties/basic.py +++ b/properties/basic.py @@ -346,7 +346,7 @@ def error(self, instance, value, error_class=None, extra=''): ) message = ( '{prefix} must be {info}. An invalid value of {val} {vtype} was ' - 'specified. {extra}'.format( + 'specified.{extra}'.format( prefix=prefix, info=self.info or 'corrected', val=print_value, @@ -737,7 +737,7 @@ def validate(self, instance, value): try: value = bool(value) except ValueError: - self.error(instance, value) + self.error(instance, value, extra='Cannot cast to boolean.') if not isinstance(value, BOOLEAN_TYPES): self.error(instance, value) return value @@ -770,7 +770,7 @@ def _in_bounds(prop, instance, value): (prop.min is not None and value < prop.min) or (prop.max is not None and value > prop.max) ): - prop.error(instance, value) + prop.error(instance, value, extra='Not within allowed range.') class Integer(Boolean): @@ -816,9 +816,13 @@ def validate(self, instance, value): try: intval = int(value) if not self.cast and abs(value - intval) > TOL: - self.error(instance, value) + self.error( + instance=instance, + value=value, + extra='Not within tolerance range of {}.'.format(TOL), + ) except (TypeError, ValueError): - self.error(instance, value) + self.error(instance, value, extra='Cannot cast to integer.') _in_bounds(self, instance, intval) return intval @@ -866,9 +870,13 @@ def validate(self, instance, value): try: floatval = float(value) if not self.cast and abs(value - floatval) > TOL: - self.error(instance, value) + self.error( + instance=instance, + value=value, + extra='Not within tolerance range of {}.'.format(TOL), + ) except (TypeError, ValueError): - self.error(instance, value) + self.error(instance, value, extra='Cannot cast to float.') _in_bounds(self, instance, floatval) return floatval @@ -912,7 +920,11 @@ def validate(self, instance, value): abs(value.real - compval.real) > TOL or abs(value.imag - compval.imag) > TOL ): - self.error(instance, value) + self.error( + instance=instance, + value=value, + extra='Not within tolerance range of {}.'.format(TOL), + ) except (TypeError, ValueError, AttributeError): self.error(instance, value) return compval @@ -1017,7 +1029,7 @@ def validate(self, instance, value): if not isinstance(value, string_types): self.error(instance, value) if self.regex is not None and self.regex.search(value) is None: #pylint: disable=no-member - self.error(instance, value) + self.error(instance, value, extra='Regex does not match.') value = value.strip(self.strip) if self.change_case == 'upper': value = value.upper() @@ -1158,7 +1170,7 @@ def validate(self, instance, value): #pyli test_val = val if self.case_sensitive else [_.upper() for _ in val] if test_value == test_key or test_value in test_val: return key - self.error(instance, value) + self.error(instance, value, extra='Not an available choice.') class Color(Property): @@ -1231,11 +1243,19 @@ def validate(self, instance, value): if isinstance(value, datetime.datetime): return value if not isinstance(value, string_types): - self.error(instance, value) + self.error( + instance=instance, + value=value, + extra='Cannot convert non-strings to datetime.', + ) try: return self.from_json(value) except ValueError: - self.error(instance, value) + self.error( + instance=instance, + value=value, + extra='Invalid format for converting to datetime.', + ) @staticmethod def to_json(value, **kwargs): diff --git a/properties/extras/web.py b/properties/extras/web.py index 74d8a5a..6c8f40d 100644 --- a/properties/extras/web.py +++ b/properties/extras/web.py @@ -48,7 +48,7 @@ def validate(self, instance, value): value = super(URL, self).validate(instance, value) parsed_url = urlparse(value) if not parsed_url.scheme or not parsed_url.netloc: - self.error(instance, value) + self.error(instance, value, extra='URL needs scheme and netloc.') parse_result = ParseResult( scheme=parsed_url.scheme, netloc=parsed_url.netloc, diff --git a/properties/math.py b/properties/math.py index f378e70..943c55e 100644 --- a/properties/math.py +++ b/properties/math.py @@ -133,7 +133,7 @@ def validate(self, instance, value): 'subclasses of numpy.ndarray' ) if value.dtype.kind not in (TYPE_MAPPINGS[typ] for typ in self.dtype): - self.error(instance, value) + self.error(instance, value, extra='Invalid dtype.') if self.shape is None: return value for shape in self.shape: @@ -144,7 +144,7 @@ def validate(self, instance, value): break else: return value - self.error(instance, value) + self.error(instance, value, extra='Invalid shape.') def equal(self, value_a, value_b): try: @@ -311,7 +311,11 @@ def validate(self, instance, value): """ if isinstance(value, string_types): if value.upper() not in VECTOR_DIRECTIONS: - self.error(instance, value) + self.error( + instance=instance, + value=value, + extra='Invalid vector string representation.', + ) value = VECTOR_DIRECTIONS[value.upper()] return super(Vector3, self).validate(instance, value) @@ -359,7 +363,11 @@ def validate(self, instance, value): value.upper() not in VECTOR_DIRECTIONS or value.upper() in ('Z', '-Z', 'UP', 'DOWN') ): - self.error(instance, value) + self.error( + instance=instance, + value=value, + extra='Invalid vector string representation.', + ) value = VECTOR_DIRECTIONS[value.upper()][:2] return super(Vector2, self).validate(instance, value) @@ -421,7 +429,11 @@ def validate(self, instance, value): for i, val in enumerate(value): if isinstance(val, string_types): if val.upper() not in VECTOR_DIRECTIONS: - self.error(instance, val) + self.error( + instance=instance, + value=value, + extra='Invalid vector string representation.', + ) value[i] = VECTOR_DIRECTIONS[val.upper()] return super(Vector3Array, self).validate(instance, value) @@ -482,11 +494,16 @@ def validate(self, instance, value): self.error(instance, value) if isinstance(value, (tuple, list)): for i, val in enumerate(value): - if ( - isinstance(val, string_types) and - val.upper() in VECTOR_DIRECTIONS and - val.upper() not in ('Z', '-Z', 'UP', 'DOWN') - ): + if isinstance(val, string_types): + if ( + val.upper() not in VECTOR_DIRECTIONS or + val.upper() in ('Z', '-Z', 'UP', 'DOWN') + ): + self.error( + instance=instance, + value=value, + extra='Invalid vector string representation.', + ) value[i] = VECTOR_DIRECTIONS[val.upper()][:2] return super(Vector2Array, self).validate(instance, value) From a9536f38fa81ea4afb40af243fed11b25386cf84 Mon Sep 17 00:00:00 2001 From: Franklin Koch Date: Wed, 28 Nov 2018 02:24:49 -0700 Subject: [PATCH 04/17] Remove unused import --- properties/base/instance.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/properties/base/instance.py b/properties/base/instance.py index 0204b5d..949fea2 100644 --- a/properties/base/instance.py +++ b/properties/base/instance.py @@ -7,7 +7,7 @@ import json from warnings import warn -from six import PY2, text_type +from six import PY2 from ..base import GENERIC_ERRORS, HasProperties, equal from .. import basic From 03b5cc2b6a425ef6189f4e7dd6d6a030fa44fb43 Mon Sep 17 00:00:00 2001 From: Franklin Koch Date: Thu, 29 Nov 2018 21:49:19 -0700 Subject: [PATCH 05/17] Fix strange import paths --- properties/base/instance.py | 2 +- properties/base/union.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/properties/base/instance.py b/properties/base/instance.py index 949fea2..f125bb9 100644 --- a/properties/base/instance.py +++ b/properties/base/instance.py @@ -9,7 +9,7 @@ from six import PY2 -from ..base import GENERIC_ERRORS, HasProperties, equal +from . import GENERIC_ERRORS, HasProperties, equal from .. import basic from .. import utils diff --git a/properties/base/union.py b/properties/base/union.py index d38435f..893173e 100644 --- a/properties/base/union.py +++ b/properties/base/union.py @@ -8,7 +8,7 @@ from six import PY2 -from ..base import GENERIC_ERRORS, HasProperties, Instance +from . import GENERIC_ERRORS, HasProperties, Instance from .. import basic from .. import utils From 076f0e29ec4db0a3e59bac92986db752214c6d66 Mon Sep 17 00:00:00 2001 From: Franklin Koch Date: Thu, 29 Nov 2018 22:04:15 -0700 Subject: [PATCH 06/17] Break out union property error collection to separate method --- properties/base/union.py | 31 ++++++++++++------------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/properties/base/union.py b/properties/base/union.py index 893173e..f4ffbb7 100644 --- a/properties/base/union.py +++ b/properties/base/union.py @@ -160,12 +160,16 @@ def _unused_default_warning(self): warn('Union prop default ignored: {}'.format(prop.default), RuntimeWarning) - def validate(self, instance, value): - """Check if value is a valid type of one of the Union props""" + def _try_prop_method(self, instance, value, method_name): + """Helper method to perform a method on each of the union props + + This method gathers all errors and returns them at the end + if the method on each of the props fails. + """ error_messages = [] for prop in self.props: try: - return prop.validate(instance, value) + return getattr(prop, method_name)(instance, value) except GENERIC_ERRORS as err: if hasattr(err, 'error_tuples'): error_messages += [ @@ -179,6 +183,10 @@ def validate(self, instance, value): extra = '' self.error(instance, value, extra=extra) + def validate(self, instance, value): + """Check if value is a valid type of one of the Union props""" + return self._try_prop_method(instance, value, 'validate') + def assert_valid(self, instance, value=None): """Check if the Union has a valid value""" valid = super(Union, self).assert_valid(instance, value) @@ -188,22 +196,7 @@ def assert_valid(self, instance, value=None): value = instance._get(self.name) if value is None: return True - error_messages = [] - for prop in self.props: - try: - return prop.assert_valid(instance, value) - except GENERIC_ERRORS as err: - if hasattr(err, 'error_tuples'): - error_messages += [ - err_tup.message for err_tup in err.error_tuples - ] - if error_messages: - extra = 'Possible explanation:' - for message in error_messages: - extra += '\n - {}'.format(message) - else: - extra = '' - self.error(instance, value, extra=extra) + return self._try_prop_method(instance, value, 'assert_valid') def serialize(self, value, **kwargs): """Return a serialized value From dd5bf4553cbc24ecc6ec6880522aed5cac1541f1 Mon Sep 17 00:00:00 2001 From: Franklin Koch Date: Thu, 29 Nov 2018 22:04:33 -0700 Subject: [PATCH 07/17] Revert a few redundant 'extra' error messages --- properties/base/containers.py | 4 ++-- properties/math.py | 24 ++++-------------------- 2 files changed, 6 insertions(+), 22 deletions(-) diff --git a/properties/base/containers.py b/properties/base/containers.py index f6b0d44..8db2d97 100644 --- a/properties/base/containers.py +++ b/properties/base/containers.py @@ -265,7 +265,7 @@ def validate(self, instance, value): pointers. """ if not self.coerce and not isinstance(value, self._class_container): - self.error(instance, value, extra='This is an incorrect type.') + self.error(instance, value) if self.coerce and not isinstance(value, CONTAINERS): value = [value] if not isinstance(value, self._class_container): @@ -569,7 +569,7 @@ def info(self): def validate(self, instance, value): if not self.coerce and not isinstance(value, self._class_container): - self.error(instance, value, extra='This is an incorrect type.') + self.error(instance, value) if self.coerce: try: value = self._class_container(value) diff --git a/properties/math.py b/properties/math.py index 943c55e..bda934d 100644 --- a/properties/math.py +++ b/properties/math.py @@ -311,11 +311,7 @@ def validate(self, instance, value): """ if isinstance(value, string_types): if value.upper() not in VECTOR_DIRECTIONS: - self.error( - instance=instance, - value=value, - extra='Invalid vector string representation.', - ) + self.error(instance, value) value = VECTOR_DIRECTIONS[value.upper()] return super(Vector3, self).validate(instance, value) @@ -363,11 +359,7 @@ def validate(self, instance, value): value.upper() not in VECTOR_DIRECTIONS or value.upper() in ('Z', '-Z', 'UP', 'DOWN') ): - self.error( - instance=instance, - value=value, - extra='Invalid vector string representation.', - ) + self.error(instance, value) value = VECTOR_DIRECTIONS[value.upper()][:2] return super(Vector2, self).validate(instance, value) @@ -429,11 +421,7 @@ def validate(self, instance, value): for i, val in enumerate(value): if isinstance(val, string_types): if val.upper() not in VECTOR_DIRECTIONS: - self.error( - instance=instance, - value=value, - extra='Invalid vector string representation.', - ) + self.error(instance, value) value[i] = VECTOR_DIRECTIONS[val.upper()] return super(Vector3Array, self).validate(instance, value) @@ -499,11 +487,7 @@ def validate(self, instance, value): val.upper() not in VECTOR_DIRECTIONS or val.upper() in ('Z', '-Z', 'UP', 'DOWN') ): - self.error( - instance=instance, - value=value, - extra='Invalid vector string representation.', - ) + self.error(instance, value) value[i] = VECTOR_DIRECTIONS[val.upper()][:2] return super(Vector2Array, self).validate(instance, value) From 2f81a37f409ae80b9fefd7c802043cd21fad4acb Mon Sep 17 00:00:00 2001 From: Franklin Koch Date: Thu, 29 Nov 2018 22:28:30 -0700 Subject: [PATCH 08/17] Add test for truncated error messages --- tests/test_errors.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/test_errors.py b/tests/test_errors.py index 0f0808a..60d08db 100644 --- a/tests/test_errors.py +++ b/tests/test_errors.py @@ -265,6 +265,24 @@ class HasSillyProp(properties.HasProperties): with self.assertRaises(SillyError): hsp.a = 'hi' + def test_long_error_truncated(self): + + class HasInt(properties.HasProperties): + + a = properties.Integer('') + + hi = HasInt() + huge_dictionary = { + 'a'*1000: [1]*1000, + 'b'*1000: [2]*1000, + 'c'*1000: [3]*1000, + } + with self.assertRaises(properties.ValidationError) as context: + hi.a = huge_dictionary + + assert len(str(context.exception)) < 1000 + assert ' ... ' in str(context.exception) + if __name__ == '__main__': unittest.main() From 32713b3dfeb88a5b70b0bd511ecfc2e86468b79e Mon Sep 17 00:00:00 2001 From: Franklin Koch Date: Thu, 29 Nov 2018 22:28:54 -0700 Subject: [PATCH 09/17] Tidy up vector array error messages for string representations --- properties/math.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/properties/math.py b/properties/math.py index bda934d..952a1f2 100644 --- a/properties/math.py +++ b/properties/math.py @@ -421,7 +421,11 @@ def validate(self, instance, value): for i, val in enumerate(value): if isinstance(val, string_types): if val.upper() not in VECTOR_DIRECTIONS: - self.error(instance, value) + self.error( + instance=instance, + value=val, + extra='This is an invalid Vector3 representation.', + ) value[i] = VECTOR_DIRECTIONS[val.upper()] return super(Vector3Array, self).validate(instance, value) @@ -487,7 +491,11 @@ def validate(self, instance, value): val.upper() not in VECTOR_DIRECTIONS or val.upper() in ('Z', '-Z', 'UP', 'DOWN') ): - self.error(instance, value) + self.error( + instance=instance, + value=val, + extra='This is an invalid Vector2 representation.', + ) value[i] = VECTOR_DIRECTIONS[val.upper()][:2] return super(Vector2Array, self).validate(instance, value) From 52a53bcfadfb95069608e13decb90af3e8e0f1cc Mon Sep 17 00:00:00 2001 From: Franklin Koch Date: Thu, 29 Nov 2018 22:29:19 -0700 Subject: [PATCH 10/17] A couple more tiny error tests --- tests/test_container.py | 3 +++ tests/test_math.py | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/test_container.py b/tests/test_container.py index f7770e7..d62cbed 100644 --- a/tests/test_container.py +++ b/tests/test_container.py @@ -941,6 +941,9 @@ class HasCoercedDict(properties.HasProperties): hcd.my_coerced_dict = key_val_list assert hcd.my_coerced_dict == {'a': 1, 'b': 2, 'c': 3} + with self.assertRaises(properties.ValidationError): + hcd.my_coerced_dict = 'a' + def test_nested_observed(self): self._test_nested_observed(True) self._test_nested_observed(False) diff --git a/tests/test_math.py b/tests/test_math.py index 3bb7077..0038b7b 100644 --- a/tests/test_math.py +++ b/tests/test_math.py @@ -220,6 +220,8 @@ class HasVec2Arr(properties.HasProperties): assert hv2.vec2.shape == (1, 2) with self.assertRaises(ValueError): hv2.vec2 = 'east' + with self.assertRaises(ValueError): + hv2.vec2 = ['diagonal'] with self.assertRaises(ValueError): hv2.vec2 = [[1., 2., 3.]] @@ -271,7 +273,7 @@ class HasVec3Arr(properties.HasProperties): hv3.vec3 = [1., 2., 3.] assert hv3.vec3.shape == (1, 3) with self.assertRaises(ValueError): - hv3.vec3 = 'diagonal' + hv3.vec3 = 'east' with self.assertRaises(ValueError): hv3.vec3 = ['diagonal'] with self.assertRaises(ValueError): From 76455fd8217c2ee65daf9f3de7d5bab4d2721ea2 Mon Sep 17 00:00:00 2001 From: Franklin Koch Date: Thu, 29 Nov 2018 22:29:50 -0700 Subject: [PATCH 11/17] Add ValueError to possible exceptions on dictionary coercion --- properties/base/containers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/properties/base/containers.py b/properties/base/containers.py index 8db2d97..edb8fbf 100644 --- a/properties/base/containers.py +++ b/properties/base/containers.py @@ -573,7 +573,7 @@ def validate(self, instance, value): if self.coerce: try: value = self._class_container(value) - except TypeError: + except (TypeError, ValueError): self.error( instance=instance, value=value, From 292b2723efa26d1b94fb1166e2cb3fdba236d8c2 Mon Sep 17 00:00:00 2001 From: Franklin Koch Date: Thu, 29 Nov 2018 22:51:45 -0700 Subject: [PATCH 12/17] =?UTF-8?q?Bump=20version:=200.5.4=20=E2=86=92=200.5?= =?UTF-8?q?.5b0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .bumpversion.cfg | 2 +- docs/conf.py | 4 ++-- properties/__init__.py | 2 +- setup.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index f93cf08..4a796bb 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,4 +1,4 @@ [bumpversion] -current_version = 0.5.4 +current_version = 0.5.5b0 files = properties/__init__.py setup.py docs/conf.py diff --git a/docs/conf.py b/docs/conf.py index 52d1fc4..24200d6 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -64,9 +64,9 @@ # built documents. # # The short X.Y version. -version = u'0.5.4' +version = u'0.5.5b0' # The full version, including alpha/beta/rc tags. -release = u'0.5.4' +release = u'0.5.5b0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/properties/__init__.py b/properties/__init__.py index ae98e8f..324e55a 100644 --- a/properties/__init__.py +++ b/properties/__init__.py @@ -89,7 +89,7 @@ class Profile(properties.HasProperties): ValidationError, ) -__version__ = '0.5.4' +__version__ = '0.5.5b0' __author__ = 'Seequent' __license__ = 'MIT' __copyright__ = 'Copyright 2018 Seequent' diff --git a/setup.py b/setup.py index 397f847..45ef4c0 100644 --- a/setup.py +++ b/setup.py @@ -29,7 +29,7 @@ EXTRAS.update({'full': sum(EXTRAS.values(), [])}) setup( name='properties', - version='0.5.4', + version='0.5.5b0', packages=find_packages(exclude=('tests',)), install_requires=['six>=1.7.3'], extras_require=EXTRAS, From 5286ee4fa71cf3ce81d4f38de6f7620360678931 Mon Sep 17 00:00:00 2001 From: Franklin Koch Date: Thu, 29 Nov 2018 23:17:48 -0700 Subject: [PATCH 13/17] Fix potentially cyclic import --- properties/base/instance.py | 2 +- properties/base/union.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/properties/base/instance.py b/properties/base/instance.py index f125bb9..2b622fc 100644 --- a/properties/base/instance.py +++ b/properties/base/instance.py @@ -9,7 +9,7 @@ from six import PY2 -from . import GENERIC_ERRORS, HasProperties, equal +from .base import GENERIC_ERRORS, HasProperties, equal from .. import basic from .. import utils diff --git a/properties/base/union.py b/properties/base/union.py index f4ffbb7..1c800c3 100644 --- a/properties/base/union.py +++ b/properties/base/union.py @@ -8,7 +8,8 @@ from six import PY2 -from . import GENERIC_ERRORS, HasProperties, Instance +from .base import GENERIC_ERRORS, HasProperties +from .instance import Instance from .. import basic from .. import utils From ad343ce23f472b25e375a713d91b1f6d3f404df6 Mon Sep 17 00:00:00 2001 From: Franklin Koch Date: Tue, 8 Jan 2019 08:50:47 -0700 Subject: [PATCH 14/17] Do not use functools.wrap for ser/deser functions --- properties/basic.py | 1 - 1 file changed, 1 deletion(-) diff --git a/properties/basic.py b/properties/basic.py index 8fc19d3..3576792 100644 --- a/properties/basic.py +++ b/properties/basic.py @@ -45,7 +45,6 @@ def accept_kwargs(func): functions always receive kwargs from serialize, but by using this, the original functions may simply take a single value. """ - @wraps(func) def wrapped(val, **kwargs): """Perform a function on a value, ignoring kwargs if necessary""" try: From 4c91459dce81e91765ea45f795693a9a21b2f2da Mon Sep 17 00:00:00 2001 From: Franklin Koch Date: Tue, 8 Jan 2019 08:58:49 -0700 Subject: [PATCH 15/17] Add deserializer test that would fail previously --- properties/basic.py | 1 - tests/test_serialization.py | 15 +++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/properties/basic.py b/properties/basic.py index 3576792..4043270 100644 --- a/properties/basic.py +++ b/properties/basic.py @@ -6,7 +6,6 @@ import collections import datetime -from functools import wraps import math import random import re diff --git a/tests/test_serialization.py b/tests/test_serialization.py index 2c1486f..61facb6 100644 --- a/tests/test_serialization.py +++ b/tests/test_serialization.py @@ -348,5 +348,20 @@ def c(self): assert properties.equal(dm1, dm3) + def test_instance_deserializer(self): + + class DeserializeClass(object): + + def __call__(self, value): + print('deserializing') + + class HasDeserializer(properties.HasProperties): + + my_int = properties.Integer( + 'Int with deserializer', + deserializer=DeserializeClass(), + ) + + if __name__ == '__main__': unittest.main() From f3e75a4e5ef37e56e0f2254d0994dfc662aa33a5 Mon Sep 17 00:00:00 2001 From: Franklin Koch Date: Tue, 15 Jan 2019 17:18:23 -0700 Subject: [PATCH 16/17] Update tests for pypng updates --- tests/test_images.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_images.py b/tests/test_images.py index 3ebb9bc..d2229c6 100644 --- a/tests/test_images.py +++ b/tests/test_images.py @@ -21,6 +21,7 @@ def test_png(self): '101011010100', '110010110101', '100010010011'] + s = [[int(v) for v in val] for val in s] f = open(png_file, 'wb') w = png.Writer(len(s[0]), len(s), greyscale=True, bitdepth=16) w.write(f, s) From fe28b972342200992c2734d231c5e13123e04918 Mon Sep 17 00:00:00 2001 From: Franklin Koch Date: Tue, 15 Jan 2019 18:20:40 -0700 Subject: [PATCH 17/17] =?UTF-8?q?Bump=20version:=200.5.5b0=20=E2=86=92=200?= =?UTF-8?q?.5.5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .bumpversion.cfg | 2 +- docs/conf.py | 4 ++-- properties/__init__.py | 2 +- setup.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 4a796bb..27e2bbd 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,4 +1,4 @@ [bumpversion] -current_version = 0.5.5b0 +current_version = 0.5.5 files = properties/__init__.py setup.py docs/conf.py diff --git a/docs/conf.py b/docs/conf.py index 24200d6..5811fcf 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -64,9 +64,9 @@ # built documents. # # The short X.Y version. -version = u'0.5.5b0' +version = u'0.5.5' # The full version, including alpha/beta/rc tags. -release = u'0.5.5b0' +release = u'0.5.5' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/properties/__init__.py b/properties/__init__.py index 324e55a..8640351 100644 --- a/properties/__init__.py +++ b/properties/__init__.py @@ -89,7 +89,7 @@ class Profile(properties.HasProperties): ValidationError, ) -__version__ = '0.5.5b0' +__version__ = '0.5.5' __author__ = 'Seequent' __license__ = 'MIT' __copyright__ = 'Copyright 2018 Seequent' diff --git a/setup.py b/setup.py index 45ef4c0..4405e25 100644 --- a/setup.py +++ b/setup.py @@ -29,7 +29,7 @@ EXTRAS.update({'full': sum(EXTRAS.values(), [])}) setup( name='properties', - version='0.5.5b0', + version='0.5.5', packages=find_packages(exclude=('tests',)), install_requires=['six>=1.7.3'], extras_require=EXTRAS,