This file guides automated coding agents working in this repository. Follow existing patterns and run the same tooling as humans.
- Language: Python 3.9+
- Package:
keyshield(src layout) - Async-first services and repositories
- Optional extras for FastAPI, SQLAlchemy, Argon2, bcrypt, aiocache
- Dependency manager:
uv - Build backend:
hatchling - Format/lint: Ruff
- Type checks: Ty and Pyrefly
- Security lint: Bandit
- Tests: pytest + pytest-cov + pytest-asyncio
uv sync --extra all --group devsource .venv/bin/activate(or Windows.venv\Scripts\Activate.ps1)
make lint- Runs:
ruff format,ruff check --fix,ty check,pyrefly check,bandit
- Runs:
- Direct commands:
uv run ruff format .uv run ruff check --fix .uv run ty check .uv run pyrefly check .uv run bandit -c pyproject.toml -r src examples -q
- Full test suite:
uv run pytest - Single test file:
uv run pytest tests/unit/test_service.py - Single test by node id:
uv run pytest tests/unit/test_service.py::test_create_api_key
- Single test by keyword:
uv run pytest -k "create_api_key"
- Run a test class:
uv run pytest tests/unit/test_service.py::TestApiKeyService
Pytest defaults include coverage reports (XML/HTML/terminal) via pyproject.toml.
Artifacts: coverage.xml, htmlcov/, junit.xml.
- Ruff handles formatting; follow it instead of manual styling.
- Line length: 120 characters.
- Indentation: 4 spaces.
- Quotes: double quotes.
- Standard library imports first, then third-party, then local.
- Prefer explicit imports over wildcard.
- Keep module-level imports; avoid local imports unless required to break cycles.
- In optional dependency modules, guard imports with
try/except ModuleNotFoundError.
- Use Python 3.9+ type hints everywhere.
- Prefer
Optional[T]andlist[T]/dict[K, V]style. - Return concrete types (
ApiKey,list[ApiKey], etc.) notAny. - Use
Annotatedfor FastAPI params when needed.
- Classes:
CamelCase(e.g.,ApiKeyService). - Functions/vars:
snake_case. - Constants:
UPPER_SNAKE_CASE. - Private helpers: prefix
_(e.g.,_verify_entity). - API key fields follow existing names (
key_id,key_secret,key_hash).
- Use
@dataclassfor small data carriers (e.g., parsed key results). - Use Pydantic models for API request/response schemas.
- Domain exceptions live in
keyshield.domain.errors. - Raise domain errors in services and repositories, translate to HTTP exceptions in API layer.
- Preserve original errors with
raise ... from exc. - Use specific exceptions (
KeyNotFound,InvalidKey,ConfigurationError) rather thanValueErrorunless appropriate. - Validate inputs early; prefer guard clauses.
- Services and repositories are async; keep APIs async throughout.
- Use
awaitfor repo calls and when composing service logic. - Avoid blocking I/O in async paths.
- Never log or return plaintext API keys after creation.
- Enforce API key parsing/validation via
ParsedApiKeyand_get_partspatterns. - Use timing jitter on auth failures (
verify_keybehavior). - Treat secret pepper as external configuration; do not hardcode.
- Use FastAPI dependency injection with
Depends. - Map domain errors to
HTTPExceptionwith correct status codes. - API responses use Pydantic models; keep output schemas stable.
- Tests live in
tests/with unit and integration subpackages. - Prefer clear arrange/act/assert structure.
- Use fixtures from
tests/conftest.pywhere available.
src/keyshield/core librarytests/unit/integration testsexamples/example applications (used in docs)
make lintis the canonical lint entrypoint.- The project warns when the default pepper is used; tests expect that behavior.
- Examples are used in docs; keep them runnable.
- No
.cursor/rules/,.cursorrules, or.github/copilot-instructions.mdfound in this repo.
- Preserve public API behavior and exception types.
- Keep docstrings consistent with existing style.
- Match current error messages when possible (tests may assert them).
- Avoid introducing extra dependencies without updating optional extras.