This file provides guidelines for AI coding agents (e.g., GitHub Copilot, Cursor, Codex) working in this repository.
This is a Python project template that provides a pre-configured, production-ready starting point for Python applications. It includes out-of-the-box support for:
- Packaging & dependency management via uv
- CLI via click
- Testing & coverage via pytest and coverage
- Linting, formatting & import sorting via ruff
- Type checking via pyright
- Pre-commit hooks via pre-commit
- Documentation via MkDocs with mkdocstrings, auto-deployed to GitHub Pages
- CI/CD via GitHub Actions
- Containerisation via Docker and Dev Containers
- Environment & Task Management via mise
├── .devcontainer/ # Dev container configuration
├── .github/ # GitHub-specific files (Actions, templates, Dependabot)
├── .vscode/ # VS Code settings
├── docs/ # Documentation source
│ ├── README.md # Project README and MkDocs home page
│ ├── CONTRIBUTING.md # Contributing guidelines
│ └── reference/ # Auto-generated API reference pages
├── project/ # Main source package (renamed via `mise run project`)
│ ├── __init__.py
│ └── app.py # CLI entry point
├── tests/ # Test suite
│ ├── conftest.py # Shared pytest fixtures and hooks
│ └── test_app.py # Sample tests
├── compose.yml # Docker Compose file
├── Dockerfile # App container
├── mise.toml # Workflow automation tasks
├── mkdocs.yml # MkDocs configuration
├── pyproject.toml # Project metadata, dependencies, and tool configuration
└── uv.lock # Locked dependency versions (do not edit manually)
Note: The
project/folder is the template placeholder. After initialising a real project withmise run project name=..., it is renamed to the chosen package name.
- mise
- Docker (for Dev Container or containerised runs)
# Install tools, project dependencies, and pre-commit hooks
mise run devmise run project --name "my-project" --description "My app" --author "Your Name" --email "you@example.com" --github "your-username"| Task | Command | Alias |
|---|---|---|
| Install dependencies | mise run install |
i |
| Update dependencies | mise run update |
u |
| Lint and format | mise run lint |
l |
| Run tests with coverage | mise run test |
t |
| Run app locally | mise run app |
a |
| Run app in Docker | docker compose run app |
|
| Serve docs locally | mise run local-docs |
d |
| Deploy docs to GitHub Pages | mise run docs |
|
| Setup dev environment | mise run dev |
|
| Full setup from scratch | mise run all |
Refer to
docs/README.mdfor the full list of available targets. Add new targets tomise.tomlas needed.
- Line length: 120 characters (configured in
pyproject.tomlunder[tool.ruff]) - Linter/formatter:
ruff— enforcesE(pycodestyle errors) andI(isort) rules - Type checker:
pyright— all imports must resolve; missing imports are errors - Pre-commit: hooks run
ruffautomatically before every commit - Run
mise run lintto format, sort imports, and check types manually - All public functions and classes must have docstrings (used by
mkdocstringsfor API docs)
- Tests live in the
tests/directory and mirror the source structure - Run the full test suite with coverage using
mise run test(runspytestviacoverage) - Coverage is measured with branch coverage enabled; the report is printed to the terminal and exported as
coverage.xml - Shared fixtures belong in
tests/conftest.py - Test files must be named
test_*.py
- Work on feature branches; open a pull request to
main - Pre-commit hooks enforce formatting and linting on every commit
- CI (
.github/workflows/check.yml) runs lint and tests on every push - Do not commit directly to
main
- Always use uv for dependency management (
uv add <package>) - Use Pydantic for data models
- Use Pydantic-settings for environment variable configuration in a
settings.pyfile
- Use pytest, not unittest
- Use
pytestmonkeypatch andpytest-mockfor mocking instead ofunittest.MagicMock - Do not cheat! Never modify source code just to make a failing test pass. Fix real bugs in source code and fix incorrect assertions in tests
Use mise tasks for all common workflows: lint, test, run locally, and deploy. Refer to docs/README.md for currently available tasks. Add new tasks to mise.toml as needed.
- Python 3.12+ required
- Dependencies are managed via
pyproject.tomland locked inuv.lock - Do not edit
uv.lockdirectly; usemise run updateto update dependencies
Every field in a Pydantic model or pydantic-settings class must be documented using Field(description="..."). This makes descriptions machine-readable and visible in generated JSON schemas.
from uuid import uuid4
from pydantic import BaseModel, Field
class Item(BaseModel, populate_by_name=True, alias_generator=to_camel):
id: str = Field(description="Unique item identifier.", default_factory=lambda:str(uuid4()))
name: str = Field(description="Human-readable item name.")All BaseModel subclasses must be defined with populate_by_name=True and alias_generator=to_camel so that JSON payloads can use camelCase while Python attributes use snake_case. Always serialise with model_dump(by_alias=True, exclude_none=True) to produce camelCase JSON output and omit unset optional fields.
from uuid import uuid4
from pydantic import BaseModel, Field
from pydantic.alias_generators import to_camel
class Item(BaseModel, populate_by_name=True, alias_generator=to_camel):
item_id: str = Field(description="Unique item identifier.", default_factory=str(uuid4()))
# Accepts {"itemId": "..."} from JSON; attribute is item.item_id
# model_dump() → {"item_id": ...}
# model_dump(by_alias=True, exclude_none=True) → {"itemId": ...}Do not use model_config = ConfigDict(...) or model_config = SettingsConfigDict(...). Pass configuration options as keyword arguments to the base class instead.
# Good
class Item(BaseModel, extra="allow", populate_by_name=True, alias_generator=to_camel): ...
class Settings(BaseSettings, case_sensitive=False): ...
# Bad
class Item(BaseModel):
model_config = ConfigDict(extra="allow")Do not add unnecessary imports like from __future__ import annotations. Always use explicit from x import y form:
from json import dumps, loads
from pytest import fixture, main, raises
from aws_cdk.aws_lambda import Code, Function, RuntimeEvery test file must end with:
if __name__ == "__main__":
main()