Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 20 additions & 47 deletions src/leadr/common/domain/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,13 @@
from pydantic import BaseModel, ConfigDict, Field


class ImmutableEntity(BaseModel):
"""Base class for immutable domain entities (append-only, no updates/deletes).
class _EntityBase(BaseModel):
"""Private shared base for Entity and ImmutableEntity.

Provides common functionality for event-sourced entities including:
Provides common fields and behaviour:
- Auto-generated UUID primary key (or typed prefixed ID in subclasses)
- Created timestamp (UTC)
- Equality and hashing based on ID

Used for entities that are never updated or deleted after creation,
such as ScoreEvent in event-sourcing patterns.

Subclasses can override the `id` field with a typed PrefixedID for better
type safety and API clarity.
"""

model_config = ConfigDict(validate_assignment=True)
Expand Down Expand Up @@ -61,7 +55,23 @@ def __hash__(self) -> int:
return hash(self.id)


class Entity(BaseModel):
class ImmutableEntity(_EntityBase):
"""Base class for immutable domain entities (append-only, no updates/deletes).

Provides common functionality for event-sourced entities including:
- Auto-generated UUID primary key (or typed prefixed ID in subclasses)
- Created timestamp (UTC)
- Equality and hashing based on ID

Used for entities that are never updated or deleted after creation,
such as ScoreEvent in event-sourcing patterns.

Subclasses can override the `id` field with a typed PrefixedID for better
type safety and API clarity.
"""


class Entity(_EntityBase):
"""Base class for all domain entities with ID and timestamps.

Provides common functionality for domain entities including:
Expand All @@ -78,17 +88,6 @@ class Entity(BaseModel):
type safety and API clarity.
"""

model_config = ConfigDict(validate_assignment=True)

id: Any = Field(
frozen=True,
default_factory=uuid4,
description="Unique identifier (auto-generated UUID or typed ID)",
)
created_at: datetime = Field(
default_factory=lambda: datetime.now(UTC),
description="Timestamp when entity was created (UTC)",
)
updated_at: datetime = Field(
default_factory=lambda: datetime.now(UTC),
description="Timestamp of last update (UTC)",
Expand Down Expand Up @@ -131,29 +130,3 @@ def restore(self) -> None:
>>> assert account.is_deleted is False
"""
self.deleted_at = None

def __eq__(self, other: object) -> bool:
"""Check equality based on ID.

Two entities are considered equal if they have the same ID and are
of the same class.

Args:
other: Object to compare with.

Returns:
True if both entities have the same ID and class.
"""
if not isinstance(other, self.__class__):
return False
return self.id == other.id

def __hash__(self) -> int:
"""Return hash based on ID.

Allows entities to be used in sets and as dictionary keys.

Returns:
Hash of the entity's ID.
"""
return hash(self.id)
Loading
Loading