mirror of
https://github.com/d3vyce/fastapi-toolsets.git
synced 2026-08-04 15:44:09 +00:00
fix: skip redundant refresh/reload round trips in create() and update()
This commit is contained in:
@@ -476,3 +476,26 @@ async def db_session(engine):
|
||||
# Drop tables after test
|
||||
async with engine.begin() as conn:
|
||||
await conn.run_sync(Base.metadata.drop_all)
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
async def db_session_expire_on_commit(engine):
|
||||
"""Session with expire_on_commit=True (the SQLAlchemy default).
|
||||
|
||||
Attributes read off an instance after commit are expired and trigger an
|
||||
implicit (sync) refresh under this setting — which fails under asyncio
|
||||
with MissingGreenlet. The other ``db_session`` fixture uses
|
||||
``expire_on_commit=False`` and would not catch that class of bug.
|
||||
"""
|
||||
async with engine.begin() as conn:
|
||||
await conn.run_sync(Base.metadata.create_all)
|
||||
|
||||
session_factory = async_sessionmaker(engine, expire_on_commit=True)
|
||||
session = session_factory()
|
||||
|
||||
try:
|
||||
yield session
|
||||
finally:
|
||||
await session.close()
|
||||
async with engine.begin() as conn:
|
||||
await conn.run_sync(Base.metadata.drop_all)
|
||||
|
||||
@@ -417,6 +417,55 @@ class TestDefaultLoadOptionsIntegration:
|
||||
assert updated.role is not None
|
||||
assert updated.role.name == "admin"
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_default_load_options_applied_to_create_expire_on_commit(
|
||||
self, db_session_expire_on_commit: AsyncSession
|
||||
):
|
||||
"""create()'s reload uses captured PK values, not an expired instance attribute.
|
||||
|
||||
Regression test for MissingGreenlet: reading a PK off `db_model` after
|
||||
commit under expire_on_commit=True (the SQLAlchemy default) would
|
||||
trigger an implicit sync refresh, which fails under asyncio.
|
||||
"""
|
||||
UserWithDefaultLoad = CrudFactory(
|
||||
User, default_load_options=[selectinload(User.role)]
|
||||
)
|
||||
role = await RoleCrud.create(
|
||||
db_session_expire_on_commit, RoleCreate(name="admin")
|
||||
)
|
||||
user = await UserWithDefaultLoad.create(
|
||||
db_session_expire_on_commit,
|
||||
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_expire_on_commit(
|
||||
self, db_session_expire_on_commit: AsyncSession
|
||||
):
|
||||
"""update()'s reload uses captured PK values, not an expired instance attribute.
|
||||
|
||||
Regression test for MissingGreenlet under expire_on_commit=True.
|
||||
"""
|
||||
UserWithDefaultLoad = CrudFactory(
|
||||
User, default_load_options=[selectinload(User.role)]
|
||||
)
|
||||
role = await RoleCrud.create(
|
||||
db_session_expire_on_commit, RoleCreate(name="admin")
|
||||
)
|
||||
user = await UserCrud.create(
|
||||
db_session_expire_on_commit,
|
||||
UserCreate(username="alice", email="alice@test.com"),
|
||||
)
|
||||
updated = await UserWithDefaultLoad.update(
|
||||
db_session_expire_on_commit,
|
||||
UserUpdate(role_id=role.id),
|
||||
filters=[User.id == user.id],
|
||||
)
|
||||
assert updated.role is not None
|
||||
assert updated.role.name == "admin"
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_load_options_overrides_default_load_options(
|
||||
self, db_session: AsyncSession
|
||||
|
||||
Reference in New Issue
Block a user