Custom Types: Foundations
Working with domain-specific data often requires extending beyond standard types. The d42 package provides an easy way to define custom types.
Consider an application that needs handle numeric strings, such as "1234"
. A unique type is required to ensure that these strings contain only numeric characters.
Step 1: Define the Basic Custom Type
First, define the NumericSchema
type:
from d42.custom_type import CustomSchema, Props
class NumericSchema(CustomSchema[Props]):
pass
Here, the generic parameter PropsType
for CustomSchema
holds the properties for custom types. Our numeric type doesn't need special properties, so we'll use the default Props
.
Next, proceed to register the custom type:
from d42.custom_type import register_type
schema_numeric = register_type("numeric", NumericSchema)
Now, the custom type is ready for use:
print(schema_numeric)
# Output:
# <NumericSchema>
This is a solid start, but it remains quite basic.
Step 2: Enhance the Custom Type
The d42 package provides options for enriching a custom type with additional functionalities.
A. Improve the Representation
The special __represent__
method is called when repr(schema)
is used, providing a custom string representation of the schema.
from d42.custom_type.visitors import Representor
class NumericSchema(CustomSchema[Props]):
def __represent__(self, visitor: Representor, indent: int = 0) -> str:
return "schema.numeric"
Now, it'll look like this:
print(schema_numeric)
# Output:
# schema.numeric
B. Add Random Generation
The special __generate__
method is invoked for creating random instances of the custom type.
from d42.custom_type.visitors import Generator
from random import randint
class NumericSchema(CustomSchema[Props]):
...
def __generate__(self, visitor: Generator) -> str:
number = randint(0, 2147483647) # int32 max
return str(number)
Test random number generation:
from d42 import fake
for _ in range(3):
print(fake(schema_numeric))
# Outputs might be:
# 1613861680
# 608712493
# 1535820500
C. Implement Validation
The __validate__
method is used to ensure that values conform to the custom type's specifications.
from typing import Any
from d42.custom_type import PathHolder, ValidationResult
from d42.custom_type.errors import TypeValidationError
from d42.custom_type.visitors import Validator
class NumericSchema(CustomSchema[Props]):
...
def __validate__(self, visitor: Validator, value: Any, path: PathHolder) -> ValidationResult:
result = visitor.make_validation_result()
if not (isinstance(value, str) and all(x in "1234567890" for x in value)):
result.add_error(TypeValidationError(path, value, "numeric str"))
return result
Now, it's possible to perform value validation:
from d42 import validate_or_fail
print(validate_or_fail(schema_numeric, "1234"))
# Output:
# True
print(validate_or_fail(schema_numeric, "abcd"))
# Output:
# d42.ValidationException:
# - Value 'abcd' must be numeric str, but <class 'str'> given
Next Steps
In the next article, the focus will be on value substitution for custom types, enhancing their functionality and usability in various scenarios.