mirror of
https://github.com/d3vyce/fastapi-toolsets.git
synced 2026-04-16 06:36:26 +02:00
fix: apply default_load_options after create() and update() to prevent MissingGreenlet on relationship access (#229)
This commit is contained in:
@@ -170,6 +170,18 @@ class AsyncCrud(Generic[ModelType]):
|
|||||||
return load_options
|
return load_options
|
||||||
return cls.default_load_options
|
return cls.default_load_options
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def _reload_with_options(
|
||||||
|
cls: type[Self], session: AsyncSession, instance: ModelType
|
||||||
|
) -> ModelType:
|
||||||
|
"""Re-query instance by PK with default_load_options applied."""
|
||||||
|
mapper = cls.model.__mapper__
|
||||||
|
pk_filters = [
|
||||||
|
getattr(cls.model, col.key) == getattr(instance, col.key)
|
||||||
|
for col in mapper.primary_key
|
||||||
|
]
|
||||||
|
return await cls.get(session, filters=pk_filters)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def _resolve_m2m(
|
async def _resolve_m2m(
|
||||||
cls: type[Self],
|
cls: type[Self],
|
||||||
@@ -705,6 +717,8 @@ class AsyncCrud(Generic[ModelType]):
|
|||||||
|
|
||||||
session.add(db_model)
|
session.add(db_model)
|
||||||
await session.refresh(db_model)
|
await session.refresh(db_model)
|
||||||
|
if cls.default_load_options:
|
||||||
|
db_model = await cls._reload_with_options(session, db_model)
|
||||||
result = cast(ModelType, db_model)
|
result = cast(ModelType, db_model)
|
||||||
if schema:
|
if schema:
|
||||||
return Response(data=schema.model_validate(result))
|
return Response(data=schema.model_validate(result))
|
||||||
@@ -1060,6 +1074,8 @@ class AsyncCrud(Generic[ModelType]):
|
|||||||
for rel_attr, related_instances in m2m_resolved.items():
|
for rel_attr, related_instances in m2m_resolved.items():
|
||||||
setattr(db_model, rel_attr, related_instances)
|
setattr(db_model, rel_attr, related_instances)
|
||||||
await session.refresh(db_model)
|
await session.refresh(db_model)
|
||||||
|
if cls.default_load_options:
|
||||||
|
db_model = await cls._reload_with_options(session, db_model)
|
||||||
if schema:
|
if schema:
|
||||||
return Response(data=schema.model_validate(db_model))
|
return Response(data=schema.model_validate(db_model))
|
||||||
return db_model
|
return db_model
|
||||||
|
|||||||
@@ -380,6 +380,43 @@ class TestDefaultLoadOptionsIntegration:
|
|||||||
assert result.data[0].role is not None
|
assert result.data[0].role is not None
|
||||||
assert result.data[0].role.name == "admin"
|
assert result.data[0].role.name == "admin"
|
||||||
|
|
||||||
|
@pytest.mark.anyio
|
||||||
|
async def test_default_load_options_applied_to_create(
|
||||||
|
self, db_session: AsyncSession
|
||||||
|
):
|
||||||
|
"""default_load_options loads relationships after create()."""
|
||||||
|
UserWithDefaultLoad = CrudFactory(
|
||||||
|
User, default_load_options=[selectinload(User.role)]
|
||||||
|
)
|
||||||
|
role = await RoleCrud.create(db_session, RoleCreate(name="admin"))
|
||||||
|
user = await UserWithDefaultLoad.create(
|
||||||
|
db_session,
|
||||||
|
UserCreate(username="alice", email="alice@test.com", role_id=role.id),
|
||||||
|
)
|
||||||
|
assert user.role is not None
|
||||||
|
assert user.role.name == "admin"
|
||||||
|
|
||||||
|
@pytest.mark.anyio
|
||||||
|
async def test_default_load_options_applied_to_update(
|
||||||
|
self, db_session: AsyncSession
|
||||||
|
):
|
||||||
|
"""default_load_options loads relationships after update()."""
|
||||||
|
UserWithDefaultLoad = CrudFactory(
|
||||||
|
User, default_load_options=[selectinload(User.role)]
|
||||||
|
)
|
||||||
|
role = await RoleCrud.create(db_session, RoleCreate(name="admin"))
|
||||||
|
user = await UserCrud.create(
|
||||||
|
db_session,
|
||||||
|
UserCreate(username="alice", email="alice@test.com"),
|
||||||
|
)
|
||||||
|
updated = await UserWithDefaultLoad.update(
|
||||||
|
db_session,
|
||||||
|
UserUpdate(role_id=role.id),
|
||||||
|
filters=[User.id == user.id],
|
||||||
|
)
|
||||||
|
assert updated.role is not None
|
||||||
|
assert updated.role.name == "admin"
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.anyio
|
||||||
async def test_load_options_overrides_default_load_options(
|
async def test_load_options_overrides_default_load_options(
|
||||||
self, db_session: AsyncSession
|
self, db_session: AsyncSession
|
||||||
|
|||||||
Reference in New Issue
Block a user