Skip to content

Commit 981095a

Browse files
refactor: Extract shared base classes to eliminate duplication (#50)
Extract shared base classes to eliminate repository and entity duplication - Add _EntityBase with shared id, created_at, __eq__, __hash__ for Entity and ImmutableEntity - Add _RepositoryBase with shared session, create, ID extraction, and pagination methods - BaseRepository and ImmutableBaseRepository become thin subclasses with only their specific behaviour - Zero breaking changes: all public class names, signatures, and imports unchanged
1 parent 2eb15a4 commit 981095a

4 files changed

Lines changed: 403 additions & 494 deletions

File tree

src/leadr/common/domain/models.py

Lines changed: 20 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,13 @@
77
from pydantic import BaseModel, ConfigDict, Field
88

99

10-
class ImmutableEntity(BaseModel):
11-
"""Base class for immutable domain entities (append-only, no updates/deletes).
10+
class _EntityBase(BaseModel):
11+
"""Private shared base for Entity and ImmutableEntity.
1212
13-
Provides common functionality for event-sourced entities including:
13+
Provides common fields and behaviour:
1414
- Auto-generated UUID primary key (or typed prefixed ID in subclasses)
1515
- Created timestamp (UTC)
1616
- Equality and hashing based on ID
17-
18-
Used for entities that are never updated or deleted after creation,
19-
such as ScoreEvent in event-sourcing patterns.
20-
21-
Subclasses can override the `id` field with a typed PrefixedID for better
22-
type safety and API clarity.
2317
"""
2418

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

6357

64-
class Entity(BaseModel):
58+
class ImmutableEntity(_EntityBase):
59+
"""Base class for immutable domain entities (append-only, no updates/deletes).
60+
61+
Provides common functionality for event-sourced entities including:
62+
- Auto-generated UUID primary key (or typed prefixed ID in subclasses)
63+
- Created timestamp (UTC)
64+
- Equality and hashing based on ID
65+
66+
Used for entities that are never updated or deleted after creation,
67+
such as ScoreEvent in event-sourcing patterns.
68+
69+
Subclasses can override the `id` field with a typed PrefixedID for better
70+
type safety and API clarity.
71+
"""
72+
73+
74+
class Entity(_EntityBase):
6575
"""Base class for all domain entities with ID and timestamps.
6676
6777
Provides common functionality for domain entities including:
@@ -78,17 +88,6 @@ class Entity(BaseModel):
7888
type safety and API clarity.
7989
"""
8090

81-
model_config = ConfigDict(validate_assignment=True)
82-
83-
id: Any = Field(
84-
frozen=True,
85-
default_factory=uuid4,
86-
description="Unique identifier (auto-generated UUID or typed ID)",
87-
)
88-
created_at: datetime = Field(
89-
default_factory=lambda: datetime.now(UTC),
90-
description="Timestamp when entity was created (UTC)",
91-
)
9291
updated_at: datetime = Field(
9392
default_factory=lambda: datetime.now(UTC),
9493
description="Timestamp of last update (UTC)",
@@ -131,29 +130,3 @@ def restore(self) -> None:
131130
>>> assert account.is_deleted is False
132131
"""
133132
self.deleted_at = None
134-
135-
def __eq__(self, other: object) -> bool:
136-
"""Check equality based on ID.
137-
138-
Two entities are considered equal if they have the same ID and are
139-
of the same class.
140-
141-
Args:
142-
other: Object to compare with.
143-
144-
Returns:
145-
True if both entities have the same ID and class.
146-
"""
147-
if not isinstance(other, self.__class__):
148-
return False
149-
return self.id == other.id
150-
151-
def __hash__(self) -> int:
152-
"""Return hash based on ID.
153-
154-
Allows entities to be used in sets and as dictionary keys.
155-
156-
Returns:
157-
Hash of the entity's ID.
158-
"""
159-
return hash(self.id)

0 commit comments

Comments
 (0)