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
56 changes: 56 additions & 0 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
name: docs

on:
push:
branches: ["master"]
paths:
- docs/**
- overrides/**
- mkdocs.yml
- pyproject.toml
- uv.lock
- src/**
- .github/workflows/docs.yml
workflow_dispatch:

permissions:
contents: read
pages: write
id-token: write

concurrency:
group: pages
cancel-in-progress: false

jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout Code Repository
uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v6
with:
enable-cache: true
cache-dependency-glob: uv.lock
- name: Set up Python
run: uv python install
- name: Create venv, install dependencies
run: uv sync --group=docs --frozen
- name: Build documentation
run: uv run mkdocs build --strict
- name: Upload Pages artifact
uses: actions/upload-pages-artifact@v5
with:
path: site

deploy:
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v5
4 changes: 2 additions & 2 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ name: Lints
on:
pull_request:
branches: ["master"]
paths-ignore: ["docs/**", "README.md", "LICENSE"]
paths-ignore: ["docs/**", "README.md", "README.ru.md", "LICENSE"]
push:
branches: ["master"]
paths-ignore: ["docs/**", "README.md", "LICENSE"]
paths-ignore: ["docs/**", "README.md", "README.ru.md", "LICENSE"]
jobs:
lint:
runs-on: ubuntu-latest
Expand Down
34 changes: 0 additions & 34 deletions .github/workflows/mkdocs-publish.yml

This file was deleted.

4 changes: 2 additions & 2 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ concurrency:
on:
pull_request:
branches: ["master"]
paths-ignore: ["docs/**", "README.md", "LICENSE"]
paths-ignore: ["docs/**", "README.md", "README.ru.md", "LICENSE"]
push:
branches: ["master"]
paths-ignore: ["docs/**", "README.md", "LICENSE"]
paths-ignore: ["docs/**", "README.md", "README.ru.md", "LICENSE"]
jobs:
run:
name: Python ${{ matrix.python-version }}
Expand Down
12 changes: 9 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ uvx --python=3.14 --from="fastapi[standard]" --with="fastapi-easy-versioning" \

- **Commits**: Conventional Commits. Do NOT add a `Co-Authored-By: Claude` trailer.
- **Release flow**: bumping the version in `pyproject.toml` (static `version = "..."`) + committing does NOT publish. PyPI publish (`release.yml`) fires ONLY when a GitHub Release is `published`, via `uv build && uv publish` over OIDC trusted publishing (no token secret). Version bumps are dedicated `chore: bump to X` commits.
- **CI**: lint (`ruff check` + `ty check src`) and tests (matrix 3.10–3.14) run on PR/push to master, ignoring `docs/**`, `README.md`, `LICENSE`. A `fastapi-compat` job additionally runs the suite against fastapi `0.95.*` (minimum), `0.136.*` (last flat-router release) and latest via `uv run --frozen --with … -m pytest --no-cov`. Docs auto-deploy to GitHub Pages on EVERY push to master.
- **CI**: lint (`ruff check` + `ty check src`) and tests (matrix 3.10–3.14) run on PR/push to master, ignoring `docs/**`, `README.md`, `README.ru.md`, `LICENSE`. A `fastapi-compat` job additionally runs the suite against fastapi `0.95.*` (minimum), `0.136.*` (last flat-router release) and latest via `uv run --frozen --with … -m pytest --no-cov`. Docs (`docs.yml`) build with `mkdocs build --strict` and deploy to GitHub Pages through the Pages Actions artifact (NOT `mkdocs gh-deploy`/the `gh-pages` branch — the Pages source is set to "GitHub Actions"), triggered by pushes to master touching `docs/**`, `overrides/**`, `mkdocs.yml`, `pyproject.toml`, `uv.lock`, `src/**` (docstrings feed the reference) or the workflow itself.
- **Dependabot** (`.github/dependabot.yml`): one grouped weekly PR bumping ONLY dev dependencies. `dependency-type: development` scopes updates to the PEP 735 `[dependency-groups]`; `[project.dependencies]` (fastapi) are classified production and deliberately excluded so their intentional lower bounds stay put. It won't touch pre-commit hook revs — bump those with `pre-commit autoupdate`.
- **pre-commit**: hooks include ruff-check, ruff-format, `uv-lock`, `ty`. Hook revs are kept in sync with the `pyproject` dev pins via `pre-commit autoupdate` (e.g. ruff `v0.15.20` ↔ `ruff~=0.15.20`). `ruff format --check` is enforced ONLY via pre-commit, not CI.
- **Type checker is `ty`** (Astral, preview — pinned `ty~=0.0.56`), NOT mypy. There is no `strict = true` analog; ty uses its default rule set and `[tool.ty.terminal] error-on-warning` defaults to `true` (warnings fail too). `[tool.ty.environment] python-version = "3.10"`. `tests` are excluded via `[tool.ty.src] exclude`. ty resolves third-party imports (fastapi) straight from `pyproject.toml` — the pre-commit hook takes NO `additional_dependencies` (unlike the old mypy hook), and checks the whole project (minus `tests`), while CI narrows to `ty check src`.
Expand All @@ -73,5 +73,11 @@ uvx --python=3.14 --from="fastapi[standard]" --with="fastapi-easy-versioning" \
- **Ruff is maximal**: `lint.select = ['ALL']` with `preview = true` (docstring `D*` rules and a few others are the main opt-outs; per-file relaxations for `examples/*` and `tests/*`). Expect new code to trip many rules, and note `fix = true` auto-applies safe fixes on `ruff check`.
- **Tests** import via the src-layout path `src.fastapi_easy_versioning`, so run from repo root. Async via `anyio` — each test file needs module-level `pytestmark = [pytest.mark.anyio]` plus the session-scoped `anyio_backend` fixture (`"asyncio"`). Shared fixtures (`app`, `client`, `middleware_setup`, `v1`, `v2`, `websocket_request`) are in `tests/conftest.py`. WebSocket tests use the `websocket_request` raw-ASGI fixture — neither httpx2 nor httpx has a native WebSocket client, and starlette's `TestClient` would require the `httpx` package (absent from the `test` group).
- **WebSocket opt-in on fastapi 0.95**: WS routes there have no route-level `dependencies=` kwarg — mark them via `Depends(versioning())` in the endpoint signature.
- **Docs are bilingual (RU + EN)** built with mkdocs-material + `mkdocs-static-i18n`. `docs/ru/*` is the source of truth; `docs/en/*` is a maintained first-class translation (no auto-translated banner). Mirror the same 5 pages (index, dependency, middleware, examples/simple, examples/multiple) in both, and update BOTH `nav:` lists in `mkdocs.yml` when adding a page.
- **Drift risk**: the quick-start example is copied near-verbatim across `README.md`, `docs/en/index.md`, `docs/ru/index.md`; the `docs/*/examples/*.md` code blocks are near-verbatim copies of `examples/*.py` (docs omit the in-file `# To run…` comment and use a clone-prefixed run command). Any public-API change must be propagated to all copies.
- **Docs are bilingual (EN + RU)** built with mkdocs-material + `mkdocs-static-i18n` + `mkdocstrings`. `docs/ru/*` is the source of truth; `docs/en/*` is a maintained first-class translation (no auto-translated banner) and is the **default** locale (English at `/`, Russian at `/ru/`). Mirror the same 10 pages in both trees: `index`, `quickstart`, `guide/{dependency,middleware,recipes,limitations}`, `examples/{simple,multiple}`, `reference/{dependency,middleware}`, `changelog`. There is now ONE `nav:` in `mkdocs.yml` plus `nav_translations` for ru — add a page to the single nav and translate its section title there.
- **Reference pages are generated** by mkdocstrings from the docstrings of the 6 public names (`::: fastapi_easy_versioning.X`), so a docstring is user-facing documentation: keep it Google-style and cross-link with `[`name`][fastapi_easy_versioning.name]`. Because the same identifiers are rendered in both locales, `autorefs.resolve_closest: true` is required — without it `--strict` aborts on "Multiple primary URLs".
- **`mkdocs build --strict` must stay clean** — it is what CI runs, and it catches broken internal links and unresolved mkdocstrings identifiers. Run it after moving or renaming any page.
- **Mermaid diagrams** use `classDef available/missing` + the `:::` operator (not per-node `style` lines). The classes carry an explicit `color:` — mermaid routes text properties of a class into the label's inline style, and without it Material paints the labels in the theme foreground, which is unreadable on the pastel fills in the slate scheme. The `pymdownx.superfences` custom fence for mermaid must stay in `mkdocs.yml`.
- **`check-yaml` runs with `--unsafe`** — mkdocs.yml carries `!!python/name:` tags the hook cannot construct.
- **`overrides/main.html`** exists solely to inject the Context7 chat widget into Material's `scripts` block (`extra_javascript` cannot carry the required `data-library` attribute). `context7.json` at the repo root drives Context7's indexing of `docs/en` — keep its `rules` in sync with the public API.
- **Drift risk**: the quick-start example is copied near-verbatim across `README.md`, `README.ru.md`, `docs/en/index.md`, `docs/ru/index.md`, `docs/*/quickstart.md`; the `docs/*/examples/*.md` code blocks are near-verbatim copies of `examples/*.py` (docs omit the in-file `# To run…` comment and use a clone-prefixed run command). Any public-API change must be propagated to all copies. Both READMEs use ABSOLUTE links — `README.md` is the PyPI long description, where relative links 404.
- **Release notes**: each release gets an entry in `docs/{en,ru}/changelog.md` (Keep a Changelog format) alongside the `chore: bump to X` commit.
17 changes: 17 additions & 0 deletions context7.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"$schema": "https://context7.com/schema/context7.json",
"projectTitle": "fastapi-easy-versioning",
"description": "Versioned APIs for FastAPI: one FastAPI sub-application per version, automatic inheritance of marked endpoints from older versions into newer ones, and a regenerated OpenAPI schema per version.",
"folders": ["docs/en"],
"excludeFolders": ["docs/en/reference"],
"rules": [
"Requires Python >= 3.10 and fastapi >=0.95.0,!=0.137.0,!=0.137.1; fastapi is the only runtime dependency.",
"A version is a FastAPI sub-application created with an api_version extra and mounted under an aggregating app: `app.mount('/v1', FastAPI(api_version=1))`. The value must be an int (0 is valid, bool is not); a sub-app without it takes no part in versioning.",
"Add VersioningMiddleware to the application that directly mounts the version sub-apps, never to the sub-apps themselves: `app.add_middleware(VersioningMiddleware)`. Several instances can coexist, one per aggregating app, so independent APIs version separately.",
"Versioning is opt-in: only endpoints carrying `Depends(versioning())` are inherited. Attach it per endpoint, via add_api_route, or on a whole APIRouter with `APIRouter(dependencies=[Depends(versioning())])`.",
"`versioning()` with no `until` means 'available through the latest version', not 'this version only'; `versioning(until=N)` caps availability at version N inclusive, and the smallest declared `until` wins when a route carries several markers.",
"Inject `Annotated[VersionInfo, Depends(versioning())]` into an endpoint to read the resolved `origin` and `until`; it works for WebSocket endpoints too.",
"Inheritance is built once, on the middleware's first ASGI event. Routes or versions added after that need an explicit `rebuild_versioning(app)`, which is idempotent.",
"Redefining an endpoint on the same path in a newer version shadows the inherited one, at runtime and in the OpenAPI schema; pass `rebuild_openapi=False` to keep inherited endpoints out of each version's /docs."
]
}
5 changes: 4 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ dependencies = [
"fastapi>=0.95.0,!=0.137.0,!=0.137.1",
]
license = { file = "LICENSE" }
keywords = ["fastapi", "versioning", "api-versioning", "openapi", "asgi"]
classifiers = [
"Development Status :: 5 - Production/Stable",
"Framework :: FastAPI",
Expand All @@ -28,8 +29,10 @@ classifiers = [

[project.urls]
Repository = "https://github.com/feodor-ra/fastapi-easy-versioning"
Homepage = "https://github.com/feodor-ra/fastapi-easy-versioning"
Homepage = "https://feodor-ra.github.io/fastapi-easy-versioning/"
Documentation = "https://feodor-ra.github.io/fastapi-easy-versioning/"
Issues = "https://github.com/feodor-ra/fastapi-easy-versioning/issues"
Changelog = "https://feodor-ra.github.io/fastapi-easy-versioning/changelog/"

[build-system]
requires = ["uv_build>=0.8.3,<0.9.0"]
Expand Down
Loading