1 · The @monk Decorator
The single entry point for binding constraints to your code. It adapts to what it wraps.
Why
Most validators force you to choose an architecture: a BaseModel for objects, a Schema class for dicts, a validator() decorator for functions. @monk is one decorator that covers all three surfaces — and on classes, it returns a regular dataclass, so the rest of your code does not need to know monk exists.
How
| Wraps | Effect |
|---|---|
class |
Becomes a @dataclass. Validation is deferred by default — you call validate(instance) explicitly. Attribute access is locked until then. |
def / async def |
Wraps the call. Arguments and return value are validated eagerly on every invocation. |
| Method | Same as a function. Always place @monk below @classmethod / @staticmethod. |
from monk import monk
@monk # deferred — call validate(user) yourself
class User: ...
@monk(defer=False) # eager — fails inside __post_init__
class StrictUser: ...
@monk
def process_user(...): ... # eager — fails on every call
Gotchas
- Deferred is the default for classes. Touching
user.emailbeforevalidate(user)raisesUnvalidatedAccessError. This is intentional: it lets you pass DTOs through layers and validate at the boundary (e.g. just before a DB write). - Globals.
MONK_DEFER=false(env var) orsettings.defer = False(code) flip the default for the whole process. - Decorator order matters on methods.
@classmethod/@staticmethodmust wrap outside@monk, otherwise binding breaks.
Next: Pillar 2 — Constraints