[{"data":1,"prerenderedAt":2017},["ShallowReactive",2],{"nav":3,"page-\u002Fadvanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002Fcross-field-validation-patterns\u002F":580,"surround-\u002Fadvanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002Fcross-field-validation-patterns\u002F":2016},[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":31,"body":582,"dateModified":1984,"datePublished":1984,"description":1985,"extension":1986,"faq":1987,"howto":1998,"meta":1999,"navigation":942,"path":32,"seo":2013,"stem":33,"type":2014,"__hash__":2015},"content\u002Fadvanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002Fcross-field-validation-patterns\u002Findex.md",{"type":583,"value":584,"toc":1970},"minimark",[585,589,596,635,644,649,685,689,711,733,748,761,863,867,882,886,893,1029,1036,1043,1058,1062,1068,1177,1180,1186,1216,1222,1226,1229,1371,1373,1379,1394,1398,1404,1539,1542,1548,1566,1572,1576,1653,1657,1663,1840,1854,1858,1861,1869,1873,1885,1898,1907,1913,1928,1932,1966],[586,587,31],"h1",{"id":588},"cross-field-validation-patterns-in-pydantic-v2",[590,591,592],"p",{},[593,594,595],"strong",{},"Key takeaways:",[597,598,599,608,622,629,632],"ul",{},[600,601,602,603,607],"li",{},"Any rule involving two fields belongs in ",[604,605,606],"code",{},"@model_validator(mode=\"after\")",", where every field is present and typed.",[600,609,610,613,614,617,618,621],{},[604,611,612],{},"@field_validator"," sees only fields ",[593,615,616],{},"declared above it",", via ",[604,619,620],{},"info.data"," — reorder the class and the rule stops firing, with no error.",[600,623,624,625,628],{},"Cross-field failures report ",[604,626,627],{},"loc: [\"body\"]",", not a field name. Plan your client error handling around that.",[600,630,631],{},"The three recurring shapes are range ordering, matching confirmation, and conditional requirement.",[600,633,634],{},"Conditional requirements stay optional in the JSON Schema; the constraint is invisible to generated clients.",[590,636,637,638,643],{},"This page builds on ",[639,640,642],"a",{"href":641},"\u002Fadvanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002F","custom validators and field constraints",", which covers single-field rules.",[645,646,648],"h2",{"id":647},"the-problem-this-solves","The Problem This Solves",[590,650,651,652,655,656,659,660,655,663,659,666,669,670,673,674,677,678,680,681,684],{},"Some rules cannot be expressed on one field. ",[604,653,654],{},"ends_on"," is only wrong relative to ",[604,657,658],{},"starts_on",". ",[604,661,662],{},"password_confirm",[604,664,665],{},"password",[604,667,668],{},"address"," is only required when ",[604,671,672],{},"method == \"delivery\"",". None of these fit a ",[604,675,676],{},"Field"," constraint or a ",[604,679,612],{},", and the natural first attempt — reach for ",[604,682,683],{},"field_validator"," and look at the other field — has a failure mode that is worse than an exception: it silently does nothing.",[645,686,688],{"id":687},"why-it-happens","Why It Happens",[590,690,691,692,694,695,698,699,702,703,707,708,710],{},"Pydantic v2 validates fields in declaration order. A ",[604,693,683],{}," can accept a ",[604,696,697],{},"ValidationInfo"," argument whose ",[604,700,701],{},".data"," attribute holds the fields validated ",[704,705,706],"em",{},"so far",". That is genuinely useful and genuinely dangerous, because ",[604,709,701],{}," is a partial dict, and everything about it depends on declaration order:",[597,712,713,723],{},[600,714,715,716,719,720,722],{},"A field declared ",[593,717,718],{},"after"," the validator's field is simply not in ",[604,721,701],{},".",[600,724,725,726,729,730,732],{},"A field that ",[593,727,728],{},"failed"," its own validation is not in ",[604,731,701],{}," either.",[590,734,735,736,739,740,743,744,747],{},"So ",[604,737,738],{},"info.data.get(\"starts_on\")"," returns ",[604,741,742],{},"None"," in both cases, and any rule guarded by ",[604,745,746],{},"if start is not None"," quietly skips. Nothing raises. The endpoint returns 200 and accepts the invalid payload.",[590,749,750,752,753,756,757,760],{},[604,751,606],{}," has neither problem. It runs only once every field has validated, and it receives the constructed model, so ",[604,754,755],{},"self.starts_on"," is a real ",[604,758,759],{},"date",". There is no ordering to remember and no partial state to defend against.",[762,763,764,856],"figure",{},[765,766,774,778,782,789,798,803,807,812,816,820,825,829,833,837,839,842,844,847,850],"svg",{"viewBox":767,"role":768,"ariaLabelledBy":769,"xmlns":772,"style":773},"0 0 720 280","img",[770,771],"cf-title","cf-desc","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;height:auto;max-width:720px;margin:2rem 0",[775,776,777],"title",{"id":770},"What a field validator can see versus what a model validator can see",[779,780,781],"desc",{"id":771},"A field validator on the second field sees only the first field through info.data, while fields declared later are absent. The model validator runs after all fields and sees every one of them.",[783,784,788],"text",{"x":785,"y":786,"style":787},"180","28","text-anchor:middle;fill:currentColor;font:600 13px sans-serif","field_validator on field B",[790,791],"rect",{"x":792,"y":793,"width":794,"height":795,"rx":796,"style":797},"40","46","280","36","7","fill:none;stroke:currentColor;stroke-width:1.4",[783,799,802],{"x":785,"y":800,"style":801},"69","text-anchor:middle;fill:currentColor;font:400 12px sans-serif","A — validated, in info.data",[790,804],{"x":792,"y":805,"width":794,"height":795,"rx":796,"style":806},"90","fill:none;stroke:#00796B;stroke-width:1.8",[783,808,811],{"x":785,"y":809,"style":810},"113","text-anchor:middle;fill:#00796B;font:600 12px sans-serif","B — the field being validated",[790,813],{"x":792,"y":814,"width":794,"height":795,"rx":796,"style":815},"134","fill:none;stroke:currentColor;stroke-width:1.4;stroke-dasharray:5 4",[783,817,819],{"x":785,"y":818,"style":801},"157","C — not yet run, absent",[783,821,824],{"x":785,"y":822,"style":823},"196","text-anchor:middle;fill:currentColor;font:400 11.5px sans-serif","a rule about C never fires",[783,826,828],{"x":827,"y":786,"style":787},"540","model_validator mode=\"after\"",[790,830],{"x":831,"y":793,"width":794,"height":795,"rx":796,"style":832},"400","fill:none;stroke:#00796B;stroke-width:1.6",[783,834,836],{"x":827,"y":800,"style":835},"text-anchor:middle;fill:#00796B;font:400 12px sans-serif","A — self.a, typed",[790,838],{"x":831,"y":805,"width":794,"height":795,"rx":796,"style":832},[783,840,841],{"x":827,"y":809,"style":835},"B — self.b, typed",[790,843],{"x":831,"y":814,"width":794,"height":795,"rx":796,"style":832},[783,845,846],{"x":827,"y":818,"style":835},"C — self.c, typed",[783,848,849],{"x":827,"y":822,"style":823},"every field present, order irrelevant",[851,852],"line",{"x1":853,"y1":793,"x2":853,"y2":854,"style":855},"360","200","stroke:currentColor;stroke-width:1;stroke-dasharray:3 4",[857,858,859,860,862],"figcaption",{},"Declaration order determines what ",[604,861,620],{}," contains; a model validator runs after construction and is not affected by it.",[645,864,866],{"id":865},"prerequisites","Prerequisites",[597,868,869,872],{},[600,870,871],{},"Pydantic 2.13.4 and FastAPI 0.139.2 — the versions behind every transcript here.",[600,873,874,875,877,878,722],{},"The ",[604,876,718],{}," mode semantics from ",[639,879,881],{"href":880},"\u002Fadvanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002Fbefore-after-and-wrap-validators\u002F","before, after and wrap validators",[645,883,885],{"id":884},"pattern-1-date-range-ordering","Pattern 1: Date Range Ordering",[590,887,888,889,892],{},"The canonical case. One ",[604,890,891],{},"model_validator"," compares the two dates and raises.",[894,895,900],"pre",{"className":896,"code":897,"language":898,"meta":899,"style":899},"language-python shiki shiki-themes github-light-high-contrast","class DateRange(BaseModel):\n    starts_on: date\n    ends_on: date\n\n    @model_validator(mode=\"after\")\n    def end_after_start(self) -> \"DateRange\":\n        if self.ends_on \u003C= self.starts_on:\n            raise ValueError(\"ends_on must be later than starts_on\")\n        return self\n","python","",[604,901,902,925,931,937,944,966,984,1004,1020],{"__ignoreMap":899},[903,904,906,910,914,918,922],"span",{"class":851,"line":905},1,[903,907,909],{"class":908},"sTJeM","class",[903,911,913],{"class":912},"sV4o_"," DateRange",[903,915,917],{"class":916},"sigWx","(",[903,919,921],{"class":920},"sacAq","BaseModel",[903,923,924],{"class":916},"):\n",[903,926,928],{"class":851,"line":927},2,[903,929,930],{"class":916},"    starts_on: date\n",[903,932,934],{"class":851,"line":933},3,[903,935,936],{"class":916},"    ends_on: date\n",[903,938,940],{"class":851,"line":939},4,[903,941,943],{"emptyLinePlaceholder":942},true,"\n",[903,945,947,951,953,956,959,963],{"class":851,"line":946},5,[903,948,950],{"class":949},"s3dhs","    @model_validator",[903,952,917],{"class":916},[903,954,955],{"class":912},"mode",[903,957,958],{"class":908},"=",[903,960,962],{"class":961},"sYEJz","\"after\"",[903,964,965],{"class":916},")\n",[903,967,969,972,975,978,981],{"class":851,"line":968},6,[903,970,971],{"class":908},"    def",[903,973,974],{"class":949}," end_after_start",[903,976,977],{"class":916},"(self) -> ",[903,979,980],{"class":961},"\"DateRange\"",[903,982,983],{"class":916},":\n",[903,985,987,990,993,996,999,1001],{"class":851,"line":986},7,[903,988,989],{"class":908},"        if",[903,991,992],{"class":920}," self",[903,994,995],{"class":916},".ends_on ",[903,997,998],{"class":908},"\u003C=",[903,1000,992],{"class":920},[903,1002,1003],{"class":916},".starts_on:\n",[903,1005,1007,1010,1013,1015,1018],{"class":851,"line":1006},8,[903,1008,1009],{"class":908},"            raise",[903,1011,1012],{"class":920}," ValueError",[903,1014,917],{"class":916},[903,1016,1017],{"class":961},"\"ends_on must be later than starts_on\"",[903,1019,965],{"class":916},[903,1021,1023,1026],{"class":851,"line":1022},9,[903,1024,1025],{"class":908},"        return",[903,1027,1028],{"class":920}," self\n",[590,1030,1031,1032,1035],{},"Real output from ",[604,1033,1034],{},"_verify\u002Foutput\u002Fpyd-cross-field.txt",":",[894,1037,1041],{"className":1038,"code":1040,"language":783,"meta":899},[1039],"language-text","$ POST \u002Franges\u002F  {\"starts_on\": \"2026-08-01\", \"ends_on\": \"2026-08-10\"}\n200 OK\n{\n  \"starts_on\": \"2026-08-01\",\n  \"ends_on\": \"2026-08-10\"\n}\n\n$ POST \u002Franges\u002F  {\"starts_on\": \"2026-08-10\", \"ends_on\": \"2026-08-01\"}\n422 Unprocessable Entity\n{\n  \"detail\": [\n    {\n      \"type\": \"value_error\",\n      \"loc\": [\n        \"body\"\n      ],\n      \"msg\": \"Value error, ends_on must be later than starts_on\",\n      \"input\": {\n        \"starts_on\": \"2026-08-10\",\n        \"ends_on\": \"2026-08-01\"\n      },\n      \"ctx\": {\n        \"error\": {}\n      }\n    }\n  ]\n}\n",[604,1042,1040],{"__ignoreMap":899},[590,1044,1045,1046,1049,1050,1053,1054,1057],{},"Two details to note. ",[604,1047,1048],{},"loc"," is ",[604,1051,1052],{},"[\"body\"]"," — the failure has no field. And ",[604,1055,1056],{},"input"," echoes the entire submitted body. That is convenient for debugging and a liability everywhere else: on a model containing anything sensitive, this body lands in your logs and in the client's console.",[645,1059,1061],{"id":1060},"pattern-2-password-confirmation","Pattern 2: Password Confirmation",[590,1063,1064,1065,1067],{},"Structurally identical, but the ",[604,1066,1056],{}," echo now matters a great deal.",[894,1069,1071],{"className":896,"code":1070,"language":898,"meta":899,"style":899},"class Signup(BaseModel):\n    email: str\n    password: str\n    password_confirm: str\n\n    @model_validator(mode=\"after\")\n    def passwords_match(self) -> \"Signup\":\n        if self.password != self.password_confirm:\n            raise ValueError(\"password_confirm does not match password\")\n        return self\n",[604,1072,1073,1086,1094,1101,1108,1112,1126,1140,1157,1170],{"__ignoreMap":899},[903,1074,1075,1077,1080,1082,1084],{"class":851,"line":905},[903,1076,909],{"class":908},[903,1078,1079],{"class":912}," Signup",[903,1081,917],{"class":916},[903,1083,921],{"class":920},[903,1085,924],{"class":916},[903,1087,1088,1091],{"class":851,"line":927},[903,1089,1090],{"class":916},"    email: ",[903,1092,1093],{"class":920},"str\n",[903,1095,1096,1099],{"class":851,"line":933},[903,1097,1098],{"class":916},"    password: ",[903,1100,1093],{"class":920},[903,1102,1103,1106],{"class":851,"line":939},[903,1104,1105],{"class":916},"    password_confirm: ",[903,1107,1093],{"class":920},[903,1109,1110],{"class":851,"line":946},[903,1111,943],{"emptyLinePlaceholder":942},[903,1113,1114,1116,1118,1120,1122,1124],{"class":851,"line":968},[903,1115,950],{"class":949},[903,1117,917],{"class":916},[903,1119,955],{"class":912},[903,1121,958],{"class":908},[903,1123,962],{"class":961},[903,1125,965],{"class":916},[903,1127,1128,1130,1133,1135,1138],{"class":851,"line":986},[903,1129,971],{"class":908},[903,1131,1132],{"class":949}," passwords_match",[903,1134,977],{"class":916},[903,1136,1137],{"class":961},"\"Signup\"",[903,1139,983],{"class":916},[903,1141,1142,1144,1146,1149,1152,1154],{"class":851,"line":1006},[903,1143,989],{"class":908},[903,1145,992],{"class":920},[903,1147,1148],{"class":916},".password ",[903,1150,1151],{"class":908},"!=",[903,1153,992],{"class":920},[903,1155,1156],{"class":916},".password_confirm:\n",[903,1158,1159,1161,1163,1165,1168],{"class":851,"line":1022},[903,1160,1009],{"class":908},[903,1162,1012],{"class":920},[903,1164,917],{"class":916},[903,1166,1167],{"class":961},"\"password_confirm does not match password\"",[903,1169,965],{"class":916},[903,1171,1173,1175],{"class":851,"line":1172},10,[903,1174,1025],{"class":908},[903,1176,1028],{"class":920},[590,1178,1179],{},"Real output:",[894,1181,1184],{"className":1182,"code":1183,"language":783,"meta":899},[1039],"$ POST \u002Fsignups\u002F  {\"email\": \"a@b.com\", \"password\": \"hunter2!\", \"password_confirm\": \"hunter3!\"}\n422 Unprocessable Entity\n{\n  \"detail\": [\n    {\n      \"type\": \"value_error\",\n      \"loc\": [\n        \"body\"\n      ],\n      \"msg\": \"Value error, password_confirm does not match password\",\n      \"input\": {\n        \"email\": \"a@b.com\",\n        \"password\": \"hunter2!\",\n        \"password_confirm\": \"hunter3!\"\n      },\n      \"ctx\": {\n        \"error\": {}\n      }\n    }\n  ]\n}\n",[604,1185,1183],{"__ignoreMap":899},[590,1187,1188,1189,1192,1193,1197,1198,1200,1201,1204,1205,1208,1209,1212,1213,1215],{},"Both passwords are in the 422 response verbatim. This is FastAPI's default ",[604,1190,1191],{},"RequestValidationError"," handler faithfully reporting what was submitted, and it is the strongest practical argument for installing a ",[639,1194,1196],{"href":1195},"\u002Fcore-architecture-routing-patterns\u002Ferror-handling-global-exceptions\u002Fcustomising-validation-error-responses\u002F","global validation error handler"," that strips ",[604,1199,1056],{}," and ",[604,1202,1203],{},"ctx"," before the body leaves the process. Use ",[604,1206,1207],{},"SecretStr"," for the fields as well — it keeps them out of tracebacks and ",[604,1210,1211],{},"repr"," — but note that the raw ",[604,1214,1056],{}," echo above happens before any of that helps, since it is the pre-validation payload.",[590,1217,1218,1219,1221],{},"Keep ",[604,1220,662],{}," on the request model only. It is a property of the form submission, not of a user, and it should not exist on any model that reaches persistence.",[645,1223,1225],{"id":1224},"pattern-3-conditional-requirement","Pattern 3: Conditional Requirement",[590,1227,1228],{},"A field that is optional in general and mandatory in one case. Declare it optional, then enforce the condition.",[894,1230,1232],{"className":896,"code":1231,"language":898,"meta":899,"style":899},"class Shipment(BaseModel):\n    \"\"\"Conditional requirement: one field is only mandatory for a particular mode.\"\"\"\n\n    method: Literal[\"pickup\", \"delivery\"]\n    address: str | None = None\n\n    @model_validator(mode=\"after\")\n    def address_required_for_delivery(self) -> \"Shipment\":\n        if self.method == \"delivery\" and not self.address:\n            raise ValueError(\"address is required when method is 'delivery'\")\n        return self\n",[604,1233,1234,1247,1252,1256,1273,1293,1297,1311,1325,1351,1364],{"__ignoreMap":899},[903,1235,1236,1238,1241,1243,1245],{"class":851,"line":905},[903,1237,909],{"class":908},[903,1239,1240],{"class":912}," Shipment",[903,1242,917],{"class":916},[903,1244,921],{"class":920},[903,1246,924],{"class":916},[903,1248,1249],{"class":851,"line":927},[903,1250,1251],{"class":961},"    \"\"\"Conditional requirement: one field is only mandatory for a particular mode.\"\"\"\n",[903,1253,1254],{"class":851,"line":933},[903,1255,943],{"emptyLinePlaceholder":942},[903,1257,1258,1261,1264,1267,1270],{"class":851,"line":939},[903,1259,1260],{"class":916},"    method: Literal[",[903,1262,1263],{"class":961},"\"pickup\"",[903,1265,1266],{"class":916},", ",[903,1268,1269],{"class":961},"\"delivery\"",[903,1271,1272],{"class":916},"]\n",[903,1274,1275,1278,1281,1284,1287,1290],{"class":851,"line":946},[903,1276,1277],{"class":916},"    address: ",[903,1279,1280],{"class":920},"str",[903,1282,1283],{"class":908}," |",[903,1285,1286],{"class":920}," None",[903,1288,1289],{"class":908}," =",[903,1291,1292],{"class":920}," None\n",[903,1294,1295],{"class":851,"line":968},[903,1296,943],{"emptyLinePlaceholder":942},[903,1298,1299,1301,1303,1305,1307,1309],{"class":851,"line":986},[903,1300,950],{"class":949},[903,1302,917],{"class":916},[903,1304,955],{"class":912},[903,1306,958],{"class":908},[903,1308,962],{"class":961},[903,1310,965],{"class":916},[903,1312,1313,1315,1318,1320,1323],{"class":851,"line":1006},[903,1314,971],{"class":908},[903,1316,1317],{"class":949}," address_required_for_delivery",[903,1319,977],{"class":916},[903,1321,1322],{"class":961},"\"Shipment\"",[903,1324,983],{"class":916},[903,1326,1327,1329,1331,1334,1337,1340,1343,1346,1348],{"class":851,"line":1022},[903,1328,989],{"class":908},[903,1330,992],{"class":920},[903,1332,1333],{"class":916},".method ",[903,1335,1336],{"class":908},"==",[903,1338,1339],{"class":961}," \"delivery\"",[903,1341,1342],{"class":908}," and",[903,1344,1345],{"class":908}," not",[903,1347,992],{"class":920},[903,1349,1350],{"class":916},".address:\n",[903,1352,1353,1355,1357,1359,1362],{"class":851,"line":1172},[903,1354,1009],{"class":908},[903,1356,1012],{"class":920},[903,1358,917],{"class":916},[903,1360,1361],{"class":961},"\"address is required when method is 'delivery'\"",[903,1363,965],{"class":916},[903,1365,1367,1369],{"class":851,"line":1366},11,[903,1368,1025],{"class":908},[903,1370,1028],{"class":920},[590,1372,1179],{},[894,1374,1377],{"className":1375,"code":1376,"language":783,"meta":899},[1039],"$ POST \u002Fshipments\u002F  {\"method\": \"delivery\"}\n422 Unprocessable Entity\n{\n  \"detail\": [\n    {\n      \"type\": \"value_error\",\n      \"loc\": [\n        \"body\"\n      ],\n      \"msg\": \"Value error, address is required when method is 'delivery'\",\n      \"input\": {\n        \"method\": \"delivery\"\n      },\n      \"ctx\": {\n        \"error\": {}\n      }\n    }\n  ]\n}\n\n$ POST \u002Fshipments\u002F  {\"method\": \"pickup\"}\n200 OK\n{\n  \"method\": \"pickup\",\n  \"address\": null\n}\n",[604,1378,1376],{"__ignoreMap":899},[590,1380,1381,1382,1384,1385,1388,1389,1393],{},"The rule works, but it is invisible to the schema: OpenAPI advertises ",[604,1383,668],{}," as optional and nothing communicates the conditional requirement. A generated client will happily construct the invalid request. If the two variants differ by more than one field, a discriminated union on ",[604,1386,1387],{},"method"," models the reality far better and puts the requirement in the schema where clients can see it — that is a ",[639,1390,1392],{"href":1391},"\u002Fadvanced-pydantic-validation-serialization\u002Fjson-schema-customization\u002F","JSON Schema customization"," concern.",[645,1395,1397],{"id":1396},"the-anti-pattern-field_validator-with-infodata","The Anti-Pattern: field_validator with info.data",[590,1399,1400,1401,1403],{},"The same date-range rule written as a ",[604,1402,683],{},", with the fields declared in an order that breaks it:",[894,1405,1407],{"className":896,"code":1406,"language":898,"meta":899,"style":899},"class FieldValidatorAttempt(BaseModel):\n    \"\"\"Trying the same rule with field_validator, which only sees fields defined ABOVE it.\"\"\"\n\n    ends_on: date\n    starts_on: date\n\n    @field_validator(\"ends_on\")\n    @classmethod\n    def end_after_start(cls, value: date, info: ValidationInfo) -> date:\n        # starts_on is declared later, so info.data never contains it here.\n        start = info.data.get(\"starts_on\")\n        if start is not None and value \u003C= start:\n            raise ValueError(\"ends_on must be later than starts_on\")\n        return value\n",[604,1408,1409,1422,1427,1431,1435,1439,1443,1455,1463,1472,1478,1493,1518,1531],{"__ignoreMap":899},[903,1410,1411,1413,1416,1418,1420],{"class":851,"line":905},[903,1412,909],{"class":908},[903,1414,1415],{"class":912}," FieldValidatorAttempt",[903,1417,917],{"class":916},[903,1419,921],{"class":920},[903,1421,924],{"class":916},[903,1423,1424],{"class":851,"line":927},[903,1425,1426],{"class":961},"    \"\"\"Trying the same rule with field_validator, which only sees fields defined ABOVE it.\"\"\"\n",[903,1428,1429],{"class":851,"line":933},[903,1430,943],{"emptyLinePlaceholder":942},[903,1432,1433],{"class":851,"line":939},[903,1434,936],{"class":916},[903,1436,1437],{"class":851,"line":946},[903,1438,930],{"class":916},[903,1440,1441],{"class":851,"line":968},[903,1442,943],{"emptyLinePlaceholder":942},[903,1444,1445,1448,1450,1453],{"class":851,"line":986},[903,1446,1447],{"class":949},"    @field_validator",[903,1449,917],{"class":916},[903,1451,1452],{"class":961},"\"ends_on\"",[903,1454,965],{"class":916},[903,1456,1457,1460],{"class":851,"line":1006},[903,1458,1459],{"class":949},"    @",[903,1461,1462],{"class":920},"classmethod\n",[903,1464,1465,1467,1469],{"class":851,"line":1022},[903,1466,971],{"class":908},[903,1468,974],{"class":949},[903,1470,1471],{"class":916},"(cls, value: date, info: ValidationInfo) -> date:\n",[903,1473,1474],{"class":851,"line":1172},[903,1475,1477],{"class":1476},"sFeEa","        # starts_on is declared later, so info.data never contains it here.\n",[903,1479,1480,1483,1485,1488,1491],{"class":851,"line":1366},[903,1481,1482],{"class":916},"        start ",[903,1484,958],{"class":908},[903,1486,1487],{"class":916}," info.data.get(",[903,1489,1490],{"class":961},"\"starts_on\"",[903,1492,965],{"class":916},[903,1494,1496,1498,1501,1504,1506,1508,1510,1513,1515],{"class":851,"line":1495},12,[903,1497,989],{"class":908},[903,1499,1500],{"class":916}," start ",[903,1502,1503],{"class":908},"is",[903,1505,1345],{"class":908},[903,1507,1286],{"class":920},[903,1509,1342],{"class":908},[903,1511,1512],{"class":916}," value ",[903,1514,998],{"class":908},[903,1516,1517],{"class":916}," start:\n",[903,1519,1521,1523,1525,1527,1529],{"class":851,"line":1520},13,[903,1522,1009],{"class":908},[903,1524,1012],{"class":920},[903,1526,917],{"class":916},[903,1528,1017],{"class":961},[903,1530,965],{"class":916},[903,1532,1534,1536],{"class":851,"line":1533},14,[903,1535,1025],{"class":908},[903,1537,1538],{"class":916}," value\n",[590,1540,1541],{},"Real output, with the same invalid payload that produced a 422 above:",[894,1543,1546],{"className":1544,"code":1545,"language":783,"meta":899},[1039],"$ POST \u002Ffield-validator-attempt\u002F  {\"starts_on\": \"2026-08-10\", \"ends_on\": \"2026-08-01\"}\n200 OK\n{\n  \"ends_on\": \"2026-08-01\",\n  \"starts_on\": \"2026-08-10\"\n}\n",[604,1547,1545],{"__ignoreMap":899},[590,1549,1550,1551,1553,1554,1556,1557,1559,1560,1049,1563,1565],{},"Accepted. ",[604,1552,658],{}," is declared second, so it is not in ",[604,1555,620],{}," when the validator for ",[604,1558,654],{}," runs, ",[604,1561,1562],{},"start",[604,1564,742],{},", and the guard skips the comparison. No warning, no error, no log line — a validation rule that exists in the source and does nothing at runtime.",[590,1567,1568,1569,1571],{},"Reordering the two field declarations would make this particular model work, which is exactly why it is dangerous. The rule's correctness depends on the order two attributes happen to appear in, so a later refactor that sorts fields alphabetically silently disables it. Reviewers do not catch this. Use ",[604,1570,891],{}," and the question never arises.",[645,1573,1575],{"id":1574},"edge-cases-and-gotchas","Edge Cases and Gotchas",[597,1577,1578,1600,1612,1625,1642],{},[600,1579,1580,1586,1587,1589,1590,1593,1594,1200,1597,722],{},[593,1581,1582,1583,722],{},"Raise, do not return ",[604,1584,1585],{},"False"," A ",[604,1588,891],{}," that returns a bool instead of ",[604,1591,1592],{},"self"," replaces the model with that bool. Always ",[604,1595,1596],{},"raise ValueError(...)",[604,1598,1599],{},"return self",[600,1601,1602,1586,1605,1607,1608,1611],{},[593,1603,1604],{},"One error at a time.",[604,1606,891],{}," stops at its first raise, so a form with three broken cross-field rules reports one. If clients need all of them, collect the failures in a list and raise once with a joined message, or raise a ",[604,1609,1610],{},"PydanticCustomError"," carrying structured context.",[600,1613,1614,1620,1621,1624],{},[593,1615,1616,1619],{},[604,1617,1618],{},"mode=\"after\""," runs on every construction."," That includes rehydrating from cache or the database. A rule that was valid when the row was written but is not valid now will make loading fail. Rules reflecting ",[704,1622,1623],{},"current"," policy belong in the service layer.",[600,1626,1627,1633,1634,1637,1638,1641],{},[593,1628,1629,1630,1632],{},"Mutation in ",[604,1631,718],{}," is not re-validated."," Normalizing ",[604,1635,1636],{},"self.address = self.address.strip()"," inside the validator skips field validation for the new value unless ",[604,1639,1640],{},"validate_assignment"," is set.",[600,1643,1644,1647,1648,1652],{},[593,1645,1646],{},"Do not put I\u002FO here."," Checking that an email is unique needs the database, and validators are synchronous and pure. That belongs behind ",[639,1649,1651],{"href":1650},"\u002Fcore-architecture-routing-patterns\u002Fdependency-injection-strategies\u002F","dependency injection"," in the service layer.",[645,1654,1656],{"id":1655},"verification","Verification",[590,1658,1659,1660,1662],{},"Assert the rejection and, just as importantly, assert that the rule fires at all — the ",[604,1661,683],{}," trap above passes a \"does it accept valid input\" test perfectly:",[894,1664,1666],{"className":896,"code":1665,"language":898,"meta":899,"style":899},"import pytest\nfrom pydantic import ValidationError\n\n\ndef test_range_rejects_reversed_dates():\n    with pytest.raises(ValidationError) as exc_info:\n        DateRange(starts_on=date(2026, 8, 10), ends_on=date(2026, 8, 1))\n    assert exc_info.value.error_count() == 1\n\n\ndef test_address_required_only_for_delivery():\n    assert Shipment(method=\"pickup\").address is None\n    with pytest.raises(ValidationError):\n        Shipment(method=\"delivery\")\n",[604,1667,1668,1676,1689,1693,1697,1708,1722,1770,1783,1787,1791,1800,1820,1827],{"__ignoreMap":899},[903,1669,1670,1673],{"class":851,"line":905},[903,1671,1672],{"class":908},"import",[903,1674,1675],{"class":916}," pytest\n",[903,1677,1678,1681,1684,1686],{"class":851,"line":927},[903,1679,1680],{"class":908},"from",[903,1682,1683],{"class":916}," pydantic ",[903,1685,1672],{"class":908},[903,1687,1688],{"class":916}," ValidationError\n",[903,1690,1691],{"class":851,"line":933},[903,1692,943],{"emptyLinePlaceholder":942},[903,1694,1695],{"class":851,"line":939},[903,1696,943],{"emptyLinePlaceholder":942},[903,1698,1699,1702,1705],{"class":851,"line":946},[903,1700,1701],{"class":908},"def",[903,1703,1704],{"class":949}," test_range_rejects_reversed_dates",[903,1706,1707],{"class":916},"():\n",[903,1709,1710,1713,1716,1719],{"class":851,"line":968},[903,1711,1712],{"class":908},"    with",[903,1714,1715],{"class":916}," pytest.raises(ValidationError) ",[903,1717,1718],{"class":908},"as",[903,1720,1721],{"class":916}," exc_info:\n",[903,1723,1724,1727,1729,1731,1734,1737,1739,1742,1744,1747,1750,1752,1754,1756,1758,1760,1762,1764,1767],{"class":851,"line":986},[903,1725,1726],{"class":916},"        DateRange(",[903,1728,658],{"class":912},[903,1730,958],{"class":908},[903,1732,1733],{"class":916},"date(",[903,1735,1736],{"class":920},"2026",[903,1738,1266],{"class":916},[903,1740,1741],{"class":920},"8",[903,1743,1266],{"class":916},[903,1745,1746],{"class":920},"10",[903,1748,1749],{"class":916},"), ",[903,1751,654],{"class":912},[903,1753,958],{"class":908},[903,1755,1733],{"class":916},[903,1757,1736],{"class":920},[903,1759,1266],{"class":916},[903,1761,1741],{"class":920},[903,1763,1266],{"class":916},[903,1765,1766],{"class":920},"1",[903,1768,1769],{"class":916},"))\n",[903,1771,1772,1775,1778,1780],{"class":851,"line":1006},[903,1773,1774],{"class":908},"    assert",[903,1776,1777],{"class":916}," exc_info.value.error_count() ",[903,1779,1336],{"class":908},[903,1781,1782],{"class":920}," 1\n",[903,1784,1785],{"class":851,"line":1022},[903,1786,943],{"emptyLinePlaceholder":942},[903,1788,1789],{"class":851,"line":1172},[903,1790,943],{"emptyLinePlaceholder":942},[903,1792,1793,1795,1798],{"class":851,"line":1366},[903,1794,1701],{"class":908},[903,1796,1797],{"class":949}," test_address_required_only_for_delivery",[903,1799,1707],{"class":916},[903,1801,1802,1804,1807,1809,1811,1813,1816,1818],{"class":851,"line":1495},[903,1803,1774],{"class":908},[903,1805,1806],{"class":916}," Shipment(",[903,1808,1387],{"class":912},[903,1810,958],{"class":908},[903,1812,1263],{"class":961},[903,1814,1815],{"class":916},").address ",[903,1817,1503],{"class":908},[903,1819,1292],{"class":920},[903,1821,1822,1824],{"class":851,"line":1520},[903,1823,1712],{"class":908},[903,1825,1826],{"class":916}," pytest.raises(ValidationError):\n",[903,1828,1829,1832,1834,1836,1838],{"class":851,"line":1533},[903,1830,1831],{"class":916},"        Shipment(",[903,1833,1387],{"class":912},[903,1835,958],{"class":908},[903,1837,1269],{"class":961},[903,1839,965],{"class":916},[590,1841,1842,1843,1845,1846,1849,1850,1853],{},"At the HTTP level, assert on the ",[604,1844,1048],{}," too. If your client depends on ",[604,1847,1848],{},"loc[-1]"," being a field name, a test that pins ",[604,1851,1852],{},"loc == [\"body\"]"," documents the contract and fails loudly if someone converts the rule to a field validator.",[645,1855,1857],{"id":1856},"trade-offs-and-when-not-to","Trade-offs and When Not To",[590,1859,1860],{},"Cross-field rules in the model are cheap, run everywhere the model is constructed, and are impossible to forget. That last property is the trade-off: a rule that is really an endpoint policy — \"an admin may submit a backdated range\" — becomes a rule the admin path cannot escape. Split the models when the policy differs, rather than threading a flag through the validator.",[590,1862,874,1863,1865,1866,1868],{},[604,1864,627],{}," shape is the other real cost. Frontends that render errors inline next to inputs need a mapping layer, and every cross-field rule you add is another entry in it. If your API's error contract is field-keyed, consider raising a ",[604,1867,1610],{}," with the target field in its context and translating it in a global handler, so the mapping lives in one place instead of being reinvented per client.",[645,1870,1872],{"id":1871},"faq","FAQ",[590,1874,1875,1878,1879,1881,1882,1884],{},[593,1876,1877],{},"Why can't field_validator see other fields?","\nIt can see some of them, through ",[604,1880,620],{},", but only the fields that were declared and validated before it. Fields declared later in the class are not in ",[604,1883,620],{}," yet, and fields that failed their own validation are missing entirely, so the rule silently does not run instead of failing.",[590,1886,1887,1890,1891,1893,1894,1897],{},[593,1888,1889],{},"What loc does a cross-field validation error have in the 422 body?","\nFor a request body it is ",[604,1892,1052],{}," with type ",[604,1895,1896],{},"value_error",", because the failure belongs to the model rather than to a single field. Clients that key error messages by field name need a translation step or a custom exception handler.",[590,1899,1900,1903,1904,1906],{},[593,1901,1902],{},"How do I attach a cross-field error to a specific field in the response?","\nRaise a ",[604,1905,1610],{}," from the model validator and add a custom exception handler that maps it onto a field, or reshape the 422 in a global handler. Pydantic itself has no way to say a model-level failure belongs to one field.",[590,1908,1909,1912],{},[593,1910,1911],{},"Should password confirmation be validated in the Pydantic model?","\nIt is a reasonable place because the rule is purely a property of the payload and needs no I\u002FO. Keep the two fields on the request model only, never on the stored model, so the confirmation never reaches the database layer.",[590,1914,1915,1918,1919,1921,1922,1924,1925,1927],{},[593,1916,1917],{},"How do I make a field required only when another field has a particular value?","\nDeclare it optional with a ",[604,1920,742],{}," default and enforce the requirement in a ",[604,1923,891],{}," with ",[604,1926,1618],{},". The field stays optional in the JSON Schema, so document the conditional rule in the field description or with a discriminated union if the variants are genuinely distinct.",[645,1929,1931],{"id":1930},"related-reading","Related Reading",[597,1933,1934,1942,1952,1960],{},[600,1935,1936,1939,1940,722],{},[593,1937,1938],{},"Up to the topic:"," ",[639,1941,642],{"href":641},[600,1943,1944,1945,1947,1948,722],{},"The mode semantics these patterns rely on are detailed in ",[639,1946,881],{"href":880},", and packaging rules for reuse is covered in ",[639,1949,1951],{"href":1950},"\u002Fadvanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002Fcreating-reusable-custom-validators-in-pydantic\u002F","creating reusable custom validators",[600,1953,1954,1955,1959],{},"If you are arriving from v1, ",[639,1956,1958],{"href":1957},"\u002Fadvanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Froot-validator-to-model-validator\u002F","@root_validator to @model_validator"," covers the decorator change these patterns depend on.",[600,1961,1962,1963,722],{},"To reshape the 422 bodies shown above, see ",[639,1964,1965],{"href":1195},"customising validation error responses",[1967,1968,1969],"style",{},"html pre.shiki code .sTJeM, html code.shiki .sTJeM{--shiki-default:#A0111F}html pre.shiki code .sV4o_, html code.shiki .sV4o_{--shiki-default:#702C00}html pre.shiki code .sigWx, html code.shiki .sigWx{--shiki-default:#0E1116}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 pre.shiki code .sYEJz, html code.shiki .sYEJz{--shiki-default:#032563}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);}html pre.shiki code .sFeEa, html code.shiki .sFeEa{--shiki-default:#66707B}",{"title":899,"searchDepth":927,"depth":927,"links":1971},[1972,1973,1974,1975,1976,1977,1978,1979,1980,1981,1982,1983],{"id":647,"depth":927,"text":648},{"id":687,"depth":927,"text":688},{"id":865,"depth":927,"text":866},{"id":884,"depth":927,"text":885},{"id":1060,"depth":927,"text":1061},{"id":1224,"depth":927,"text":1225},{"id":1396,"depth":927,"text":1397},{"id":1574,"depth":927,"text":1575},{"id":1655,"depth":927,"text":1656},{"id":1856,"depth":927,"text":1857},{"id":1871,"depth":927,"text":1872},{"id":1930,"depth":927,"text":1931},"2026-07-20","Validate one field against another in FastAPI: date ranges, password confirmation and conditional rules with model_validator, plus the real 422 body for each.","md",[1988,1990,1992,1994,1996],{"q":1877,"a":1989},"It can see some of them, through info.data, but only the fields that were declared and validated before it. Fields declared later in the class are not in info.data yet, and fields that failed their own validation are missing entirely, so the rule silently does not run instead of failing.",{"q":1889,"a":1991},"For a request body it is [\"body\"] with type value_error, because the failure belongs to the model rather than to a single field. Clients that key error messages by field name need a translation step or a custom exception handler.",{"q":1902,"a":1993},"Raise a PydanticCustomError from the model validator and add a custom exception handler that maps it onto a field, or reshape the 422 in a global handler. Pydantic itself has no way to say a model-level failure belongs to one field.",{"q":1911,"a":1995},"It is a reasonable place because the rule is purely a property of the payload and needs no I\u002FO. Keep the two fields on the request model only, never on the stored model, so the confirmation never reaches the database layer.",{"q":1917,"a":1997},"Declare it optional with a None default and enforce the requirement in a model_validator with mode='after'. The field stays optional in the JSON Schema, so document the conditional rule in the field description or with a discriminated union if the variants are genuinely distinct.",null,{"slug":2000,"breadcrumb":2001},"cross-field-validation-patterns",[2002,2005,2008,2010],{"label":2003,"path":2004},"Home","\u002F",{"label":2006,"path":2007},"Advanced Pydantic Validation & Serialization","\u002Fadvanced-pydantic-validation-serialization\u002F",{"label":2009,"path":641},"Custom Validators & Field Constraints",{"label":2011,"path":2012},"Cross-Field Validation Patterns","\u002Fadvanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002Fcross-field-validation-patterns\u002F",{"title":31,"description":1985},"article","whK6IeiLKauBvPqbYZb_Byn95ngiu6KEzIP8gtvzanY",[1998,1998],1784588202620]