Cadwyn: Production-Ready FastAPI API Versioning
Cadwyn is a Python library that provides production-ready, Stripe-like API versioning for FastAPI applications. It allows developers to maintain only the latest version of their API implementation, automatically generating older versions and handling backward compatibility. This approach encapsulates version changes in independent modules, simplifying business logic. The current version is 6.2.0, with an active development and release cadence.
Warnings
- breaking Cadwyn v6.0.0 removed support for FastAPI versions older than 0.128.0 and Pydantic v1. Ensure your FastAPI and Pydantic installations meet the new minimum requirements.
- breaking Python 3.9 support was removed in Cadwyn v6.0.3. Projects using older Python versions will not be compatible with recent Cadwyn releases.
- deprecated The `api_version_header_name` argument in `Cadwyn` is deprecated in favor of `api_version_parameter_name`. Also, `cadwyn.Cadwyn.add_header_versioned_routers` is deprecated and likely removed in favor of `generate_and_include_versioned_routers`.
- gotcha Cadwyn v6.x+ stores versions as strings (preferably ISO dates) internally. While date types can still be passed to `cadwyn.Version`, string types (like 'YYYY-MM-DD') are guaranteed to be supported in the future.
- gotcha Cadwyn aims to eliminate explicit version checks in business logic. Directly checking `api_version_var.get() >= date(...)` is discouraged and often indicates a missing or incorrectly applied version change.
- gotcha Cadwyn does not automatically edit Python import statements when generating schemas. If you import from versioned code within other versioned code, use relative imports to ensure they resolve correctly after code generation.
Install
-
pip install cadwyn
Imports
- Cadwyn
from cadwyn import Cadwyn
- VersionedAPIRouter
from cadwyn import VersionedAPIRouter
- VersionBundle
from cadwyn import VersionBundle
- schema
from cadwyn.structure import schema
- VersionChange
from cadwyn.structure import VersionChange
Quickstart
import datetime
from fastapi import FastAPI
from pydantic import BaseModel, Field
from cadwyn import Cadwyn, VersionBundle, VersionedAPIRouter
from cadwyn.structure import VersionChange, schema
# 1. Define your schemas in the 'latest' version
class UserCreateRequest(BaseModel):
name: str
email: str
class UserResource(BaseModel):
id: int
name: str
email: str
# 2. Define your API versions
# Use ISO date strings for versions for future compatibility (v6.x+)
VERSION_2024_01_01 = datetime.date(2024, 1, 1)
VERSION_2023_01_01 = datetime.date(2023, 1, 1)
# 3. Define version changes
class ChangeEmailFieldToOptional(VersionChange):
description = 'Make email field optional in UserCreateRequest'
version = VERSION_2023_01_01
@schema(UserCreateRequest).alter
def alter_user_create_request(cls):
cls.email = Field(default=None, union_of=[cls.email, type(None)])
# 4. Create a VersionBundle
version_bundle = VersionBundle(
latest_version=VERSION_2024_01_01,
old_versions=[VERSION_2023_01_01],
version_changes=[ChangeEmailFieldToOptional],
)
# 5. Initialize Cadwyn and the versioned router
app = FastAPI()
cadwyn_app = Cadwyn(
versions=version_bundle,
api_version_header_name='x-api-version',
latest_schemas_package=__name__,
old_versions_package=__name__
)
# Use a standard FastAPI router but include it via Cadwyn
router = VersionedAPIRouter(version_bundle=version_bundle)
@router.post('/users', response_model=UserResource)
async def create_user(user: UserCreateRequest):
# Business logic always works with the latest schema
return {"id": 1, "name": user.name, "email": user.email}
@router.get('/users/{user_id}', response_model=UserResource)
async def get_user(user_id: int):
return {"id": user_id, "name": "John Doe", "email": "john.doe@example.com"}
# Cadwyn generates and includes versioned routes
cadwyn_app.generate_and_include_versioned_routers(app, router)
# To run: uvicorn your_module_name:app --reload
# Test with curl -H 'x-api-version: 2024-01-01' -X POST -H 'Content-Type: application/json' -d '{"name": "Alice", "email": "alice@example.com"}' http://localhost:8000/users
# Test with curl -H 'x-api-version: 2023-01-01' -X POST -H 'Content-Type: application/json' -d '{"name": "Bob"}' http://localhost:8000/users