[{"data":1,"prerenderedAt":2227},["ShallowReactive",2],{"nav":3,"page-\u002Fcore-architecture-routing-patterns\u002Fmodular-router-organization\u002Fversioning-apis-with-routers\u002F":580,"surround-\u002Fcore-architecture-routing-patterns\u002Fmodular-router-organization\u002Fversioning-apis-with-routers\u002F":2226},[4,186,386],{"title":5,"path":6,"stem":7,"children":8},"Advanced Pydantic Validation Serialization","\u002Fadvanced-pydantic-validation-serialization","advanced-pydantic-validation-serialization",[9,12,42,66,90,114,150,174],{"title":10,"path":6,"stem":11},"Advanced Pydantic Validation and Serialization","advanced-pydantic-validation-serialization\u002Findex",{"title":13,"path":14,"stem":15,"children":16},"Custom Validators and Field Constraints in Pydantic","\u002Fadvanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints","advanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002Findex",[17,18,24,30,36],{"title":13,"path":14,"stem":15},{"title":19,"path":20,"stem":21,"children":22},"Before, After and Wrap Validators in Pydantic v2","\u002Fadvanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002Fbefore-after-and-wrap-validators","advanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002Fbefore-after-and-wrap-validators\u002Findex",[23],{"title":19,"path":20,"stem":21},{"title":25,"path":26,"stem":27,"children":28},"Creating Reusable Custom Validators in Pydantic","\u002Fadvanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002Fcreating-reusable-custom-validators-in-pydantic","advanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002Fcreating-reusable-custom-validators-in-pydantic\u002Findex",[29],{"title":25,"path":26,"stem":27},{"title":31,"path":32,"stem":33,"children":34},"Cross-Field Validation Patterns in Pydantic v2","\u002Fadvanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002Fcross-field-validation-patterns","advanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002Fcross-field-validation-patterns\u002Findex",[35],{"title":31,"path":32,"stem":33},{"title":37,"path":38,"stem":39,"children":40},"Pydantic v2 Async Custom Validator: What to Do Instead","\u002Fadvanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002Fpydantic-v2-async-custom-validator","advanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002Fpydantic-v2-async-custom-validator\u002Findex",[41],{"title":37,"path":38,"stem":39},{"title":43,"path":44,"stem":45,"children":46},"JSON Schema Customization in Pydantic and FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Fjson-schema-customization","advanced-pydantic-validation-serialization\u002Fjson-schema-customization\u002Findex",[47,48,54,60],{"title":43,"path":44,"stem":45},{"title":49,"path":50,"stem":51,"children":52},"Customizing OpenAPI Schema Generation in FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Fjson-schema-customization\u002Fcustomizing-openapi-schema-generation-in-fastapi","advanced-pydantic-validation-serialization\u002Fjson-schema-customization\u002Fcustomizing-openapi-schema-generation-in-fastapi\u002Findex",[53],{"title":49,"path":50,"stem":51},{"title":55,"path":56,"stem":57,"children":58},"Discriminated Unions in OpenAPI with Pydantic","\u002Fadvanced-pydantic-validation-serialization\u002Fjson-schema-customization\u002Fdiscriminated-unions-in-openapi","advanced-pydantic-validation-serialization\u002Fjson-schema-customization\u002Fdiscriminated-unions-in-openapi\u002Findex",[59],{"title":55,"path":56,"stem":57},{"title":61,"path":62,"stem":63,"children":64},"Examples in the OpenAPI Schema with FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Fjson-schema-customization\u002Fexamples-in-openapi-schema","advanced-pydantic-validation-serialization\u002Fjson-schema-customization\u002Fexamples-in-openapi-schema\u002Findex",[65],{"title":61,"path":62,"stem":63},{"title":67,"path":68,"stem":69,"children":70},"Nested Model Serialization in FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Fnested-model-serialization","advanced-pydantic-validation-serialization\u002Fnested-model-serialization\u002Findex",[71,72,78,84],{"title":67,"path":68,"stem":69},{"title":73,"path":74,"stem":75,"children":76},"Excluding Fields Per Endpoint in FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Fnested-model-serialization\u002Fexcluding-fields-per-endpoint","advanced-pydantic-validation-serialization\u002Fnested-model-serialization\u002Fexcluding-fields-per-endpoint\u002Findex",[77],{"title":73,"path":74,"stem":75},{"title":79,"path":80,"stem":81,"children":82},"Handling Deeply Nested JSON Models Efficiently","\u002Fadvanced-pydantic-validation-serialization\u002Fnested-model-serialization\u002Fhandling-deeply-nested-json-models-efficiently","advanced-pydantic-validation-serialization\u002Fnested-model-serialization\u002Fhandling-deeply-nested-json-models-efficiently\u002Findex",[83],{"title":79,"path":80,"stem":81},{"title":85,"path":86,"stem":87,"children":88},"Self-Referencing and Recursive Models in Pydantic v2","\u002Fadvanced-pydantic-validation-serialization\u002Fnested-model-serialization\u002Fself-referencing-and-recursive-models","advanced-pydantic-validation-serialization\u002Fnested-model-serialization\u002Fself-referencing-and-recursive-models\u002Findex",[89],{"title":85,"path":86,"stem":87},{"title":91,"path":92,"stem":93,"children":94},"Performance Optimization for Pydantic Models in FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Fperformance-optimization-for-models","advanced-pydantic-validation-serialization\u002Fperformance-optimization-for-models\u002Findex",[95,96,102,108],{"title":91,"path":92,"stem":93},{"title":97,"path":98,"stem":99,"children":100},"model_construct and When to Skip Validation in Pydantic","\u002Fadvanced-pydantic-validation-serialization\u002Fperformance-optimization-for-models\u002Fmodel-construct-when-to-skip-validation","advanced-pydantic-validation-serialization\u002Fperformance-optimization-for-models\u002Fmodel-construct-when-to-skip-validation\u002Findex",[101],{"title":97,"path":98,"stem":99},{"title":103,"path":104,"stem":105,"children":106},"Pydantic Model Serialization Performance in FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Fperformance-optimization-for-models\u002Fpydantic-model-serialization-performance","advanced-pydantic-validation-serialization\u002Fperformance-optimization-for-models\u002Fpydantic-model-serialization-performance\u002Findex",[107],{"title":103,"path":104,"stem":105},{"title":109,"path":110,"stem":111,"children":112},"TypeAdapter for Non-Model Types in Pydantic","\u002Fadvanced-pydantic-validation-serialization\u002Fperformance-optimization-for-models\u002Ftypeadapter-for-non-model-types","advanced-pydantic-validation-serialization\u002Fperformance-optimization-for-models\u002Ftypeadapter-for-non-model-types\u002Findex",[113],{"title":109,"path":110,"stem":111},{"title":115,"path":116,"stem":117,"children":118},"Pydantic V2 Migration Guide for FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide","advanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Findex",[119,120,126,132,138,144],{"title":115,"path":116,"stem":117},{"title":121,"path":122,"stem":123,"children":124},"Migrate @validator to @field_validator in Pydantic v2","\u002Fadvanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Fmigrate-validator-to-field-validator","advanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Fmigrate-validator-to-field-validator\u002Findex",[125],{"title":121,"path":122,"stem":123},{"title":127,"path":128,"stem":129,"children":130},"Migrating from Pydantic v1 to v2 Without Breaking APIs","\u002Fadvanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Fmigrating-from-pydantic-v1-to-v2-without-breaking-apis","advanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Fmigrating-from-pydantic-v1-to-v2-without-breaking-apis\u002Findex",[131],{"title":127,"path":128,"stem":129},{"title":133,"path":134,"stem":135,"children":136},"model_config vs class Config in Pydantic v2","\u002Fadvanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Fmodel-config-vs-class-config","advanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Fmodel-config-vs-class-config\u002Findex",[137],{"title":133,"path":134,"stem":135},{"title":139,"path":140,"stem":141,"children":142},"Replacing json_encoders with field_serializer in Pydantic v2","\u002Fadvanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Freplacing-json-encoders-with-field-serializer","advanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Freplacing-json-encoders-with-field-serializer\u002Findex",[143],{"title":139,"path":140,"stem":141},{"title":145,"path":146,"stem":147,"children":148},"Migrating @root_validator to @model_validator in Pydantic v2","\u002Fadvanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Froot-validator-to-model-validator","advanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Froot-validator-to-model-validator\u002Findex",[149],{"title":145,"path":146,"stem":147},{"title":151,"path":152,"stem":153,"children":154},"Request Validation Patterns in FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Frequest-validation-patterns","advanced-pydantic-validation-serialization\u002Frequest-validation-patterns\u002Findex",[155,156,162,168],{"title":151,"path":152,"stem":153},{"title":157,"path":158,"stem":159,"children":160},"Optional vs Nullable Fields in Pydantic and FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Frequest-validation-patterns\u002Foptional-vs-nullable-fields","advanced-pydantic-validation-serialization\u002Frequest-validation-patterns\u002Foptional-vs-nullable-fields\u002Findex",[161],{"title":157,"path":158,"stem":159},{"title":163,"path":164,"stem":165,"children":166},"Query, Path and Body Parameter Validation in FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Frequest-validation-patterns\u002Fquery-path-and-body-parameter-validation","advanced-pydantic-validation-serialization\u002Frequest-validation-patterns\u002Fquery-path-and-body-parameter-validation\u002Findex",[167],{"title":163,"path":164,"stem":165},{"title":169,"path":170,"stem":171,"children":172},"Validating File Uploads and Forms in FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Frequest-validation-patterns\u002Fvalidating-file-uploads-and-forms","advanced-pydantic-validation-serialization\u002Frequest-validation-patterns\u002Fvalidating-file-uploads-and-forms\u002Findex",[173],{"title":169,"path":170,"stem":171},{"title":175,"path":176,"stem":177,"children":178},"Type Hinting and IDE Integration in FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Ftype-hinting-ide-integration","advanced-pydantic-validation-serialization\u002Ftype-hinting-ide-integration\u002Findex",[179,180],{"title":175,"path":176,"stem":177},{"title":181,"path":182,"stem":183,"children":184},"Annotated Dependencies and Reusable Types in FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Ftype-hinting-ide-integration\u002Fannotated-dependencies-and-reusable-types","advanced-pydantic-validation-serialization\u002Ftype-hinting-ide-integration\u002Fannotated-dependencies-and-reusable-types\u002Findex",[185],{"title":181,"path":182,"stem":183},{"title":187,"path":188,"stem":189,"children":190},"Async Background Tasks Observability","\u002Fasync-background-tasks-observability","async-background-tasks-observability",[191,194,224,254,284,308,338,362],{"title":192,"path":188,"stem":193},"Async, Background Tasks, and Observability in FastAPI","async-background-tasks-observability\u002Findex",{"title":195,"path":196,"stem":197,"children":198},"Async Correctness and Concurrency in FastAPI","\u002Fasync-background-tasks-observability\u002Fasync-correctness-concurrency","async-background-tasks-observability\u002Fasync-correctness-concurrency\u002Findex",[199,200,206,212,218],{"title":195,"path":196,"stem":197},{"title":201,"path":202,"stem":203,"children":204},"Concurrent Requests with asyncio.gather in FastAPI","\u002Fasync-background-tasks-observability\u002Fasync-correctness-concurrency\u002Fconcurrent-requests-with-asyncio-gather","async-background-tasks-observability\u002Fasync-correctness-concurrency\u002Fconcurrent-requests-with-asyncio-gather\u002Findex",[205],{"title":201,"path":202,"stem":203},{"title":207,"path":208,"stem":209,"children":210},"FastAPI async def vs def: Performance and When to Use Each","\u002Fasync-background-tasks-observability\u002Fasync-correctness-concurrency\u002Ffastapi-async-def-vs-def-performance","async-background-tasks-observability\u002Fasync-correctness-concurrency\u002Ffastapi-async-def-vs-def-performance\u002Findex",[211],{"title":207,"path":208,"stem":209},{"title":213,"path":214,"stem":215,"children":216},"Fixing Blocking Calls in Async FastAPI Routes","\u002Fasync-background-tasks-observability\u002Fasync-correctness-concurrency\u002Ffixing-blocking-calls-in-async-routes","async-background-tasks-observability\u002Fasync-correctness-concurrency\u002Ffixing-blocking-calls-in-async-routes\u002Findex",[217],{"title":213,"path":214,"stem":215},{"title":219,"path":220,"stem":221,"children":222},"Running Sync Code in a Threadpool in FastAPI","\u002Fasync-background-tasks-observability\u002Fasync-correctness-concurrency\u002Frunning-sync-code-in-a-threadpool","async-background-tasks-observability\u002Fasync-correctness-concurrency\u002Frunning-sync-code-in-a-threadpool\u002Findex",[223],{"title":219,"path":220,"stem":221},{"title":225,"path":226,"stem":227,"children":228},"Async Database Sessions in FastAPI","\u002Fasync-background-tasks-observability\u002Fasync-database-sessions","async-background-tasks-observability\u002Fasync-database-sessions\u002Findex",[229,230,236,242,248],{"title":225,"path":226,"stem":227},{"title":231,"path":232,"stem":233,"children":234},"Async SQLAlchemy Session per Request in FastAPI","\u002Fasync-background-tasks-observability\u002Fasync-database-sessions\u002Fasync-sqlalchemy-session-per-request","async-background-tasks-observability\u002Fasync-database-sessions\u002Fasync-sqlalchemy-session-per-request\u002Findex",[235],{"title":231,"path":232,"stem":233},{"title":237,"path":238,"stem":239,"children":240},"Fixing asyncpg Connection Pool Exhaustion in FastAPI","\u002Fasync-background-tasks-observability\u002Fasync-database-sessions\u002Ffixing-asyncpg-pool-exhaustion","async-background-tasks-observability\u002Fasync-database-sessions\u002Ffixing-asyncpg-pool-exhaustion\u002Findex",[241],{"title":237,"path":238,"stem":239},{"title":243,"path":244,"stem":245,"children":246},"Testing with Async Database Fixtures in FastAPI","\u002Fasync-background-tasks-observability\u002Fasync-database-sessions\u002Ftesting-with-async-database-fixtures","async-background-tasks-observability\u002Fasync-database-sessions\u002Ftesting-with-async-database-fixtures\u002Findex",[247],{"title":243,"path":244,"stem":245},{"title":249,"path":250,"stem":251,"children":252},"Transaction Management and Rollback in FastAPI","\u002Fasync-background-tasks-observability\u002Fasync-database-sessions\u002Ftransaction-management-and-rollback","async-background-tasks-observability\u002Fasync-database-sessions\u002Ftransaction-management-and-rollback\u002Findex",[253],{"title":249,"path":250,"stem":251},{"title":255,"path":256,"stem":257,"children":258},"Background Task Processing in FastAPI","\u002Fasync-background-tasks-observability\u002Fbackground-task-processing","async-background-tasks-observability\u002Fbackground-task-processing\u002Findex",[259,260,266,272,278],{"title":255,"path":256,"stem":257},{"title":261,"path":262,"stem":263,"children":264},"FastAPI BackgroundTasks vs Celery vs ARQ","\u002Fasync-background-tasks-observability\u002Fbackground-task-processing\u002Ffastapi-backgroundtasks-vs-celery-vs-arq","async-background-tasks-observability\u002Fbackground-task-processing\u002Ffastapi-backgroundtasks-vs-celery-vs-arq\u002Findex",[265],{"title":261,"path":262,"stem":263},{"title":267,"path":268,"stem":269,"children":270},"Retry and Idempotency for FastAPI Background Tasks","\u002Fasync-background-tasks-observability\u002Fbackground-task-processing\u002Fretry-and-idempotency-for-tasks","async-background-tasks-observability\u002Fbackground-task-processing\u002Fretry-and-idempotency-for-tasks\u002Findex",[271],{"title":267,"path":268,"stem":269},{"title":273,"path":274,"stem":275,"children":276},"Running ARQ Workers with FastAPI","\u002Fasync-background-tasks-observability\u002Fbackground-task-processing\u002Frunning-arq-workers-with-fastapi","async-background-tasks-observability\u002Fbackground-task-processing\u002Frunning-arq-workers-with-fastapi\u002Findex",[277],{"title":273,"path":274,"stem":275},{"title":279,"path":280,"stem":281,"children":282},"When FastAPI BackgroundTasks Silently Fails","\u002Fasync-background-tasks-observability\u002Fbackground-task-processing\u002Fwhen-backgroundtasks-silently-fails","async-background-tasks-observability\u002Fbackground-task-processing\u002Fwhen-backgroundtasks-silently-fails\u002Findex",[283],{"title":279,"path":280,"stem":281},{"title":285,"path":286,"stem":287,"children":288},"Caching Strategies in FastAPI","\u002Fasync-background-tasks-observability\u002Fcaching-strategies","async-background-tasks-observability\u002Fcaching-strategies\u002Findex",[289,290,296,302],{"title":285,"path":286,"stem":287},{"title":291,"path":292,"stem":293,"children":294},"Cache Invalidation Patterns in FastAPI","\u002Fasync-background-tasks-observability\u002Fcaching-strategies\u002Fcache-invalidation-patterns-in-fastapi","async-background-tasks-observability\u002Fcaching-strategies\u002Fcache-invalidation-patterns-in-fastapi\u002Findex",[295],{"title":291,"path":292,"stem":293},{"title":297,"path":298,"stem":299,"children":300},"Caching Dependency Results in FastAPI","\u002Fasync-background-tasks-observability\u002Fcaching-strategies\u002Fcaching-dependency-results","async-background-tasks-observability\u002Fcaching-strategies\u002Fcaching-dependency-results\u002Findex",[301],{"title":297,"path":298,"stem":299},{"title":303,"path":304,"stem":305,"children":306},"Redis Response Caching in FastAPI","\u002Fasync-background-tasks-observability\u002Fcaching-strategies\u002Fredis-response-caching-in-fastapi","async-background-tasks-observability\u002Fcaching-strategies\u002Fredis-response-caching-in-fastapi\u002Findex",[307],{"title":303,"path":304,"stem":305},{"title":309,"path":310,"stem":311,"children":312},"Observability and Tracing in FastAPI","\u002Fasync-background-tasks-observability\u002Fobservability-and-tracing","async-background-tasks-observability\u002Fobservability-and-tracing\u002Findex",[313,314,320,326,332],{"title":309,"path":310,"stem":311},{"title":315,"path":316,"stem":317,"children":318},"Correlating Logs, Traces and Errors in FastAPI","\u002Fasync-background-tasks-observability\u002Fobservability-and-tracing\u002Fcorrelating-logs-traces-and-errors","async-background-tasks-observability\u002Fobservability-and-tracing\u002Fcorrelating-logs-traces-and-errors\u002Findex",[319],{"title":315,"path":316,"stem":317},{"title":321,"path":322,"stem":323,"children":324},"Instrumenting FastAPI with OpenTelemetry","\u002Fasync-background-tasks-observability\u002Fobservability-and-tracing\u002Finstrumenting-fastapi-with-opentelemetry","async-background-tasks-observability\u002Fobservability-and-tracing\u002Finstrumenting-fastapi-with-opentelemetry\u002Findex",[325],{"title":321,"path":322,"stem":323},{"title":327,"path":328,"stem":329,"children":330},"Prometheus Metrics for FastAPI","\u002Fasync-background-tasks-observability\u002Fobservability-and-tracing\u002Fprometheus-metrics-for-fastapi","async-background-tasks-observability\u002Fobservability-and-tracing\u002Fprometheus-metrics-for-fastapi\u002Findex",[331],{"title":327,"path":328,"stem":329},{"title":333,"path":334,"stem":335,"children":336},"Structured JSON Logging with Request IDs in FastAPI","\u002Fasync-background-tasks-observability\u002Fobservability-and-tracing\u002Fstructured-json-logging-with-request-ids","async-background-tasks-observability\u002Fobservability-and-tracing\u002Fstructured-json-logging-with-request-ids\u002Findex",[337],{"title":333,"path":334,"stem":335},{"title":339,"path":340,"stem":341,"children":342},"Rate Limiting and Throttling in FastAPI","\u002Fasync-background-tasks-observability\u002Frate-limiting-throttling","async-background-tasks-observability\u002Frate-limiting-throttling\u002Findex",[343,344,350,356],{"title":339,"path":340,"stem":341},{"title":345,"path":346,"stem":347,"children":348},"FastAPI Rate Limiting with Redis and SlowAPI","\u002Fasync-background-tasks-observability\u002Frate-limiting-throttling\u002Ffastapi-rate-limiting-with-redis-slowapi","async-background-tasks-observability\u002Frate-limiting-throttling\u002Ffastapi-rate-limiting-with-redis-slowapi\u002Findex",[349],{"title":345,"path":346,"stem":347},{"title":351,"path":352,"stem":353,"children":354},"Per-User Token Bucket Throttling in FastAPI","\u002Fasync-background-tasks-observability\u002Frate-limiting-throttling\u002Fper-user-token-bucket-throttling","async-background-tasks-observability\u002Frate-limiting-throttling\u002Fper-user-token-bucket-throttling\u002Findex",[355],{"title":351,"path":352,"stem":353},{"title":357,"path":358,"stem":359,"children":360},"Rate Limit Headers and 429 Responses in FastAPI","\u002Fasync-background-tasks-observability\u002Frate-limiting-throttling\u002Frate-limit-headers-and-429-responses","async-background-tasks-observability\u002Frate-limiting-throttling\u002Frate-limit-headers-and-429-responses\u002Findex",[361],{"title":357,"path":358,"stem":359},{"title":363,"path":364,"stem":365,"children":366},"Testing FastAPI Applications","\u002Fasync-background-tasks-observability\u002Ftesting-fastapi-applications","async-background-tasks-observability\u002Ftesting-fastapi-applications\u002Findex",[367,368,374,380],{"title":363,"path":364,"stem":365},{"title":369,"path":370,"stem":371,"children":372},"Mocking External Services in FastAPI Tests","\u002Fasync-background-tasks-observability\u002Ftesting-fastapi-applications\u002Fmocking-external-services-in-tests","async-background-tasks-observability\u002Ftesting-fastapi-applications\u002Fmocking-external-services-in-tests\u002Findex",[373],{"title":369,"path":370,"stem":371},{"title":375,"path":376,"stem":377,"children":378},"TestClient vs httpx AsyncClient in FastAPI","\u002Fasync-background-tasks-observability\u002Ftesting-fastapi-applications\u002Ftestclient-vs-httpx-asyncclient","async-background-tasks-observability\u002Ftesting-fastapi-applications\u002Ftestclient-vs-httpx-asyncclient\u002Findex",[379],{"title":375,"path":376,"stem":377},{"title":381,"path":382,"stem":383,"children":384},"Testing Async FastAPI Endpoints with pytest-asyncio","\u002Fasync-background-tasks-observability\u002Ftesting-fastapi-applications\u002Ftesting-async-endpoints-with-pytest-asyncio","async-background-tasks-observability\u002Ftesting-fastapi-applications\u002Ftesting-async-endpoints-with-pytest-asyncio\u002Findex",[385],{"title":381,"path":382,"stem":383},{"title":387,"path":388,"stem":389,"children":390},"Core Architecture Routing Patterns","\u002Fcore-architecture-routing-patterns","core-architecture-routing-patterns",[391,394,412,436,472,496,526,556],{"title":392,"path":388,"stem":393},"FastAPI Core Architecture and Routing Patterns","core-architecture-routing-patterns\u002Findex",{"title":395,"path":396,"stem":397,"children":398},"Application Factory Patterns in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fapplication-factory-patterns","core-architecture-routing-patterns\u002Fapplication-factory-patterns\u002Findex",[399,400,406],{"title":395,"path":396,"stem":397},{"title":401,"path":402,"stem":403,"children":404},"FastAPI App Factory Pattern for Testing and Deployment","\u002Fcore-architecture-routing-patterns\u002Fapplication-factory-patterns\u002Ffastapi-app-factory-pattern-for-testing-and-deployment","core-architecture-routing-patterns\u002Fapplication-factory-patterns\u002Ffastapi-app-factory-pattern-for-testing-and-deployment\u002Findex",[405],{"title":401,"path":402,"stem":403},{"title":407,"path":408,"stem":409,"children":410},"Lifespan Events vs Startup and Shutdown in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fapplication-factory-patterns\u002Flifespan-events-vs-startup-shutdown","core-architecture-routing-patterns\u002Fapplication-factory-patterns\u002Flifespan-events-vs-startup-shutdown\u002Findex",[411],{"title":407,"path":408,"stem":409},{"title":413,"path":414,"stem":415,"children":416},"Configuration Management in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fconfiguration-management","core-architecture-routing-patterns\u002Fconfiguration-management\u002Findex",[417,418,424,430],{"title":413,"path":414,"stem":415},{"title":419,"path":420,"stem":421,"children":422},"Managing Environment Variables with Pydantic Settings","\u002Fcore-architecture-routing-patterns\u002Fconfiguration-management\u002Fmanaging-environment-variables-with-pydantic-settings","core-architecture-routing-patterns\u002Fconfiguration-management\u002Fmanaging-environment-variables-with-pydantic-settings\u002Findex",[423],{"title":419,"path":420,"stem":421},{"title":425,"path":426,"stem":427,"children":428},"Pydantic Settings vs Dynaconf vs python-decouple","\u002Fcore-architecture-routing-patterns\u002Fconfiguration-management\u002Fpydantic-settings-vs-dynaconf-vs-python-decouple","core-architecture-routing-patterns\u002Fconfiguration-management\u002Fpydantic-settings-vs-dynaconf-vs-python-decouple\u002Findex",[429],{"title":425,"path":426,"stem":427},{"title":431,"path":432,"stem":433,"children":434},"Secrets and .env Files Per Environment in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fconfiguration-management\u002Fsecrets-and-env-files-per-environment","core-architecture-routing-patterns\u002Fconfiguration-management\u002Fsecrets-and-env-files-per-environment\u002Findex",[435],{"title":431,"path":432,"stem":433},{"title":437,"path":438,"stem":439,"children":440},"Dependency Injection Strategies in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fdependency-injection-strategies","core-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Findex",[441,442,448,454,460,466],{"title":437,"path":438,"stem":439},{"title":443,"path":444,"stem":445,"children":446},"Best Practices for FastAPI Dependency Injection","\u002Fcore-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Fbest-practices-for-fastapi-dependency-injection","core-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Fbest-practices-for-fastapi-dependency-injection\u002Findex",[447],{"title":443,"path":444,"stem":445},{"title":449,"path":450,"stem":451,"children":452},"Dependency Caching and use_cache in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Fdependency-caching-and-use-cache","core-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Fdependency-caching-and-use-cache\u002Findex",[453],{"title":449,"path":450,"stem":451},{"title":455,"path":456,"stem":457,"children":458},"Fixing FastAPI Dependency Injection Circular Imports","\u002Fcore-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Ffastapi-dependency-injection-circular-import-fix","core-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Ffastapi-dependency-injection-circular-import-fix\u002Findex",[459],{"title":455,"path":456,"stem":457},{"title":461,"path":462,"stem":463,"children":464},"Overriding Dependencies in FastAPI Tests","\u002Fcore-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Foverriding-dependencies-in-tests","core-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Foverriding-dependencies-in-tests\u002Findex",[465],{"title":461,"path":462,"stem":463},{"title":467,"path":468,"stem":469,"children":470},"Yield Dependencies and Cleanup Order in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Fyield-dependencies-and-cleanup-order","core-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Fyield-dependencies-and-cleanup-order\u002Findex",[471],{"title":467,"path":468,"stem":469},{"title":473,"path":474,"stem":475,"children":476},"Error Handling and Global Exceptions in FastAPI","\u002Fcore-architecture-routing-patterns\u002Ferror-handling-global-exceptions","core-architecture-routing-patterns\u002Ferror-handling-global-exceptions\u002Findex",[477,478,484,490],{"title":473,"path":474,"stem":475},{"title":479,"path":480,"stem":481,"children":482},"Customising Validation Error Responses in FastAPI","\u002Fcore-architecture-routing-patterns\u002Ferror-handling-global-exceptions\u002Fcustomising-validation-error-responses","core-architecture-routing-patterns\u002Ferror-handling-global-exceptions\u002Fcustomising-validation-error-responses\u002Findex",[483],{"title":479,"path":480,"stem":481},{"title":485,"path":486,"stem":487,"children":488},"Global Exception Handlers for Consistent API Responses","\u002Fcore-architecture-routing-patterns\u002Ferror-handling-global-exceptions\u002Fglobal-exception-handlers-for-consistent-api-responses","core-architecture-routing-patterns\u002Ferror-handling-global-exceptions\u002Fglobal-exception-handlers-for-consistent-api-responses\u002Findex",[489],{"title":485,"path":486,"stem":487},{"title":491,"path":492,"stem":493,"children":494},"HTTPException vs Custom Exception Classes in FastAPI","\u002Fcore-architecture-routing-patterns\u002Ferror-handling-global-exceptions\u002Fhttpexception-vs-custom-exception-classes","core-architecture-routing-patterns\u002Ferror-handling-global-exceptions\u002Fhttpexception-vs-custom-exception-classes\u002Findex",[495],{"title":491,"path":492,"stem":493},{"title":497,"path":498,"stem":499,"children":500},"Middleware Implementation in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fmiddleware-implementation","core-architecture-routing-patterns\u002Fmiddleware-implementation\u002Findex",[501,502,508,514,520],{"title":497,"path":498,"stem":499},{"title":503,"path":504,"stem":505,"children":506},"CORS Middleware Configuration in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fmiddleware-implementation\u002Fcors-middleware-configuration","core-architecture-routing-patterns\u002Fmiddleware-implementation\u002Fcors-middleware-configuration\u002Findex",[507],{"title":503,"path":504,"stem":505},{"title":509,"path":510,"stem":511,"children":512},"Implementing Custom Middleware for Request Tracing","\u002Fcore-architecture-routing-patterns\u002Fmiddleware-implementation\u002Fimplementing-custom-middleware-for-request-tracing","core-architecture-routing-patterns\u002Fmiddleware-implementation\u002Fimplementing-custom-middleware-for-request-tracing\u002Findex",[513],{"title":509,"path":510,"stem":511},{"title":515,"path":516,"stem":517,"children":518},"Middleware Execution Order in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fmiddleware-implementation\u002Fmiddleware-execution-order","core-architecture-routing-patterns\u002Fmiddleware-implementation\u002Fmiddleware-execution-order\u002Findex",[519],{"title":515,"path":516,"stem":517},{"title":521,"path":522,"stem":523,"children":524},"Middleware vs Dependencies: When to Use Which","\u002Fcore-architecture-routing-patterns\u002Fmiddleware-implementation\u002Fmiddleware-vs-dependencies-when-to-use-which","core-architecture-routing-patterns\u002Fmiddleware-implementation\u002Fmiddleware-vs-dependencies-when-to-use-which\u002Findex",[525],{"title":521,"path":522,"stem":523},{"title":527,"path":528,"stem":529,"children":530},"Modular Router Organization in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fmodular-router-organization","core-architecture-routing-patterns\u002Fmodular-router-organization\u002Findex",[531,532,538,544,550],{"title":527,"path":528,"stem":529},{"title":533,"path":534,"stem":535,"children":536},"APIRouter Prefix vs Sub-Application Mounting in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fmodular-router-organization\u002Fapirouter-prefix-vs-sub-application-mounting","core-architecture-routing-patterns\u002Fmodular-router-organization\u002Fapirouter-prefix-vs-sub-application-mounting\u002Findex",[537],{"title":533,"path":534,"stem":535},{"title":539,"path":540,"stem":541,"children":542},"How to Structure Large FastAPI Projects for Scale","\u002Fcore-architecture-routing-patterns\u002Fmodular-router-organization\u002Fhow-to-structure-large-fastapi-projects-for-scale","core-architecture-routing-patterns\u002Fmodular-router-organization\u002Fhow-to-structure-large-fastapi-projects-for-scale\u002Findex",[543],{"title":539,"path":540,"stem":541},{"title":545,"path":546,"stem":547,"children":548},"Router Tags and OpenAPI Grouping in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fmodular-router-organization\u002Frouter-tags-and-openapi-grouping","core-architecture-routing-patterns\u002Fmodular-router-organization\u002Frouter-tags-and-openapi-grouping\u002Findex",[549],{"title":545,"path":546,"stem":547},{"title":551,"path":552,"stem":553,"children":554},"Versioning APIs with FastAPI Routers","\u002Fcore-architecture-routing-patterns\u002Fmodular-router-organization\u002Fversioning-apis-with-routers","core-architecture-routing-patterns\u002Fmodular-router-organization\u002Fversioning-apis-with-routers\u002Findex",[555],{"title":551,"path":552,"stem":553},{"title":557,"path":558,"stem":559,"children":560},"The FastAPI Request\u002FResponse Lifecycle","\u002Fcore-architecture-routing-patterns\u002Frequest-response-lifecycle","core-architecture-routing-patterns\u002Frequest-response-lifecycle\u002Findex",[561,562,568,574],{"title":557,"path":558,"stem":559},{"title":563,"path":564,"stem":565,"children":566},"How a Request Flows Through FastAPI","\u002Fcore-architecture-routing-patterns\u002Frequest-response-lifecycle\u002Fhow-a-request-flows-through-fastapi","core-architecture-routing-patterns\u002Frequest-response-lifecycle\u002Fhow-a-request-flows-through-fastapi\u002Findex",[567],{"title":563,"path":564,"stem":565},{"title":569,"path":570,"stem":571,"children":572},"Response Model and Serialization Order","\u002Fcore-architecture-routing-patterns\u002Frequest-response-lifecycle\u002Fresponse-model-and-serialization-order","core-architecture-routing-patterns\u002Frequest-response-lifecycle\u002Fresponse-model-and-serialization-order\u002Findex",[573],{"title":569,"path":570,"stem":571},{"title":575,"path":576,"stem":577,"children":578},"Streaming and File Responses in FastAPI","\u002Fcore-architecture-routing-patterns\u002Frequest-response-lifecycle\u002Fstreaming-and-file-responses","core-architecture-routing-patterns\u002Frequest-response-lifecycle\u002Fstreaming-and-file-responses\u002Findex",[579],{"title":575,"path":576,"stem":577},{"id":581,"title":551,"body":582,"dateModified":2195,"datePublished":2195,"description":2196,"extension":2197,"faq":2198,"howto":2209,"meta":2210,"navigation":870,"path":552,"seo":2223,"stem":553,"type":2224,"__hash__":2225},"content\u002Fcore-architecture-routing-patterns\u002Fmodular-router-organization\u002Fversioning-apis-with-routers\u002Findex.md",{"type":583,"value":584,"toc":2184},"minimark",[585,589,596,623,632,637,648,651,754,758,777,780,799,803,818,1534,1551,1554,1795,1798,1805,1825,1830,1833,1840,1852,1855,1859,1862,2029,2035,2043,2047,2054,2060,2074,2085,2095,2099,2105,2111,2127,2133,2139,2143,2180],[586,587,551],"h1",{"id":588},"versioning-apis-with-fastapi-routers",[590,591,592],"p",{},[593,594,595],"strong",{},"Key takeaways:",[597,598,599,608,611,617,620],"ul",{},[600,601,602,603,607],"li",{},"Give each version its own ",[604,605,606],"code",{},"APIRouter"," with a version prefix, and include both into one app.",[600,609,610],{},"Share only genuinely stable fields through a base model; each version owns its response model.",[600,612,613,616],{},[604,614,615],{},"deprecated=True"," on a router marks every operation beneath it in the generated OpenAPI document.",[600,618,619],{},"Never edit a shipped version's model to serve the new version — that is the breakage versioning prevents.",[600,621,622],{},"Instrument per-version traffic so the delete date comes from data, not a calendar.",[590,624,625,626,631],{},"This guide builds on ",[627,628,630],"a",{"href":629},"\u002Fcore-architecture-routing-patterns\u002Fmodular-router-organization\u002F","Modular Router Organization",", which covers composing domain routers; here the composition axis is the API version.",[633,634,636],"h2",{"id":635},"the-problem-this-solves","The Problem This Solves",[590,638,639,640,643,644,647],{},"You shipped ",[604,641,642],{},"\u002Fusers\u002F{id}"," returning a single ",[604,645,646],{},"name"," string. Two years later the product needs given and family names separately, plus a locale. Mobile clients from three releases ago still call the old shape and cannot be forced to upgrade. You need both shapes served correctly, from one deployment, with the old one clearly labelled as on its way out — and you need the new work not to break the old shape by accident.",[590,649,650],{},"The accidental-breakage part is the hard bit. Most FastAPI versioning failures are not routing failures; they are model failures. Someone adds a field to the model that both versions return, or tightens a constraint, and v1's contract changes without a single line of v1 code being touched.",[652,653,659,663,667,678,685,690,699,703,709,715,719,722,724,729,732,735,740,743,749],"svg",{"viewBox":654,"role":655,"ariaLabel":656,"xmlns":657,"style":658},"0 0 720 310","img","Two API versions built from one shared core model into one OpenAPI document","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;height:auto;max-width:720px;margin:2rem 0",[660,661,662],"title",{},"Two router versions sharing one core model",[664,665,666],"desc",{},"A shared UserCore model feeds a deprecated v1 router serving a single name field and a current v2 router serving split name fields; both routers are included into one FastAPI application that publishes a single OpenAPI document.",[668,669],"rect",{"x":670,"y":671,"width":672,"height":673,"rx":674,"fill":675,"stroke":676,"strokeWidth":677},"250","16","220","52","8","none","#00796B","2",[679,680,684],"text",{"x":681,"y":682,"style":683},"360","38","text-anchor:middle;fill:#00796B;font:700 14px sans-serif","UserCore",[679,686,689],{"x":681,"y":687,"style":688},"57","text-anchor:middle;fill:currentColor;font:400 12px sans-serif","id · email — stable",[691,692],"line",{"x1":693,"y1":694,"x2":695,"y2":696,"stroke":697,"strokeWidth":698},"320","68","200","108","currentColor","1.5",[691,700],{"x1":701,"y1":694,"x2":702,"y2":696,"stroke":697,"strokeWidth":698},"400","520",[668,704],{"x":705,"y":706,"width":707,"height":708,"rx":674,"fill":675,"stroke":697,"strokeWidth":698},"40","110","280","92",[679,710,714],{"x":711,"y":712,"style":713},"180","136","text-anchor:middle;fill:currentColor;font:700 13px sans-serif","\u002Fv1 router — deprecated",[679,716,718],{"x":711,"y":717,"style":688},"160","UserV1(UserCore)",[679,720,721],{"x":711,"y":711,"style":688},"name: str",[668,723],{"x":701,"y":706,"width":707,"height":708,"rx":674,"fill":675,"stroke":676,"strokeWidth":677},[679,725,728],{"x":726,"y":712,"style":727},"540","text-anchor:middle;fill:#00796B;font:700 13px sans-serif","\u002Fv2 router — current",[679,730,731],{"x":726,"y":717,"style":688},"UserV2(UserCore)",[679,733,734],{"x":726,"y":711,"style":688},"given_name, family_name, locale",[691,736],{"x1":711,"y1":737,"x2":738,"y2":739,"stroke":697,"strokeWidth":698},"202","300","242",[691,741],{"x1":726,"y1":737,"x2":742,"y2":739,"stroke":697,"strokeWidth":698},"420",[668,744],{"x":745,"y":746,"width":747,"height":748,"rx":674,"fill":675,"stroke":676,"strokeWidth":677},"190","244","340","46",[679,750,753],{"x":681,"y":751,"style":752},"273","text-anchor:middle;fill:#00796B;font:600 13px sans-serif","one app · one \u002Fopenapi.json",[633,755,757],{"id":756},"why-it-happens","Why It Happens",[590,759,760,763,764,768,769,772,773,776],{},[604,761,762],{},"include_router"," copies each route into the parent's routing table with the prefix applied, and it records the response model attached to that route at declaration time. There is no linkage back to the router afterwards. So the version boundary in FastAPI is entirely a ",[765,766,767],"em",{},"code organisation"," boundary — the framework will happily let ",[604,770,771],{},"\u002Fv1"," and ",[604,774,775],{},"\u002Fv2"," point at the same handler, the same model, and the same database query.",[590,778,779],{},"That means the framework gives you the routing for free and gives you nothing at all for the contract. The contract discipline has to come from how you structure the models: a version's response model must be a leaf that nothing else edits.",[590,781,782,783,785,786,772,789,792,793,795,796,798],{},"Inheritance is the tool that fits. A ",[604,784,684],{}," holding only the fields that are genuinely identical across versions, with ",[604,787,788],{},"UserV1",[604,790,791],{},"UserV2"," inheriting from it, gives you one place to fix an email-field typo and two places that are free to diverge. Adding a field to ",[604,794,791],{}," cannot touch ",[604,797,788],{},", because inheritance flows downward only.",[633,800,802],{"id":801},"the-fix","The Fix",[590,804,805,806,810,811,813,814,817],{},"One module per version, each exporting a router, both included in the ",[627,807,809],{"href":808},"\u002Fcore-architecture-routing-patterns\u002Fapplication-factory-patterns\u002F","application factory",". The v1 router carries ",[604,812,615],{},"; both carry explicit ",[604,815,816],{},"operation_id"," values so generated clients get stable method names.",[819,820,825],"pre",{"className":821,"code":822,"language":823,"meta":824,"style":824},"language-python shiki shiki-themes github-light-high-contrast","\"\"\"Run \u002Fv1 and \u002Fv2 side by side from shared models, and mark v1 deprecated in OpenAPI.\"\"\"\nfrom fastapi import APIRouter, FastAPI\nfrom pydantic import BaseModel, Field\n\n\nclass UserCore(BaseModel):\n    \"\"\"Fields that are identical in every version. Changing this changes both.\"\"\"\n\n    id: int\n    email: str\n\n\nclass UserV1(UserCore):\n    \"\"\"v1 shipped a single `name` string.\"\"\"\n\n    name: str\n\n\nclass UserV2(UserCore):\n    \"\"\"v2 split the name and added a field. v1 clients never see it.\"\"\"\n\n    given_name: str\n    family_name: str\n    locale: str = Field(default=\"en-GB\")\n\n\nRECORDS = {1: {\"id\": 1, \"email\": \"ada@example.com\", \"given\": \"Ada\", \"family\": \"Lovelace\"}}\n\nv1 = APIRouter(prefix=\"\u002Fv1\", tags=[\"users v1\"], deprecated=True)\n\n\n@v1.get(\"\u002Fusers\u002F{user_id}\", response_model=UserV1, operation_id=\"get_user_v1\")\nasync def get_user_v1(user_id: int) -> UserV1:\n    row = RECORDS[user_id]\n    return UserV1(id=row[\"id\"], email=row[\"email\"], name=f\"{row['given']} {row['family']}\")\n\n\nv2 = APIRouter(prefix=\"\u002Fv2\", tags=[\"users v2\"])\n\n\n@v2.get(\"\u002Fusers\u002F{user_id}\", response_model=UserV2, operation_id=\"get_user_v2\")\nasync def get_user_v2(user_id: int) -> UserV2:\n    row = RECORDS[user_id]\n    return UserV2(\n        id=row[\"id\"], email=row[\"email\"], given_name=row[\"given\"], family_name=row[\"family\"]\n    )\n\n\napp = FastAPI(title=\"Versioned API\")\napp.include_router(v1)\napp.include_router(v2)\n","python","",[604,826,827,835,852,865,872,877,897,903,908,920,929,934,939,953,959,964,972,977,982,996,1002,1007,1015,1023,1050,1055,1060,1118,1123,1168,1173,1178,1215,1236,1250,1320,1325,1330,1361,1366,1371,1403,1420,1431,1439,1486,1492,1497,1502,1522,1528],{"__ignoreMap":824},[828,829,831],"span",{"class":691,"line":830},1,[828,832,834],{"class":833},"sYEJz","\"\"\"Run \u002Fv1 and \u002Fv2 side by side from shared models, and mark v1 deprecated in OpenAPI.\"\"\"\n",[828,836,838,842,846,849],{"class":691,"line":837},2,[828,839,841],{"class":840},"sTJeM","from",[828,843,845],{"class":844},"sigWx"," fastapi ",[828,847,848],{"class":840},"import",[828,850,851],{"class":844}," APIRouter, FastAPI\n",[828,853,855,857,860,862],{"class":691,"line":854},3,[828,856,841],{"class":840},[828,858,859],{"class":844}," pydantic ",[828,861,848],{"class":840},[828,863,864],{"class":844}," BaseModel, Field\n",[828,866,868],{"class":691,"line":867},4,[828,869,871],{"emptyLinePlaceholder":870},true,"\n",[828,873,875],{"class":691,"line":874},5,[828,876,871],{"emptyLinePlaceholder":870},[828,878,880,883,887,890,894],{"class":691,"line":879},6,[828,881,882],{"class":840},"class",[828,884,886],{"class":885},"sV4o_"," UserCore",[828,888,889],{"class":844},"(",[828,891,893],{"class":892},"sacAq","BaseModel",[828,895,896],{"class":844},"):\n",[828,898,900],{"class":691,"line":899},7,[828,901,902],{"class":833},"    \"\"\"Fields that are identical in every version. Changing this changes both.\"\"\"\n",[828,904,906],{"class":691,"line":905},8,[828,907,871],{"emptyLinePlaceholder":870},[828,909,911,914,917],{"class":691,"line":910},9,[828,912,913],{"class":892},"    id",[828,915,916],{"class":844},": ",[828,918,919],{"class":892},"int\n",[828,921,923,926],{"class":691,"line":922},10,[828,924,925],{"class":844},"    email: ",[828,927,928],{"class":892},"str\n",[828,930,932],{"class":691,"line":931},11,[828,933,871],{"emptyLinePlaceholder":870},[828,935,937],{"class":691,"line":936},12,[828,938,871],{"emptyLinePlaceholder":870},[828,940,942,944,947,949,951],{"class":691,"line":941},13,[828,943,882],{"class":840},[828,945,946],{"class":885}," UserV1",[828,948,889],{"class":844},[828,950,684],{"class":892},[828,952,896],{"class":844},[828,954,956],{"class":691,"line":955},14,[828,957,958],{"class":833},"    \"\"\"v1 shipped a single `name` string.\"\"\"\n",[828,960,962],{"class":691,"line":961},15,[828,963,871],{"emptyLinePlaceholder":870},[828,965,967,970],{"class":691,"line":966},16,[828,968,969],{"class":844},"    name: ",[828,971,928],{"class":892},[828,973,975],{"class":691,"line":974},17,[828,976,871],{"emptyLinePlaceholder":870},[828,978,980],{"class":691,"line":979},18,[828,981,871],{"emptyLinePlaceholder":870},[828,983,985,987,990,992,994],{"class":691,"line":984},19,[828,986,882],{"class":840},[828,988,989],{"class":885}," UserV2",[828,991,889],{"class":844},[828,993,684],{"class":892},[828,995,896],{"class":844},[828,997,999],{"class":691,"line":998},20,[828,1000,1001],{"class":833},"    \"\"\"v2 split the name and added a field. v1 clients never see it.\"\"\"\n",[828,1003,1005],{"class":691,"line":1004},21,[828,1006,871],{"emptyLinePlaceholder":870},[828,1008,1010,1013],{"class":691,"line":1009},22,[828,1011,1012],{"class":844},"    given_name: ",[828,1014,928],{"class":892},[828,1016,1018,1021],{"class":691,"line":1017},23,[828,1019,1020],{"class":844},"    family_name: ",[828,1022,928],{"class":892},[828,1024,1026,1029,1032,1035,1038,1041,1044,1047],{"class":691,"line":1025},24,[828,1027,1028],{"class":844},"    locale: ",[828,1030,1031],{"class":892},"str",[828,1033,1034],{"class":840}," =",[828,1036,1037],{"class":844}," Field(",[828,1039,1040],{"class":885},"default",[828,1042,1043],{"class":840},"=",[828,1045,1046],{"class":833},"\"en-GB\"",[828,1048,1049],{"class":844},")\n",[828,1051,1053],{"class":691,"line":1052},25,[828,1054,871],{"emptyLinePlaceholder":870},[828,1056,1058],{"class":691,"line":1057},26,[828,1059,871],{"emptyLinePlaceholder":870},[828,1061,1063,1066,1068,1071,1074,1077,1080,1082,1084,1087,1090,1092,1095,1097,1100,1102,1105,1107,1110,1112,1115],{"class":691,"line":1062},27,[828,1064,1065],{"class":892},"RECORDS",[828,1067,1034],{"class":840},[828,1069,1070],{"class":844}," {",[828,1072,1073],{"class":892},"1",[828,1075,1076],{"class":844},": {",[828,1078,1079],{"class":833},"\"id\"",[828,1081,916],{"class":844},[828,1083,1073],{"class":892},[828,1085,1086],{"class":844},", ",[828,1088,1089],{"class":833},"\"email\"",[828,1091,916],{"class":844},[828,1093,1094],{"class":833},"\"ada@example.com\"",[828,1096,1086],{"class":844},[828,1098,1099],{"class":833},"\"given\"",[828,1101,916],{"class":844},[828,1103,1104],{"class":833},"\"Ada\"",[828,1106,1086],{"class":844},[828,1108,1109],{"class":833},"\"family\"",[828,1111,916],{"class":844},[828,1113,1114],{"class":833},"\"Lovelace\"",[828,1116,1117],{"class":844},"}}\n",[828,1119,1121],{"class":691,"line":1120},28,[828,1122,871],{"emptyLinePlaceholder":870},[828,1124,1126,1129,1131,1134,1137,1139,1142,1144,1147,1149,1152,1155,1158,1161,1163,1166],{"class":691,"line":1125},29,[828,1127,1128],{"class":844},"v1 ",[828,1130,1043],{"class":840},[828,1132,1133],{"class":844}," APIRouter(",[828,1135,1136],{"class":885},"prefix",[828,1138,1043],{"class":840},[828,1140,1141],{"class":833},"\"\u002Fv1\"",[828,1143,1086],{"class":844},[828,1145,1146],{"class":885},"tags",[828,1148,1043],{"class":840},[828,1150,1151],{"class":844},"[",[828,1153,1154],{"class":833},"\"users v1\"",[828,1156,1157],{"class":844},"], ",[828,1159,1160],{"class":885},"deprecated",[828,1162,1043],{"class":840},[828,1164,1165],{"class":892},"True",[828,1167,1049],{"class":844},[828,1169,1171],{"class":691,"line":1170},30,[828,1172,871],{"emptyLinePlaceholder":870},[828,1174,1176],{"class":691,"line":1175},31,[828,1177,871],{"emptyLinePlaceholder":870},[828,1179,1181,1185,1187,1190,1193,1196,1198,1201,1203,1206,1208,1210,1213],{"class":691,"line":1180},32,[828,1182,1184],{"class":1183},"s3dhs","@v1.get",[828,1186,889],{"class":844},[828,1188,1189],{"class":833},"\"\u002Fusers\u002F",[828,1191,1192],{"class":840},"{user_id}",[828,1194,1195],{"class":833},"\"",[828,1197,1086],{"class":844},[828,1199,1200],{"class":885},"response_model",[828,1202,1043],{"class":840},[828,1204,1205],{"class":844},"UserV1, ",[828,1207,816],{"class":885},[828,1209,1043],{"class":840},[828,1211,1212],{"class":833},"\"get_user_v1\"",[828,1214,1049],{"class":844},[828,1216,1218,1221,1224,1227,1230,1233],{"class":691,"line":1217},33,[828,1219,1220],{"class":840},"async",[828,1222,1223],{"class":840}," def",[828,1225,1226],{"class":1183}," get_user_v1",[828,1228,1229],{"class":844},"(user_id: ",[828,1231,1232],{"class":892},"int",[828,1234,1235],{"class":844},") -> UserV1:\n",[828,1237,1239,1242,1244,1247],{"class":691,"line":1238},34,[828,1240,1241],{"class":844},"    row ",[828,1243,1043],{"class":840},[828,1245,1246],{"class":892}," RECORDS",[828,1248,1249],{"class":844},"[user_id]\n",[828,1251,1253,1256,1259,1262,1264,1267,1269,1271,1274,1276,1278,1280,1282,1284,1286,1289,1291,1294,1296,1299,1302,1305,1307,1309,1312,1314,1316,1318],{"class":691,"line":1252},35,[828,1254,1255],{"class":840},"    return",[828,1257,1258],{"class":844}," UserV1(",[828,1260,1261],{"class":885},"id",[828,1263,1043],{"class":840},[828,1265,1266],{"class":844},"row[",[828,1268,1079],{"class":833},[828,1270,1157],{"class":844},[828,1272,1273],{"class":885},"email",[828,1275,1043],{"class":840},[828,1277,1266],{"class":844},[828,1279,1089],{"class":833},[828,1281,1157],{"class":844},[828,1283,646],{"class":885},[828,1285,1043],{"class":840},[828,1287,1288],{"class":840},"f",[828,1290,1195],{"class":833},[828,1292,1293],{"class":840},"{",[828,1295,1266],{"class":844},[828,1297,1298],{"class":833},"'given'",[828,1300,1301],{"class":844},"]",[828,1303,1304],{"class":840},"}",[828,1306,1070],{"class":840},[828,1308,1266],{"class":844},[828,1310,1311],{"class":833},"'family'",[828,1313,1301],{"class":844},[828,1315,1304],{"class":840},[828,1317,1195],{"class":833},[828,1319,1049],{"class":844},[828,1321,1323],{"class":691,"line":1322},36,[828,1324,871],{"emptyLinePlaceholder":870},[828,1326,1328],{"class":691,"line":1327},37,[828,1329,871],{"emptyLinePlaceholder":870},[828,1331,1333,1336,1338,1340,1342,1344,1347,1349,1351,1353,1355,1358],{"class":691,"line":1332},38,[828,1334,1335],{"class":844},"v2 ",[828,1337,1043],{"class":840},[828,1339,1133],{"class":844},[828,1341,1136],{"class":885},[828,1343,1043],{"class":840},[828,1345,1346],{"class":833},"\"\u002Fv2\"",[828,1348,1086],{"class":844},[828,1350,1146],{"class":885},[828,1352,1043],{"class":840},[828,1354,1151],{"class":844},[828,1356,1357],{"class":833},"\"users v2\"",[828,1359,1360],{"class":844},"])\n",[828,1362,1364],{"class":691,"line":1363},39,[828,1365,871],{"emptyLinePlaceholder":870},[828,1367,1369],{"class":691,"line":1368},40,[828,1370,871],{"emptyLinePlaceholder":870},[828,1372,1374,1377,1379,1381,1383,1385,1387,1389,1391,1394,1396,1398,1401],{"class":691,"line":1373},41,[828,1375,1376],{"class":1183},"@v2.get",[828,1378,889],{"class":844},[828,1380,1189],{"class":833},[828,1382,1192],{"class":840},[828,1384,1195],{"class":833},[828,1386,1086],{"class":844},[828,1388,1200],{"class":885},[828,1390,1043],{"class":840},[828,1392,1393],{"class":844},"UserV2, ",[828,1395,816],{"class":885},[828,1397,1043],{"class":840},[828,1399,1400],{"class":833},"\"get_user_v2\"",[828,1402,1049],{"class":844},[828,1404,1406,1408,1410,1413,1415,1417],{"class":691,"line":1405},42,[828,1407,1220],{"class":840},[828,1409,1223],{"class":840},[828,1411,1412],{"class":1183}," get_user_v2",[828,1414,1229],{"class":844},[828,1416,1232],{"class":892},[828,1418,1419],{"class":844},") -> UserV2:\n",[828,1421,1423,1425,1427,1429],{"class":691,"line":1422},43,[828,1424,1241],{"class":844},[828,1426,1043],{"class":840},[828,1428,1246],{"class":892},[828,1430,1249],{"class":844},[828,1432,1434,1436],{"class":691,"line":1433},44,[828,1435,1255],{"class":840},[828,1437,1438],{"class":844}," UserV2(\n",[828,1440,1442,1445,1447,1449,1451,1453,1455,1457,1459,1461,1463,1466,1468,1470,1472,1474,1477,1479,1481,1483],{"class":691,"line":1441},45,[828,1443,1444],{"class":885},"        id",[828,1446,1043],{"class":840},[828,1448,1266],{"class":844},[828,1450,1079],{"class":833},[828,1452,1157],{"class":844},[828,1454,1273],{"class":885},[828,1456,1043],{"class":840},[828,1458,1266],{"class":844},[828,1460,1089],{"class":833},[828,1462,1157],{"class":844},[828,1464,1465],{"class":885},"given_name",[828,1467,1043],{"class":840},[828,1469,1266],{"class":844},[828,1471,1099],{"class":833},[828,1473,1157],{"class":844},[828,1475,1476],{"class":885},"family_name",[828,1478,1043],{"class":840},[828,1480,1266],{"class":844},[828,1482,1109],{"class":833},[828,1484,1485],{"class":844},"]\n",[828,1487,1489],{"class":691,"line":1488},46,[828,1490,1491],{"class":844},"    )\n",[828,1493,1495],{"class":691,"line":1494},47,[828,1496,871],{"emptyLinePlaceholder":870},[828,1498,1500],{"class":691,"line":1499},48,[828,1501,871],{"emptyLinePlaceholder":870},[828,1503,1505,1508,1510,1513,1515,1517,1520],{"class":691,"line":1504},49,[828,1506,1507],{"class":844},"app ",[828,1509,1043],{"class":840},[828,1511,1512],{"class":844}," FastAPI(",[828,1514,660],{"class":885},[828,1516,1043],{"class":840},[828,1518,1519],{"class":833},"\"Versioned API\"",[828,1521,1049],{"class":844},[828,1523,1525],{"class":691,"line":1524},50,[828,1526,1527],{"class":844},"app.include_router(v1)\n",[828,1529,1531],{"class":691,"line":1530},51,[828,1532,1533],{"class":844},"app.include_router(v2)\n",[590,1535,1536,1537,1540,1541,1543,1544,772,1547,1550],{},"Note that the ",[765,1538,1539],{},"storage"," shape (",[604,1542,1065],{},", with ",[604,1545,1546],{},"given",[604,1548,1549],{},"family",") belongs to neither version. Both versions adapt from it. That is the second half of the discipline: if v1's handler reads the database row directly into a response model, then a migration that renames a column becomes a v1 API change.",[590,1552,1553],{},"To prove the deprecation flag actually reaches the document rather than trusting that it does, this example reads its own generated OpenAPI back:",[819,1555,1557],{"className":821,"code":1556,"language":823,"meta":824,"style":824},"@app.get(\"\u002F_meta\u002Fversions\")\nasync def versions() -> dict:\n    \"\"\"Read the REAL generated OpenAPI document back and report each operation's state.\"\"\"\n    spec = app.openapi()\n    out = []\n    for path, methods in sorted(spec[\"paths\"].items()):\n        for method, op in sorted(methods.items()):\n            if path.startswith(\"\u002F_meta\"):\n                continue\n            out.append(\n                {\n                    \"path\": path,\n                    \"method\": method.upper(),\n                    \"operationId\": op[\"operationId\"],\n                    \"deprecated\": op.get(\"deprecated\", False),\n                    \"responseSchema\": op[\"responses\"][\"200\"][\"content\"][\"application\u002Fjson\"][\n                        \"schema\"\n                    ][\"$ref\"],\n                }\n            )\n    return {\"operations\": out}\n",[604,1558,1559,1571,1589,1594,1604,1614,1637,1652,1665,1670,1675,1680,1688,1696,1710,1729,1758,1763,1773,1778,1783],{"__ignoreMap":824},[828,1560,1561,1564,1566,1569],{"class":691,"line":830},[828,1562,1563],{"class":1183},"@app.get",[828,1565,889],{"class":844},[828,1567,1568],{"class":833},"\"\u002F_meta\u002Fversions\"",[828,1570,1049],{"class":844},[828,1572,1573,1575,1577,1580,1583,1586],{"class":691,"line":837},[828,1574,1220],{"class":840},[828,1576,1223],{"class":840},[828,1578,1579],{"class":1183}," versions",[828,1581,1582],{"class":844},"() -> ",[828,1584,1585],{"class":892},"dict",[828,1587,1588],{"class":844},":\n",[828,1590,1591],{"class":691,"line":854},[828,1592,1593],{"class":833},"    \"\"\"Read the REAL generated OpenAPI document back and report each operation's state.\"\"\"\n",[828,1595,1596,1599,1601],{"class":691,"line":867},[828,1597,1598],{"class":844},"    spec ",[828,1600,1043],{"class":840},[828,1602,1603],{"class":844}," app.openapi()\n",[828,1605,1606,1609,1611],{"class":691,"line":874},[828,1607,1608],{"class":844},"    out ",[828,1610,1043],{"class":840},[828,1612,1613],{"class":844}," []\n",[828,1615,1616,1619,1622,1625,1628,1631,1634],{"class":691,"line":879},[828,1617,1618],{"class":840},"    for",[828,1620,1621],{"class":844}," path, methods ",[828,1623,1624],{"class":840},"in",[828,1626,1627],{"class":892}," sorted",[828,1629,1630],{"class":844},"(spec[",[828,1632,1633],{"class":833},"\"paths\"",[828,1635,1636],{"class":844},"].items()):\n",[828,1638,1639,1642,1645,1647,1649],{"class":691,"line":899},[828,1640,1641],{"class":840},"        for",[828,1643,1644],{"class":844}," method, op ",[828,1646,1624],{"class":840},[828,1648,1627],{"class":892},[828,1650,1651],{"class":844},"(methods.items()):\n",[828,1653,1654,1657,1660,1663],{"class":691,"line":905},[828,1655,1656],{"class":840},"            if",[828,1658,1659],{"class":844}," path.startswith(",[828,1661,1662],{"class":833},"\"\u002F_meta\"",[828,1664,896],{"class":844},[828,1666,1667],{"class":691,"line":910},[828,1668,1669],{"class":840},"                continue\n",[828,1671,1672],{"class":691,"line":922},[828,1673,1674],{"class":844},"            out.append(\n",[828,1676,1677],{"class":691,"line":931},[828,1678,1679],{"class":844},"                {\n",[828,1681,1682,1685],{"class":691,"line":936},[828,1683,1684],{"class":833},"                    \"path\"",[828,1686,1687],{"class":844},": path,\n",[828,1689,1690,1693],{"class":691,"line":941},[828,1691,1692],{"class":833},"                    \"method\"",[828,1694,1695],{"class":844},": method.upper(),\n",[828,1697,1698,1701,1704,1707],{"class":691,"line":955},[828,1699,1700],{"class":833},"                    \"operationId\"",[828,1702,1703],{"class":844},": op[",[828,1705,1706],{"class":833},"\"operationId\"",[828,1708,1709],{"class":844},"],\n",[828,1711,1712,1715,1718,1721,1723,1726],{"class":691,"line":961},[828,1713,1714],{"class":833},"                    \"deprecated\"",[828,1716,1717],{"class":844},": op.get(",[828,1719,1720],{"class":833},"\"deprecated\"",[828,1722,1086],{"class":844},[828,1724,1725],{"class":892},"False",[828,1727,1728],{"class":844},"),\n",[828,1730,1731,1734,1736,1739,1742,1745,1747,1750,1752,1755],{"class":691,"line":966},[828,1732,1733],{"class":833},"                    \"responseSchema\"",[828,1735,1703],{"class":844},[828,1737,1738],{"class":833},"\"responses\"",[828,1740,1741],{"class":844},"][",[828,1743,1744],{"class":833},"\"200\"",[828,1746,1741],{"class":844},[828,1748,1749],{"class":833},"\"content\"",[828,1751,1741],{"class":844},[828,1753,1754],{"class":833},"\"application\u002Fjson\"",[828,1756,1757],{"class":844},"][\n",[828,1759,1760],{"class":691,"line":974},[828,1761,1762],{"class":833},"                        \"schema\"\n",[828,1764,1765,1768,1771],{"class":691,"line":979},[828,1766,1767],{"class":844},"                    ][",[828,1769,1770],{"class":833},"\"$ref\"",[828,1772,1709],{"class":844},[828,1774,1775],{"class":691,"line":984},[828,1776,1777],{"class":844},"                }\n",[828,1779,1780],{"class":691,"line":998},[828,1781,1782],{"class":844},"            )\n",[828,1784,1785,1787,1789,1792],{"class":691,"line":1004},[828,1786,1255],{"class":840},[828,1788,1070],{"class":844},[828,1790,1791],{"class":833},"\"operations\"",[828,1793,1794],{"class":844},": out}\n",[590,1796,1797],{},"Both versions answering, and the generated document describing them, in one real run of this app:",[819,1799,1803],{"className":1800,"code":1802,"language":679,"meta":824},[1801],"language-text","$ GET \u002Fv1\u002Fusers\u002F1\n200 OK\n{\n  \"id\": 1,\n  \"email\": \"ada@example.com\",\n  \"name\": \"Ada Lovelace\"\n}\n\n$ GET \u002Fv2\u002Fusers\u002F1\n200 OK\n{\n  \"id\": 1,\n  \"email\": \"ada@example.com\",\n  \"given_name\": \"Ada\",\n  \"family_name\": \"Lovelace\",\n  \"locale\": \"en-GB\"\n}\n\n$ GET \u002F_meta\u002Fversions\n200 OK\n{\n  \"operations\": [\n    {\n      \"path\": \"\u002Fv1\u002Fusers\u002F{user_id}\",\n      \"method\": \"GET\",\n      \"operationId\": \"get_user_v1\",\n      \"deprecated\": true,\n      \"responseSchema\": \"#\u002Fcomponents\u002Fschemas\u002FUserV1\"\n    },\n    {\n      \"path\": \"\u002Fv2\u002Fusers\u002F{user_id}\",\n      \"method\": \"GET\",\n      \"operationId\": \"get_user_v2\",\n      \"deprecated\": false,\n      \"responseSchema\": \"#\u002Fcomponents\u002Fschemas\u002FUserV2\"\n    }\n  ]\n}\n",[604,1804,1802],{"__ignoreMap":824},[590,1806,1807,1808,1811,1812,1815,1816,1819,1820,772,1822,1824],{},"Three things in that transcript are worth pausing on. ",[604,1809,1810],{},"locale"," appears in the v2 body with its default of ",[604,1813,1814],{},"en-GB"," and does not appear in the v1 body at all — the response model, not the handler, decides what ships. The v1 operation carries ",[604,1817,1818],{},"\"deprecated\": true"," while v2 does not, from a single flag on the router. And the two operations reference distinct schemas, ",[604,1821,788],{},[604,1823,791],{},", so a generated client produces two distinct types rather than one union that fits neither.",[1826,1827,1829],"h3",{"id":1828},"where-the-version-boundary-should-stop","Where the version boundary should stop",[590,1831,1832],{},"The routers and the response models are versioned. Almost nothing else should be.",[590,1834,1835,1836,1839],{},"Duplicating the service layer per version is the most expensive mistake available here, because it doubles the surface every bug fix has to be applied to, and the two copies drift within months. One ",[604,1837,1838],{},"UserService"," serves both versions; each version's handler adapts its output into that version's model. The adapter is usually three or four lines, and those three or four lines are the entire cost of keeping the old contract alive.",[590,1841,1842,1843,1847,1848,1851],{},"Dependencies follow the same rule. Authentication, rate limiting, tracing and database sessions are cross-cutting and belong on the app or on a shared parent router, not duplicated per version — see ",[627,1844,1846],{"href":1845},"\u002Fcore-architecture-routing-patterns\u002Fdependency-injection-strategies\u002F","Dependency Injection Strategies"," for where to attach them. The exception is a dependency whose ",[765,1849,1850],{},"behaviour"," is part of the contract: if v1 promised a permissive pagination limit and v2 tightened it, that dependency is versioned data, and it goes with the version.",[590,1853,1854],{},"The practical test is to ask what a change to the shared thing would do to v1. If the answer is \"nothing visible\", share it. If the answer is \"v1's responses change\", it is not shared — it is v2's, and v1 needs its own copy frozen at the shape it shipped with.",[633,1856,1858],{"id":1857},"verification","Verification",[590,1860,1861],{},"The regression you are guarding against is a v2 change leaking into v1, and it is cheap to lock down. Snapshot v1's schema and assert on it:",[819,1863,1865],{"className":821,"code":1864,"language":823,"meta":824,"style":824},"def test_v1_contract_is_frozen():\n    schema = app.openapi()[\"components\"][\"schemas\"][\"UserV1\"]\n    assert sorted(schema[\"properties\"]) == [\"email\", \"id\", \"name\"]\n    assert sorted(schema[\"required\"]) == [\"email\", \"id\", \"name\"]\n\n\ndef test_v1_response_has_no_v2_fields():\n    body = client.get(\"\u002Fv1\u002Fusers\u002F1\").json()\n    assert \"locale\" not in body and \"given_name\" not in body\n",[604,1866,1867,1878,1903,1938,1967,1971,1975,1984,2000],{"__ignoreMap":824},[828,1868,1869,1872,1875],{"class":691,"line":830},[828,1870,1871],{"class":840},"def",[828,1873,1874],{"class":1183}," test_v1_contract_is_frozen",[828,1876,1877],{"class":844},"():\n",[828,1879,1880,1883,1885,1888,1891,1893,1896,1898,1901],{"class":691,"line":837},[828,1881,1882],{"class":844},"    schema ",[828,1884,1043],{"class":840},[828,1886,1887],{"class":844}," app.openapi()[",[828,1889,1890],{"class":833},"\"components\"",[828,1892,1741],{"class":844},[828,1894,1895],{"class":833},"\"schemas\"",[828,1897,1741],{"class":844},[828,1899,1900],{"class":833},"\"UserV1\"",[828,1902,1485],{"class":844},[828,1904,1905,1908,1910,1913,1916,1919,1922,1925,1927,1929,1931,1933,1936],{"class":691,"line":854},[828,1906,1907],{"class":840},"    assert",[828,1909,1627],{"class":892},[828,1911,1912],{"class":844},"(schema[",[828,1914,1915],{"class":833},"\"properties\"",[828,1917,1918],{"class":844},"]) ",[828,1920,1921],{"class":840},"==",[828,1923,1924],{"class":844}," [",[828,1926,1089],{"class":833},[828,1928,1086],{"class":844},[828,1930,1079],{"class":833},[828,1932,1086],{"class":844},[828,1934,1935],{"class":833},"\"name\"",[828,1937,1485],{"class":844},[828,1939,1940,1942,1944,1946,1949,1951,1953,1955,1957,1959,1961,1963,1965],{"class":691,"line":867},[828,1941,1907],{"class":840},[828,1943,1627],{"class":892},[828,1945,1912],{"class":844},[828,1947,1948],{"class":833},"\"required\"",[828,1950,1918],{"class":844},[828,1952,1921],{"class":840},[828,1954,1924],{"class":844},[828,1956,1089],{"class":833},[828,1958,1086],{"class":844},[828,1960,1079],{"class":833},[828,1962,1086],{"class":844},[828,1964,1935],{"class":833},[828,1966,1485],{"class":844},[828,1968,1969],{"class":691,"line":874},[828,1970,871],{"emptyLinePlaceholder":870},[828,1972,1973],{"class":691,"line":879},[828,1974,871],{"emptyLinePlaceholder":870},[828,1976,1977,1979,1982],{"class":691,"line":899},[828,1978,1871],{"class":840},[828,1980,1981],{"class":1183}," test_v1_response_has_no_v2_fields",[828,1983,1877],{"class":844},[828,1985,1986,1989,1991,1994,1997],{"class":691,"line":905},[828,1987,1988],{"class":844},"    body ",[828,1990,1043],{"class":840},[828,1992,1993],{"class":844}," client.get(",[828,1995,1996],{"class":833},"\"\u002Fv1\u002Fusers\u002F1\"",[828,1998,1999],{"class":844},").json()\n",[828,2001,2002,2004,2007,2010,2013,2016,2019,2022,2024,2026],{"class":691,"line":910},[828,2003,1907],{"class":840},[828,2005,2006],{"class":833}," \"locale\"",[828,2008,2009],{"class":840}," not",[828,2011,2012],{"class":840}," in",[828,2014,2015],{"class":844}," body ",[828,2017,2018],{"class":840},"and",[828,2020,2021],{"class":833}," \"given_name\"",[828,2023,2009],{"class":840},[828,2025,2012],{"class":840},[828,2027,2028],{"class":844}," body\n",[590,2030,2031,2032,2034],{},"The first test fails the moment someone adds a field to ",[604,2033,684],{}," intending to serve v2. That is the alarm you want, and it is why the shared base should stay small: every field you put in it is a field two versions have to agree on forever.",[590,2036,2037,2038,2042],{},"For deployed systems, add a per-version request counter — a label on your metrics middleware is enough, and ",[627,2039,2041],{"href":2040},"\u002Fasync-background-tasks-observability\u002Fobservability-and-tracing\u002Fprometheus-metrics-for-fastapi\u002F","Prometheus metrics for FastAPI"," covers the wiring. Deprecation without a traffic graph is guesswork.",[633,2044,2046],{"id":2045},"trade-offs-and-when-not-to","Trade-offs and When Not To",[590,2048,2049,2050,2053],{},"URL-prefix versioning is not free. Every route exists twice in the routing table, every version needs its own tests, and the ",[604,2051,2052],{},"\u002Fdocs"," page grows. In exchange you get a version that is visible in access logs, in a curl command, in an error report from a customer, and in the OpenAPI document — which is what makes it the default choice.",[590,2055,2056,2059],{},[593,2057,2058],{},"Do not version at all if you can avoid it."," Additive changes — a new optional response field, a new optional query parameter — are backward compatible and need no version bump. Reserve versions for changes that genuinely break a client: removing a field, renaming one, tightening a validation rule, or changing a status code.",[590,2061,2062,2065,2066,2069,2070,2073],{},[593,2063,2064],{},"Do not version per endpoint."," A ",[604,2067,2068],{},"\u002Fusers\u002Fv2"," alongside ",[604,2071,2072],{},"\u002Forders\u002Fv1"," produces a matrix that no client can reason about. Version the whole surface at once, even when only one endpoint changed, so a consumer can say \"we are on v2\" and mean something.",[590,2075,2076,2079,2080,2084],{},[593,2077,2078],{},"Consider a sub-application instead"," when a version needs its own middleware stack or its own lifespan — a v3 written against a different auth scheme, say. ",[627,2081,2083],{"href":2082},"\u002Fcore-architecture-routing-patterns\u002Fmodular-router-organization\u002Fapirouter-prefix-vs-sub-application-mounting\u002F","APIRouter Prefix vs Sub-Application Mounting"," sets out that decision in full; the short version is that a mounted app gets isolation and loses the single unified schema.",[590,2086,2087,2090,2091,2094],{},[593,2088,2089],{},"Header-based versioning"," is defensible when URLs must be permanent, for example when they are used as resource identifiers elsewhere. Implement it as a dependency that reads ",[604,2092,2093],{},"Accept"," or a custom header and dispatches, and accept that you have given up on the version being visible in your logs and dashboards unless you add it there yourself.",[633,2096,2098],{"id":2097},"faq","FAQ",[590,2100,2101,2104],{},[593,2102,2103],{},"Should I version with a URL prefix or a header?","\nA URL prefix is the pragmatic default because it is visible in logs, curl commands, dashboards and OpenAPI without any custom negotiation code. Header versioning keeps URLs stable but hides the version from every tool you debug with, and it needs a dependency to read and dispatch on the header.",[590,2106,2107,2110],{},[593,2108,2109],{},"Can two versions share the same Pydantic model?","\nThey can share a base model of genuinely stable fields, but each version should own its own response model that inherits from it. The moment a shared model is edited to satisfy v2, v1's contract changes silently, which is exactly the failure versioning exists to prevent.",[590,2112,2113,2116,2117,2119,2120,2122,2123,2126],{},[593,2114,2115],{},"How do I mark a version deprecated in the OpenAPI document?","\nPass ",[604,2118,615],{}," to the ",[604,2121,606],{},", or to individual path operations. FastAPI writes ",[604,2124,2125],{},"deprecated: true"," onto every affected operation, Swagger UI strikes them through, and client generators emit deprecation annotations that surface in consumers' IDEs.",[590,2128,2129,2132],{},[593,2130,2131],{},"Does running two versions in one app slow anything down?","\nRoute matching is a linear scan over compiled path regexes, so a second version adds negligible per-request cost. The real cost is maintenance: every bug fix has to be assessed against both versions, which is the argument for deleting old versions rather than keeping them cheap.",[590,2134,2135,2138],{},[593,2136,2137],{},"When can I actually delete v1?","\nWhen traffic to it reaches zero and stays there. Instrument per-version request counts before you announce the deprecation, since the decision to delete should be made from a graph rather than from a date in a changelog.",[633,2140,2142],{"id":2141},"related-reading","Related Reading",[597,2144,2145,2154,2161,2166,2173],{},[600,2146,2147,2150,2151,2153],{},[593,2148,2149],{},"Up to the topic:"," ",[627,2152,630],{"href":629}," explains the router composition this guide versions along.",[600,2155,2156,2157,2160],{},"Where the versioned routers are assembled: ",[627,2158,2159],{"href":808},"Application Factory Patterns",".",[600,2162,2163,2164,2160],{},"The isolation alternative: ",[627,2165,2083],{"href":2082},[600,2167,2168,2169,2160],{},"Making versions legible in the generated docs and clients: ",[627,2170,2172],{"href":2171},"\u002Fcore-architecture-routing-patterns\u002Fmodular-router-organization\u002Frouter-tags-and-openapi-grouping\u002F","Router Tags and OpenAPI Grouping",[600,2174,2175,2176,2160],{},"Laying out the version packages on disk: ",[627,2177,2179],{"href":2178},"\u002Fcore-architecture-routing-patterns\u002Fmodular-router-organization\u002Fhow-to-structure-large-fastapi-projects-for-scale\u002F","Structuring Large FastAPI Projects for Scale",[2181,2182,2183],"style",{},"html pre.shiki code .sYEJz, html code.shiki .sYEJz{--shiki-default:#032563}html pre.shiki code .sTJeM, html code.shiki .sTJeM{--shiki-default:#A0111F}html pre.shiki code .sigWx, html code.shiki .sigWx{--shiki-default:#0E1116}html pre.shiki code .sV4o_, html code.shiki .sV4o_{--shiki-default:#702C00}html pre.shiki code .sacAq, html code.shiki .sacAq{--shiki-default:#023B95}html pre.shiki code .s3dhs, html code.shiki .s3dhs{--shiki-default:#622CBC}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":824,"searchDepth":837,"depth":837,"links":2185},[2186,2187,2188,2191,2192,2193,2194],{"id":635,"depth":837,"text":636},{"id":756,"depth":837,"text":757},{"id":801,"depth":837,"text":802,"children":2189},[2190],{"id":1828,"depth":854,"text":1829},{"id":1857,"depth":837,"text":1858},{"id":2045,"depth":837,"text":2046},{"id":2097,"depth":837,"text":2098},{"id":2141,"depth":837,"text":2142},"2026-07-20","Run \u002Fv1 and \u002Fv2 side by side in one FastAPI app: share core models between versions, keep each version's schema honest, and deprecate a version in OpenAPI.","md",[2199,2201,2203,2205,2207],{"q":2103,"a":2200},"A URL prefix is the pragmatic default because it is visible in logs, curl commands, dashboards and OpenAPI without any custom negotiation code. Header versioning keeps URLs stable but hides the version from every tool you debug with, and it needs a dependency to read and dispatch on the header.",{"q":2109,"a":2202},"They can share a base model of genuinely stable fields, but each version should own its own response model that inherits from it. The moment a shared model is edited to satisfy v2, v1's contract changes silently, which is exactly the failure versioning exists to prevent.",{"q":2115,"a":2204},"Pass deprecated=True to the APIRouter, or to individual path operations. FastAPI writes deprecated: true onto every affected operation, Swagger UI strikes them through, and client generators emit deprecation annotations that surface in consumers' IDEs.",{"q":2131,"a":2206},"Route matching is a linear scan over compiled path regexes, so a second version adds negligible per-request cost. The real cost is maintenance: every bug fix has to be assessed against both versions, which is the argument for deleting old versions rather than keeping them cheap.",{"q":2137,"a":2208},"When traffic to it reaches zero and stays there. Instrument per-version request counts before you announce the deprecation, since the decision to delete should be made from a graph rather than from a date in a changelog.",null,{"slug":2211,"breadcrumb":2212},"versioning-apis-with-routers",[2213,2216,2219,2220],{"label":2214,"path":2215},"Home","\u002F",{"label":2217,"path":2218},"Core Architecture & Routing Patterns","\u002Fcore-architecture-routing-patterns\u002F",{"label":630,"path":629},{"label":2221,"path":2222},"Versioning APIs with Routers","\u002Fcore-architecture-routing-patterns\u002Fmodular-router-organization\u002Fversioning-apis-with-routers\u002F",{"title":551,"description":2196},"article","iLT-XBakjk9cfsfQ1qT1cEFEK21PcoIOJcsOQfXOMOc",[2209,2209],1784588202739]