Skip to content

Commit

Permalink
Feature/sqlite repo (#56)
Browse files Browse the repository at this point in the history
Implements database configuration for sqlite repo in issue #26 by:
- Implementing configure_database_session(services, settings) and
on_start
-  Adding support libraries to `pyproject.toml`
  • Loading branch information
codecakes authored Sep 2, 2024
1 parent f973b3d commit e8fb0a6
Showing 7 changed files with 439 additions and 18 deletions.
336 changes: 319 additions & 17 deletions poetry.lock

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -14,6 +14,11 @@ Hypercorn = "^0.17.3"
MarkupSafe = "^2.1.3"
uvloop = {version = "^0.20.0", markers = "sys_platform != 'win32'"}
pydantic-settings = {version = "^2.3.4", markers = "sys_platform != 'win32'"}
cython = "^3.0.11"
sqlalchemy = {version="^2.0.23", markers = "sys_platform != 'win32'", extras = ["asyncio"]}
alembic = "^1.13.2"
aiosqlite = "^0.20.0"
sqlmodel = {version="^0.0.21"}

# [tool.poetry.group.dev.dependencies]

90 changes: 90 additions & 0 deletions xcov19/app/database.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
from collections.abc import AsyncGenerator
import sys
from rodi import Container
from sqlmodel import SQLModel

from xcov19.app.settings import Settings
from sqlalchemy.ext.asyncio import (
create_async_engine,
AsyncEngine,
AsyncSession,
async_sessionmaker,
)

import logging
from sqlalchemy.pool import AsyncAdaptedQueuePool

db_logger = logging.getLogger(__name__)
db_fmt = logging.Formatter(
"DATABASE:%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)
stream_handler = logging.StreamHandler(sys.stdout)
stream_handler.setFormatter(db_fmt)

db_logger.setLevel(logging.INFO)
db_logger.addHandler(stream_handler)


class SessionFactory:
"""Class to remember sessionmaker factory constructor for DI container.
Use like this to retrieve sessionmaker from DI container:
container.resolve(SessionFactory)
It is already added as in `configure_database_session`:
container.add_singleton_by_factory(SessionFactory(engine), SessionFactory)
"""

def __init__(self, engine: AsyncEngine):
self._engine = engine

def __call__(self) -> async_sessionmaker[AsyncSession]:
return async_sessionmaker(
self._engine, class_=AsyncSession, expire_on_commit=False
)


async def setup_database(engine: AsyncEngine) -> None:
"""Sets up tables for database."""
async with engine.begin() as conn:
await conn.run_sync(SQLModel.metadata.create_all)


async def create_async_session(
AsyncSessionFactory: async_sessionmaker[AsyncSession],
) -> AsyncGenerator[AsyncSession, None]:
"""Create an asynchronous database session."""
async with AsyncSessionFactory() as session:
try:
yield session
finally:
await session.close()


async def start_db_session(container: Container):
"""Starts a new database session given SessionFactory."""
# add LocalAsyncSession
local_async_session = create_async_session(
container.resolve(async_sessionmaker[AsyncSession])
)
container.add_instance(local_async_session, AsyncSession)


def configure_database_session(container: Container, settings: Settings) -> Container:
"""Configure database session setup for the application."""
# add engine
db_logger.info(f"""====== Configuring database session. ======
DB_ENGINE_URL: {settings.db_engine_url}
""")
engine = create_async_engine(
settings.db_engine_url, echo=True, poolclass=AsyncAdaptedQueuePool
)
container.add_instance(engine, AsyncEngine)

# add sessionmaker
container.add_singleton_by_factory(
SessionFactory(engine), async_sessionmaker[AsyncSession]
)

db_logger.info("====== Database session configured. ======")
return container
20 changes: 19 additions & 1 deletion xcov19/app/main.py
Original file line number Diff line number Diff line change
@@ -3,8 +3,13 @@
"""

from blacksheep import Application
from rodi import Container
from rodi import Container, ContainerProtocol

from xcov19.app.database import (
configure_database_session,
setup_database,
start_db_session,
)
from xcov19.app.auth import configure_authentication
from xcov19.app.controllers import controller_router
from xcov19.app.docs import configure_docs
@@ -13,6 +18,8 @@
from xcov19.app.services import configure_services
from xcov19.app.settings import load_settings, Settings

from sqlalchemy.ext.asyncio import AsyncEngine


def configure_application(
services: Container,
@@ -27,7 +34,18 @@ def configure_application(
configure_authentication(app, settings)
configure_middleware(app, origin_header_middleware)
configure_docs(app, settings)
configure_database_session(services, settings)
return app


app = configure_application(*configure_services(load_settings()))


@app.on_start
async def on_start():
container: ContainerProtocol = app.services
if not isinstance(container, Container):
raise ValueError("Container is not a valid container")
await start_db_session(container)
engine = container.resolve(AsyncEngine)
await setup_database(engine)
1 change: 1 addition & 0 deletions xcov19/app/services.py
Original file line number Diff line number Diff line change
@@ -14,6 +14,7 @@

from rodi import Container


from xcov19.app.settings import Settings
from xcov19.services.geolocation import (
LocationQueryServiceInterface,
2 changes: 2 additions & 0 deletions xcov19/app/settings.py
Original file line number Diff line number Diff line change
@@ -34,6 +34,8 @@ class Settings(BaseSettings):
# export app_app='{"show_error_details": True}'
app: App = App()

db_engine_url: str = "sqlite+aiosqlite:///" # "sqlite+aiosqlite:///xcov19.db"

model_config = SettingsConfigDict(env_prefix="APP_")


3 changes: 3 additions & 0 deletions xcov19/dev.py
Original file line number Diff line number Diff line change
@@ -44,6 +44,9 @@

config.bind = [f"0.0.0.0:{port}"]
config.debug = True
config.accesslog = "-"
config.errorlog = "-"

config.use_reloader = True

asyncio.run(serve(app, config))

0 comments on commit e8fb0a6

Please sign in to comment.