Skip to content

Commit 8846697

Browse files
committed
starlette example
1 parent 38b14df commit 8846697

29 files changed

+1066
-2
lines changed

Makefile

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,13 @@ lint:
55
flake8 .
66

77
test:
8-
pytest --cov context_async_sqlalchemy examples/fastapi_example/tests --cov-report=term-missing
8+
pytest --cov context_async_sqlalchemy examples/fastapi_example/tests examples/starlette_example/tests --cov-report=term-missing
9+
10+
test_fastapi:
11+
pytest examples/fastapi_example/tests
12+
13+
test_starlette:
14+
pytest examples/starlette_example/tests
915

1016
uv:
1117
uv sync
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Starlette example
2+
3+
An example of how to use the library with Starlette
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
"""
2+
An example of how to use the library with Starlette
3+
"""
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
"""
2+
The module that defines the database connection parameters
3+
"""
4+
5+
from sqlalchemy.ext.asyncio import (
6+
async_sessionmaker,
7+
AsyncEngine,
8+
AsyncSession,
9+
create_async_engine,
10+
)
11+
12+
from context_async_sqlalchemy import DBConnect
13+
14+
15+
def create_engine(host: str) -> AsyncEngine:
16+
"""
17+
database connection parameters
18+
"""
19+
20+
# In production code, you will probably take these parameters from env
21+
pg_user = "krylosov-aa"
22+
pg_password = ""
23+
pg_port = 6432
24+
pg_db = "test"
25+
return create_async_engine(
26+
f"postgresql+asyncpg://"
27+
f"{pg_user}:{pg_password}"
28+
f"@{host}:{pg_port}"
29+
f"/{pg_db}",
30+
future=True,
31+
pool_pre_ping=True,
32+
)
33+
34+
35+
def create_session_maker(
36+
engine: AsyncEngine,
37+
) -> async_sessionmaker[AsyncSession]:
38+
"""session parameters"""
39+
return async_sessionmaker(
40+
engine, class_=AsyncSession, expire_on_commit=False
41+
)
42+
43+
44+
connection = DBConnect(
45+
host="127.0.0.1",
46+
engine_creator=create_engine,
47+
session_maker_creator=create_session_maker,
48+
)

examples/starlette_example/main.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
"""Application entry point"""
2+
3+
from .setup_app import setup_app
4+
5+
6+
app = setup_app()
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import uuid
2+
from typing import Any
3+
from pydantic import GetCoreSchemaHandler
4+
from pydantic_core.core_schema import IsInstanceSchema, is_instance_schema
5+
from sqlalchemy import text, UUID, Text
6+
from sqlalchemy.orm import (
7+
DeclarativeBase,
8+
Mapped,
9+
mapped_column,
10+
MappedColumn,
11+
)
12+
13+
14+
class BaseTable(DeclarativeBase):
15+
"""Base table for all tables"""
16+
17+
@classmethod
18+
def __get_pydantic_core_schema__(
19+
cls,
20+
_source_type: Any,
21+
_handler: GetCoreSchemaHandler,
22+
) -> IsInstanceSchema:
23+
return is_instance_schema(cls)
24+
25+
26+
def uuid_pk_column() -> MappedColumn[uuid.UUID]:
27+
"""Allows you to reuse a column definition without inheritance"""
28+
return mapped_column(
29+
UUID, primary_key=True, server_default=text("gen_random_uuid()")
30+
)
31+
32+
33+
class ExampleTable(BaseTable):
34+
"""just a table for example"""
35+
36+
id: Mapped[uuid.UUID] = uuid_pk_column()
37+
text: Mapped[str] = mapped_column(Text)
38+
39+
__tablename__ = "example"
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Handlers example
2+
3+
A module that contains all the application handlers.
4+
5+
The examples are fictional, but they’re designed to demonstrate the
6+
various capabilities of the library.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
"""
2+
A module that contains all the application handlers.
3+
"""
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
from starlette.requests import Request
2+
from starlette.responses import JSONResponse
3+
from context_async_sqlalchemy import atomic_db_session, db_session
4+
from sqlalchemy import insert
5+
6+
from ..database import connection
7+
from ..models import ExampleTable
8+
9+
10+
async def handler_with_db_session_and_atomic(_: Request) -> JSONResponse:
11+
"""
12+
Let's imagine you already have a function that works with a contextual
13+
session, and its use case calls autocommit at the end of the request.
14+
You want to reuse this function, but you need to commit immediately,
15+
rather than wait for the request to complete.
16+
"""
17+
# the transaction will be committed or rolled back automatically
18+
# using the context manager
19+
async with atomic_db_session(connection):
20+
await _insert_1()
21+
22+
# This is a new transaction in the same connection
23+
await _insert_1()
24+
25+
return JSONResponse({})
26+
27+
28+
async def _insert_1() -> None:
29+
session = await db_session(connection)
30+
stmt = insert(ExampleTable).values(
31+
text="example_with_db_session_and_atomic"
32+
)
33+
await session.execute(stmt)
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
from starlette.requests import Request
2+
from starlette.responses import JSONResponse
3+
from context_async_sqlalchemy import (
4+
atomic_db_session,
5+
close_db_session,
6+
db_session,
7+
)
8+
from sqlalchemy import insert
9+
10+
from ..database import connection
11+
from ..models import ExampleTable
12+
13+
14+
async def handler_with_early_connection_close(_: Request) -> JSONResponse:
15+
"""
16+
An example when you can return a connection to the connection pool for a
17+
long period of work unrelated to the database
18+
"""
19+
async with atomic_db_session(connection):
20+
await _insert()
21+
22+
await close_db_session(connection)
23+
24+
...
25+
# There's a lot of work going on here.
26+
...
27+
28+
# new connect and new transaction
29+
await _insert()
30+
31+
return JSONResponse({})
32+
33+
34+
async def _insert() -> None:
35+
"""
36+
Let's imagine that the same function is useful to us in another handle,
37+
where it is good to use it in a common transaction for the entire
38+
request.
39+
"""
40+
session = await db_session(connection)
41+
stmt = insert(ExampleTable).values(
42+
text="example_with_early_connection_close"
43+
)
44+
await session.execute(stmt)

0 commit comments

Comments
 (0)