Skip to main content

modkit-api-versioning

Small, framework-independent path-version policies with an optional FastAPI adapter. Python 3.12+; MIT. No response-envelope, logging, diagnostics, or sibling Modkit dependency.

pip install modkit-api-versioning
# Optional routing/context integration:
pip install 'modkit-api-versioning[fastapi]'

Explicit selection

from modkit_api_versioning import PathVersionManager, VersionPolicy

manager = PathVersionManager(VersionPolicy(("stable", "preview"), prefix="/api"))
assert manager.prefix("stable") == "/api/stable"
assert manager.resolve("/api/preview/items") == "preview"
assert manager.resolve("/health") is None

The configured prefix reserves a namespace. Without service, its first segment is always a version: /api/users raises UnsupportedVersion unless users is a supported label. Put ordinary resource routes outside this namespace. /api alone is versionless. No header/query selection, ordering, latest fallback, or semantic interpretation of version labels occurs.

Optional service="inventory" uses /api/stable/inventory for versioned routes and /api/inventory for versionless routes. Paths for other services return None. A service name cannot also be a supported version. Versionless resolution returns None unless both default_version="stable" and versionless="default" are set. An explicit unknown version never uses that default. default_version alone does not activate fallback. No service names or supported labels are built in.

Policies are immutable; supplied version sequences are copied to tuples. Labels and service names must begin with ASCII alphanumeric and contain only those characters, ., _, or -. Prefixes are absolute nonempty paths of those segments without a trailing slash. Duplicate labels and invalid settings raise ValueError. UnsupportedVersion subclasses ValueError.

resolve accepts decoded absolute paths, not URLs: query/fragment characters, percent escapes, controls, backslashes, repeated separators and dot traversal are rejected with ValueError. A trailing slash is accepted. It does not URL-decode, normalize or redirect requests. ASGI servers supply decoded paths; query strings remain separate. Core resolution never creates an HTTP response.

Optional FastAPI integration

from fastapi import APIRouter, FastAPI
from modkit_api_versioning import PathVersionManager, VersionPolicy
from modkit_api_versioning.fastapi import (
    VersionContext, VersionContextMiddleware, build_router,
)

manager = PathVersionManager(VersionPolicy(("stable", "preview")))
context = VersionContext()
items = APIRouter()

@items.get("/items")
async def list_items():
    return {"version": context.get(), "items": []}

app = FastAPI()
app.add_middleware(VersionContextMiddleware, manager=manager, context=context)
app.include_router(build_router(manager, version="stable", routers=[items]))
app.include_router(build_router(manager, version="preview", routers=[items]))

build_router creates a fresh router, leaving children unchanged. Native FastAPI includes reference children rather than deep-copying them; finish route registration before building/version-mounting routers. Collision validation is a build-time check, not an ongoing registry. The adapter uses FastAPI's effective-route iterator for nested include validation, so its FastAPI dependency is bounded to the tested minor series. Explicit version=None creates a versionless mount. It checks duplicate child HTTP path/method pairs (including equivalent parameter names). This check is conservative: parameter converters are not distinguished, so same-shaped int and uuid routes are also rejected. It does not inspect routes already installed on your app. Parent conflicts, static/dynamic route precedence, custom operation IDs, authentication, and response models remain application-owned. Native FastAPI OpenAPI generation is preserved; do not reuse explicit operation IDs across mounts. Tags are optional.

Middleware is opt-in and scoped to the configured prefix. It maps invalid or unsupported paths inside that prefix to an empty 404, with no envelope or version headers. Other paths pass through unchanged with no selected version. It never redirects or rewrites successful responses. Context is instance-owned, request-local, reset even after exceptions, and absent outside HTTP requests. Use a distinct context per independently configured application. Install after any path-rewriting middleware according to your application's ASGI ordering.

The first release intentionally excludes legacy redirects, version-discovery endpoints, deprecation metadata, header/query negotiation, and schema transforms. Migration-specific behavior stays with the application. This generic API is not a drop-in replacement for any source application's helpers.

Development and verification

python -m venv .venv
# Activate the venv using your platform's standard command.
python -m pip install -r requirements-dev.txt
python -m pytest
python -m ruff check src tests scripts examples
python -m ruff format --check src tests scripts examples
python -m mypy src
python -m build
python -m twine check dist/*
python scripts/check_wheel.py dist/modkit_api_versioning-0.1.0-py3-none-any.whl
python scripts/check_wheel.py dist/modkit_api_versioning-0.1.0-py3-none-any.whl --extras

The checker creates fresh environments outside the checkout, verifies import provenance/metadata and dependency independence, runs copied behavioral tests, and executes the unrelated inventory sample. CI builds again from the sdist and checks both wheels with and without the FastAPI extra, on Linux, Windows and macOS with Python 3.12–3.14. Configured CI is not a claim of completed hosted runs.

Version 0.1.0 is an initial API; breaking changes before 1.0 increment the minor version. Derived path-routing/context mechanics retain the source MIT copyright in LICENSE. Distribution artifacts exclude private development-policy files.

Portable Conventional Commit checks are vendored under .development-standards. For a reviewed commit range, run python .development-standards/standards.py check --base BASE --head HEAD. These checks and CI require no external private policy checkout. Local instruction files are intentionally not distributed.

Metadata

Release files for modkit-api-versioning 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for modkit-api-versioning 0.1.0
File Size Uploaded Provenance Actions
modkit_api_versioning-0.1.0.tar.gz 14.8 kB With provenance Details for modkit_api_versioning-0.1.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for modkit-api-versioning 0.1.0
File Interpreter ABI Platform Actions
modkit_api_versioning-0.1.0-py3-none-any.whl Python 3 none any Details for modkit_api_versioning-0.1.0-py3-none-any.whl

Total release size: 22.9 kB

Release files / modkit_api_versioning-0.1.0.tar.gz

Download URL modkit_api_versioning-0.1.0.tar.gz
Size 14.8 kB
Tags Source
SHA-256 checksum
How to use checksums
a7418c55b80538670789e2e73181b0b02b84db078cf540bb0558ba4e8f056723
BLAKE2b-256 checksum
How to use checksums
566d5449bba8e4417b6776760456ae5ab2a86fb178a71706d6f3865bb8211eb6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 30, 2026.

Transparency log

Release files / modkit_api_versioning-0.1.0-py3-none-any.whl

Download URL modkit_api_versioning-0.1.0-py3-none-any.whl
Size 8.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
648096e454e650d4004f17ddaa8be6b861c3028f2b93ca2afcd8f4ac59b48c2b
BLAKE2b-256 checksum
How to use checksums
9f8e2cf164f108ced3f04e066b0b7d6e6c695c6edbdfe8a94e849f874c934315
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 30, 2026.

Transparency log

Security for modkit-api-versioning 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

Both files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for modkit_api_versioning-0.1.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 30, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page