5.4 KiB
5.4 KiB
trigger, description
| trigger | description |
|---|---|
| model_decision | When working on a Python backend project, setting up Python project structure, or organizing Python services and APIs |
Python Backend Layout
Use this structure for Python backend applications. The vertical slice principle applies — features are packages, not technical layers.
apps/
backend/ # Backend application source code
pyproject.toml # Project metadata, dependencies, tool configs
src/
yourapp/ # Importable package (src-layout, preferred)
main.py # Entry point: creates app, wires dependencies, starts server
platform/ # Foundational technical concerns (the "Framework")
database.py # DB engine and session factory
server.py # ASGI app setup (FastAPI/Starlette router, middleware)
logger.py # structlog / logging configuration
config.py # Settings (pydantic-settings BaseSettings)
features/ # Business Features (Vertical Slices)
task/ # Task management
__init__.py
# --- Public API ---
service.py # TaskService class (public API of this feature)
# --- Delivery (HTTP) ---
router.py # FastAPI APIRouter with HTTP endpoints
schemas.py # Pydantic request/response models (API boundary only)
test_router.py # Component tests (TestClient + mock service)
# --- Domain (Business Logic) ---
logic.py # Pure domain functions (no I/O)
models.py # Domain dataclasses (Task, Priority, etc.)
errors.py # Feature-specific exceptions
test_logic.py # Unit tests (pure functions — no mocks needed)
# --- Storage (Data Access) ---
storage.py # TaskStorage Protocol (interface)
storage_pg.py # PostgreSQL implementation (asyncpg / SQLAlchemy)
storage_mock.py # InMemoryTaskStorage (test implementation)
test_storage_pg.py # Integration tests (real DB via testcontainers)
order/ # Order management
service.py
router.py
schemas.py
logic.py
models.py
storage.py
storage_pg.py
storage_mock.py
...
tests/ # Optional: top-level E2E tests (cross-feature boundaries)
e2e/
test_create_task_api.e2e.py
Key Python conventions:
src/layout is strongly preferred — prevents accidental imports of the development tree and matches packaging best practices (PEP 517 /pypa/build)pyproject.tomlis the single configuration file forruff,mypy,pytest,bandit, and packaging metadata — do not createsetup.cfg,.flake8, ormypy.inifiles- Feature packages use
__init__.py— keep it empty or use it solely to re-export the feature's public API platform/holds technical infrastructure that features depend on (database sessions, configuration, logging); features never import each other'splatform/code directly- Tests co-locate with the code they test (
test_*.pyin the same directory) except for E2E tests which go intests/e2e/ storage_mock.pyis a production-quality in-memory implementation, not aunittest.Mock— it is the recommended test double for unit and integration tests
Dependency Wiring (main.py)
# src/yourapp/main.py
from yourapp.platform.database import create_engine
from yourapp.platform.server import create_app
from yourapp.features.task.storage_pg import PostgresTaskStorage
from yourapp.features.task.service import TaskService
from yourapp.features.task.router import build_router as build_task_router
def create_application() -> FastAPI:
engine = create_engine()
task_storage = PostgresTaskStorage(engine=engine)
task_service = TaskService(storage=task_storage)
app = create_app()
app.include_router(build_task_router(service=task_service))
return app
app = create_application()
Configuration (platform/config.py)
# src/yourapp/platform/config.py
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
database_url: str
secret_key: str
debug: bool = False
model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8")
def get_settings() -> Settings:
return Settings()
pyproject.toml Baseline
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "yourapp"
requires-python = ">=3.11"
[tool.hatch.build.targets.wheel]
packages = ["src/yourapp"]
[tool.ruff]
line-length = 100
target-version = "py311"
[tool.ruff.lint]
select = ["E", "F", "I", "N", "UP", "S", "B", "ANN"]
[tool.mypy]
strict = true
python_version = "3.11"
[tool.pytest.ini_options]
testpaths = ["src"]
asyncio_mode = "auto"
[tool.bandit]
skips = ["B101"] # assert statements allowed in test files
Related Principles
- Project Structure @project-structure.md (core philosophy)
- Python Idioms and Patterns @python-idioms-and-patterns.md (coding idioms, error handling, naming)