Skip to content
Merged
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: 2 additions & 2 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,10 @@ jobs:
uv run alembic upgrade head
uv run pytest . --cov=. --cov-report xml
env:
ENVIRONMENT: dev
SERVICE_ENVIRONMENT: ci
PYTHONDONTWRITEBYTECODE: 1
PYTHONUNBUFFERED: 1
DB_HOST: 127.0.0.1
DB_DSN: postgresql+asyncpg://postgres:password@127.0.0.1/postgres
- name: Upload coverage to Codecov
uses: codecov/[email protected]
env:
Expand Down
4 changes: 2 additions & 2 deletions app/__main__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import granian
from granian.constants import Interfaces, Loops
from granian.log import LogLevels

from app.settings import settings

Expand All @@ -10,7 +11,6 @@
address="0.0.0.0", # noqa: S104
port=settings.app_port,
interface=Interfaces.ASGI,
log_dictconfig={"root": {"level": "INFO"}} if not settings.debug else {},
log_level=settings.log_level,
log_level=LogLevels(settings.log_level),
loop=Loops.uvloop,
).serve()
43 changes: 25 additions & 18 deletions app/application.py
Original file line number Diff line number Diff line change
@@ -1,33 +1,40 @@
import dataclasses

import litestar
import modern_di_litestar
from advanced_alchemy.exceptions import DuplicateKeyError
from litestar.openapi import OpenAPIConfig
from litestar.openapi.plugins import SwaggerRenderPlugin
from lite_bootstrap import LitestarBootstrapper
from litestar.config.app import AppConfig
from opentelemetry.instrumentation.asyncpg import AsyncPGInstrumentor
from opentelemetry.instrumentation.sqlalchemy import SQLAlchemyInstrumentor

from app import exceptions, ioc
from app.api.decks import ROUTER
from app.settings import settings


