Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 85 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -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.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<p align="center">
<img width="320" height="320" src="template/docs/cosmic_fastapi.png" alt='cosmic fast api'>
<img width="320" height="320" src="docs/cosmic_fastapi.png" alt='cosmic fast api'>
</p>

<p align="center">
Expand All @@ -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.
Expand Down
26 changes: 26 additions & 0 deletions docs/adr/README.md
Original file line number Diff line number Diff line change
@@ -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.
File renamed without changes
2 changes: 1 addition & 1 deletion template/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
29 changes: 10 additions & 19 deletions template/README.md.jinja
Original file line number Diff line number Diff line change
@@ -1,24 +1,14 @@
<p align="center">
<img width="320" height="320" src="docs/cosmic_fastapi.png" alt='cosmic fast api'>
</p>

<p align="center">
<em>Kickoff your app like a kangaroo through the stars: clean, fast, and unapologetically structured.</em>
</p>

---

# {{ 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

<!-- TOC -->

* [Cosmic FastAPI](#cosmic-fastapi)
* [{{ project_name }}](#{{ project_slug }})
* [Content](#content)
* [About](#about)
* [Features](#features)
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
6 changes: 3 additions & 3 deletions template/docs/adr/0001-modern-cosmic-python-standard.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 2 additions & 3 deletions template/docs/adr/README.md
Original file line number Diff line number Diff line change
@@ -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.

Expand Down Expand Up @@ -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 |
Expand Down
2 changes: 1 addition & 1 deletion template/migrations/README
Original file line number Diff line number Diff line change
@@ -1 +1 @@
Alembic migration environment for Cosmic FastAPI.
Alembic migration environment for this service.
5 changes: 5 additions & 0 deletions tests/test_bake.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
[
Expand Down
Loading