[{"data":1,"prerenderedAt":2292},["ShallowReactive",2],{"nav":3,"page-\u002Fadvanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Froot-validator-to-model-validator\u002F":580,"surround-\u002Fadvanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Froot-validator-to-model-validator\u002F":2291},[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":145,"body":582,"dateModified":2260,"datePublished":2260,"description":2261,"extension":2262,"faq":2263,"howto":2274,"meta":2275,"navigation":766,"path":146,"seo":2288,"stem":147,"type":2289,"__hash__":2290},"content\u002Fadvanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Froot-validator-to-model-validator\u002Findex.md",{"type":583,"value":584,"toc":2241},"minimark",[585,589,596,668,681,686,695,936,947,951,969,993,999,1139,1143,1156,1160,1171,1177,1183,1204,1211,1217,1220,1687,1694,1701,1718,1731,1747,1751,1774,1791,1797,1814,1818,1910,1914,1921,2083,2093,2097,2106,2118,2122,2131,2146,2164,2179,2195,2199,2237],[586,587,145],"h1",{"id":588},"migrating-root_validator-to-model_validator-in-pydantic-v2",[590,591,592],"p",{},[593,594,595],"strong",{},"Key takeaways:",[597,598,599,611,633,651,661],"ul",{},[600,601,602,606,607,610],"li",{},[603,604,605],"code",{},"@root_validator(pre=True)"," becomes ",[603,608,609],{},"@model_validator(mode=\"before\")"," — still a classmethod, still receives raw input.",[600,612,613,606,616,619,620,624,625,628,629,632],{},[603,614,615],{},"@root_validator()",[603,617,618],{},"@model_validator(mode=\"after\")"," — now an ",[621,622,623],"em",{},"instance"," method receiving ",[603,626,627],{},"self",", not a ",[603,630,631],{},"values"," dict.",[600,634,635,636,638,639,642,643,646,647,650],{},"Subscripting ",[603,637,627],{}," in an ",[603,640,641],{},"after"," validator raises ",[603,644,645],{},"TypeError",", which is ",[593,648,649],{},"not"," converted to a 422.",[600,652,653,656,657,660],{},[603,654,655],{},"mode=\"before\""," input is not guaranteed to be a dict; guard with ",[603,658,659],{},"isinstance",".",[600,662,663,664,667],{},"A model-level failure reports ",[603,665,666],{},"loc: [\"body\"]",", not a field name.",[590,669,670,671,676,677,660],{},"This is one of two decorator migrations in the ",[672,673,675],"a",{"href":674},"\u002Fadvanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002F","Pydantic V2 Migration Guide","; the field-level equivalent is ",[672,678,680],{"href":679},"\u002Fadvanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Fmigrate-validator-to-field-validator\u002F","migrating @validator to @field_validator",[682,683,685],"h2",{"id":684},"the-problem-this-solves","The Problem This Solves",[590,687,688,691,692,694],{},[603,689,690],{},"@root_validator"," was the v1 tool for any rule spanning more than one field. It came in two flavours, both classmethods, both handed a mutable ",[603,693,631],{}," dict:",[696,697,702],"pre",{"className":698,"code":699,"language":700,"meta":701,"style":701},"language-python shiki shiki-themes github-light-high-contrast","# Pydantic v1 — the idiom being replaced. NOT executable on v2.\nclass Order(BaseModel):\n    quantity: int\n    unit_price: float\n    total: float\n\n    @root_validator(pre=True)\n    def fill_total(cls, values):\n        if \"total\" not in values:\n            values[\"total\"] = values[\"quantity\"] * values[\"unit_price\"]\n        return values\n\n    @root_validator()\n    def total_must_match(cls, values):\n        if values[\"total\"] != values[\"quantity\"] * values[\"unit_price\"]:\n            raise ValueError(\"total does not match\")\n        return values\n","python","",[603,703,704,713,735,744,753,761,768,788,800,819,852,861,866,874,884,913,929],{"__ignoreMap":701},[705,706,709],"span",{"class":707,"line":708},"line",1,[705,710,712],{"class":711},"sFeEa","# Pydantic v1 — the idiom being replaced. NOT executable on v2.\n",[705,714,716,720,724,728,732],{"class":707,"line":715},2,[705,717,719],{"class":718},"sTJeM","class",[705,721,723],{"class":722},"sV4o_"," Order",[705,725,727],{"class":726},"sigWx","(",[705,729,731],{"class":730},"sacAq","BaseModel",[705,733,734],{"class":726},"):\n",[705,736,738,741],{"class":707,"line":737},3,[705,739,740],{"class":726},"    quantity: ",[705,742,743],{"class":730},"int\n",[705,745,747,750],{"class":707,"line":746},4,[705,748,749],{"class":726},"    unit_price: ",[705,751,752],{"class":730},"float\n",[705,754,756,759],{"class":707,"line":755},5,[705,757,758],{"class":726},"    total: ",[705,760,752],{"class":730},[705,762,764],{"class":707,"line":763},6,[705,765,767],{"emptyLinePlaceholder":766},true,"\n",[705,769,771,775,777,779,782,785],{"class":707,"line":770},7,[705,772,774],{"class":773},"s3dhs","    @root_validator",[705,776,727],{"class":726},[705,778,696],{"class":722},[705,780,781],{"class":718},"=",[705,783,784],{"class":730},"True",[705,786,787],{"class":726},")\n",[705,789,791,794,797],{"class":707,"line":790},8,[705,792,793],{"class":718},"    def",[705,795,796],{"class":773}," fill_total",[705,798,799],{"class":726},"(cls, values):\n",[705,801,803,806,810,813,816],{"class":707,"line":802},9,[705,804,805],{"class":718},"        if",[705,807,809],{"class":808},"sYEJz"," \"total\"",[705,811,812],{"class":718}," not",[705,814,815],{"class":718}," in",[705,817,818],{"class":726}," values:\n",[705,820,822,825,828,831,833,836,839,841,844,846,849],{"class":707,"line":821},10,[705,823,824],{"class":726},"            values[",[705,826,827],{"class":808},"\"total\"",[705,829,830],{"class":726},"] ",[705,832,781],{"class":718},[705,834,835],{"class":726}," values[",[705,837,838],{"class":808},"\"quantity\"",[705,840,830],{"class":726},[705,842,843],{"class":718},"*",[705,845,835],{"class":726},[705,847,848],{"class":808},"\"unit_price\"",[705,850,851],{"class":726},"]\n",[705,853,855,858],{"class":707,"line":854},11,[705,856,857],{"class":718},"        return",[705,859,860],{"class":726}," values\n",[705,862,864],{"class":707,"line":863},12,[705,865,767],{"emptyLinePlaceholder":766},[705,867,869,871],{"class":707,"line":868},13,[705,870,774],{"class":773},[705,872,873],{"class":726},"()\n",[705,875,877,879,882],{"class":707,"line":876},14,[705,878,793],{"class":718},[705,880,881],{"class":773}," total_must_match",[705,883,799],{"class":726},[705,885,887,889,891,893,895,898,900,902,904,906,908,910],{"class":707,"line":886},15,[705,888,805],{"class":718},[705,890,835],{"class":726},[705,892,827],{"class":808},[705,894,830],{"class":726},[705,896,897],{"class":718},"!=",[705,899,835],{"class":726},[705,901,838],{"class":808},[705,903,830],{"class":726},[705,905,843],{"class":718},[705,907,835],{"class":726},[705,909,848],{"class":808},[705,911,912],{"class":726},"]:\n",[705,914,916,919,922,924,927],{"class":707,"line":915},16,[705,917,918],{"class":718},"            raise",[705,920,921],{"class":730}," ValueError",[705,923,727],{"class":726},[705,925,926],{"class":808},"\"total does not match\"",[705,928,787],{"class":726},[705,930,932,934],{"class":707,"line":931},17,[705,933,857],{"class":718},[705,935,860],{"class":726},[590,937,938,939,942,943,946],{},"The mechanical part of the migration is easy: rename the decorator and pass a mode. The part that bites is that the ",[621,940,941],{},"post"," flavour changed shape entirely. In v1 both flavours saw a dict; in v2 only ",[603,944,945],{},"before"," does.",[682,948,950],{"id":949},"why-it-happens","Why It Happens",[590,952,953,954,956,957,960,961,964,965,968],{},"The v1 ",[603,955,631],{}," dict in a post-root-validator was a partial, untyped view of the model under construction. If any field had failed, it was simply absent — which is why v1 root validators were full of ",[603,958,959],{},"values.get(\"x\")"," and ",[603,962,963],{},"if \"x\" in values"," defensiveness, and why ",[603,966,967],{},"skip_on_failure=True"," existed at all.",[590,970,971,972,974,975,978,979,981,982,984,985,988,989,992],{},"Pydantic v2 restructured this. Field validation runs to completion first. If any field fails, the model is never constructed and ",[603,973,641],{}," validators never run — there is no partial state to defend against. If every field succeeds, the core builds the model instance and passes ",[621,976,977],{},"that"," to each ",[603,980,641],{}," validator. So the argument is ",[603,983,627],{},", the fields are typed, and ",[603,986,987],{},"self.total"," is a ",[603,990,991],{},"float"," rather than whatever the client sent.",[590,994,995,996,998],{},"That is the whole reason for the signature change: ",[603,997,641],{}," runs on the other side of construction, so there is no dict left to hand you.",[1000,1001,1002,1129],"figure",{},[1003,1004,1012,1016,1020,1029,1036,1041,1046,1050,1053,1056,1060,1063,1067,1071,1074,1077,1081,1084,1090,1095,1099,1102,1106,1109,1113,1116,1120,1125],"svg",{"viewBox":1005,"role":1006,"ariaLabelledBy":1007,"xmlns":1010,"style":1011},"0 0 720 250","img",[1008,1009],"mv-title","mv-desc","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;height:auto;max-width:720px;margin:2rem 0",[1013,1014,1015],"title",{"id":1008},"Where before and after model validators run relative to model construction",[1017,1018,1019],"desc",{"id":1009},"Raw input passes to a before validator which receives a dict, then field validation and type coercion, then model construction, then an after validator which receives the model instance.",[1021,1022],"rect",{"x":1023,"y":1024,"width":1025,"height":1026,"rx":1027,"style":1028},"16","76","108","52","8","fill:none;stroke:currentColor;stroke-width:1.4",[1030,1031,1035],"text",{"x":1032,"y":1033,"style":1034},"70","99","text-anchor:middle;fill:currentColor;font:500 12px sans-serif","raw input",[1030,1037,1040],{"x":1032,"y":1038,"style":1039},"116","text-anchor:middle;fill:currentColor;font:400 11px sans-serif","untyped",[1021,1042],{"x":1043,"y":1024,"width":1044,"height":1026,"rx":1027,"style":1045},"150","128","fill:none;stroke:#00796B;stroke-width:1.7",[1030,1047,655],{"x":1048,"y":1033,"style":1049},"214","text-anchor:middle;fill:#00796B;font:600 12px sans-serif",[1030,1051,1052],{"x":1048,"y":1038,"style":1039},"gets a dict",[1021,1054],{"x":1055,"y":1024,"width":1044,"height":1026,"rx":1027,"style":1028},"304",[1030,1057,1059],{"x":1058,"y":1033,"style":1034},"368","fields coerce",[1030,1061,1062],{"x":1058,"y":1038,"style":1039},"per-field rules",[1021,1064],{"x":1065,"y":1024,"width":1066,"height":1026,"rx":1027,"style":1028},"458","112",[1030,1068,1070],{"x":1069,"y":1033,"style":1034},"514","construct",[1030,1072,1073],{"x":1069,"y":1038,"style":1039},"instance made",[1021,1075],{"x":1076,"y":1024,"width":1066,"height":1026,"rx":1027,"style":1045},"596",[1030,1078,1080],{"x":1079,"y":1033,"style":1049},"652","mode=\"after\"",[1030,1082,1083],{"x":1079,"y":1038,"style":1039},"gets self",[707,1085],{"x1":1086,"y1":1087,"x2":1088,"y2":1087,"style":1089},"124","102","142","stroke:currentColor;stroke-width:1.5",[1091,1092],"polygon",{"points":1093,"style":1094},"142,98 150,102 142,106","fill:currentColor",[707,1096],{"x1":1097,"y1":1087,"x2":1098,"y2":1087,"style":1089},"278","296",[1091,1100],{"points":1101,"style":1094},"296,98 304,102 296,106",[707,1103],{"x1":1104,"y1":1087,"x2":1105,"y2":1087,"style":1089},"432","450",[1091,1107],{"points":1108,"style":1094},"450,98 458,102 450,106",[707,1110],{"x1":1111,"y1":1087,"x2":1112,"y2":1087,"style":1089},"570","588",[1091,1114],{"points":1115,"style":1094},"588,98 596,102 588,106",[707,1117],{"x1":1058,"y1":1044,"x2":1058,"y2":1118,"style":1119},"164","stroke:currentColor;stroke-width:1.3;stroke-dasharray:4 3",[1030,1121,1124],{"x":1058,"y":1122,"style":1123},"182","text-anchor:middle;fill:currentColor;font:400 11.5px sans-serif","any field fails here and construction stops",[1030,1126,1128],{"x":1058,"y":1127,"style":1123},"200","so the after validator never runs",[1130,1131,1132,1133,1135,1136,1138],"figcaption",{},"Construction sits between the two modes. That boundary is why ",[603,1134,945],{}," gets a dict and ",[603,1137,641],{}," gets the model.",[682,1140,1142],{"id":1141},"prerequisites","Prerequisites",[597,1144,1145,1148],{},[600,1146,1147],{},"Pydantic 2.13.4 and FastAPI 0.139.2 — every transcript below came from that pair.",[600,1149,1150,1151,1155],{},"Familiarity with ",[672,1152,1154],{"href":1153},"\u002Fadvanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002F","custom validators and field constraints",", since model validators run after field ones.",[682,1157,1159],{"id":1158},"step-by-step-implementation","Step-by-Step Implementation",[1161,1162,1164,1165,1168,1169],"h3",{"id":1163},"_1-translate-pretrue-to-modebefore","1. Translate ",[603,1166,1167],{},"pre=True"," to ",[603,1170,655],{},[590,1172,1173,1174,1176],{},"The ",[603,1175,945],{}," case is almost a straight rename. It is still a classmethod, it still receives the raw input, it still returns whatever the next stage should validate. The one change worth making while you are in there is to stop assuming a dict.",[1161,1178,1180,1181],{"id":1179},"_2-translate-the-post-validator-to-modeafter","2. Translate the post validator to ",[603,1182,1080],{},[590,1184,1185,1186,1189,1190,1193,1194,1197,1198,1201,1202,660],{},"Drop ",[603,1187,1188],{},"@classmethod",". Change the signature to ",[603,1191,1192],{},"(self) -> \"Model\"",". Replace every ",[603,1195,1196],{},"values[\"x\"]"," with ",[603,1199,1200],{},"self.x",". Return ",[603,1203,627],{},[1161,1205,1207,1208],{"id":1206},"_3-delete-skip_on_failure","3. Delete ",[603,1209,1210],{},"skip_on_failure",[590,1212,1213,1214,1216],{},"It no longer exists and is no longer needed: ",[603,1215,641],{}," validators only run when every field validated.",[590,1218,1219],{},"Here is the migrated model, run for real:",[696,1221,1223],{"className":698,"code":1222,"language":700,"meta":701,"style":701},"\"\"\"Migrating v1 @root_validator to v2 @model_validator, including the wrong-signature error.\"\"\"\nimport json\nfrom typing import Any\n\nfrom fastapi import FastAPI\nfrom pydantic import BaseModel, ValidationError, model_validator\n\napp = FastAPI()\n\n\nclass Order(BaseModel):\n    \"\"\"mode='before' receives the raw input mapping, exactly like v1's root_validator(pre=True).\"\"\"\n\n    quantity: int\n    unit_price: float\n    total: float\n\n    @model_validator(mode=\"before\")\n    @classmethod\n    def fill_total(cls, data: Any) -> Any:\n        if isinstance(data, dict) and \"total\" not in data:\n            data = {**data, \"total\": data[\"quantity\"] * data[\"unit_price\"]}\n        return data\n\n    @model_validator(mode=\"after\")\n    def total_must_match(self) -> \"Order\":\n        # mode='after' gets the constructed model, so fields are typed and attribute-accessed.\n        if abs(self.total - self.quantity * self.unit_price) > 1e-9:\n            raise ValueError(\"total does not match quantity * unit_price\")\n        return self\n\n\nclass BrokenOrder(BaseModel):\n    \"\"\"The common migration mistake: an 'after' validator written as if it received a dict.\"\"\"\n\n    quantity: int\n    unit_price: float\n\n    @model_validator(mode=\"after\")\n    def subscript_like_v1(self):\n        # WRONG: in mode='after' `self` is the model, not the v1 `values` dict.\n        if self[\"quantity\"] \u003C 1:\n            raise ValueError(\"quantity must be positive\")\n        return self\n",[603,1224,1225,1230,1238,1251,1255,1267,1279,1283,1293,1297,1301,1313,1318,1322,1328,1334,1340,1344,1362,1371,1381,1410,1446,1454,1459,1475,1491,1497,1536,1550,1558,1563,1568,1582,1588,1593,1600,1607,1612,1627,1638,1644,1666,1680],{"__ignoreMap":701},[705,1226,1227],{"class":707,"line":708},[705,1228,1229],{"class":808},"\"\"\"Migrating v1 @root_validator to v2 @model_validator, including the wrong-signature error.\"\"\"\n",[705,1231,1232,1235],{"class":707,"line":715},[705,1233,1234],{"class":718},"import",[705,1236,1237],{"class":726}," json\n",[705,1239,1240,1243,1246,1248],{"class":707,"line":737},[705,1241,1242],{"class":718},"from",[705,1244,1245],{"class":726}," typing ",[705,1247,1234],{"class":718},[705,1249,1250],{"class":726}," Any\n",[705,1252,1253],{"class":707,"line":746},[705,1254,767],{"emptyLinePlaceholder":766},[705,1256,1257,1259,1262,1264],{"class":707,"line":755},[705,1258,1242],{"class":718},[705,1260,1261],{"class":726}," fastapi ",[705,1263,1234],{"class":718},[705,1265,1266],{"class":726}," FastAPI\n",[705,1268,1269,1271,1274,1276],{"class":707,"line":763},[705,1270,1242],{"class":718},[705,1272,1273],{"class":726}," pydantic ",[705,1275,1234],{"class":718},[705,1277,1278],{"class":726}," BaseModel, ValidationError, model_validator\n",[705,1280,1281],{"class":707,"line":770},[705,1282,767],{"emptyLinePlaceholder":766},[705,1284,1285,1288,1290],{"class":707,"line":790},[705,1286,1287],{"class":726},"app ",[705,1289,781],{"class":718},[705,1291,1292],{"class":726}," FastAPI()\n",[705,1294,1295],{"class":707,"line":802},[705,1296,767],{"emptyLinePlaceholder":766},[705,1298,1299],{"class":707,"line":821},[705,1300,767],{"emptyLinePlaceholder":766},[705,1302,1303,1305,1307,1309,1311],{"class":707,"line":854},[705,1304,719],{"class":718},[705,1306,723],{"class":722},[705,1308,727],{"class":726},[705,1310,731],{"class":730},[705,1312,734],{"class":726},[705,1314,1315],{"class":707,"line":863},[705,1316,1317],{"class":808},"    \"\"\"mode='before' receives the raw input mapping, exactly like v1's root_validator(pre=True).\"\"\"\n",[705,1319,1320],{"class":707,"line":868},[705,1321,767],{"emptyLinePlaceholder":766},[705,1323,1324,1326],{"class":707,"line":876},[705,1325,740],{"class":726},[705,1327,743],{"class":730},[705,1329,1330,1332],{"class":707,"line":886},[705,1331,749],{"class":726},[705,1333,752],{"class":730},[705,1335,1336,1338],{"class":707,"line":915},[705,1337,758],{"class":726},[705,1339,752],{"class":730},[705,1341,1342],{"class":707,"line":931},[705,1343,767],{"emptyLinePlaceholder":766},[705,1345,1347,1350,1352,1355,1357,1360],{"class":707,"line":1346},18,[705,1348,1349],{"class":773},"    @model_validator",[705,1351,727],{"class":726},[705,1353,1354],{"class":722},"mode",[705,1356,781],{"class":718},[705,1358,1359],{"class":808},"\"before\"",[705,1361,787],{"class":726},[705,1363,1365,1368],{"class":707,"line":1364},19,[705,1366,1367],{"class":773},"    @",[705,1369,1370],{"class":730},"classmethod\n",[705,1372,1374,1376,1378],{"class":707,"line":1373},20,[705,1375,793],{"class":718},[705,1377,796],{"class":773},[705,1379,1380],{"class":726},"(cls, data: Any) -> Any:\n",[705,1382,1384,1386,1389,1392,1395,1398,1401,1403,1405,1407],{"class":707,"line":1383},21,[705,1385,805],{"class":718},[705,1387,1388],{"class":730}," isinstance",[705,1390,1391],{"class":726},"(data, ",[705,1393,1394],{"class":730},"dict",[705,1396,1397],{"class":726},") ",[705,1399,1400],{"class":718},"and",[705,1402,809],{"class":808},[705,1404,812],{"class":718},[705,1406,815],{"class":718},[705,1408,1409],{"class":726}," data:\n",[705,1411,1413,1416,1418,1421,1424,1427,1429,1432,1434,1436,1438,1441,1443],{"class":707,"line":1412},22,[705,1414,1415],{"class":726},"            data ",[705,1417,781],{"class":718},[705,1419,1420],{"class":726}," {",[705,1422,1423],{"class":718},"**",[705,1425,1426],{"class":726},"data, ",[705,1428,827],{"class":808},[705,1430,1431],{"class":726},": data[",[705,1433,838],{"class":808},[705,1435,830],{"class":726},[705,1437,843],{"class":718},[705,1439,1440],{"class":726}," data[",[705,1442,848],{"class":808},[705,1444,1445],{"class":726},"]}\n",[705,1447,1449,1451],{"class":707,"line":1448},23,[705,1450,857],{"class":718},[705,1452,1453],{"class":726}," data\n",[705,1455,1457],{"class":707,"line":1456},24,[705,1458,767],{"emptyLinePlaceholder":766},[705,1460,1462,1464,1466,1468,1470,1473],{"class":707,"line":1461},25,[705,1463,1349],{"class":773},[705,1465,727],{"class":726},[705,1467,1354],{"class":722},[705,1469,781],{"class":718},[705,1471,1472],{"class":808},"\"after\"",[705,1474,787],{"class":726},[705,1476,1478,1480,1482,1485,1488],{"class":707,"line":1477},26,[705,1479,793],{"class":718},[705,1481,881],{"class":773},[705,1483,1484],{"class":726},"(self) -> ",[705,1486,1487],{"class":808},"\"Order\"",[705,1489,1490],{"class":726},":\n",[705,1492,1494],{"class":707,"line":1493},27,[705,1495,1496],{"class":711},"        # mode='after' gets the constructed model, so fields are typed and attribute-accessed.\n",[705,1498,1500,1502,1505,1507,1509,1512,1515,1518,1521,1523,1525,1528,1531,1534],{"class":707,"line":1499},28,[705,1501,805],{"class":718},[705,1503,1504],{"class":730}," abs",[705,1506,727],{"class":726},[705,1508,627],{"class":730},[705,1510,1511],{"class":726},".total ",[705,1513,1514],{"class":718},"-",[705,1516,1517],{"class":730}," self",[705,1519,1520],{"class":726},".quantity ",[705,1522,843],{"class":718},[705,1524,1517],{"class":730},[705,1526,1527],{"class":726},".unit_price) ",[705,1529,1530],{"class":718},">",[705,1532,1533],{"class":730}," 1e-9",[705,1535,1490],{"class":726},[705,1537,1539,1541,1543,1545,1548],{"class":707,"line":1538},29,[705,1540,918],{"class":718},[705,1542,921],{"class":730},[705,1544,727],{"class":726},[705,1546,1547],{"class":808},"\"total does not match quantity * unit_price\"",[705,1549,787],{"class":726},[705,1551,1553,1555],{"class":707,"line":1552},30,[705,1554,857],{"class":718},[705,1556,1557],{"class":730}," self\n",[705,1559,1561],{"class":707,"line":1560},31,[705,1562,767],{"emptyLinePlaceholder":766},[705,1564,1566],{"class":707,"line":1565},32,[705,1567,767],{"emptyLinePlaceholder":766},[705,1569,1571,1573,1576,1578,1580],{"class":707,"line":1570},33,[705,1572,719],{"class":718},[705,1574,1575],{"class":722}," BrokenOrder",[705,1577,727],{"class":726},[705,1579,731],{"class":730},[705,1581,734],{"class":726},[705,1583,1585],{"class":707,"line":1584},34,[705,1586,1587],{"class":808},"    \"\"\"The common migration mistake: an 'after' validator written as if it received a dict.\"\"\"\n",[705,1589,1591],{"class":707,"line":1590},35,[705,1592,767],{"emptyLinePlaceholder":766},[705,1594,1596,1598],{"class":707,"line":1595},36,[705,1597,740],{"class":726},[705,1599,743],{"class":730},[705,1601,1603,1605],{"class":707,"line":1602},37,[705,1604,749],{"class":726},[705,1606,752],{"class":730},[705,1608,1610],{"class":707,"line":1609},38,[705,1611,767],{"emptyLinePlaceholder":766},[705,1613,1615,1617,1619,1621,1623,1625],{"class":707,"line":1614},39,[705,1616,1349],{"class":773},[705,1618,727],{"class":726},[705,1620,1354],{"class":722},[705,1622,781],{"class":718},[705,1624,1472],{"class":808},[705,1626,787],{"class":726},[705,1628,1630,1632,1635],{"class":707,"line":1629},40,[705,1631,793],{"class":718},[705,1633,1634],{"class":773}," subscript_like_v1",[705,1636,1637],{"class":726},"(self):\n",[705,1639,1641],{"class":707,"line":1640},41,[705,1642,1643],{"class":711},"        # WRONG: in mode='after' `self` is the model, not the v1 `values` dict.\n",[705,1645,1647,1649,1651,1654,1656,1658,1661,1664],{"class":707,"line":1646},42,[705,1648,805],{"class":718},[705,1650,1517],{"class":730},[705,1652,1653],{"class":726},"[",[705,1655,838],{"class":808},[705,1657,830],{"class":726},[705,1659,1660],{"class":718},"\u003C",[705,1662,1663],{"class":730}," 1",[705,1665,1490],{"class":726},[705,1667,1669,1671,1673,1675,1678],{"class":707,"line":1668},43,[705,1670,918],{"class":718},[705,1672,921],{"class":730},[705,1674,727],{"class":726},[705,1676,1677],{"class":808},"\"quantity must be positive\"",[705,1679,787],{"class":726},[705,1681,1683,1685],{"class":707,"line":1682},44,[705,1684,857],{"class":718},[705,1686,1557],{"class":730},[590,1688,1689,1690,1693],{},"Real output from ",[603,1691,1692],{},"_verify\u002Foutput\u002Fpyd-root-to-model-validator.txt",":",[696,1695,1699],{"className":1696,"code":1698,"language":1030,"meta":701},[1697],"language-text","$ POST \u002Forders\u002F  {\"quantity\": 3, \"unit_price\": 12.5}\n200 OK\n{\n  \"quantity\": 3,\n  \"unit_price\": 12.5,\n  \"total\": 37.5\n}\n\n$ POST \u002Forders\u002F  {\"quantity\": 3, \"unit_price\": 12.5, \"total\": 99.0}\n422 Unprocessable Entity\n{\n  \"detail\": [\n    {\n      \"type\": \"value_error\",\n      \"loc\": [\n        \"body\"\n      ],\n      \"msg\": \"Value error, total does not match quantity * unit_price\",\n      \"input\": {\n        \"quantity\": 3,\n        \"unit_price\": 12.5,\n        \"total\": 99.0\n      },\n      \"ctx\": {\n        \"error\": {}\n      }\n    }\n  ]\n}\n\n$ POST \u002Forders\u002Fbroken  {\"quantity\": 3, \"unit_price\": 12.5}\n200 OK\n{\n  \"exception_type\": \"TypeError\",\n  \"message\": \"'BrokenOrder' object is not subscriptable\"\n}\n",[603,1700,1698],{"__ignoreMap":701},[590,1702,1703,1704,1706,1707,1710,1711,1714,1715,1717],{},"The first request shows the ",[603,1705,945],{}," validator doing its job: ",[603,1708,1709],{},"total"," was absent from the payload and the response contains ",[603,1712,1713],{},"37.5",". The second shows the ",[603,1716,641],{}," validator rejecting an inconsistent payload.",[590,1719,1720,1721,1724,1725,1728,1729,660],{},"The third is the point of the page. ",[603,1722,1723],{},"BrokenOrder"," is what a mechanical migration produces — decorator renamed, body untouched — and the endpoint catches the exception so it can be printed. The real message is ",[603,1726,1727],{},"'BrokenOrder' object is not subscriptable",", and its type is ",[603,1730,645],{},[590,1732,1733,1734,960,1737,1740,1741,1743,1744,1746],{},"That type matters enormously. Pydantic converts ",[603,1735,1736],{},"ValueError",[603,1738,1739],{},"AssertionError"," raised inside validators into validation errors. It does not convert ",[603,1742,645],{},". So a half-migrated ",[603,1745,641],{}," validator does not produce a 422 telling you the input was bad — it propagates out of model construction, past FastAPI's request-validation handling, and becomes a 500 with a traceback. In a codebase with dozens of root validators, a handful of them turning into 500s only on the code paths where they were reached is a genuinely unpleasant way to discover the migration was incomplete.",[682,1748,1750],{"id":1749},"reading-the-422-shape","Reading the 422 Shape",[590,1752,1753,1754,1757,1758,1761,1762,1765,1766,1769,1770,1773],{},"Note the ",[603,1755,1756],{},"loc"," in the second response: ",[603,1759,1760],{},"[\"body\"]",". Not ",[603,1763,1764],{},"[\"body\", \"total\"]",". A model validator's failure belongs to the model, so Pydantic has no field to attribute it to. Clients that build form-field error maps by reading ",[603,1767,1768],{},"loc[-1]"," will get ",[603,1771,1772],{},"\"body\""," for every cross-model rule.",[590,1775,1776,1777,1780,1781,1783,1784,1787,1788,1693],{},"Note also ",[603,1778,1779],{},"\"ctx\": {\"error\": {}}"," — that is FastAPI's JSON encoding of the original ",[603,1782,1736],{}," object in the error context, which does not serialize to anything useful. Calling ",[603,1785,1786],{},"ValidationError.json()"," directly instead gives the readable form. Same failure, captured through ",[603,1789,1790],{},"exc.json()",[696,1792,1795],{"className":1793,"code":1794,"language":1030,"meta":701},[1697],"$ POST \u002Forders\u002Fvalidation-error  {\"quantity\": 3, \"unit_price\": 12.5, \"total\": 99.0}\n200 OK\n{\n  \"errors\": [\n    {\n      \"type\": \"value_error\",\n      \"loc\": [],\n      \"msg\": \"Value error, total does not match quantity * unit_price\",\n      \"input\": {\n        \"quantity\": 3,\n        \"unit_price\": 12.5,\n        \"total\": 99.0\n      },\n      \"ctx\": {\n        \"error\": \"total does not match quantity * unit_price\"\n      },\n      \"url\": \"https:\u002F\u002Ferrors.pydantic.dev\u002F2.13\u002Fv\u002Fvalue_error\"\n    }\n  ]\n}\n",[603,1796,1794],{"__ignoreMap":701},[590,1798,1799,1800,1802,1803,1806,1807,1809,1810,660],{},"Standalone, the ",[603,1801,1756],{}," is ",[603,1804,1805],{},"[]"," — FastAPI prepends ",[603,1808,1772],{}," when the model is a request body. If you are reshaping these responses, see ",[672,1811,1813],{"href":1812},"\u002Fcore-architecture-routing-patterns\u002Ferror-handling-global-exceptions\u002Fcustomising-validation-error-responses\u002F","customising validation error responses",[682,1815,1817],{"id":1816},"edge-cases-and-gotchas","Edge Cases and Gotchas",[597,1819,1820,1840,1865,1882,1904],{},[600,1821,1822,1827,1828,1830,1831,1833,1834,1836,1837,1839],{},[593,1823,1824,1826],{},[603,1825,1188],{}," on the wrong mode."," ",[603,1829,945],{}," needs it; ",[603,1832,641],{}," must not have it. Decorating an ",[603,1835,641],{}," validator with ",[603,1838,1188],{}," means the first argument is the class, not the instance, so attribute access reads class-level metadata instead of the validated values.",[600,1841,1842,1845,1846,1848,1849,1851,1852,1854,1855,638,1858,1860,1861,1864],{},[593,1843,1844],{},"Forgetting to return."," Both modes must return — ",[603,1847,945],{}," returns the data, ",[603,1850,641],{}," returns ",[603,1853,627],{},". A missing ",[603,1856,1857],{},"return",[603,1859,641],{}," validator makes Pydantic treat ",[603,1862,1863],{},"None"," as the validated model.",[600,1866,1867,1872,1873,638,1875,1877,1878,1881],{},[593,1868,1869,1870,660],{},"Mutating in ",[603,1871,641],{}," You can assign to ",[603,1874,627],{},[603,1876,641],{}," validator, but by default that assignment is not re-validated unless ",[603,1879,1880],{},"validate_assignment"," is set. Assigning a value that violates the field's own type will stick.",[600,1883,1884,1827,1889,1892,1893,1896,1897,1900,1901,1903],{},[593,1885,1886,1888],{},[603,1887,945],{}," input is not always a dict.",[603,1890,1891],{},"model_validate"," accepts arbitrary objects, and with ",[603,1894,1895],{},"from_attributes=True"," it accepts ORM rows. The ",[603,1898,1899],{},"isinstance(data, dict)"," guard in the example is not decoration; without it the validator raises ",[603,1902,645],{}," the first time anyone constructs the model from a row.",[600,1905,1906,1909],{},[593,1907,1908],{},"Ordering between multiple validators."," When a model carries several validators of the same mode, the order they fire in is a detail of how the core assembles the schema rather than something to design around. If two rules interact, merge them into one validator so the sequence is explicit in the code.",[682,1911,1913],{"id":1912},"verification","Verification",[590,1915,1916,1917,1920],{},"Test the failure path explicitly, and assert on the error type rather than just that ",[621,1918,1919],{},"something"," raised:",[696,1922,1924],{"className":698,"code":1923,"language":700,"meta":701,"style":701},"import pytest\nfrom pydantic import ValidationError\n\n\ndef test_total_mismatch_is_a_validation_error():\n    with pytest.raises(ValidationError) as exc_info:\n        Order(quantity=3, unit_price=12.5, total=99.0)\n    assert exc_info.value.errors()[0][\"type\"] == \"value_error\"\n\n\ndef test_before_validator_fills_total():\n    assert Order(quantity=3, unit_price=12.5).total == 37.5\n",[603,1925,1926,1933,1944,1948,1952,1963,1977,2012,2037,2041,2045,2054],{"__ignoreMap":701},[705,1927,1928,1930],{"class":707,"line":708},[705,1929,1234],{"class":718},[705,1931,1932],{"class":726}," pytest\n",[705,1934,1935,1937,1939,1941],{"class":707,"line":715},[705,1936,1242],{"class":718},[705,1938,1273],{"class":726},[705,1940,1234],{"class":718},[705,1942,1943],{"class":726}," ValidationError\n",[705,1945,1946],{"class":707,"line":737},[705,1947,767],{"emptyLinePlaceholder":766},[705,1949,1950],{"class":707,"line":746},[705,1951,767],{"emptyLinePlaceholder":766},[705,1953,1954,1957,1960],{"class":707,"line":755},[705,1955,1956],{"class":718},"def",[705,1958,1959],{"class":773}," test_total_mismatch_is_a_validation_error",[705,1961,1962],{"class":726},"():\n",[705,1964,1965,1968,1971,1974],{"class":707,"line":763},[705,1966,1967],{"class":718},"    with",[705,1969,1970],{"class":726}," pytest.raises(ValidationError) ",[705,1972,1973],{"class":718},"as",[705,1975,1976],{"class":726}," exc_info:\n",[705,1978,1979,1982,1985,1987,1990,1993,1996,1998,2001,2003,2005,2007,2010],{"class":707,"line":770},[705,1980,1981],{"class":726},"        Order(",[705,1983,1984],{"class":722},"quantity",[705,1986,781],{"class":718},[705,1988,1989],{"class":730},"3",[705,1991,1992],{"class":726},", ",[705,1994,1995],{"class":722},"unit_price",[705,1997,781],{"class":718},[705,1999,2000],{"class":730},"12.5",[705,2002,1992],{"class":726},[705,2004,1709],{"class":722},[705,2006,781],{"class":718},[705,2008,2009],{"class":730},"99.0",[705,2011,787],{"class":726},[705,2013,2014,2017,2020,2023,2026,2029,2031,2034],{"class":707,"line":790},[705,2015,2016],{"class":718},"    assert",[705,2018,2019],{"class":726}," exc_info.value.errors()[",[705,2021,2022],{"class":730},"0",[705,2024,2025],{"class":726},"][",[705,2027,2028],{"class":808},"\"type\"",[705,2030,830],{"class":726},[705,2032,2033],{"class":718},"==",[705,2035,2036],{"class":808}," \"value_error\"\n",[705,2038,2039],{"class":707,"line":802},[705,2040,767],{"emptyLinePlaceholder":766},[705,2042,2043],{"class":707,"line":821},[705,2044,767],{"emptyLinePlaceholder":766},[705,2046,2047,2049,2052],{"class":707,"line":854},[705,2048,1956],{"class":718},[705,2050,2051],{"class":773}," test_before_validator_fills_total",[705,2053,1962],{"class":726},[705,2055,2056,2058,2061,2063,2065,2067,2069,2071,2073,2075,2078,2080],{"class":707,"line":863},[705,2057,2016],{"class":718},[705,2059,2060],{"class":726}," Order(",[705,2062,1984],{"class":722},[705,2064,781],{"class":718},[705,2066,1989],{"class":730},[705,2068,1992],{"class":726},[705,2070,1995],{"class":722},[705,2072,781],{"class":718},[705,2074,2000],{"class":730},[705,2076,2077],{"class":726},").total ",[705,2079,2033],{"class":718},[705,2081,2082],{"class":730}," 37.5\n",[590,2084,2085,2086,2089,2090,2092],{},"During the migration itself, the highest-value check is a grep: search for ",[603,2087,2088],{},"values["," inside anything decorated ",[603,2091,618],{},". Every hit is a latent 500.",[682,2094,2096],{"id":2095},"trade-offs-and-when-not-to","Trade-offs and When Not To",[590,2098,2099,2101,2102,2105],{},[603,2100,1080],{}," is the right default for cross-field rules and you should reach for it first. But it runs on every construction of the model, including internal ones — deserializing from a cache, rebuilding from the database — and a rule expressed as an invariant will reject data that was already persisted. If a rule is really a ",[621,2103,2104],{},"request-time"," policy rather than a property of the data, put it in the endpoint or the service layer, where it can be applied selectively and where it can be async.",[590,2107,2108,2110,2111,2114,2115,2117],{},[603,2109,655],{}," is powerful precisely because it runs on unvalidated input, which is also why it is a poor place for anything but shape normalization. Computing a derived field there, as ",[603,2112,2113],{},"fill_total"," does, is fine. Doing arithmetic there on values you have not confirmed are numbers is how you get a ",[603,2116,645],{}," instead of a 422.",[682,2119,2121],{"id":2120},"faq","FAQ",[590,2123,2124,2127,2128,2130],{},[593,2125,2126],{},"Why does model_validator(mode='after') receive self instead of a values dict?","\nBecause by that point validation has already succeeded and the model instance exists. Pydantic v2 constructs the model first, then hands it to ",[603,2129,641],{}," validators, so fields are typed and accessed as attributes. There is no dict to pass because the dict stage is over.",[590,2132,2133,2136,2139,2140,2142,2143,2145],{},[593,2134,2135],{},"What error do I get if I subscript self in an after validator?",[603,2137,2138],{},"TypeError: 'Model' object is not subscriptable",". Because it is a ",[603,2141,645],{}," rather than a ",[603,2144,1736],{},", Pydantic does not convert it into a validation error, so it propagates out of model construction and surfaces as a 500 from FastAPI rather than a 422.",[590,2147,2148,2151,2152,2154,2155,2157,2158,2160,2161,660],{},[593,2149,2150],{},"Does model_validator(mode='before') need the @classmethod decorator?","\nYes. A ",[603,2153,945],{}," validator runs before any instance exists, so it is a classmethod receiving the raw input. An ",[603,2156,641],{}," validator is an instance method taking ",[603,2159,627],{}," and must not be decorated with ",[603,2162,2163],{},"classmethod",[590,2165,2166,2169,2170,1802,2172,2174,2175,2178],{},[593,2167,2168],{},"What is the loc of a model_validator error in the 422 response?","\nIt is the location of the model itself, not a field. For a request body the ",[603,2171,1756],{},[603,2173,1760],{}," with type ",[603,2176,2177],{},"value_error",", because the failure belongs to the whole model rather than to any one field.",[590,2180,2181,2184,2185,2187,2188,2191,2192,2194],{},[593,2182,2183],{},"Can a before validator receive something other than a dict?","\nYes. ",[603,2186,1891],{}," can be handed an arbitrary object, and with ",[603,2189,2190],{},"from_attributes"," it can be an ORM row. Always guard with ",[603,2193,659],{}," before subscripting, or the validator breaks for inputs it was never tested against.",[682,2196,2198],{"id":2197},"related-reading","Related Reading",[597,2200,2201,2209,2219,2230],{},[600,2202,2203,2206,2207,660],{},[593,2204,2205],{},"Up to the topic:"," the ",[672,2208,675],{"href":674},[600,2210,2211,2212,2214,2215,660],{},"The field-level counterpart is ",[672,2213,680],{"href":679},", and the whole-migration sequencing lives in ",[672,2216,2218],{"href":2217},"\u002Fadvanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Fmigrating-from-pydantic-v1-to-v2-without-breaking-apis\u002F","migrating without breaking APIs",[600,2220,2221,2222,960,2226,660],{},"For what to write once the decorator is migrated, see ",[672,2223,2225],{"href":2224},"\u002Fadvanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002Fcross-field-validation-patterns\u002F","cross-field validation patterns",[672,2227,2229],{"href":2228},"\u002Fadvanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002Fbefore-after-and-wrap-validators\u002F","before, after and wrap validators",[600,2231,2232,2233,660],{},"Serialization moved in the same release; see ",[672,2234,2236],{"href":2235},"\u002Fadvanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Freplacing-json-encoders-with-field-serializer\u002F","replacing json_encoders with field_serializer",[2238,2239,2240],"style",{},"html pre.shiki code .sFeEa, html code.shiki .sFeEa{--shiki-default:#66707B}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);}",{"title":701,"searchDepth":715,"depth":715,"links":2242},[2243,2244,2245,2246,2254,2255,2256,2257,2258,2259],{"id":684,"depth":715,"text":685},{"id":949,"depth":715,"text":950},{"id":1141,"depth":715,"text":1142},{"id":1158,"depth":715,"text":1159,"children":2247},[2248,2250,2252],{"id":1163,"depth":737,"text":2249},"1. Translate pre=True to mode=\"before\"",{"id":1179,"depth":737,"text":2251},"2. Translate the post validator to mode=\"after\"",{"id":1206,"depth":737,"text":2253},"3. Delete skip_on_failure",{"id":1749,"depth":715,"text":1750},{"id":1816,"depth":715,"text":1817},{"id":1912,"depth":715,"text":1913},{"id":2095,"depth":715,"text":2096},{"id":2120,"depth":715,"text":2121},{"id":2197,"depth":715,"text":2198},"2026-07-20","Convert Pydantic v1 root_validator to v2 model_validator: the before and after modes, the signature change from a values dict to self, and the real error shown.","md",[2264,2266,2268,2270,2272],{"q":2126,"a":2265},"Because by that point validation has already succeeded and the model instance exists. Pydantic v2 constructs the model first, then hands it to after validators, so fields are typed and accessed as attributes. There is no dict to pass because the dict stage is over.",{"q":2135,"a":2267},"TypeError: 'Model' object is not subscriptable. Because it is a TypeError rather than a ValueError, Pydantic does not convert it into a validation error, so it propagates out of model construction and surfaces as a 500 from FastAPI rather than a 422.",{"q":2150,"a":2269},"Yes. A before validator runs before any instance exists, so it is a classmethod receiving the raw input. An after validator is an instance method taking self and must not be decorated with classmethod.",{"q":2168,"a":2271},"It is the location of the model itself, not a field. For a request body the loc is [\"body\"] with type value_error, because the failure belongs to the whole model rather than to any one field.",{"q":2183,"a":2273},"Yes. model_validate can be handed an arbitrary object, and with from_attributes it can be an ORM row. Always guard with isinstance before subscripting, or the validator breaks for inputs it was never tested against.",null,{"slug":2276,"breadcrumb":2277},"root-validator-to-model-validator",[2278,2281,2284,2285],{"label":2279,"path":2280},"Home","\u002F",{"label":2282,"path":2283},"Advanced Pydantic Validation & Serialization","\u002Fadvanced-pydantic-validation-serialization\u002F",{"label":675,"path":674},{"label":2286,"path":2287},"root_validator to model_validator","\u002Fadvanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Froot-validator-to-model-validator\u002F",{"title":145,"description":2261},"article","yP0g0DZbSwV7el__YPx9rzNEYE489GHR8SB1ElQwUMA",[2274,2274],1784588202620]