def build_app() -> litestar.Litestar:
return litestar.Litestar(
debug=settings.debug,
exception_handlers={
DuplicateKeyError: exceptions.duplicate_key_error_handler,
},
route_handlers=[ROUTER],
plugins=[modern_di_litestar.ModernDIPlugin()],
dependencies={
"decks_service": modern_di_litestar.FromDI(ioc.Dependencies.decks_service),
"cards_service": modern_di_litestar.FromDI(ioc.Dependencies.cards_service),
},
openapi_config=OpenAPIConfig(
title="Litestar Example",
description="Example of Litestar with Scalar OpenAPI docs",
version="0.0.1",
render_plugins=[SwaggerRenderPlugin()],
bootstrap_config = dataclasses.replace(
settings.api_bootstrapper_config,
application_config=AppConfig(
exception_handlers={
DuplicateKeyError: exceptions.duplicate_key_error_handler,
},
route_handlers=[ROUTER],
plugins=[modern_di_litestar.ModernDIPlugin()],
dependencies={
"decks_service": modern_di_litestar.FromDI(ioc.Dependencies.decks_service),
"cards_service": modern_di_litestar.FromDI(ioc.Dependencies.cards_service),
},
request_max_body_size=settings.request_max_body_size,
),
opentelemetry_instrumentors=[
SQLAlchemyInstrumentor(),
AsyncPGInstrumentor(capture_parameters=True), # type: ignore[no-untyped-call]
],
)
bootstrapper = LitestarBootstrapper(bootstrap_config=bootstrap_config)
return bootstrapper.bootstrap()


application = build_app()
6 changes: 3 additions & 3 deletions app/resources/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@
async def create_sa_engine() -> typing.AsyncIterator[sa.AsyncEngine]:
logger.info("Initializing SQLAlchemy engine")
engine = sa.create_async_engine(
url=settings.db_dsn,
echo=settings.debug,
echo_pool=settings.debug,
url=settings.db_dsn_parsed,
echo=settings.service_debug,
echo_pool=settings.service_debug,
pool_size=settings.db_pool_size,
pool_pre_ping=settings.db_pool_pre_ping,
max_overflow=settings.db_max_overflow,
Expand Down
58 changes: 38 additions & 20 deletions app/settings.py
Original file line number Diff line number Diff line change
@@ -1,35 +1,53 @@
import pydantic_settings
from granian.log import LogLevels
from sqlalchemy.engine.url import URL
from lite_bootstrap import LitestarConfig
from sqlalchemy.engine.url import URL, make_url


class Settings(pydantic_settings.BaseSettings):
debug: bool = False
log_level: LogLevels = LogLevels.info

db_driver: str = "postgresql+asyncpg"
db_host: str = "db"
db_port: int = 5432
db_user: str = "postgres"
db_password: str = "password"
db_database: str = "postgres"
service_name: str = "FastAPI template"
service_version: str = "1.0.0"
service_environment: str = "local"
service_debug: bool = False
log_level: str = "info"

db_dsn: str = "postgresql+asyncpg://postgres:password@db/postgres"
db_pool_size: int = 5
db_max_overflow: int = 0
db_echo: bool = False
db_pool_pre_ping: bool = True

app_port: int = 8000

opentelemetry_endpoint: str = ""
sentry_dsn: str = ""
logging_buffer_capacity: int = 0
swagger_offline_docs: bool = True

cors_allowed_origins: list[str] = ["http://localhost:5173"]
cors_allowed_methods: list[str] = [""]
cors_allowed_headers: list[str] = [""]
cors_exposed_headers: list[str] = []

request_max_body_size: int = 1024 * 1024 # 1MB limit

@property
def db_dsn_parsed(self) -> URL:
return make_url(self.db_dsn)

@property
def db_dsn(self) -> URL:
return URL.create(
self.db_driver,
self.db_user,
self.db_password,
self.db_host,
self.db_port,
self.db_database,
def api_bootstrapper_config(self) -> LitestarConfig:
return LitestarConfig(
service_name=settings.service_name,
service_version=settings.service_version,
service_environment=settings.service_environment,
service_debug=settings.service_debug,
opentelemetry_endpoint=settings.opentelemetry_endpoint,
sentry_dsn=settings.sentry_dsn,
cors_allowed_origins=settings.cors_allowed_origins,
cors_allowed_methods=settings.cors_allowed_methods,
cors_allowed_headers=settings.cors_allowed_headers,
cors_exposed_headers=settings.cors_exposed_headers,
logging_buffer_capacity=settings.logging_buffer_capacity,
swagger_offline_docs=settings.swagger_offline_docs,
)


Expand Down
7 changes: 3 additions & 4 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ services:
build:
context: .
dockerfile: ./Dockerfile
args:
- ENVIRONMENT=dev
restart: always
volumes:
- .:/code
Expand All @@ -15,8 +13,9 @@ services:
db:
condition: service_healthy
environment:
- DEBUG=true
- DB_ECHO=true
- SERVICE_DEBUG=true
- SERVICE_ENVIRONMENT=ci
- DB_DSN=postgresql+asyncpg://postgres:password@db/postgres
command:
["uv", "run", "python", "-m", "app"]

Expand Down
2 changes: 1 addition & 1 deletion migrations/env.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@


def get_dsn() -> URL:
db_dsn = settings.db_dsn
db_dsn = settings.db_dsn_parsed
return db_dsn.set(drivername="postgresql")


Expand Down
4 changes: 4 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ authors = [
license = "MIT License"
dependencies = [
"litestar",
"lite-bootstrap[litestar-all]",
"advanced-alchemy",
"pydantic-settings",
"granian",
Expand All @@ -19,6 +20,9 @@ dependencies = [
"psycopg2",
"sqlalchemy[asyncio]",
"asyncpg",
# tracing
"opentelemetry-instrumentation-asyncpg",
"opentelemetry-instrumentation-sqlalchemy",
]

[dependency-groups]
Expand Down
Loading