Attribute and Mapping Access¶
Declared fields can be accessed as either attributes or mapping keys.
from typing import Annotated
from dictify import Field, Model
class User(Model):
username: Annotated[str, Field(required=True)]
email: Annotated[str, Field(required=True).match(r".+@.+")]
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"
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())
Strict Mode¶
Model instances are strict by default.
_strict=Truerejects undeclared keys and undeclared public attributes_strict=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.
Mapping input remains useful for JSON-like data, while keyword input fits Python object construction:
User({"username": "user", "email": "user@example.com"})
User(username="user", email="user@example.com")
Model subclasses also expose an inspectable keyword constructor signature for tools that use inspect.signature(), including CLI libraries such as Cyclopts. See CLI and AI Agent Inputs for a full example.
import inspect
assert str(inspect.signature(User)) == "(*, username: str, email: str, _strict: bool = True)"
Post Validation¶
Override post_validate() when validation depends on multiple fields.
from typing import Annotated
from dictify import Field, Model
class User(Model):
username: Annotated[str, Field(required=True).match(r"[a-zA-Z0-9 ._-]+$")]
email: Annotated[str, Field(required=True).match(r".+@.+")]
email_backup: Annotated[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__().