From d626161ee0216cfa04a59f42b1e14d0400258f49 Mon Sep 17 00:00:00 2001 From: Baiheng Xie <874256269@qq.com> Date: Wed, 7 Jan 2026 12:09:27 +0800 Subject: [PATCH 1/2] feat: update automatically on row modifications --- src/shared/mixins.py | 5 +++- tests/shared/__init__.py | 0 tests/shared/test_mixins.py | 52 +++++++++++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 tests/shared/__init__.py create mode 100644 tests/shared/test_mixins.py diff --git a/src/shared/mixins.py b/src/shared/mixins.py index 8deae15..78d2e30 100644 --- a/src/shared/mixins.py +++ b/src/shared/mixins.py @@ -5,4 +5,7 @@ class TimestampMixin: created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc)) - updated_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc)) + updated_at: datetime = Field( + default_factory=lambda: datetime.now(timezone.utc), + sa_column_kwargs={"onupdate": lambda: datetime.now(timezone.utc)}, + ) diff --git a/tests/shared/__init__.py b/tests/shared/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/shared/test_mixins.py b/tests/shared/test_mixins.py new file mode 100644 index 0000000..45593c1 --- /dev/null +++ b/tests/shared/test_mixins.py @@ -0,0 +1,52 @@ +from time import sleep + +from sqlmodel import Field, Session, SQLModel, create_engine + +from src.shared.mixins import TimestampMixin + + +class SampleModel(SQLModel, TimestampMixin, table=True): + id: int | None = Field(default=None, primary_key=True) + name: str + + +def test_timestamp_mixin_updated_at_updates(): + engine = create_engine("sqlite:///:memory:") + SQLModel.metadata.create_all(engine) + + with Session(engine) as session: + record = SampleModel(name="original") + session.add(record) + session.commit() + session.refresh(record) + initial_updated_at = record.updated_at + + sleep(0.01) + + record.name = "modified" + session.add(record) + session.commit() + session.refresh(record) + + assert record.updated_at > initial_updated_at + + +def test_timestamp_mixin_created_at_unchanged(): + engine = create_engine("sqlite:///:memory:") + SQLModel.metadata.create_all(engine) + + with Session(engine) as session: + record = SampleModel(name="original") + session.add(record) + session.commit() + session.refresh(record) + initial_created_at = record.created_at + + sleep(0.01) + + record.name = "modified" + session.add(record) + session.commit() + session.refresh(record) + + assert record.created_at == initial_created_at From 458d43d21aa43a304a221b5ce39bd1dc9b90d6fe Mon Sep 17 00:00:00 2001 From: Baiheng Xie <874256269@qq.com> Date: Wed, 7 Jan 2026 12:15:36 +0800 Subject: [PATCH 2/2] refactor: convert tests to use async session and fixtures --- tests/shared/test_mixins.py | 57 +++++++++++++++++++++++-------------- 1 file changed, 36 insertions(+), 21 deletions(-) diff --git a/tests/shared/test_mixins.py b/tests/shared/test_mixins.py index 45593c1..658c41d 100644 --- a/tests/shared/test_mixins.py +++ b/tests/shared/test_mixins.py @@ -1,6 +1,9 @@ -from time import sleep +from asyncio import sleep -from sqlmodel import Field, Session, SQLModel, create_engine +import pytest +from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine +from sqlalchemy.orm import sessionmaker +from sqlmodel import Field, SQLModel from src.shared.mixins import TimestampMixin @@ -10,43 +13,55 @@ class SampleModel(SQLModel, TimestampMixin, table=True): name: str -def test_timestamp_mixin_updated_at_updates(): - engine = create_engine("sqlite:///:memory:") - SQLModel.metadata.create_all(engine) +@pytest.fixture +async def mixin_db(): + engine = create_async_engine( + "sqlite+aiosqlite:///:memory:", + echo=False, + connect_args={"check_same_thread": False}, + ) + async_session = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False) - with Session(engine) as session: + async with engine.begin() as conn: + await conn.run_sync(SQLModel.metadata.create_all) + + yield async_session + + async with engine.begin() as conn: + await conn.run_sync(SQLModel.metadata.drop_all) + + +async def test_timestamp_mixin_updated_at_updates(mixin_db): + async with mixin_db() as session: record = SampleModel(name="original") session.add(record) - session.commit() - session.refresh(record) + await session.commit() + await session.refresh(record) initial_updated_at = record.updated_at - sleep(0.01) + await sleep(0.01) record.name = "modified" session.add(record) - session.commit() - session.refresh(record) + await session.commit() + await session.refresh(record) assert record.updated_at > initial_updated_at -def test_timestamp_mixin_created_at_unchanged(): - engine = create_engine("sqlite:///:memory:") - SQLModel.metadata.create_all(engine) - - with Session(engine) as session: +async def test_timestamp_mixin_created_at_unchanged(mixin_db): + async with mixin_db() as session: record = SampleModel(name="original") session.add(record) - session.commit() - session.refresh(record) + await session.commit() + await session.refresh(record) initial_created_at = record.created_at - sleep(0.01) + await sleep(0.01) record.name = "modified" session.add(record) - session.commit() - session.refresh(record) + await session.commit() + await session.refresh(record) assert record.created_at == initial_created_at