# 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 ```python 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 ```python 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 ```python 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`: ```python # 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: ```python 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 `Base` instead) - CLI output models (use plain `pydantic.BaseModel` or dataclasses) - Configuration models (use `pydantic_settings.BaseSettings`) --- ## Related Documentation - [Architecture Overview](../architecture.md) — domain layer description - [ADR-004 Data Validation](../adr/ADR-004-data-validation.md) — validation strategy - [ADR-001 Layered Architecture](../adr/ADR-001-layered-architecture.md) — layer boundaries