111 lines
3.7 KiB
Python
111 lines
3.7 KiB
Python
|
import uuid
|
||
|
|
||
|
|
||
|
class Property(object):
|
||
|
"""Represent a property of STIX data type.
|
||
|
|
||
|
Subclasses can define the following attributes as keyword arguments to
|
||
|
__init__():
|
||
|
|
||
|
- `required` - If `True`, the property must be provided when creating an
|
||
|
object with that property. No default value exists for these properties.
|
||
|
(Default: `False`)
|
||
|
- `fixed` - This provides a constant default value. Users are free to
|
||
|
provide this value explicity when constructing an object (which allows
|
||
|
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
|
||
|
an error. This is semantically equivalent to defining both:
|
||
|
- a `validate()` function that checks if the value matches the fixed
|
||
|
value, and
|
||
|
- a `default()` function that returns the fixed value.
|
||
|
(Default: `None`)
|
||
|
|
||
|
Subclasses can also define the following functions.
|
||
|
|
||
|
- `def clean(self, value) -> any:`
|
||
|
- Transform `value` into a valid value for this property. This should
|
||
|
raise a ValueError if such no such transformation is possible.
|
||
|
- `def validate(self, value) -> any:`
|
||
|
- check that `value` is valid for this property. This should return
|
||
|
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):`
|
||
|
- provide a default value for this property.
|
||
|
- `default()` can return the special value `NOW` to use the current
|
||
|
time. This is useful when several timestamps in the same object need
|
||
|
to use the same default value, so calling now() for each field--
|
||
|
likely several microseconds apart-- does not work.
|
||
|
|
||
|
Subclasses can instead provide lambda functions for `clean`, and `default`
|
||
|
as keyword arguments. `validate` should not be provided as a lambda since
|
||
|
lambdas cannot raise their own exceptions.
|
||
|
"""
|
||
|
|
||
|
def __init__(self, required=False, fixed=None, clean=None, validate=None, default=None):
|
||
|
self.required = required
|
||
|
if fixed:
|
||
|
self.validate = lambda x: x == fixed
|
||
|
self.default = lambda: fixed
|
||
|
if clean:
|
||
|
self.clean = clean
|
||
|
if validate:
|
||
|
self.validate = validate
|
||
|
if default:
|
||
|
self.default = default
|
||
|
|
||
|
def clean(self, value):
|
||
|
raise NotImplementedError
|
||
|
|
||
|
def validate(self, value):
|
||
|
try:
|
||
|
value = self.clean(value)
|
||
|
except NotImplemetedError:
|
||
|
pass
|
||
|
return value
|
||
|
|
||
|
|
||
|
class TypeProperty(Property):
|
||
|
|
||
|
def __init__(self, type):
|
||
|
self.type = type
|
||
|
|
||
|
def validate(self, value):
|
||
|
if value != self.type:
|
||
|
raise ValueError("Invalid type value")
|
||
|
|
||
|
def default(self):
|
||
|
return self.type
|
||
|
|
||
|
|
||
|
class List(Property):
|
||
|
|
||
|
def __init__(self, contained):
|
||
|
"""
|
||
|
contained should be a type whose constructor creates an object from the value
|
||
|
"""
|
||
|
self.contained = contained
|
||
|
|
||
|
def validate(self, value):
|
||
|
# TODO: ensure iterable
|
||
|
for item in value:
|
||
|
self.contained.validate(item)
|
||
|
|
||
|
def clean(self, value):
|
||
|
return [self.contained(x) for x in value]
|
||
|
|
||
|
|
||
|
class IDProperty(Property):
|
||
|
|
||
|
def __init__(self, type):
|
||
|
self.type = type
|
||
|
|
||
|
def validate(self, value):
|
||
|
# TODO: validate GUID as well
|
||
|
return value.startswith(self.type + "--")
|
||
|
|
||
|
def default(self):
|
||
|
return self.type + "--" + str(uuid.uuid4())
|