Skip to content

Commit 57e94c3

Browse files
committed
refactor: check for model conversion methods in entities at runtime instead
1 parent 276b5ba commit 57e94c3

File tree

2 files changed

+25
-16
lines changed

2 files changed

+25
-16
lines changed

backend/src/core/database.py

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
1-
from abc import abstractmethod
2-
from typing import Any, AsyncGenerator, Self
1+
from typing import AsyncGenerator
32

4-
from pydantic import BaseModel
53
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
64
from sqlalchemy.orm import DeclarativeBase
75

@@ -39,13 +37,8 @@ def database_url(database: str = env.POSTGRES_DATABASE) -> str:
3937
)
4038

4139

42-
class EntityBase[DataModel: BaseModel, DtoModel: BaseModel](DeclarativeBase):
43-
@classmethod
44-
@abstractmethod
45-
def from_model(cls, data: DataModel, /, *args: Any, **kwargs: Any) -> Self: ...
46-
47-
@abstractmethod
48-
def to_model(self) -> DtoModel: ...
40+
class EntityBase(DeclarativeBase):
41+
pass
4942

5043

5144
async def get_session() -> AsyncGenerator[AsyncSession, None]:

backend/test/utils/resource_test_utils.py

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,14 @@ class ResourceTestUtils[
3434
delegating directly to the base class if not changing default behavior.
3535
3636
Type Parameters:
37-
ResourceEntity: The SQLAlchemy entity class that implements EntityProtocl.
38-
ResourceData: The Pydantic model representing the resource's data object used to create entities.
39-
OtherModels: Additional Pydantic models that may be used in assertions,
40-
represented by a union if multiple models are applicable. Primarily used for typing in the assert_matches method.
37+
- ResourceEntity: The SQLAlchemy entity class that implements EntityBase.
38+
- Note: the default implementation expects `from_model`
39+
and `to_model` methods to be present. However, these methods are not required in the type definition to allow for flexibility,
40+
expecting a subclass to override `next_entity` and/or `entity_to_dict` as needed if these methods are not present.
41+
- ResourceData: The Pydantic model representing the resource's data object used to create entities.
42+
- OtherModels: Additional Pydantic models that may be used in assertions
43+
- Represented by a union if multiple models are applicable.
44+
- Primarily used for typing in the assert_matches method.
4145
4246
Attributes:
4347
session (AsyncSession): The SQLAlchemy async database session.
@@ -163,7 +167,13 @@ async def next_entity(self, **overrides: Any) -> ResourceEntity:
163167
**overrides: Fields to override in the generated entity.
164168
"""
165169
data = await self.next_data(**overrides)
166-
return self._ResourceEntity.from_model(data)
170+
if not hasattr(self._ResourceEntity, "from_model") or not callable(
171+
getattr(self._ResourceEntity, "from_model")
172+
):
173+
raise AttributeError(
174+
f"{self._ResourceEntity.__name__} must implement a 'from_model' classmethod"
175+
)
176+
return self._ResourceEntity.from_model(data) # type: ignore
167177

168178
async def create_many(self, *, i: int, **overrides: Any) -> list[ResourceEntity]:
169179
"""Create multiple resource entities in the database, applying any overrides to every entity.
@@ -196,7 +206,13 @@ async def get_all(self) -> list[ResourceEntity]:
196206

197207
def entity_to_dict(self, entity: ResourceEntity) -> dict:
198208
"""Convert a resource entity to a dict via its model representation."""
199-
return entity.to_model().model_dump()
209+
if not hasattr(self._ResourceEntity, "to_model") or not callable(
210+
getattr(self._ResourceEntity, "to_model")
211+
):
212+
raise AttributeError(
213+
f"{self._ResourceEntity.__name__} must implement a 'to_model' method"
214+
)
215+
return entity.to_model().model_dump() # type: ignore
200216

201217
def assert_matches(
202218
self,

0 commit comments

Comments
 (0)