{"library":"cadwyn","title":"Cadwyn: Production-Ready FastAPI API Versioning","description":"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.","language":"python","status":"active","last_verified":"Wed May 20","install":{"commands":["pip install cadwyn"],"cli":{"name":"cadwyn","version":"Traceback (most recent call last):"}},"imports":["from cadwyn import Cadwyn","from cadwyn import VersionedAPIRouter","from cadwyn import VersionBundle","from cadwyn.structure import schema","from cadwyn.structure import VersionChange"],"auth":{"required":false,"env_vars":[]},"quickstart":{"code":"import datetime\nfrom fastapi import FastAPI\nfrom pydantic import BaseModel, Field\nfrom cadwyn import Cadwyn, VersionBundle, VersionedAPIRouter\nfrom cadwyn.structure import VersionChange, schema\n\n# 1. Define your schemas in the 'latest' version\nclass UserCreateRequest(BaseModel):\n    name: str\n    email: str\n\nclass UserResource(BaseModel):\n    id: int\n    name: str\n    email: str\n\n# 2. Define your API versions\n# Use ISO date strings for versions for future compatibility (v6.x+)\nVERSION_2024_01_01 = datetime.date(2024, 1, 1)\nVERSION_2023_01_01 = datetime.date(2023, 1, 1)\n\n# 3. Define version changes\nclass ChangeEmailFieldToOptional(VersionChange):\n    description = 'Make email field optional in UserCreateRequest'\n    version = VERSION_2023_01_01\n\n    @schema(UserCreateRequest).alter\n    def alter_user_create_request(cls):\n        cls.email = Field(default=None, union_of=[cls.email, type(None)])\n\n# 4. Create a VersionBundle\nversion_bundle = VersionBundle(\n    latest_version=VERSION_2024_01_01,\n    old_versions=[VERSION_2023_01_01],\n    version_changes=[ChangeEmailFieldToOptional],\n)\n\n# 5. Initialize Cadwyn and the versioned router\napp = FastAPI()\ncadwyn_app = Cadwyn(\n    versions=version_bundle,\n    api_version_header_name='x-api-version',\n    latest_schemas_package=__name__,\n    old_versions_package=__name__\n)\n\n# Use a standard FastAPI router but include it via Cadwyn\nrouter = VersionedAPIRouter(version_bundle=version_bundle)\n\n@router.post('/users', response_model=UserResource)\nasync def create_user(user: UserCreateRequest):\n    # Business logic always works with the latest schema\n    return {\"id\": 1, \"name\": user.name, \"email\": user.email}\n\n@router.get('/users/{user_id}', response_model=UserResource)\nasync def get_user(user_id: int):\n    return {\"id\": user_id, \"name\": \"John Doe\", \"email\": \"john.doe@example.com\"}\n\n# Cadwyn generates and includes versioned routes\ncadwyn_app.generate_and_include_versioned_routers(app, router)\n\n# To run: uvicorn your_module_name:app --reload\n# 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\n# Test with curl -H 'x-api-version: 2023-01-01' -X POST -H 'Content-Type: application/json' -d '{\"name\": \"Bob\"}' http://localhost:8000/users","lang":"python","description":"This quickstart demonstrates how to set up a basic FastAPI application with Cadwyn for API versioning. It defines two versions (2024-01-01 and 2023-01-01), with a version change making the `email` field optional in `UserCreateRequest` for the older version. The `Cadwyn` instance handles the generation and inclusion of versioned routers, allowing your core business logic to interact only with the latest schema.","tag":null,"tag_description":null,"last_tested":"2026-04-24","results":[{"runtime":"python:3.10-alpine","exit_code":1},{"runtime":"python:3.10-slim","exit_code":1},{"runtime":"python:3.11-alpine","exit_code":1},{"runtime":"python:3.11-slim","exit_code":1},{"runtime":"python:3.12-alpine","exit_code":1},{"runtime":"python:3.12-slim","exit_code":1},{"runtime":"python:3.13-alpine","exit_code":1},{"runtime":"python:3.13-slim","exit_code":1},{"runtime":"python:3.9-alpine","exit_code":1},{"runtime":"python:3.9-slim","exit_code":1}]},"compatibility":{"tag":"verified","tag_description":"installs cleanly on critical runtimes, fast import, recently tested","last_tested":"2026-05-20","installed_version":"6.0.3","pypi_latest":"6.2.2","is_stale":true,"summary":{"python_range":"3.10–3.9","success_rate":100,"avg_install_s":4,"avg_import_s":1.61,"wheel_type":"wheel"},"results":[{"runtime":"python:3.10-alpine","python_version":"3.10","os_libc":"alpine (musl)","variant":"cadwyn","exit_code":0,"wheel_type":"wheel","failure_reason":null,"import_side_effects":"clean","install_time_s":null,"import_time_s":1.46,"mem_mb":18.5,"disk_size":"33.7M"},{"runtime":"python:3.10-alpine","python_version":"3.10","os_libc":"alpine (musl)","variant":"cadwyn","exit_code":0,"wheel_type":null,"failure_reason":null,"import_side_effects":null,"install_time_s":null,"import_time_s":1.74,"mem_mb":18.5,"disk_size":"33.6M"},{"runtime":"python:3.10-slim","python_version":"3.10","os_libc":"slim (glibc)","variant":"cadwyn","exit_code":0,"wheel_type":"wheel","failure_reason":null,"import_side_effects":"clean","install_time_s":4.5,"import_time_s":1.05,"mem_mb":18.5,"disk_size":"33M"},{"runtime":"python:3.10-slim","python_version":"3.10","os_libc":"slim (glibc)","variant":"cadwyn","exit_code":0,"wheel_type":null,"failure_reason":null,"import_side_effects":null,"install_time_s":null,"import_time_s":1.34,"mem_mb":18.5,"disk_size":"33M"},{"runtime":"python:3.11-alpine","python_version":"3.11","os_libc":"alpine (musl)","variant":"cadwyn","exit_code":0,"wheel_type":"wheel","failure_reason":null,"import_side_effects":"clean","install_time_s":null,"import_time_s":1.7,"mem_mb":20.1,"disk_size":"37.0M"},{"runtime":"python:3.11-alpine","python_version":"3.11","os_libc":"alpine (musl)","variant":"cadwyn","exit_code":0,"wheel_type":null,"failure_reason":null,"import_side_effects":null,"install_time_s":null,"import_time_s":2.41,"mem_mb":20.1,"disk_size":"36.9M"},{"runtime":"python:3.11-slim","python_version":"3.11","os_libc":"slim (glibc)","variant":"cadwyn","exit_code":0,"wheel_type":"wheel","failure_reason":null,"import_side_effects":"clean","install_time_s":3.8,"import_time_s":1.52,"mem_mb":20.1,"disk_size":"37M"},{"runtime":"python:3.11-slim","python_version":"3.11","os_libc":"slim (glibc)","variant":"cadwyn","exit_code":0,"wheel_type":null,"failure_reason":null,"import_side_effects":null,"install_time_s":null,"import_time_s":1.75,"mem_mb":20.1,"disk_size":"37M"},{"runtime":"python:3.12-alpine","python_version":"3.12","os_libc":"alpine (musl)","variant":"cadwyn","exit_code":0,"wheel_type":"wheel","failure_reason":null,"import_side_effects":"clean","install_time_s":null,"import_time_s":1.6,"mem_mb":19.8,"disk_size":"28.4M"},{"runtime":"python:3.12-alpine","python_version":"3.12","os_libc":"alpine (musl)","variant":"cadwyn","exit_code":0,"wheel_type":null,"failure_reason":null,"import_side_effects":null,"install_time_s":null,"import_time_s":2.17,"mem_mb":19.8,"disk_size":"28.3M"},{"runtime":"python:3.12-slim","python_version":"3.12","os_libc":"slim (glibc)","variant":"cadwyn","exit_code":0,"wheel_type":"wheel","failure_reason":null,"import_side_effects":"clean","install_time_s":3.3,"import_time_s":1.63,"mem_mb":19.8,"disk_size":"28M"},{"runtime":"python:3.12-slim","python_version":"3.12","os_libc":"slim (glibc)","variant":"cadwyn","exit_code":0,"wheel_type":null,"failure_reason":null,"import_side_effects":null,"install_time_s":null,"import_time_s":2.03,"mem_mb":19.8,"disk_size":"28M"},{"runtime":"python:3.13-alpine","python_version":"3.13","os_libc":"alpine (musl)","variant":"cadwyn","exit_code":0,"wheel_type":"wheel","failure_reason":null,"import_side_effects":"clean","install_time_s":null,"import_time_s":1.26,"mem_mb":19,"disk_size":"28.2M"},{"runtime":"python:3.13-alpine","python_version":"3.13","os_libc":"alpine (musl)","variant":"cadwyn","exit_code":0,"wheel_type":null,"failure_reason":null,"import_side_effects":null,"install_time_s":null,"import_time_s":1.68,"mem_mb":19,"disk_size":"28.0M"},{"runtime":"python:3.13-slim","python_version":"3.13","os_libc":"slim (glibc)","variant":"cadwyn","exit_code":0,"wheel_type":"wheel","failure_reason":null,"import_side_effects":"clean","install_time_s":3.4,"import_time_s":1.26,"mem_mb":19,"disk_size":"28M"},{"runtime":"python:3.13-slim","python_version":"3.13","os_libc":"slim (glibc)","variant":"cadwyn","exit_code":0,"wheel_type":null,"failure_reason":null,"import_side_effects":null,"install_time_s":null,"import_time_s":1.6,"mem_mb":19,"disk_size":"28M"},{"runtime":"python:3.9-alpine","python_version":"3.9","os_libc":"alpine (musl)","variant":"cadwyn","exit_code":0,"wheel_type":"wheel","failure_reason":null,"import_side_effects":"clean","install_time_s":null,"import_time_s":1.39,"mem_mb":18.5,"disk_size":"32.9M"},{"runtime":"python:3.9-alpine","python_version":"3.9","os_libc":"alpine (musl)","variant":"cadwyn","exit_code":0,"wheel_type":null,"failure_reason":null,"import_side_effects":null,"install_time_s":null,"import_time_s":1.86,"mem_mb":18.5,"disk_size":"32.8M"},{"runtime":"python:3.9-slim","python_version":"3.9","os_libc":"slim (glibc)","variant":"cadwyn","exit_code":0,"wheel_type":"wheel","failure_reason":null,"import_side_effects":"clean","install_time_s":5.2,"import_time_s":1.32,"mem_mb":18.5,"disk_size":"33M"},{"runtime":"python:3.9-slim","python_version":"3.9","os_libc":"slim (glibc)","variant":"cadwyn","exit_code":0,"wheel_type":null,"failure_reason":null,"import_side_effects":null,"install_time_s":null,"import_time_s":1.49,"mem_mb":18.5,"disk_size":"33M"}]}}