Skip to content

Commit e13a7c9

Browse files
authored
migrate to lite-bootstrap (#15)
1 parent bb36a82 commit e13a7c9

File tree

9 files changed

+540
-107
lines changed

9 files changed

+540
-107
lines changed

.github/workflows/main.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,10 @@ jobs:
5151
uv run alembic upgrade head
5252
uv run pytest . --cov=. --cov-report xml
5353
env:
54-
ENVIRONMENT: dev
54+
SERVICE_ENVIRONMENT: ci
5555
PYTHONDONTWRITEBYTECODE: 1
5656
PYTHONUNBUFFERED: 1
57-
DB_HOST: 127.0.0.1
57+
DB_DSN: postgresql+asyncpg://postgres:password@127.0.0.1/postgres
5858
- name: Upload coverage to Codecov
5959
uses: codecov/[email protected]
6060
env:

app/__main__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import granian
22
from granian.constants import Interfaces, Loops
3+
from granian.log import LogLevels
34

45
from app.settings import settings
56

@@ -10,7 +11,6 @@
1011
address="0.0.0.0", # noqa: S104
1112
port=settings.app_port,
1213
interface=Interfaces.ASGI,
13-
log_dictconfig={"root": {"level": "INFO"}} if not settings.debug else {},
14-
log_level=settings.log_level,
14+
log_level=LogLevels(settings.log_level),
1515
loop=Loops.uvloop,
1616
).serve()

app/application.py

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,40 @@
1+
import dataclasses
2+
13
import litestar
24
import modern_di_litestar
35
from advanced_alchemy.exceptions import DuplicateKeyError
4-
from litestar.openapi import OpenAPIConfig
5-
from litestar.openapi.plugins import SwaggerRenderPlugin
6+
from lite_bootstrap import LitestarBootstrapper
7+
from litestar.config.app import AppConfig
8+
from opentelemetry.instrumentation.asyncpg import AsyncPGInstrumentor
9+
from opentelemetry.instrumentation.sqlalchemy import SQLAlchemyInstrumentor
610

711
from app import exceptions, ioc
812
from app.api.decks import ROUTER
913
from app.settings import settings
1014

1115

