Field¶
Field can be used by itself for partial or standalone validation. Values are validated whenever they are assigned.
from dictify import Field
username = Field(required=True).instance(str).match(r"[a-zA-Z0-9 ._-]+$")
email = Field(required=True).instance(str).match(r".+@.+")
username.value = "user"
email.value = "user@example.com"
Invalid assignments raise Field.VerifyError, and the previous valid values stay unchanged.
Model¶
For structured documents, define annotated fields on a Model subclass and use Field(...) for options or additional validators.
from datetime import UTC, datetime
from typing import Annotated
from dictify import Field, Model
class Contact(Model):
type: str = Field(required=True).verify(
lambda value: value in ["phone", "email", "address"]
)
note: str = Field().verify(lambda value: len(value) <= 250)
value: str = Field(required=True).verify(lambda value: len(value) <= 1000)
class User(Model):
username: str = Field(required=True).match(r"[a-zA-Z0-9 ._-]+$")
email: Annotated[str, "primary email"] = Field(required=True).match(r".+@.+")
contacts: list[Contact] = Field()
created_at: datetime = Field(default=lambda: datetime.now(UTC))
Attribute Access¶
Declared fields can be accessed as either attributes or mapping keys.
user = User({"username": "user", "email": "user@example.com"})
user.username = "new-user"
user["email"] = "new@example.com"
assert user.username == "new-user"
assert user["email"] == "new@example.com"
Strict Mode¶
Model instances are strict by default.
strict=Truerejects undeclared keys and undeclared public attributesstrict=Falsestores undeclared keys and public attributes as extra model data
user = User({"username": "user", "email": "user@example.com"}, strict=False)
user.nickname = "nick"
user["age"] = 30
assert user.nickname == "nick"
assert user["nickname"] == "nick"
assert dict(user)["age"] == 30
With strict=True, both user["age"] = 30 and user.age = 30 are rejected.
Annotated¶
Annotated[...] metadata is allowed on model field annotations.
from typing import Annotated
class User(Model):
email: Annotated[str, "primary email"] = Field(required=True)
dictify uses str as the runtime field type and ignores the extra metadata.
Runtime support for declarations like email: str = Field(...) is complete. Static type checker support for that pattern is still limited and may require cast(Any, Field(...)) depending on the checker.
Do not declare a second Field(...) inside Annotated[...] when the class attribute is already assigned to Field(...).
# Invalid: ambiguous double-field declaration.
email: Annotated[str, Field(required=True)] = Field()
Native Data¶
Use dict(model) or model.dict() when you need plain Python data.
dict(model)returns a shallowdictmodel.dict()recursively converts nestedModelandListOfvalues
import json
payload = user.dict()
message = json.dumps(user.dict())
Partial Data Validation¶
Standalone Field usage is useful when you want to validate a single value without building the full model.
from dictify import Field
email_field = Field(required=True).instance(str).match(r".+@.+")
email_field.value = "user@example.com"
You can also reuse a model field definition directly:
from dictify import Field, Model
class User(Model):
email: str = Field(required=True).match(r".+@.+")
User.email.value = "user@example.com"
User.email is the shared class-level field definition. When you want an isolated standalone validator, prefer User.email.clone().
AI Skill¶
dictify also ships a packaged AI skill that can be installed with the built-in CLI:
dictify ai-skill-install
The installer prompts for the destination folder and defaults to ./.agents/skills/dictify-usage.
See AI Skill.
Post Validation¶
Override post_validate() when validation depends on multiple fields.
from dictify import Field, Model
class User(Model):
username: str = Field(required=True).match(r"[a-zA-Z0-9 ._-]+$")
email: str = Field(required=True).match(r".+@.+")
email_backup: str = Field(required=True).match(r".+@.+")
def post_validate(self):
assert self.get("email") != self.get("email_backup")
post_validate() runs after successful model creation and after successful mutations such as __setitem__(), update(), setdefault(), and __delitem__().