A simple Python module for performing model validations
Project description
pymodelio
A simple Python module for performing model validations
How to use the module
Declaring the models
@pymodelio_model
class Component:
__serial_no: Attribute[str](default_factory=lambda: uuid.uuid4().__str__())
@pymodelio_model
class CPU(Component):
frequency: Attribute[int](validator=IntValidator())
cores: Attribute[int](validator=IntValidator())
@staticmethod
def from_dict(data: dict, auto_validate: bool = True) -> 'CPU':
return CPU(
frequency=data.get('frequency'),
cores=data.get('cores'),
auto_validate=auto_validate
)
@pymodelio_model
class RAM(Component):
frequency: Attribute[int](validator=IntValidator())
size: Attribute[int](validator=IntValidator())
@staticmethod
def from_dict(data: dict, auto_validate: bool = True) -> 'RAM':
return RAM(
frequency=data.get('frequency'),
size=data.get('size'),
auto_validate=auto_validate
)
@pymodelio_model
class Disk(Component):
size: Attribute[int](validator=IntValidator())
@staticmethod
def from_dict(data: dict, auto_validate: bool = True) -> 'Disk':
return Disk(
size=data.get('size'),
auto_validate=auto_validate
)
@pymodelio_model
class Computer(Component):
_cpu: Attribute[CPU](validator=Validator(expected_type=CPU))
_rams: Attribute[List[RAM]](validator=ListValidator(elements_type=RAM))
_disks: Attribute[List[Disk]](validator=ListValidator(elements_type=Disk))
@property
def serial_no(self) -> str:
return self.__serial_no
@property
def cpu(self) -> CPU:
return self._cpu
@property
def rams(self) -> List[RAM]:
return self._rams
@property
def disks(self) -> List[Disk]:
return self._disks
@staticmethod
def from_dict(data: dict, auto_validate: bool = True) -> 'Computer':
return Computer(
serial_no=data.get('serial_no') if 'serial_no' in data else UNDEFINED,
cpu=CPU.from_dict(data.get('cpu'), auto_validate=False),
rams=[RAM.from_dict(x, auto_validate=False) for x in data.get('rams')],
disks=[Disk.from_dict(x, auto_validate=False) for x in data.get('disks')],
auto_validate=auto_validate
)
Using this specific models
computer = Computer.from_dict({
'serial_no': '123e4567-e89b-12d3-a456-426614174000',
'cpu': {
'frequency': 3500,
'cores': 8
},
'rams': [
{
'frequency': 1600,
'size': 8
},
{
'frequency': 1800,
'size': 16
}
],
'disks': [
{
'size': 1024
},
{
'size': 512
}
]
})
Customizing the models initialization workflow
@pymodelio_model
class Model:
model_attr: Attribute[str]
@classmethod
def __before_init__(cls, *args, **kwargs) -> None:
# This method is called before everything when the model constructor is called
# It receives the same parameters the constructor gets
pass
@classmethod
def __before_validate__(cls) -> None:
# This method is called after initializing the model attributes but just before
# performing the model validations (it will be executed even if
# auto_validate = False)
pass
@classmethod
def __once_validated__(cls) -> None:
# This method is called just after performing the model validations initializing
# the model attributes but before performing the model validations (it will be
# executed even if auto_validate = False)
pass
Not initable attributes
@pymodelio_model
class Model:
non_initable_model_attr: Attribute[str](initable=False, default_factory=lambda: 'Non initable default value')
# WARNING: This will raise a NameError('non_initable_model_attr attribute is not
# initable for class Model')
Model(non_initable_model_attr='custom value')
Considerations
When a class attribute has the annotation Attribute[<type>], it will be transformed into an instance attribute during the model initialization.
When defining a protected or private model attribute with underscore or double underscore respectively, if that property can be set by the model constructor, it's value will be obtained from an attribute with the same name but without underscores. For instance:
@pymodelio_model
class Component:
__serial_no: Attribute[str]
_model_name: Attribute[str]
@property
def serial_no(self) -> str:
return self.__serial_no
@property
def model_name(self) -> str:
return self._model_name
component = Component(serial_no='123e4567-e89b-12d3-a456-426614174000', model_name='ABC123')
print(component.serial_no) # It will print '123e4567-e89b-12d3-a456-426614174000'
print(component.model_name) # It will print 'ABC123'
Available validators
Validator
A generic validator for any type passed by parameter. It is also capable of validating other models. Validated value must implement validate method in order to be considered a model by this validator.
Other validators inherit from this one.
Validator(expected_type: Union[type, List[type]] = None, nullable: bool = False, message: str = None)
StringValidator
StringValidator(min_len: int = 0, max_len: int = math.inf, fixed_len: int = None, regex: str = None, nullable: bool = False, message: str = None)
NumericValidator
NumericValidator(expected_type: type, min_value: Number = -math.inf, max_value: Number = math.inf, nullable: bool = False, message: str = None)
IntValidator
A subclass of NumericValidator specific for integers.
IntValidator(min_value: Number = -math.inf, max_value: Number = math.inf, nullable: bool = False, message: str = None)
FloatValidator
A subclass of NumericValidator specific for float numbers.
FloatValidator(min_value: Number = -math.inf, max_value: Number = math.inf, nullable: bool = False, message: str = None)
DatetimeValidator
DatetimeValidator(nullable: bool = False, message: str = None)
DictValidator
DictValidator(nullable: bool = False, message: str = None)
ListValidator
A validator for list of any type that allows nested models. Validated list elements must implement validate method in order to be considered a model by this validator.
ListValidator(elements_type: Union[type, List[type]], nullable: bool = False, message: str = None)
Let's compare the same code using raw python against using pymodelio
Using raw python
class RawPythonChildModel:
def __int__(self, public_child_attr: int) -> None:
self.public_child_attr = public_child_attr
self.validate()
def validate(self) -> None:
assert isinstance(self.public_child_attr, int), 'public_child_attr is not a valid int'
class RawPythonModel:
_PUBLIC_ATTR_MIN_VALUE = 0
_PUBLIC_ATTR_MAX_VALUE = 0
_PROTECTED_ATTR_FIXED_LENGTH = 5
_PROTECTED_ATTR_REGEX = '^[A-Z]+$' # Only capitalized chars
def __init__(self, public_attr: int, protected_attr: str, private_attr: datetime,
child_model_attr: RawPythonChildModel, children_model_attr: List[RawPythonChildModel],
optional_attr: dict = None) -> None:
self.public_attr = public_attr
self._protected_attr = protected_attr
self.__private_attr = private_attr
self.child_model_attr = child_model_attr
self.children_model_attr = children_model_attr
self.optional_attr = {} if optional_attr is None else optional_attr
self.non_initable_attr = []
self.validate()
def validate(self) -> None:
assert isinstance(self.public_attr, int), 'public_child_attr is not a valid int'
assert self.public_attr >= self._PUBLIC_ATTR_MIN_VALUE, \
f'public_child_attr is lower than {self._PUBLIC_ATTR_MIN_VALUE}'
assert self.public_attr <= self._PUBLIC_ATTR_MAX_VALUE, \
f'public_child_attr is greater than {self._PUBLIC_ATTR_MAX_VALUE}'
assert isinstance(self._protected_attr, str), '_protected_attr is not a valid str'
assert len(self._protected_attr) == self._PROTECTED_ATTR_FIXED_LENGTH, \
f'_protected_attr length is different than {self._PROTECTED_ATTR_FIXED_LENGTH}'
assert re.compile(self._PROTECTED_ATTR_REGEX).match(self._protected_attr) is not None, \
'_protected_attr does not match configured regex'
assert isinstance(self.child_model_attr, RawPythonChildModel), \
'child_model_attr is not a valid RawPythonChildModel'
self.child_model_attr.validate()
assert isinstance(self.children_model_attr, list), 'children_model_attr is not a valid list'
for child_model in self.children_model_attr:
child_model.validate()
assert isinstance(self.__private_attr, datetime), '__private_attr is not a valid datetime'
assert isinstance(self.optional_attr, dict), 'optional_attr is not a valid dict'
Using pymodelio
pymodelio model validation errors also give more information about the full path of nested structures. In case of lists, including the index of the list element where the error occurred.
@pymodelio_model
class PymodelioChildModel:
public_child_attr: Attribute[int](validator=IntValidator())
@pymodelio_model
class PymodelioModel:
public_attr: Attribute[int](validator=IntValidator(min_value=0, max_value=10))
_protected_attr: Attribute[str](validator=StringValidator(fixed_len=5, regex='^[A-Z]+$')) # Only capitalized chars
__private_attr: Attribute[datetime](validator=DatetimeValidator())
child_model_attr: Attribute[PymodelioChildModel](validator=Validator(expected_type=PymodelioChildModel))
children_model_attr: Attribute[List[PymodelioChildModel]](
validator=ListValidator(elements_type=PymodelioChildModel))
optional_attr: Attribute[dict](validator=DictValidator())
non_initable_attr: Attribute[List[str]](initable=False, default_factory=list)
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distributions
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pymodelio-0.0.3-py3-none-any.whl.
File metadata
- Download URL: pymodelio-0.0.3-py3-none-any.whl
- Upload date:
- Size: 12.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/4.0.1 CPython/3.10.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2da5621343458f3de6d29914014dec3cfd4b10260b51851286e4267274aca18c
|
|
| MD5 |
b08dd5d4ad264a73965a05911627191f
|
|
| BLAKE2b-256 |
8155d66273ec1c98c97e81d0265835ee2c00dd80dc4b30dbb4081cae418387a2
|