1216
def build_app() -> litestar.Litestar:
13-
return litestar.Litestar(
14-
debug=settings.debug,
15-
exception_handlers={
16-
DuplicateKeyError: exceptions.duplicate_key_error_handler,
17-
},
18-
route_handlers=[ROUTER],
19-
plugins=[modern_di_litestar.ModernDIPlugin()],
20-
dependencies={
21-
"decks_service": modern_di_litestar.FromDI(ioc.Dependencies.decks_service),
22-
"cards_service": modern_di_litestar.FromDI(ioc.Dependencies.cards_service),
23-
},
24-
openapi_config=OpenAPIConfig(
25-
title="Litestar Example",
26-
description="Example of Litestar with Scalar OpenAPI docs",
27-
version="0.0.1",
28-
render_plugins=[SwaggerRenderPlugin()],
17+
bootstrap_config = dataclasses.replace(
18+
settings.api_bootstrapper_config,
19+
application_config=AppConfig(
20+
exception_handlers={
21+
DuplicateKeyError: exceptions.duplicate_key_error_handler,
22+
},
23+
route_handlers=[ROUTER],
24+
plugins=[modern_di_litestar.ModernDIPlugin()],
25+
dependencies={
26+
"decks_service": modern_di_litestar.FromDI(ioc.Dependencies.decks_service),
27+
"cards_service": modern_di_litestar.FromDI(ioc.Dependencies.cards_service),
28+
},
29+
request_max_body_size=settings.request_max_body_size,
2930
),
31+
opentelemetry_instrumentors=[
32+
SQLAlchemyInstrumentor(),
33+
AsyncPGInstrumentor(capture_parameters=True), # type: ignore[no-untyped-call]
34+
],
3035
)
36+
bootstrapper = LitestarBootstrapper(bootstrap_config=bootstrap_config)
37+
return bootstrapper.bootstrap()
3138

3239

3340
application = build_app()

app/resources/db.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@
1212
async def create_sa_engine() -> typing.AsyncIterator[sa.AsyncEngine]:
1313
logger.info("Initializing SQLAlchemy engine")
1414
engine = sa.create_async_engine(
15-
url=settings.db_dsn,
16-
echo=settings.debug,
17-
echo_pool=settings.debug,
15+
url=settings.db_dsn_parsed,
16+
echo=settings.service_debug,
17+
echo_pool=settings.service_debug,
1818
pool_size=settings.db_pool_size,
1919
pool_pre_ping=settings.db_pool_pre_ping,
2020
max_overflow=settings.db_max_overflow,

app/settings.py

Lines changed: 38 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,53 @@
11
import pydantic_settings
2-
from granian.log import LogLevels
3-
from sqlalchemy.engine.url import URL
2+
from lite_bootstrap import LitestarConfig
3+
from sqlalchemy.engine.url import URL, make_url
44

55

66
class Settings(pydantic_settings.BaseSettings):
7-
debug: bool = False
8-
log_level: LogLevels = LogLevels.info
9-
10-
db_driver: str = "postgresql+asyncpg"
11-
db_host: str = "db"
12-
db_port: int = 5432
13-
db_user: str = "postgres"
14-
db_password: str = "password"
15-
db_database: str = "postgres"
7+
service_name: str = "FastAPI template"
8+
service_version: str = "1.0.0"
9+
service_environment: str = "local"
10+
service_debug: bool = False
11+
log_level: str = "info"
1612

13+
db_dsn: str = "postgresql+asyncpg://postgres:password@db/postgres"
1714
db_pool_size: int = 5
1815
db_max_overflow: int = 0
19-
db_echo: bool = False
2016
db_pool_pre_ping: bool = True
2117

2218
app_port: int = 8000
2319

20+
opentelemetry_endpoint: str = ""
21+
sentry_dsn: str = ""
22+
logging_buffer_capacity: int = 0
23+
swagger_offline_docs: bool = True
24+
25+
cors_allowed_origins: list[str] = ["http://localhost:5173"]
26+
cors_allowed_methods: list[str] = [""]
27+
cors_allowed_headers: list[str] = [""]
28+
cors_exposed_headers: list[str] = []
29+
30+
request_max_body_size: int = 1024 * 1024 # 1MB limit
31+
32+
@property
33+
def db_dsn_parsed(self) -> URL:
34+
return make_url(self.db_dsn)
35+
2436
@property
25-
def db_dsn(self) -> URL:
26-
return URL.create(
27-
self.db_driver,
28-
self.db_user,
29-
self.db_password,
30-
self.db_host,
31-
self.db_port,
32-
self.db_database,
37+
def api_bootstrapper_config(self) -> LitestarConfig:
38+
return LitestarConfig(
39+
service_name=settings.service_name,
40+
service_version=settings.service_version,
41+
service_environment=settings.service_environment,
42+
service_debug=settings.service_debug,
43+
opentelemetry_endpoint=settings.opentelemetry_endpoint,
44+
sentry_dsn=settings.sentry_dsn,
45+
cors_allowed_origins=settings.cors_allowed_origins,
46+
cors_allowed_methods=settings.cors_allowed_methods,
47+
cors_allowed_headers=settings.cors_allowed_headers,
48+
cors_exposed_headers=settings.cors_exposed_headers,
49+
logging_buffer_capacity=settings.logging_buffer_capacity,
50+
swagger_offline_docs=settings.swagger_offline_docs,
3351
)
3452

3553

docker-compose.yml

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@ services:
33
build:
44
context: .
55
dockerfile: ./Dockerfile
6-
args:
7-
- ENVIRONMENT=dev
86
restart: always
97
volumes:
108
- .:/code
@@ -15,8 +13,9 @@ services:
1513
db:
1614
condition: service_healthy
1715
environment:
18-
- DEBUG=true
19-
- DB_ECHO=true
16+
- SERVICE_DEBUG=true
17+
- SERVICE_ENVIRONMENT=ci
18+
- DB_DSN=postgresql+asyncpg://postgres:password@db/postgres
2019
command:
2120
["uv", "run", "python", "-m", "app"]
2221

migrations/env.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99

1010
def get_dsn() -> URL:
11-
db_dsn = settings.db_dsn
11+
db_dsn = settings.db_dsn_parsed
1212
return db_dsn.set(drivername="postgresql")
1313

1414

pyproject.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ authors = [
1010
license = "MIT License"
1111
dependencies = [
1212
"litestar",
13+
"lite-bootstrap[litestar-all]",
1314
"advanced-alchemy",
1415
"pydantic-settings",
1516
"granian",
@@ -19,6 +20,9 @@ dependencies = [
1920
"psycopg2",
2021
"sqlalchemy[asyncio]",
2122
"asyncpg",
23+
# tracing
24+
"opentelemetry-instrumentation-asyncpg",
25+
"opentelemetry-instrumentation-sqlalchemy",
2226
]
2327

2428
[dependency-groups]

0 commit comments

Comments
 (0)