This commit is contained in:
John Lancaster
2026-06-16 21:49:35 -05:00
parent ece3e68915
commit 336bfafa03
+128 -61
View File
@@ -43,6 +43,134 @@ Create a clean, maintainable app structure where:
- Consider production-minded pool settings (`pool_pre_ping=True` and sensible recycle/timeout values). - Consider production-minded pool settings (`pool_pre_ping=True` and sensible recycle/timeout values).
- Prefer Alembic for schema migrations. - Prefer Alembic for schema migrations.
## Suggested Project Structure
Base structure (no database required):
```text
.
├─ pyproject.toml
├─ .env.example
├─ README.md
├─ src/
│ └─ app/
│ ├─ __init__.py
│ ├─ main.py
│ ├─ config.py
│ ├─ logging.py
│ ├─ api/
│ │ ├─ __init__.py
│ │ └─ health.py
│ ├─ services/
│ │ ├─ __init__.py
│ │ └─ example_service.py
│ ├─ ui/
│ │ ├─ __init__.py
│ │ ├─ components/
│ │ │ ├─ __init__.py
│ │ │ └─ nav.py
│ │ └─ pages/
│ │ ├─ __init__.py
│ │ ├─ home.py
│ │ ├─ dashboard.py
│ │ └─ about.py
│ └─ bootstrap.py
└─ tests/
├─ test_health.py
└─ test_pages_registration.py
```
### Conceptual boundaries for the base structure
- `main.py`: application entrypoint only; wires `create_app()` and process startup concerns.
- `bootstrap.py`: app composition layer; owns app factory, router registration, page registration, and lifespan wiring.
- `config.py`: configuration boundary; centralizes settings parsing and typed config objects.
- `logging.py`: observability boundary; centralizes logging format/levels/handlers so modules do not configure logging ad hoc.
- `api/`: transport boundary for HTTP endpoints; validate/shape request-response objects and delegate business work to `services/`.
- `services/`: use-case/business boundary; orchestrates domain behavior and dependencies, independent of UI rendering.
- `ui/pages/`: route-level UI boundary; one module per page, focused on layout/event handling for that page.
- `ui/components/`: reusable presentation boundary; shared UI widgets/composition utilities with no business side effects.
- `tests/`: behavior boundary; verify public behavior (health endpoint, page registration, service outcomes), not private implementation details.
### Dependency direction to keep clear
- Prefer this dependency flow:
- `main/bootstrap` -> `config/logging` + `api` + `ui/pages` + `services`
- `api` -> `services`
- `ui/pages` -> `ui/components` + `services`
- `services` -> pure helpers/clients (and later `db/` if enabled)
- Avoid reverse imports (for example, `services` importing from `ui/pages` or `api`).
- Keep page modules and API handlers as thin adapters; keep business decisions in `services/`.
## Extending This Pattern with a Database (Optional)
If database access is needed, extend with:
```text
.
├─ alembic.ini
├─ alembic/
│ ├─ env.py
│ └─ versions/
├─ src/
│ └─ app/
│ └─ db/
│ ├─ __init__.py
│ ├─ base.py
│ ├─ session.py
│ ├─ models/
│ │ ├─ __init__.py
│ │ └─ example.py
│ └─ repositories/
│ ├─ __init__.py
│ └─ example_repo.py
└─ tests/
└─ test_db_session_dependency.py
```
### Recommended database layering
- Keep `db/` focused on persistence primitives:
- `session.py`: engine + session factory + dependency helpers
- `models/`: ORM table mappings only
- `repositories/`: query/persistence operations only
- Keep transaction orchestration in `services/`, not in UI pages.
- Keep `ui/pages/` free of SQLAlchemy details; pages call services.
- Keep API handlers thin and delegate data work to services/repositories.
### Session and transaction pattern to request
- Use one engine and one sessionmaker per process, initialized at app startup.
- Use a `get_db_session()` dependency with `yield` for request-scoped sessions.
- In write flows, always use context managers for commit/rollback safety.
- In read-only flows, avoid unnecessary transactions and keep queries narrowly scoped.
- Do not share a session across concurrent tasks; open a new session per task/unit of work.
### Migration and schema workflow
- Treat Alembic migrations as the source of truth for schema evolution.
- Prefer:
- `alembic revision --autogenerate -m "..."`
- review migration script
- `alembic upgrade head`
- Avoid relying on `metadata.create_all()` for production schema management.
- Include a lightweight startup check that logs current migration state (without mutating schema).
### Configuration and runtime concerns
- Read `DATABASE_URL` from settings (`pydantic-settings`) and keep secrets out of source control.
- Configure pool options appropriate for environment (for example: `pool_pre_ping`, timeout/recycle tuning).
- Keep SQL echo/debug logging disabled in production by default.
- Consider a readiness probe (`/readyz`) that verifies DB connectivity when your deployment needs it.
### Testing guidance for DB-enabled projects
- Unit test repositories against a disposable test database.
- Integration test `get_db_session()` lifecycle and rollback behavior on failures.
- Add migration tests that validate model changes are represented in Alembic revisions.
- Add API/service tests for critical read/write paths (including uniqueness and constraint errors).
- Keep DB tests isolated and deterministic (fresh schema + transactional cleanup per test module/session).
## Extending This Pattern with LangGraph (Optional) ## Extending This Pattern with LangGraph (Optional)
If your app needs multi-step AI workflows, tool-calling loops, or human-in-the-loop approvals, a clean extension is to add a dedicated LangGraph domain layer. If your app needs multi-step AI workflows, tool-calling loops, or human-in-the-loop approvals, a clean extension is to add a dedicated LangGraph domain layer.
@@ -108,67 +236,6 @@ If your app needs multi-step AI workflows, tool-calling loops, or human-in-the-l
- Add tests ensuring `thread_id` reuse resumes correctly and new IDs start fresh sessions. - Add tests ensuring `thread_id` reuse resumes correctly and new IDs start fresh sessions.
- Add regression tests for state-schema evolution and serialization compatibility. - Add regression tests for state-schema evolution and serialization compatibility.
## Suggested Project Structure
Base structure (no database required):
```text
.
├─ pyproject.toml
├─ .env.example
├─ README.md
├─ src/
│ └─ app/
│ ├─ __init__.py
│ ├─ main.py
│ ├─ config.py
│ ├─ logging.py
│ ├─ api/
│ │ ├─ __init__.py
│ │ └─ health.py
│ ├─ services/
│ │ ├─ __init__.py
│ │ └─ example_service.py
│ ├─ ui/
│ │ ├─ __init__.py
│ │ ├─ components/
│ │ │ ├─ __init__.py
│ │ │ └─ nav.py
│ │ └─ pages/
│ │ ├─ __init__.py
│ │ ├─ home.py
│ │ ├─ dashboard.py
│ │ └─ about.py
│ └─ bootstrap.py
└─ tests/
├─ test_health.py
└─ test_pages_registration.py
```
If database access is needed, extend with:
```text
.
├─ alembic.ini
├─ alembic/
│ ├─ env.py
│ └─ versions/
├─ src/
│ └─ app/
│ └─ db/
│ ├─ __init__.py
│ ├─ base.py
│ ├─ session.py
│ ├─ models/
│ │ ├─ __init__.py
│ │ └─ example.py
│ └─ repositories/
│ ├─ __init__.py
│ └─ example_repo.py
└─ tests/
└─ test_db_session_dependency.py
```
## Implementation Notes ## Implementation Notes
- Prefer an explicit page registration function pattern, for example: - Prefer an explicit page registration function pattern, for example: