Custom Types: Value Substitution
This article is a sequel to the previous article and focuses on implementing value substitution for custom types.
Step 1. Enhance Definitions
First, enhance the NumericProps
class by adding a value
property. This property will hold the value of the numeric type.
from niltype import Nilable
from d42.custom_type import Props
class NumericProps(Props):
@property
def value(self) -> Nilable[str]:
return self.get("value")
Then, implement the __call__
method in NumericSchema
. This method enables declaring a schema with a value, like schema_numeric("42")
.
from typing import Self
from d42.custom_type import CustomSchema, Props
class NumericSchema(CustomSchema[NumericProps]):
def __call__(self, value: str) -> Self:
return self.__class__(self.props.update(value=value))
Step 2. Refine Type Representation
Modify the __represent__
method in NumericSchema
to include the numeric value in its representation.
from niltype import Nil
from d42.custom_type.visitors import Representor
class NumericSchema(CustomSchema[NumericProps]):
...
def __represent__(self, visitor: Representor, indent: int = 0) -> str:
if self.props.value is not Nil:
return f"{visitor.name}.numeric({self.props.value!r})"
return f"{visitor.name}.numeric"
print(schema_numeric("1234"))
# Output:
# schema.numeric('1234')
Step 3. Fine-Tune Type Generation
Adjust the __generate__
method in NumericSchema
to account for a predefined value. If a value is already specified, it returns this value instead of generating a new one.
from random import randint
from niltype import Nil
from d42.custom_type.visitors import Generator
class NumericSchema(CustomSchema[NumericProps]):
...
def __generate__(self, visitor: Generator) -> str:
if self.props.value is not Nil:
return self.props.value
number = randint(0, 2147483647) # int32 max
return str(number)
from d42 import fake
sch = schema_numeric("1234")
print(fake(sch))
# Output:
# 1234
Step 4. Implement Value Validation
Enhance the __validate__
method to include an additional check. If a specific value is set in the schema's properties, it validates that the incoming value matches this set value.
from typing import Any
from niltype import Nil
from d42.custom_type.visitors import Validator
from d42.custom_type import PathHolder, ValidationResult
from d42.custom_type.errors import TypeValidationError, ValueValidationError
class NumericSchema(CustomSchema[NumericProps]):
def __validate__(self, visitor: Validator, value: Any, path: PathHolder) -> ValidationResult:
result = visitor.make_validation_result()
if not (isinstance(value, str) and all(char in "1234567890" for char in value)):
result.add_error(TypeValidationError(path, value, "numeric str"))
if (self.props.value is not Nil) and (value != self.props.value):
result.add_error(ValueValidationError(path, value, self.props.value))
return result
from d42 import validate_or_fail
sch = schema_numeric("1234")
print(validate_or_fail(sch, "1234"))
# Output:
# True
print(validate_or_fail(sch, "5678"))
# Output:
# d42.ValidationException:
# - Value <class 'str'> must be equal to '1234', but '5678' given
Step 5. Add Value Substitution
The __substitute__
method is used for value substitution in custom types. It first validates the incoming value and, if valid, updates the properties with this new value.
Unlike other features such as representation, generation, and validation, which can be implemented independently and as needed, value substitution based on validation. So first need to validate incoming value and then update props.
from typing import Any, Self
from d42.custom_type.visitors import Substitutor
from d42.custom_type.utils import make_substitution_error
class NumericSchema(CustomSchema[NumericProps]):
...
def __substitute__(self, visitor: Substitutor, value: Any) -> Self:
result = visitor.validator.visit(self, value=value)
if result.has_errors():
raise make_substitution_error(result, visitor.formatter)
return self.__class__(self.props.update(value=value))
print(schema_numeric % "1234")
# Output
# schema.numeric("1234")
print(schema_numeric("1234") % "5678")
# Output:
# d42.substitution.errors.SubstitutionError:
# - Value <class 'str'> must be equal to '1234', but '5678' given
For the complete code snippet of this implementation, please refer to this page