Field()¶
dictify.Field() creates a reusable validator object.
Field(
required: bool = False,
default: Any = UNDEF,
grant: list[Any] | None = None,
)
For Model classes, prefer Python annotations for the base field type:
class User(Model):
email: str = Field(required=True)
Use Field(...) to add options and extra validators.
Required Fields¶
Set required=True when a value must be present.
Required fields raise Field.RequiredError when:
- You read
field.valuebefore assigning a valid value. - You create a
Modelwithout providing the required field. - You delete a required field from a model.
Default Values¶
Defaults can be static values or factories.
from datetime import UTC, datetime
import uuid
Field(default=0)
Field(default=uuid.uuid4)
Field(default=lambda: datetime.now(UTC))
Defaults are applied when:
- A standalone
Fieldis created. Field.reset()is called.- A
Modelis created without a value for that field. - A model field with a default is deleted.
Granted Values¶
Granted values always pass validation, even if later validators would reject them.
field = Field(grant=[None]).instance(str)
field.value = None
Model Field Typing¶
Model field types come from annotations.
from typing import Annotated
class User(Model):
email: Annotated[str, "primary email"] = Field(required=True).match(r".+@.+")
age: int | None = Field(default=None)
Annotated[...] metadata is ignored for runtime typing unless it contains a Field(...), which is rejected as ambiguous when the class attribute is also assigned to Field(...).
Validation Methods¶
Field validators can be chained.
username = Field(required=True).instance(str).match(r"[a-zA-Z0-9 ._-]+$")
instance(type_)¶
Verify that the assigned value is an instance of the given type.
For Model fields, prefer annotations for the base type. instance(...) remains useful for standalone Field validation and compatibility.
email = Field().instance(str)
email.value = "user@example.com"
number = Field().instance((int, float))
number.value = 0
number.value = 0.1
listof(type_=UNDEF, validate=None)¶
Validate that the value is a list, optionally checking each member type and applying a member validator.
from datetime import datetime
days = Field().listof(str)
days.value = ["Mo", "Tu", "We"]
def timestamp_validate(value):
datetime.fromisoformat(value)
timestamps = Field().listof(str, validate=timestamp_validate)
timestamps.value = ["2021-06-15T05:10:33.376787"]
For Model classes, you can often use list[Contact] in the annotation instead.
match(regex, flags=0)¶
Use re.match() against the assigned value.
email = Field(required=True).instance(str).match(r".+@.+")
search(regex, flags=0)¶
Use re.search() against the assigned value.
text = Field().instance(str).search(r"dictify")
model(model_cls)¶
Validate nested document data through another Model.
from dictify import Field, Model
class Money(Model):
unit: str = Field(required=True).verify(lambda value: value in ["USD", "GBP"])
amount: int | float = Field(required=True)
payment = Field().model(Money)
payment.value = {"unit": "USD", "amount": 10.0}
For Model classes, you can often use Money directly in the annotation instead.
verify(func, message=None)¶
Use a predicate-style callable for validation.
The callable may raise its own exception or return a truthy value. Falsy
return values fail validation and surface as Field.VerifyError.
age = Field().instance(int).verify(
lambda value: 0 <= value <= 150,
"Age range must be 0 to 150",
)
func(fn)¶
Use a callable to transform or validate the value.
If the callable returns a value, that value becomes the field value. If it
raises an exception, validation fails and surfaces as Field.VerifyError.
from datetime import datetime
timestamp = Field().instance(str).func(datetime.fromisoformat)
Standalone Field State¶
Field also stores its own value, so it can be used directly outside of a model.
field = Field(required=True).instance(str)
field.value = "hello"
field.reset()
When a Field is declared on a Model class, that class attribute acts as a shared schema definition. Individual model instances keep their runtime values separately.