Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,10 @@ mdx-include = "^1.4.1"
coverage = {extras = ["toml"], version = ">=6.2,<8.0"}
fastapi = "^0.103.2"
ruff = "^0.1.2"
# For FastAPI tests
httpx = "0.24.1"
testcontainers = "^3.7.1"
psycopg2-binary = "^2.9.7"
asyncpg = "^0.28.0"
Comment on lines +50 to +52
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It may be good idea in general to introduce testcontainers and test SQLModel with real Postgres, but for the reasons of this PR I think we can just use aiosqlite driver and in-memory DB


[build-system]
requires = ["poetry-core"]
Expand Down
2 changes: 2 additions & 0 deletions sqlmodel/ext/asyncio/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from .engine import create_async_engine as create_async_engine
from .session import AsyncSession as AsyncSession
10 changes: 10 additions & 0 deletions sqlmodel/ext/asyncio/engine.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from typing import Any

from sqlalchemy.ext.asyncio import AsyncEngine
from sqlalchemy.ext.asyncio import create_async_engine as _create_async_engine


# create_async_engine by default already has future set to be true.
# Porting this over to sqlmodel to make it easier to use.
def create_async_engine(*args: Any, **kwargs: Any) -> AsyncEngine:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This way we don't have autocompletion and type hints for parameters

return _create_async_engine(*args, **kwargs)
52 changes: 52 additions & 0 deletions tests/test_async.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import asyncio
from typing import Generator, Optional

import pytest
from sqlmodel import Field, SQLModel, select
from sqlmodel.ext.asyncio import AsyncSession, create_async_engine
from testcontainers.postgres import PostgresContainer


# The first time this test is run, it will download the postgres image which can take
# a while. Subsequent runs will be much faster.
@pytest.fixture(scope="module")
def postgres_container_url() -> Generator[str, None, None]:
with PostgresContainer("postgres:13") as postgres:
postgres.driver = "asyncpg"
yield postgres.get_connection_url()


async def _test_async_create(postgres_container_url: str) -> None:
class Hero(SQLModel, table=True):
# SQLModel.metadata is a singleton and the Hero Class has already been defined.
# If I flush the metadata during this test, it will cause test_enum to fail
# because in that file, the model isn't defined within a function. For now, the
# workaround is to set extend_existing to True. In the future, test setup and
# teardown should be refactored to avoid this issue.
__table_args__ = {"extend_existing": True}
Comment on lines +21 to +26
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can clear DB before each test, that's common approach.
Or, use in-memory SQLite DB with aiosqlite driver


id: Optional[int] = Field(default=None, primary_key=True)
name: str
secret_name: str
age: Optional[int] = None

hero_create = Hero(name="Deadpond", secret_name="Dive Wilson")

engine = create_async_engine(postgres_container_url)
async with engine.begin() as conn:
await conn.run_sync(SQLModel.metadata.create_all)

async with AsyncSession(engine) as session:
session.add(hero_create)
await session.commit()
await session.refresh(hero_create)

async with AsyncSession(engine) as session:
statement = select(Hero).where(Hero.name == "Deadpond")
results = await session.exec(statement)
hero_query = results.one()
assert hero_create == hero_query


def test_async_create(postgres_container_url: str) -> None:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need to use @pytest.mark.anyio to run async tests: https://anyio.readthedocs.io/en/stable/testing.html

asyncio.run(_test_async_create(postgres_container_url))