Combine clean() and validate()
parent
cf0b56c04f
commit
635a3ec389
|
@ -39,7 +39,7 @@ class _STIXBase(collections.Mapping):
|
||||||
|
|
||||||
if prop_name in kwargs:
|
if prop_name in kwargs:
|
||||||
try:
|
try:
|
||||||
kwargs[prop_name] = prop.validate(kwargs[prop_name])
|
kwargs[prop_name] = prop.clean(kwargs[prop_name])
|
||||||
except ValueError as exc:
|
except ValueError as exc:
|
||||||
msg = "Invalid value for {0} '{1}': {2}"
|
msg = "Invalid value for {0} '{1}': {2}"
|
||||||
raise ValueError(msg.format(self.__class__.__name__,
|
raise ValueError(msg.format(self.__class__.__name__,
|
||||||
|
|
|
@ -22,7 +22,7 @@ class Property(object):
|
||||||
you to copy *all* values from an existing object to a new object), but
|
you to copy *all* values from an existing object to a new object), but
|
||||||
if the user provides a value other than the `fixed` value, it will raise
|
if the user provides a value other than the `fixed` value, it will raise
|
||||||
an error. This is semantically equivalent to defining both:
|
an error. This is semantically equivalent to defining both:
|
||||||
- a `validate()` function that checks if the value matches the fixed
|
- a `clean()` function that checks if the value matches the fixed
|
||||||
value, and
|
value, and
|
||||||
- a `default()` function that returns the fixed value.
|
- a `default()` function that returns the fixed value.
|
||||||
(Default: `None`)
|
(Default: `None`)
|
||||||
|
@ -30,15 +30,10 @@ class Property(object):
|
||||||
Subclasses can also define the following functions.
|
Subclasses can also define the following functions.
|
||||||
|
|
||||||
- `def clean(self, value) -> any:`
|
- `def clean(self, value) -> any:`
|
||||||
- Transform `value` into a valid value for this property. This should
|
- Return a value that is valid for this property. If `value` is not
|
||||||
raise a ValueError if such no such transformation is possible.
|
valid for this property, this will attempt to transform it first. If
|
||||||
- `def validate(self, value) -> any:`
|
`value` is not valid and no such transformation is possible, it should
|
||||||
- check that `value` is valid for this property. This should return
|
raise a ValueError.
|
||||||
a valid value (possibly modified) for this property, or raise a
|
|
||||||
ValueError if the value is not valid.
|
|
||||||
(Default: if `clean` is defined, it will attempt to call `clean` and
|
|
||||||
return the result or pass on a ValueError that `clean` raises. If
|
|
||||||
`clean` is not defined, this will return `value` unmodified).
|
|
||||||
- `def default(self):`
|
- `def default(self):`
|
||||||
- provide a default value for this property.
|
- provide a default value for this property.
|
||||||
- `default()` can return the special value `NOW` to use the current
|
- `default()` can return the special value `NOW` to use the current
|
||||||
|
@ -46,36 +41,27 @@ class Property(object):
|
||||||
to use the same default value, so calling now() for each field--
|
to use the same default value, so calling now() for each field--
|
||||||
likely several microseconds apart-- does not work.
|
likely several microseconds apart-- does not work.
|
||||||
|
|
||||||
Subclasses can instead provide lambda functions for `clean`, and `default`
|
Subclasses can instead provide a lambda function for `default as a keyword
|
||||||
as keyword arguments. `validate` should not be provided as a lambda since
|
argument. `clean` should not be provided as a lambda since lambdas cannot
|
||||||
lambdas cannot raise their own exceptions.
|
raise their own exceptions.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def _default_validate(self, value):
|
def _default_clean(self, value):
|
||||||
if value != self._fixed_value:
|
if value != self._fixed_value:
|
||||||
raise ValueError("must equal '{0}'.".format(self._fixed_value))
|
raise ValueError("must equal '{0}'.".format(self._fixed_value))
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def __init__(self, required=False, fixed=None, clean=None, default=None, type=None):
|
def __init__(self, required=False, fixed=None, default=None, type=None):
|
||||||
self.required = required
|
self.required = required
|
||||||
self.type = type
|
self.type = type
|
||||||
if fixed:
|
if fixed:
|
||||||
self._fixed_value = fixed
|
self._fixed_value = fixed
|
||||||
self.validate = self._default_validate
|
self.clean = self._default_clean
|
||||||
self.default = lambda: fixed
|
self.default = lambda: fixed
|
||||||
if clean:
|
|
||||||
self.clean = clean
|
|
||||||
if default:
|
if default:
|
||||||
self.default = default
|
self.default = default
|
||||||
|
|
||||||
def clean(self, value):
|
def clean(self, value):
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def validate(self, value):
|
|
||||||
try:
|
|
||||||
value = self.clean(value)
|
|
||||||
except NotImplementedError:
|
|
||||||
pass
|
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def __call__(self, value=None):
|
def __call__(self, value=None):
|
||||||
|
@ -95,14 +81,14 @@ class ListProperty(Property):
|
||||||
self.contained = bool
|
self.contained = bool
|
||||||
elif inspect.isclass(contained) and issubclass(contained, Property):
|
elif inspect.isclass(contained) and issubclass(contained, Property):
|
||||||
# If it's a class and not an instance, instantiate it so that
|
# If it's a class and not an instance, instantiate it so that
|
||||||
# validate() can be called on it, and ListProperty.validate() will
|
# clean() can be called on it, and ListProperty.clean() will
|
||||||
# use __call__ when it appends the item.
|
# use __call__ when it appends the item.
|
||||||
self.contained = contained()
|
self.contained = contained()
|
||||||
else:
|
else:
|
||||||
self.contained = contained
|
self.contained = contained
|
||||||
super(ListProperty, self).__init__(**kwargs)
|
super(ListProperty, self).__init__(**kwargs)
|
||||||
|
|
||||||
def validate(self, value):
|
def clean(self, value):
|
||||||
try:
|
try:
|
||||||
iter(value)
|
iter(value)
|
||||||
except TypeError:
|
except TypeError:
|
||||||
|
@ -111,11 +97,11 @@ class ListProperty(Property):
|
||||||
result = []
|
result = []
|
||||||
for item in value:
|
for item in value:
|
||||||
try:
|
try:
|
||||||
valid = self.contained.validate(item)
|
valid = self.contained.clean(item)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise
|
raise
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
# type of list has no validate() function (eg. built in Python types)
|
# type of list has no clean() function (eg. built in Python types)
|
||||||
# TODO Should we raise an error here?
|
# TODO Should we raise an error here?
|
||||||
valid = item
|
valid = item
|
||||||
|
|
||||||
|
@ -140,13 +126,6 @@ class StringProperty(Property):
|
||||||
def clean(self, value):
|
def clean(self, value):
|
||||||
return self.string_type(value)
|
return self.string_type(value)
|
||||||
|
|
||||||
def validate(self, value):
|
|
||||||
try:
|
|
||||||
val = self.clean(value)
|
|
||||||
except ValueError:
|
|
||||||
raise
|
|
||||||
return val
|
|
||||||
|
|
||||||
|
|
||||||
class TypeProperty(Property):
|
class TypeProperty(Property):
|
||||||
def __init__(self, type):
|
def __init__(self, type):
|
||||||
|
@ -159,7 +138,7 @@ class IDProperty(Property):
|
||||||
self.required_prefix = type + "--"
|
self.required_prefix = type + "--"
|
||||||
super(IDProperty, self).__init__()
|
super(IDProperty, self).__init__()
|
||||||
|
|
||||||
def validate(self, value):
|
def clean(self, value):
|
||||||
if not value.startswith(self.required_prefix):
|
if not value.startswith(self.required_prefix):
|
||||||
raise ValueError("must start with '{0}'.".format(self.required_prefix))
|
raise ValueError("must start with '{0}'.".format(self.required_prefix))
|
||||||
try:
|
try:
|
||||||
|
@ -191,18 +170,12 @@ class BooleanProperty(Property):
|
||||||
if value == 0:
|
if value == 0:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
raise ValueError("not a coercible boolean value.")
|
raise ValueError("must be a boolean value.")
|
||||||
|
|
||||||
def validate(self, value):
|
|
||||||
try:
|
|
||||||
return self.clean(value)
|
|
||||||
except ValueError:
|
|
||||||
raise ValueError("must be a boolean value.")
|
|
||||||
|
|
||||||
|
|
||||||
class TimestampProperty(Property):
|
class TimestampProperty(Property):
|
||||||
|
|
||||||
def validate(self, value):
|
def clean(self, value):
|
||||||
if isinstance(value, dt.date):
|
if isinstance(value, dt.date):
|
||||||
if hasattr(value, 'hour'):
|
if hasattr(value, 'hour'):
|
||||||
return value
|
return value
|
||||||
|
@ -236,7 +209,7 @@ class ReferenceProperty(Property):
|
||||||
self.type = type
|
self.type = type
|
||||||
super(ReferenceProperty, self).__init__(required, type=type)
|
super(ReferenceProperty, self).__init__(required, type=type)
|
||||||
|
|
||||||
def validate(self, value):
|
def clean(self, value):
|
||||||
if isinstance(value, _STIXBase):
|
if isinstance(value, _STIXBase):
|
||||||
value = value.id
|
value = value.id
|
||||||
if self.type:
|
if self.type:
|
||||||
|
@ -255,7 +228,7 @@ class SelectorProperty(Property):
|
||||||
# ignore type
|
# ignore type
|
||||||
super(SelectorProperty, self).__init__()
|
super(SelectorProperty, self).__init__()
|
||||||
|
|
||||||
def validate(self, value):
|
def clean(self, value):
|
||||||
if not SELECTOR_REGEX.match(value):
|
if not SELECTOR_REGEX.match(value):
|
||||||
raise ValueError("values must adhere to selector syntax")
|
raise ValueError("values must adhere to selector syntax")
|
||||||
return value
|
return value
|
||||||
|
|
|
@ -12,10 +12,10 @@ def test_property():
|
||||||
assert p.required is False
|
assert p.required is False
|
||||||
|
|
||||||
|
|
||||||
def test_basic_validate():
|
def test_basic_clean():
|
||||||
class Prop(Property):
|
class Prop(Property):
|
||||||
|
|
||||||
def validate(self, value):
|
def clean(self, value):
|
||||||
if value == 42:
|
if value == 42:
|
||||||
return value
|
return value
|
||||||
else:
|
else:
|
||||||
|
@ -23,9 +23,9 @@ def test_basic_validate():
|
||||||
|
|
||||||
p = Prop()
|
p = Prop()
|
||||||
|
|
||||||
assert p.validate(42) == 42
|
assert p.clean(42) == 42
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
p.validate(41)
|
p.clean(41)
|
||||||
|
|
||||||
|
|
||||||
def test_default_field():
|
def test_default_field():
|
||||||
|
@ -42,53 +42,53 @@ def test_default_field():
|
||||||
def test_fixed_property():
|
def test_fixed_property():
|
||||||
p = Property(fixed="2.0")
|
p = Property(fixed="2.0")
|
||||||
|
|
||||||
assert p.validate("2.0")
|
assert p.clean("2.0")
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
assert p.validate("x") is False
|
assert p.clean("x") is False
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
assert p.validate(2.0) is False
|
assert p.clean(2.0) is False
|
||||||
|
|
||||||
assert p.default() == "2.0"
|
assert p.default() == "2.0"
|
||||||
assert p.validate(p.default())
|
assert p.clean(p.default())
|
||||||
|
|
||||||
|
|
||||||
def test_list_property():
|
def test_list_property():
|
||||||
p = ListProperty(StringProperty)
|
p = ListProperty(StringProperty)
|
||||||
|
|
||||||
assert p.validate(['abc', 'xyz'])
|
assert p.clean(['abc', 'xyz'])
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
p.validate([])
|
p.clean([])
|
||||||
|
|
||||||
|
|
||||||
def test_string_property():
|
def test_string_property():
|
||||||
prop = StringProperty()
|
prop = StringProperty()
|
||||||
|
|
||||||
assert prop.validate('foobar')
|
assert prop.clean('foobar')
|
||||||
assert prop.validate(1)
|
assert prop.clean(1)
|
||||||
assert prop.validate([1, 2, 3])
|
assert prop.clean([1, 2, 3])
|
||||||
|
|
||||||
|
|
||||||
def test_type_property():
|
def test_type_property():
|
||||||
prop = TypeProperty('my-type')
|
prop = TypeProperty('my-type')
|
||||||
|
|
||||||
assert prop.validate('my-type')
|
assert prop.clean('my-type')
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
prop.validate('not-my-type')
|
prop.clean('not-my-type')
|
||||||
assert prop.validate(prop.default())
|
assert prop.clean(prop.default())
|
||||||
|
|
||||||
|
|
||||||
def test_id_property():
|
def test_id_property():
|
||||||
idprop = IDProperty('my-type')
|
idprop = IDProperty('my-type')
|
||||||
|
|
||||||
assert idprop.validate('my-type--90aaca8a-1110-5d32-956d-ac2f34a1bd8c')
|
assert idprop.clean('my-type--90aaca8a-1110-5d32-956d-ac2f34a1bd8c')
|
||||||
with pytest.raises(ValueError) as excinfo:
|
with pytest.raises(ValueError) as excinfo:
|
||||||
idprop.validate('not-my-type--90aaca8a-1110-5d32-956d-ac2f34a1bd8c')
|
idprop.clean('not-my-type--90aaca8a-1110-5d32-956d-ac2f34a1bd8c')
|
||||||
assert str(excinfo.value) == "must start with 'my-type--'."
|
assert str(excinfo.value) == "must start with 'my-type--'."
|
||||||
with pytest.raises(ValueError) as excinfo:
|
with pytest.raises(ValueError) as excinfo:
|
||||||
idprop.validate('my-type--foo')
|
idprop.clean('my-type--foo')
|
||||||
assert str(excinfo.value) == "must have a valid version 4 UUID after the prefix."
|
assert str(excinfo.value) == "must have a valid version 4 UUID after the prefix."
|
||||||
|
|
||||||
assert idprop.validate(idprop.default())
|
assert idprop.clean(idprop.default())
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("value", [
|
@pytest.mark.parametrize("value", [
|
||||||
|
@ -110,7 +110,7 @@ def test_id_property():
|
||||||
def test_boolean_property_valid(value):
|
def test_boolean_property_valid(value):
|
||||||
bool_prop = BooleanProperty()
|
bool_prop = BooleanProperty()
|
||||||
|
|
||||||
assert bool_prop.validate(value) is not None
|
assert bool_prop.clean(value) is not None
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("value", [
|
@pytest.mark.parametrize("value", [
|
||||||
|
@ -123,15 +123,15 @@ def test_boolean_property_valid(value):
|
||||||
def test_boolean_property_invalid(value):
|
def test_boolean_property_invalid(value):
|
||||||
bool_prop = BooleanProperty()
|
bool_prop = BooleanProperty()
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
bool_prop.validate(value)
|
bool_prop.clean(value)
|
||||||
|
|
||||||
|
|
||||||
def test_reference_property():
|
def test_reference_property():
|
||||||
ref_prop = ReferenceProperty()
|
ref_prop = ReferenceProperty()
|
||||||
|
|
||||||
assert ref_prop.validate("my-type--3a331bfe-0566-55e1-a4a0-9a2cd355a300")
|
assert ref_prop.clean("my-type--3a331bfe-0566-55e1-a4a0-9a2cd355a300")
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
ref_prop.validate("foo")
|
ref_prop.clean("foo")
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("value", [
|
@pytest.mark.parametrize("value", [
|
||||||
|
@ -141,12 +141,12 @@ def test_reference_property():
|
||||||
])
|
])
|
||||||
def test_timestamp_property_valid(value):
|
def test_timestamp_property_valid(value):
|
||||||
ts_prop = TimestampProperty()
|
ts_prop = TimestampProperty()
|
||||||
assert ts_prop.validate(value) == FAKE_TIME
|
assert ts_prop.clean(value) == FAKE_TIME
|
||||||
|
|
||||||
|
|
||||||
def test_timestamp_property_invalid():
|
def test_timestamp_property_invalid():
|
||||||
ts_prop = TimestampProperty()
|
ts_prop = TimestampProperty()
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
ts_prop.validate(1)
|
ts_prop.clean(1)
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
ts_prop.validate("someday sometime")
|
ts_prop.clean("someday sometime")
|
||||||
|
|
Loading…
Reference in New Issue