Skip to content

Commit b8f017e

Browse files
committed
update packages
1 parent fa634f8 commit b8f017e

3 files changed

Lines changed: 78 additions & 32 deletions

File tree

‎CLAUDE.md‎

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Commands
6+
7+
All development runs through Docker Compose via `just` (see `Justfile`). The app and Postgres come up together; running tests/migrations outside Docker is not the supported path.
8+
9+
- `just` — full pipeline: install, lint, build, test
10+
- `just run` — start the API (runs `alembic upgrade head` then `python -m app`); exposed on `:8000`
11+
- `just test [pytest-args]` — downgrades DB to `base`, upgrades to `head`, then runs pytest. Wraps with `down` before and after. Pass through args, e.g. `just test tests/test_decks.py::test_create -k pattern -x`
12+
- `just migration -m "message"` — autogenerate an Alembic revision against an up-to-date DB
13+
- `just lint` — `eof-fixer`, `ruff format`, `ruff check --fix`, then `ty check` (this project uses `ty`, not mypy — suppress with `# ty: ignore[<rule>]`)
14+
- `just build` / `just down` / `just sh` — build image / tear down stack / shell into the app container
15+
- `just install` — `uv lock --upgrade` + `uv sync --all-extras --all-groups --frozen`
16+
17+
Python is 3.14, dependencies managed by `uv`. Inside the container, raw commands look like `uv run pytest ...`, `uv run alembic ...`.
18+
19+
## Architecture
20+
21+
**Stack**: Litestar + SQLAlchemy 2 (async) + advanced-alchemy + Alembic + Postgres + Granian (ASGI server) + modern-di (IoC) + lite-bootstrap (observability/CORS/Sentry/OTel wiring).
22+
23+
**Request flow**: `app/__main__.py` → `granian` → `app.application:build_app` (factory) → `LitestarBootstrapper` from `lite-bootstrap` wraps a `litestar.Litestar` with OpenTelemetry (asyncpg + SQLAlchemy instrumentors), Sentry, CORS, Swagger, etc., based on `Settings.api_bootstrapper_config`.
24+
25+
**Dependency injection** (`app/ioc.py`): `modern_di.Container` is created in `build_app` with the `Dependencies` group and attached via `modern_di_litestar.ModernDIPlugin`. Route handlers receive repositories as parameters; `application.py` declares them with `modern_di_litestar.FromDI(...)` so Litestar resolves them per-request. Provider scopes:
26+
- `database_engine` — application-scoped factory, finalizer disposes the engine
27+
- `session` — request-scoped, finalizer closes the session
28+
- `*_repository` — request-scoped, depend on `session`, configured with `auto_commit=True` (advanced-alchemy commits on success / rolls back on exception)
29+
30+
**Persistence**: Models inherit `advanced_alchemy.base.BigIntAuditBase` (gives `id: BigInt`, `created_at`, `updated_at`). Metadata is shared with `orm.DeclarativeBase.metadata` in `app/models.py` so Alembic autogen sees everything. Repositories are `SQLAlchemyAsyncRepositoryService[Model]` with a nested `BaseRepository(SQLAlchemyAsyncRepository[Model])`. Custom `CustomAsyncSession` in `app/resources/db.py` overrides `close()` so test transactions are not actually closed when the session is bound to an `AsyncConnection` — this is what makes the per-test rollback fixture work.
31+
32+
**Test isolation** (`tests/conftest.py`): `db_session` fixture opens a connection, starts a transaction, starts a SAVEPOINT, then **overrides** `Dependencies.database_engine` in the DI container to return that connection. All requests in the test reuse this connection; the outer transaction is rolled back in teardown, so DB state is clean between tests with no truncation needed. `app` and `client` fixtures build the real app and run it through `httpx.ASGITransport` + `asgi_lifespan.LifespanManager`. Polyfactory `SQLAlchemyFactory` is wired up via `set_async_session_in_base_sqlalchemy_factory`.
33+
34+
**Migrations**: `migrations/env.py` reads `app.models.METADATA` and rewrites the DSN driver from `postgresql+asyncpg` → `postgresql` (Alembic uses sync psycopg2). Always run autogen against an upgraded DB — `just migration` enforces this.
35+
36+
**Settings** (`app/settings.py`): `pydantic_settings.BaseSettings` reads from env vars (see `docker-compose.yml` for `SERVICE_DEBUG`, `SERVICE_ENVIRONMENT`, `DB_DSN`). `api_bootstrapper_config` builds the `LitestarConfig` consumed by `lite-bootstrap`.
37+
38+
## Conventions
39+
40+
- Routes live in `app/api/`, registered into a single `ROUTER` (prefix `/api`) referenced from `application.py`. Add a new resource by creating `app/api/<name>.py`, defining handlers + a `Router`, and adding it to `application.build_app`.
41+
- Pydantic schemas in `app/schemas.py` use `from_attributes=True` (via `Base`) so they validate directly from ORM instances (`schemas.X.model_validate(orm_instance)`).
42+
- Domain exceptions: register handlers in `application.build_app`'s `exception_handlers` dict (see `DuplicateKeyError` → `exceptions.duplicate_key_error_handler`). For per-handler 404s the code raises `litestar.exceptions.HTTPException` directly.
43+
- `ruff` is configured with `select = ["ALL"]` and a line length of 120 — expect strict lint. Type-check with `ty`; use `# ty: ignore[<rule>]` for suppressions (already used for `invalid-argument-type` around `LifespanManager` / `ASGITransport` / DTO list construction).

‎pyproject.toml‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,9 @@ isort.no-lines-before = ["standard-library", "local-folder"]
7474
addopts = "--cov=. --cov-report term-missing"
7575
asyncio_mode = "auto"
7676
asyncio_default_fixture_loop_scope = "function"
77+
filterwarnings = [
78+
"ignore::lite_bootstrap.InstrumentNotReadyWarning",
79+
]
7780

7881
[tool.coverage.report]
7982
exclude_also = [

0 commit comments

Comments
 (0)