4.2 KiB
Domain Base Model
Package: cleveragents.domain.models.base
Introduced: v3.8.0 (issue #1941)
The DomainBaseModel is a shared Pydantic base class that centralises the
common model_config previously duplicated across 14 domain model classes.
All standard domain-layer Pydantic models should inherit from
DomainBaseModel instead of pydantic.BaseModel directly.
Purpose
Before DomainBaseModel was introduced, every domain model file contained
an identical model_config = ConfigDict(...) block. This created a
maintenance burden: any change to the shared configuration had to be
propagated manually to all 14 files, and it was easy for individual models
to drift out of sync.
DomainBaseModel solves this by defining the configuration in exactly one
place. Future changes to the shared config automatically propagate to every
consumer.
Usage
from cleveragents.domain.models.base import DomainBaseModel
class MyDomainModel(DomainBaseModel):
name: str
value: int
That's all that's required. The model inherits all five configuration options described below.
Configuration Options
DomainBaseModel sets the following model_config options:
| Option | Value | Effect |
|---|---|---|
str_strip_whitespace |
True |
Leading/trailing whitespace is stripped from all str fields on assignment and validation |
validate_assignment |
True |
Field assignments after construction are validated just like constructor arguments |
arbitrary_types_allowed |
False |
All field types must be Pydantic-compatible; arbitrary Python objects are not permitted |
populate_by_name |
True |
Models can be constructed using either the Python field name or the JSON alias |
use_enum_values |
True |
Enum fields are stored and serialised as their underlying primitive values rather than as enum instances |
Example: Whitespace Stripping
from cleveragents.domain.models.base import DomainBaseModel
class ProjectName(DomainBaseModel):
name: str
p = ProjectName(name=" my-project ")
assert p.name == "my-project" # whitespace stripped automatically
Example: Assignment Validation
from cleveragents.domain.models.base import DomainBaseModel
from pydantic import field_validator
class BoundedValue(DomainBaseModel):
count: int
@field_validator("count")
@classmethod
def must_be_positive(cls, v: int) -> int:
if v < 0:
raise ValueError("count must be non-negative")
return v
bv = BoundedValue(count=5)
bv.count = -1 # raises ValidationError — validate_assignment=True
Migration Guide
If you are adding a new domain model, inherit from DomainBaseModel:
# Before
from pydantic import BaseModel, ConfigDict
class MyModel(BaseModel):
model_config = ConfigDict(
str_strip_whitespace=True,
validate_assignment=True,
arbitrary_types_allowed=False,
populate_by_name=True,
use_enum_values=True,
)
name: str
# After
from cleveragents.domain.models.base import DomainBaseModel
class MyModel(DomainBaseModel):
name: str
If you need to override one of the shared options for a specific model,
you can still do so by declaring model_config on the subclass — Pydantic
merges configs with the subclass taking precedence:
from pydantic import ConfigDict
from cleveragents.domain.models.base import DomainBaseModel
class SpecialModel(DomainBaseModel):
# Allow arbitrary types for this specific model only
model_config = ConfigDict(arbitrary_types_allowed=True)
data: object
Scope
DomainBaseModel is intended for domain-layer models only. Do not use
it for:
- Infrastructure ORM models (use SQLAlchemy
Baseinstead) - CLI output models (use plain
pydantic.BaseModelor dataclasses) - Configuration models (use
pydantic_settings.BaseSettings)
Related Documentation
- Architecture Overview — domain layer description
- ADR-004 Data Validation — validation strategy
- ADR-001 Layered Architecture — layer boundaries