Skip to content

Commit

Permalink
change sqlalchemy sessionmaker_service
Browse files Browse the repository at this point in the history
sessionmake_service returns a single async_sessionmaker, bound to all defined engines, instead of having one async_sessionmaker to each engine
  • Loading branch information
livioribeiro committed Apr 3, 2024
1 parent 40b362d commit a7ff0f9
Show file tree
Hide file tree
Showing 6 changed files with 92 additions and 38 deletions.
11 changes: 5 additions & 6 deletions src/selva/ext/data/sqlalchemy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
from selva.di.container import Container
from selva.di.decorator import service as service_decorator

from .service import make_engine_service, make_sessionmaker_service # noqa: F401
from .service import make_engine_service # noqa: F401
from .service import engine_dict_service, sessionmaker_service


def selva_extension(container: Container, settings: Settings):
Expand All @@ -17,8 +18,6 @@ def selva_extension(container: Container, settings: Settings):
container.register(
service_decorator(make_engine_service(name), name=service_name)
)
container.register(
service_decorator(
make_sessionmaker_service(service_name), name=service_name
)
)

container.register(service_decorator(engine_dict_service))
container.register(service_decorator(sessionmaker_service))
20 changes: 13 additions & 7 deletions src/selva/ext/data/sqlalchemy/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from sqlalchemy.ext.asyncio import AsyncEngine, async_sessionmaker, create_async_engine

from selva.configuration.settings import Settings
from selva.di import Inject
from selva.di import Container, Inject
from selva.ext.data.sqlalchemy.settings import SqlAlchemySettings


Expand All @@ -26,10 +26,16 @@ async def engine_service(
return engine_service


def make_sessionmaker_service(name: str):
async def sessionmaker_service(
engine: Annotated[AsyncEngine, Inject(name=name)],
) -> async_sessionmaker:
return async_sessionmaker(bind=engine)
async def engine_dict_service(
di: Container, settings: Settings
) -> dict[str, AsyncEngine]:
return {
db: await di.get(AsyncEngine, name=db if db != "default" else None)
for db in settings.data.sqlalchemy
}

return sessionmaker_service

async def sessionmaker_service(engines: dict[str, AsyncEngine]) -> async_sessionmaker:
default = engines.pop("default", None)

return async_sessionmaker(bind=default, binds=engines, expire_on_commit=False)
8 changes: 4 additions & 4 deletions tests/ext/data/sqlalchemy/application_named.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,20 @@

from asgikit.responses import respond_text
from sqlalchemy import text
from sqlalchemy.ext.asyncio import async_sessionmaker
from sqlalchemy.ext.asyncio import AsyncEngine

from selva.di import Inject
from selva.web import controller, get


@controller
class Controller:
sessionmaker: Annotated[async_sessionmaker, Inject(name="other")]
engine: Annotated[AsyncEngine, Inject(name="other")]

@get
async def index(self, request):
async with self.sessionmaker() as session:
result = await session.execute(text("select sqlite_version()"))
async with self.engine.begin() as conn:
result = await conn.execute(text("select sqlite_version()"))
version = result.first()[0]

await respond_text(request.response, version)
2 changes: 1 addition & 1 deletion tests/ext/data/sqlalchemy/test_application.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,4 @@ async def test_application(application: str, database: str):
client = AsyncClient(app=app)
response = await client.get("http://localhost:8000/")

assert response.status_code == HTTPStatus.OK
assert response.status_code == HTTPStatus.OK, response.text
65 changes: 54 additions & 11 deletions tests/ext/data/sqlalchemy/test_service.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
from sqlalchemy import text
from sqlalchemy.ext.asyncio import AsyncEngine, create_async_engine

from selva.configuration.defaults import default_settings
from selva.configuration.settings import Settings
from selva.di.container import Container
from selva.ext.data.sqlalchemy.service import (
engine_dict_service,
make_engine_service,
make_sessionmaker_service,
sessionmaker_service,
)


Expand Down Expand Up @@ -96,17 +99,13 @@ async def test_make_engine_service_with_execution_options():
}
)

engine_service = make_engine_service("default")
async for engine in engine_service(settings):
sessionmaker_service = make_sessionmaker_service("default")
sessionmaker = await sessionmaker_service(engine)
async for engine in make_engine_service("default")(settings):
async with engine.connect() as conn:
result = await conn.execute(text("select 1"))
isolation_level = result.context.execution_options["isolation_level"]
assert isolation_level == "READ UNCOMMITTED"

async with sessionmaker() as session:
result = await session.execute(text("select 1"))
assert (
result.context.execution_options["isolation_level"]
== "READ UNCOMMITTED"
)
await engine.dispose()


async def test_make_engine_service_alternative_name():
Expand All @@ -126,3 +125,47 @@ async def test_make_engine_service_alternative_name():
engine_service = make_engine_service("other")(settings)
async for engine in engine_service:
assert engine is not None
await engine.dispose()


async def test_sessionmaker_service():
engine = create_async_engine("sqlite+aiosqlite:///:memory:")

sessionmaker = await sessionmaker_service({"default": engine})
async with sessionmaker() as session:
result = await session.execute(text("select 1"))
assert result.scalar() == 1

await engine.dispose()


async def test_engine_dict_service():
ioc = Container()

settings = Settings(
default_settings
| {
"data": {
"sqlalchemy": {
"default": {
"url": "sqlite+aiosqlite:///:memory:",
},
"other": {
"url": "sqlite+aiosqlite:///:memory:",
},
},
},
}
)

ioc.define(Settings, settings)
ioc.define(AsyncEngine, create_async_engine(settings.data.sqlalchemy.default.url))
ioc.define(
AsyncEngine,
create_async_engine(settings.data.sqlalchemy.other.url),
name="other",
)

engines = await engine_dict_service(ioc, settings)

assert set(engines.keys()) == {"default", "other"}
24 changes: 15 additions & 9 deletions tests/ext/data/sqlalchemy/test_service_postgres.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,11 @@

import pytest
from sqlalchemy import make_url, text
from sqlalchemy.ext.asyncio import create_async_engine

from selva.configuration.defaults import default_settings
from selva.configuration.settings import Settings
from selva.ext.data.sqlalchemy.service import (
make_engine_service,
make_sessionmaker_service,
)
from selva.ext.data.sqlalchemy.service import make_engine_service, sessionmaker_service

from .test_service import _test_engine_service

Expand Down Expand Up @@ -122,11 +120,19 @@ async def test_make_engine_service_with_execution_options():
}
)

engine_service = make_engine_service("default")
engine = await anext(engine_service(settings))
sessionmaker_service = make_sessionmaker_service("default")
sessionmaker = await sessionmaker_service(engine)
async for engine in make_engine_service("default")(settings):
async with engine.connect() as conn:
result = await conn.execute(text("select 1"))
isolation_level = result.context.execution_options["isolation_level"]
assert isolation_level == "READ COMMITTED"


async def test_sessionmaker_service():
engine = create_async_engine(POSTGRES_URL)

sessionmaker = await sessionmaker_service({"default": engine})
async with sessionmaker() as session:
result = await session.execute(text("select 1"))
assert result.context.execution_options["isolation_level"] == "READ COMMITTED"
assert result.scalar() == 1

await engine.dispose()

0 comments on commit a7ff0f9

Please sign in to comment.