diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..9cb6f96 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,85 @@ +# Contributing to the Cosmic FastAPI template + +This repository is a [Copier](https://copier.readthedocs.io/) template. Thanks for helping evolve it. + +## Author-facing vs generated project + +The repository serves two audiences, kept physically separated (see +[ADR 0015](docs/adr/0015-copier-template-engine.md)): + +- **The repository root is author-facing** — it documents how to use and evolve the template and is never + rendered into a generated project. This includes `copier.yml`, the root `README.md`, this `CONTRIBUTING.md`, + the bake-and-test suite in `tests/`, the template CI workflow in `.github/workflows/`, the root + `pyproject.toml`, `.gitignore`, `.pre-commit-config.yaml`, and the template-meta ADRs in `docs/adr/`. +- **The `template/` directory is the generated project.** Copier renders it via `_subdirectory: template` + with `_templates_suffix: .jinja`: files ending in `.jinja` are rendered, everything else is copied + verbatim, and path segments containing `{{ ... }}` always render. Generated docs must read as the new + project itself — never hardcode the brand "Cosmic FastAPI" or `src/template`; use a Copier variable + (`{{ project_name }}`, `{{ package_name }}`, ...) or generic prose. + +## Copier variables and feature flags + +The questions in `copier.yml` drive project identity and optional content: + +| Variable | Description | +|------------------------|--------------------------------------------------------------| +| `project_name` | Human-readable project name (Swagger title) | +| `project_slug` | Kebab-case slug for repo, container, and database names | +| `package_name` | Importable Python package name (snake_case) | +| `project_description` | One-line project description | +| `author_name` | Author or organization name | +| `author_email` | Author contact email | +| `author_url` | Author or project URL | +| `github_owner` | GitHub owner/org used in clone URLs and image tags | +| `license` | License (`MIT`, `Apache-2.0`, `BSD-3-Clause`, `Proprietary`) | +| `python_version` | Minimum supported Python version | +| `database` | `postgres` or `sqlite` | +| `database_url` | Default SQLAlchemy async database URL | +| `include_user_example` | Include the example User domain slice | + +Two feature axes shape generated output: `database` (PostgreSQL by default, SQLite optional — see ADR 0018) +and `include_user_example` (the example User slice). Guard optional files with `_exclude` patterns or +templated names in `copier.yml`, and cover both states in the bake matrix. + +Do not rename `copier.yml` variables and do not template `uv.lock` — a post-copy task regenerates it with +`uv lock`. Wrap literal GitHub Actions `${{ ... }}` and other literal brace blocks in `{% raw %}{% endraw %}` +inside `.jinja` files. + +## Bake and test + +The template cannot import its own package in place once the package path contains Jinja +(`src/{{ package_name }}/`). It is validated by **baking**: rendering it to a temporary directory and running +the generated project's full offline quality gate. + +```bash +uv sync +uv run pytest +``` + +`tests/test_bake.py` snapshots the tracked working tree, renders it for each cell of the +`database × include_user_example` matrix (four combos), then inside each baked project runs `uv sync`, +`ruff check`, `ruff format --check`, `pyrefly check`, `pytest -m "not integration"` at 100% coverage, and +`make adr-check` to validate the generated ADR registry. The PostgreSQL integration tier requires Docker and +runs in a dedicated CI stage (see ADR 0019), not during the bake. The same matrix runs in CI via +`.github/workflows/template-ci.yml`. + +## Adding or superseding an ADR + +There are two ADR sets, sharing one historical number sequence: + +- **Generated-standard ADRs** live in `template/docs/adr/`. They describe the runtime architecture a new + project inherits and ship into generated projects as starter records. They are validated by + `make adr-check` inside a generated project (and during the bake). Add the next number, update + `template/docs/adr/README.md`, and include an `Agent Guidance` section for accepted ADRs. To supersede one, + set its status to `Superseded` and link `Superseded by` to an accepted replacement. +- **Template-meta ADRs** live in the root `docs/adr/` and record decisions about the template itself (the + engine, the author-vs-generated layout, the bake workflow). Update the root `docs/adr/README.md` registry. + +The number sequence is shared, so the generated set has a gap at 0015 (the Copier-engine decision is +template-meta and lives at the root). The registry validator has no contiguity requirement, so gaps are fine. + +## Conventional commits + +Use [Conventional Commits](https://www.conventionalcommits.org/) for all Git history (see +[ADR 0009](template/docs/adr/0009-conventional-commits.md)): `feat:`, `fix:`, `docs:`, `refactor:`, +`test:`, `chore:`, and so on. diff --git a/README.md b/README.md index 41eb8f6..ac5b350 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@
-
+
@@ -16,7 +16,7 @@ guidelines: a domain-first architecture with Pydantic 2, SQLAlchemy 2, Alembic, pytest suite at 100% coverage. This repository serves two audiences, kept physically separated (see -[ADR 0015](template/docs/adr/0015-copier-template-engine.md)): +[ADR 0015](docs/adr/0015-copier-template-engine.md)): - **Author-facing** (this `README`, `copier.yml`, the bake-and-test suite) lives at the repository root and is never rendered into a generated project. diff --git a/template/docs/adr/0015-copier-template-engine.md b/docs/adr/0015-copier-template-engine.md similarity index 100% rename from template/docs/adr/0015-copier-template-engine.md rename to docs/adr/0015-copier-template-engine.md diff --git a/docs/adr/README.md b/docs/adr/README.md new file mode 100644 index 0000000..7badacd --- /dev/null +++ b/docs/adr/README.md @@ -0,0 +1,26 @@ +# Template Architecture Decision Records + +This directory records decisions about the **template itself** — how this +repository is authored, generated, and evolved. These are author-facing records; +they are never rendered into a generated project. + +The **generated service's** architectural standard (the Cosmic Python defaults a +new project inherits) lives in [`template/docs/adr/`](../../template/docs/adr/). + +ADR numbers continue one shared historical sequence across both sets. The +generated set therefore has a gap at 0015 — that number belongs to the +template-engine decision recorded here. + +## Decision Records + +| ADR | Decision | Status | +| --- | --- | --- | +| [0015](0015-copier-template-engine.md) | Copier as the Template Engine | Accepted | + +## Creating a template-meta ADR + +Record a new ADR here when changing how the template is authored or generated +(the engine, the author-vs-generated layout, the bake-and-test workflow). Assign +the next four-digit number in the shared sequence and keep the decision focused. +Decisions about the generated service's runtime architecture belong in +[`template/docs/adr/`](../../template/docs/adr/) instead. diff --git a/template/docs/cosmic_fastapi.png b/docs/cosmic_fastapi.png similarity index 100% rename from template/docs/cosmic_fastapi.png rename to docs/cosmic_fastapi.png diff --git a/template/.gitignore b/template/.gitignore index 36eb4c5..d9f943d 100644 --- a/template/.gitignore +++ b/template/.gitignore @@ -62,7 +62,7 @@ local_settings.py db.sqlite3 db.sqlite3-journal -# SQLite databases (default DATABASE_URL writes ./cosmic-fastapi.db) +# Local SQLite database files *.db # Flask stuff: diff --git a/template/README.md.jinja b/template/README.md.jinja index 8dc3d6f..9c4ae1e 100644 --- a/template/README.md.jinja +++ b/template/README.md.jinja @@ -1,24 +1,14 @@ -
-
-
-Kickoff your app like a kangaroo through the stars: clean, fast, and unapologetically structured. -
- ---- - # {{ project_name }} -**{{ project_name }}** is a [FastAPI](https://fastapi.tiangolo.com/) project generated from the -[Cosmic FastAPI](https://github.com/{{ github_owner }}/cosmic-fastapi) template, following -the [Cosmic Python](https://www.cosmicpython.com/) guidelines. +**{{ project_name }}** is a [FastAPI](https://fastapi.tiangolo.com/) service following the +[Cosmic Python](https://www.cosmicpython.com/) guidelines: a domain-first architecture with Pydantic 2, +SQLAlchemy 2, Alembic, uv, Ruff, Pyrefly, and a pytest suite. ## Content -* [Cosmic FastAPI](#cosmic-fastapi) +* [{{ project_name }}](#{{ project_slug }}) * [Content](#content) * [About](#about) * [Features](#features) @@ -52,13 +42,13 @@ the [Cosmic Python](https://www.cosmicpython.com/) guidelines. ### Features -This template includes `FastAPI` using the fastest `Pydantic V2` model validation. Designed for Event-Driven -Architecture (EDA) and Domain-Driven Design (DDD). Providing a clean and simple structure to start a new project. With +This project includes `FastAPI` using the fastest `Pydantic V2` model validation. Designed for Event-Driven +Architecture (EDA) and Domain-Driven Design (DDD). Providing a clean and simple structure to build on. With the addition of `pre-commit` hooks to ensure code quality. And ready to be used in a `CI/CD` GitHub Workflows pipeline. ### Architecture Decision Records -The [Architecture Decision Records](docs/adr/README.md) define the modern Cosmic Python standard used by this template. +The [Architecture Decision Records](docs/adr/README.md) define the modern Cosmic Python standard used by this project. They are the first reference for people and coding agents extending the project. The records preserve the book's domain-first philosophy while adopting FastAPI, Pydantic 2, SQLAlchemy 2, Alembic, uv, Ruff, Pyrefly, and pytest. Repository-level [agent instructions](AGENTS.md) turn those decisions into an implementation workflow. @@ -409,8 +399,9 @@ more details or visit https://mit-license.org/. ## Acknowledgements -This project was generated by [{{ author_name }}]({{ author_url }}) <[{{ author_email }}](mailto:{{ author_email }})> -from the [Cosmic FastAPI](https://github.com/{{ github_owner }}/cosmic-fastapi) template. +This project was generated by [{{ author_name }}]({{ author_url }}) <[{{ author_email }}](mailto:{{ author_email }})>. + +Generated from the [Cosmic FastAPI](https://github.com/tomasanchez/cosmic-fastapi) template. Deeply inspired by [FastAPI-MVC](https://github.com/fastapi-mvc/fastapi-mvc) following [Cosmic Python](https://www.cosmicpython.com/) guidelines for project structure. diff --git a/template/docs/adr/0001-modern-cosmic-python-standard.md b/template/docs/adr/0001-modern-cosmic-python-standard.md index b99c290..2eceb43 100644 --- a/template/docs/adr/0001-modern-cosmic-python-standard.md +++ b/template/docs/adr/0001-modern-cosmic-python-standard.md @@ -5,9 +5,9 @@ ## Context -Cosmic FastAPI is a reusable project template for people and coding agents. It -is inspired by *Architecture Patterns with Python*, also known as Cosmic -Python. The book's central goal is to keep business logic easy to understand, +This project follows a modern Cosmic Python standard for people and coding +agents. It is inspired by *Architecture Patterns with Python*, also known as +Cosmic Python. The book's central goal is to keep business logic easy to understand, fast to test, and independent of delivery and infrastructure choices. Python application tooling has moved forward. FastAPI, Pydantic 2, SQLAlchemy diff --git a/template/docs/adr/README.md b/template/docs/adr/README.md index fb422e0..fb8643a 100644 --- a/template/docs/adr/README.md +++ b/template/docs/adr/README.md @@ -1,7 +1,7 @@ # Architecture Decision Records -This directory records the architectural defaults for projects created from -Cosmic FastAPI. The template modernizes the intent of +This directory records the architectural defaults for this project. The standard +modernizes the intent of [Architecture Patterns with Python](https://www.cosmicpython.com/book/preface.html) instead of copying its implementation line for line. @@ -33,7 +33,6 @@ treated as migration work, not as a precedent to repeat. | [0012](0012-camel-case-json-message-contracts.md) | Camel-Case JSON Message Contracts | Accepted | | [0013](0013-aggregates-define-consistency-boundaries.md) | Aggregates Define Consistency Boundaries | Accepted | | [0014](0014-cqrs-read-models-are-purpose-built.md) | CQRS Read Models Are Purpose Built | Accepted | -| [0015](0015-copier-template-engine.md) | Copier as the Template Engine | Accepted | | [0016](0016-aggregate-persistence-write-back.md) | Aggregate Persistence Write-Back on Commit | Accepted | | [0017](0017-async-persistence-by-default.md) | Async Persistence and Application Code by Default | Accepted | | [0018](0018-postgresql-default-with-pgvector.md) | PostgreSQL by Default with a pgvector Image; SQLite Optional | Accepted | diff --git a/template/migrations/README b/template/migrations/README index 1cbb48f..3a90342 100644 --- a/template/migrations/README +++ b/template/migrations/README @@ -1 +1 @@ -Alembic migration environment for Cosmic FastAPI. +Alembic migration environment for this service. diff --git a/tests/test_bake.py b/tests/test_bake.py index 196c935..2ac125b 100644 --- a/tests/test_bake.py +++ b/tests/test_bake.py @@ -223,6 +223,11 @@ def test_baked_project_passes_offline_quality_gate(baked_project: Path) -> None: pyrefly = _run(["uv", "run", "pyrefly", "check"], baked_project) assert pyrefly.returncode == 0, f"pyrefly failed:\n{pyrefly.stdout}\n{pyrefly.stderr}" + # Mirror the generated project's `make adr-check`: validate the ADR registry + # (now without 0015) for every matrix cell. + adr_check = _run(["uv", "run", "python", "scripts/prune_decisions.py", "check"], baked_project) + assert adr_check.returncode == 0, f"adr-check failed:\n{adr_check.stdout}\n{adr_check.stderr}" + # The offline gate excludes the PostgreSQL integration tier (ADR 0019). tests = _run( [