[{"data":1,"prerenderedAt":2255},["ShallowReactive",2],{"nav":3,"page-\u002Fadvanced-pydantic-validation-serialization\u002Frequest-validation-patterns\u002Foptional-vs-nullable-fields\u002F":580,"surround-\u002Fadvanced-pydantic-validation-serialization\u002Frequest-validation-patterns\u002Foptional-vs-nullable-fields\u002F":2254},[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":157,"body":582,"dateModified":2220,"datePublished":2220,"description":2221,"extension":2222,"faq":2223,"howto":2237,"meta":2238,"navigation":932,"path":158,"seo":2251,"stem":159,"type":2252,"__hash__":2253},"content\u002Fadvanced-pydantic-validation-serialization\u002Frequest-validation-patterns\u002Foptional-vs-nullable-fields\u002Findex.md",{"type":583,"value":584,"toc":2204},"minimark",[585,589,596,658,671,779,784,807,817,821,844,858,880,884,887,1119,1125,1132,1155,1163,1169,1192,1201,1207,1231,1236,1242,1261,1266,1382,1385,1389,1396,1475,1481,1490,1496,1502,1739,1742,1748,1767,1777,1781,1813,1817,1824,1917,1920,1983,2005,2009,2027,2036,2052,2058,2062,2080,2095,2113,2139,2151,2155,2200],[586,587,157],"h1",{"id":588},"optional-vs-nullable-fields-in-pydantic-and-fastapi",[590,591,592],"p",{},[593,594,595],"strong",{},"Key takeaways:",[597,598,599,608,621,636,646],"ul",{},[600,601,602,603,607],"li",{},"Nullability comes from the annotation (",[604,605,606],"code",{},"str | None","); optionality comes from having a default. They are independent.",[600,609,610,612,613,616,617,620],{},[604,611,606],{}," with no default is ",[593,614,615],{},"required but nullable"," — omitting it is a 422, sending ",[604,618,619],{},"null"," is fine.",[600,622,623,624,627,628,631,632,635],{},"Pydantic v1 gave ",[604,625,626],{},"Optional[x]"," an implicit ",[604,629,630],{},"None"," default; v2 does not, which is why migrations produce new ",[604,633,634],{},"missing"," errors.",[600,637,638,641,642,645],{},[604,639,640],{},"exclude_unset"," distinguishes \"never sent\" from \"sent as null\"; ",[604,643,644],{},"exclude_none"," cannot.",[600,647,648,649,653,654,657],{},"For PATCH, make every field optional ",[650,651,652],"em",{},"and"," nullable, then merge with ",[604,655,656],{},"model_dump(exclude_unset=True)",".",[590,659,660,661,666,667,670],{},"This page belongs to ",[662,663,665],"a",{"href":664},"\u002Fadvanced-pydantic-validation-serialization\u002Frequest-validation-patterns\u002F","Request Validation Patterns"," and resolves the single most-conflated pair of concepts at the FastAPI boundary. Every 422 and every 200 below is real output from ",[604,668,669],{},"_verify\u002Fexamples\u002Fval-optional-nullable.py"," on FastAPI 0.139.2, Pydantic 2.13.4 and Python 3.12.",[672,673,674,775],"figure",{},[675,676,684,685,684,689,684,693,684,700,684,704,684,709,684,712,684,720,684,725,684,730,684,734,684,738,684,741,684,744,684,746,684,749,684,753,684,755,684,759,684,761,684,764,684,767,684,769],"svg",{"viewBox":677,"role":678,"ariaLabelledBy":679,"xmlns":682,"style":683},"0 0 720 300","img",[680,681],"onv-title","onv-desc","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;height:auto;max-width:720px;margin:2rem 0","\n  ",[686,687,688],"title",{"id":680},"The two independent axes: required versus optional, and nullable versus not",[690,691,692],"desc",{"id":681},"A two-by-two matrix. The columns are whether the field has a default; the rows are whether the annotation admits None. The four cells show the resulting declaration and what happens when the field is omitted or sent as null.",[694,695,699],"text",{"x":696,"y":697,"style":698},"240","30","text-anchor:middle;fill:currentColor;font:600 13px sans-serif","no default (required)",[694,701,703],{"x":702,"y":697,"style":698},"520","has a default (optional)",[694,705,708],{"x":706,"y":707,"style":698},"66","96","str",[694,710,606],{"x":706,"y":711,"style":698},"196",[713,714],"rect",{"x":715,"y":716,"width":696,"height":717,"rx":718,"style":719},"120","48","86","8","fill:#E0F2F1;stroke:#00796B;stroke-width:1.6px",[694,721,724],{"x":696,"y":722,"style":723},"72","text-anchor:middle;fill:#00796B;font:700 12.5px monospace","name: str",[694,726,729],{"x":696,"y":727,"style":728},"94","text-anchor:middle;fill:currentColor;font:400 11.5px sans-serif","omit  => 422 missing",[694,731,733],{"x":696,"y":732,"style":728},"114","null  => 422 string_type",[713,735],{"x":736,"y":716,"width":696,"height":717,"rx":718,"style":737},"400","fill:#FFFFFF;stroke:#00796B;stroke-width:1.6px",[694,739,740],{"x":702,"y":722,"style":723},"name: str = \"x\"",[694,742,743],{"x":702,"y":727,"style":728},"omit  => 200, uses \"x\"",[694,745,733],{"x":702,"y":732,"style":728},[713,747],{"x":715,"y":748,"width":696,"height":717,"rx":718,"style":737},"150",[694,750,752],{"x":696,"y":751,"style":723},"174","name: str | None",[694,754,729],{"x":696,"y":711,"style":728},[694,756,758],{"x":696,"y":757,"style":728},"216","null  => 200, value None",[713,760],{"x":736,"y":748,"width":696,"height":717,"rx":718,"style":719},[694,762,763],{"x":702,"y":751,"style":723},"name: str | None = None",[694,765,766],{"x":702,"y":711,"style":728},"omit  => 200, value None",[694,768,758],{"x":702,"y":757,"style":728},[694,770,774],{"x":771,"y":772,"style":773},"360","270","text-anchor:middle;fill:currentColor;font:400 12px sans-serif","Only model_fields_set tells the bottom-right cell's two paths apart.",[776,777,778],"figcaption",{},"Nullability and optionality are separate switches. The bottom-left cell — required but nullable — is the one that surprises people after a v1 migration.",[780,781,783],"h2",{"id":782},"the-problem-this-solves","The Problem This Solves",[590,785,786,787,790,791,794,795,798,799,802,803,806],{},"A client sends ",[604,788,789],{},"{\"bio\": null}"," to clear a user's bio. Your API returns 200 and the bio is unchanged, because your update code did ",[604,792,793],{},"if patch.bio: user.bio = patch.bio",". Another client omits ",[604,796,797],{},"bio"," entirely and gets 422, because someone wrote ",[604,800,801],{},"bio: str | None"," expecting that to mean optional. A third writes ",[604,804,805],{},"{\"timezone\": null}"," intending \"leave it alone\" and wipes the field.",[590,808,809,810,813,814,816],{},"All three are the same confusion: ",[593,811,812],{},"\"can this be null\" and \"must this be present\" are different questions",", and JSON has a third state — absent — that Python's ",[604,815,630],{}," does not naturally represent.",[780,818,820],{"id":819},"why-it-happens","Why It Happens",[590,822,823,824,827,828,830,831,833,834,836,837,840,841,843],{},"Pydantic builds a core schema per field with two independent properties. The ",[593,825,826],{},"annotation"," decides which values validate: ",[604,829,708],{}," accepts strings, ",[604,832,606],{}," accepts strings and ",[604,835,630],{},". The ",[593,838,839],{},"default"," decides what happens when the key is absent: if there is one, it is used; if there is not, the field is required and its absence is a ",[604,842,634],{}," error.",[590,845,846,847,849,850,853,854,857],{},"Nothing links them. ",[604,848,606],{}," says nothing about presence, and ",[604,851,852],{},"= None"," says nothing about which values are legal — the default is not itself validated by default, which is why ",[604,855,856],{},"x: int = None"," is accepted at class-definition time and then blows up when you use it.",[590,859,860,861,864,865,868,869,871,872,876,877,879],{},"Pydantic v1 ",[650,862,863],{},"did"," link them, as a convenience: ",[604,866,867],{},"Optional[str]"," implied ",[604,870,852],{},". Pydantic v2 removed that special case deliberately, on the grounds that a type should not silently change a field's required-ness. The removal is correct and is also the single largest source of surprise 422s during a ",[662,873,875],{"href":874},"\u002Fadvanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Fmigrating-from-pydantic-v1-to-v2-without-breaking-apis\u002F","v1 to v2 migration"," — every annotation-only ",[604,878,626],{}," in your codebase became required the day you upgraded.",[780,881,883],{"id":882},"the-four-cases-with-real-output","The Four Cases, With Real Output",[590,885,886],{},"One model holding all four combinations:",[888,889,894],"pre",{"className":890,"code":891,"language":892,"meta":893,"style":893},"language-python shiki shiki-themes github-light-high-contrast","class FourWays(BaseModel):\n    \"\"\"One model holding all four combinations of required-ness and nullability.\"\"\"\n\n    required_strict: str                    # required, may not be null\n    required_nullable: str | None           # required, MAY be null (no default!)\n    optional_strict: str = \"default\"        # optional, may not be null\n    optional_nullable: str | None = None    # optional, may be null\n\n\n@app.post(\"\u002Ffour-ways\u002F\")\nasync def four_ways(payload: FourWays) -> dict[str, Any]:\n    return {\n        \"dumped\": payload.model_dump(),\n        \"fields_set\": sorted(payload.model_fields_set),\n        \"exclude_unset\": payload.model_dump(exclude_unset=True),\n        \"exclude_none\": payload.model_dump(exclude_none=True),\n    }\n","python","",[604,895,896,920,927,934,946,963,980,999,1004,1009,1024,1044,1053,1062,1077,1097,1113],{"__ignoreMap":893},[897,898,901,905,909,913,917],"span",{"class":899,"line":900},"line",1,[897,902,904],{"class":903},"sTJeM","class",[897,906,908],{"class":907},"sV4o_"," FourWays",[897,910,912],{"class":911},"sigWx","(",[897,914,916],{"class":915},"sacAq","BaseModel",[897,918,919],{"class":911},"):\n",[897,921,923],{"class":899,"line":922},2,[897,924,926],{"class":925},"sYEJz","    \"\"\"One model holding all four combinations of required-ness and nullability.\"\"\"\n",[897,928,930],{"class":899,"line":929},3,[897,931,933],{"emptyLinePlaceholder":932},true,"\n",[897,935,937,940,942],{"class":899,"line":936},4,[897,938,939],{"class":911},"    required_strict: ",[897,941,708],{"class":915},[897,943,945],{"class":944},"sFeEa","                    # required, may not be null\n",[897,947,949,952,954,957,960],{"class":899,"line":948},5,[897,950,951],{"class":911},"    required_nullable: ",[897,953,708],{"class":915},[897,955,956],{"class":903}," |",[897,958,959],{"class":915}," None",[897,961,962],{"class":944},"           # required, MAY be null (no default!)\n",[897,964,966,969,971,974,977],{"class":899,"line":965},6,[897,967,968],{"class":911},"    optional_strict: ",[897,970,708],{"class":915},[897,972,973],{"class":903}," =",[897,975,976],{"class":925}," \"default\"",[897,978,979],{"class":944},"        # optional, may not be null\n",[897,981,983,986,988,990,992,994,996],{"class":899,"line":982},7,[897,984,985],{"class":911},"    optional_nullable: ",[897,987,708],{"class":915},[897,989,956],{"class":903},[897,991,959],{"class":915},[897,993,973],{"class":903},[897,995,959],{"class":915},[897,997,998],{"class":944},"    # optional, may be null\n",[897,1000,1002],{"class":899,"line":1001},8,[897,1003,933],{"emptyLinePlaceholder":932},[897,1005,1007],{"class":899,"line":1006},9,[897,1008,933],{"emptyLinePlaceholder":932},[897,1010,1012,1016,1018,1021],{"class":899,"line":1011},10,[897,1013,1015],{"class":1014},"s3dhs","@app.post",[897,1017,912],{"class":911},[897,1019,1020],{"class":925},"\"\u002Ffour-ways\u002F\"",[897,1022,1023],{"class":911},")\n",[897,1025,1027,1030,1033,1036,1039,1041],{"class":899,"line":1026},11,[897,1028,1029],{"class":903},"async",[897,1031,1032],{"class":903}," def",[897,1034,1035],{"class":1014}," four_ways",[897,1037,1038],{"class":911},"(payload: FourWays) -> dict[",[897,1040,708],{"class":915},[897,1042,1043],{"class":911},", Any]:\n",[897,1045,1047,1050],{"class":899,"line":1046},12,[897,1048,1049],{"class":903},"    return",[897,1051,1052],{"class":911}," {\n",[897,1054,1056,1059],{"class":899,"line":1055},13,[897,1057,1058],{"class":925},"        \"dumped\"",[897,1060,1061],{"class":911},": payload.model_dump(),\n",[897,1063,1065,1068,1071,1074],{"class":899,"line":1064},14,[897,1066,1067],{"class":925},"        \"fields_set\"",[897,1069,1070],{"class":911},": ",[897,1072,1073],{"class":915},"sorted",[897,1075,1076],{"class":911},"(payload.model_fields_set),\n",[897,1078,1080,1083,1086,1088,1091,1094],{"class":899,"line":1079},15,[897,1081,1082],{"class":925},"        \"exclude_unset\"",[897,1084,1085],{"class":911},": payload.model_dump(",[897,1087,640],{"class":907},[897,1089,1090],{"class":903},"=",[897,1092,1093],{"class":915},"True",[897,1095,1096],{"class":911},"),\n",[897,1098,1100,1103,1105,1107,1109,1111],{"class":899,"line":1099},16,[897,1101,1102],{"class":925},"        \"exclude_none\"",[897,1104,1085],{"class":911},[897,1106,644],{"class":907},[897,1108,1090],{"class":903},[897,1110,1093],{"class":915},[897,1112,1096],{"class":911},[897,1114,1116],{"class":899,"line":1115},17,[897,1117,1118],{"class":911},"    }\n",[590,1120,1121,1124],{},[593,1122,1123],{},"Empty body."," Only the two fields without defaults are reported:",[888,1126,1130],{"className":1127,"code":1129,"language":694,"meta":893},[1128],"language-text","$ POST \u002Ffour-ways\u002F  {}\n422 Unprocessable Entity\n{\n  \"detail\": [\n    {\n      \"type\": \"missing\",\n      \"loc\": [\n        \"body\",\n        \"required_strict\"\n      ],\n      \"msg\": \"Field required\",\n      \"input\": {}\n    },\n    {\n      \"type\": \"missing\",\n      \"loc\": [\n        \"body\",\n        \"required_nullable\"\n      ],\n      \"msg\": \"Field required\",\n      \"input\": {}\n    }\n  ]\n}\n",[604,1131,1129],{"__ignoreMap":893},[590,1133,1134,1137,1138,1140,1141,1144,1145,1148,1149,1152,1153,657],{},[604,1135,1136],{},"required_nullable"," is ",[604,1139,606],{}," and it is still required. That is the whole lesson of this page in one transcript. Note also ",[604,1142,1143],{},"\"input\": {}"," — for a missing ",[650,1146,1147],{},"body"," field, ",[604,1150,1151],{},"input"," is the enclosing object, not ",[604,1154,619],{},[590,1156,1157,1162],{},[593,1158,1159,1160,657],{},"The two required fields supplied, one of them as ",[604,1161,619],{}," Accepted:",[888,1164,1167],{"className":1165,"code":1166,"language":694,"meta":893},[1128],"$ POST \u002Ffour-ways\u002F  {\"required_strict\": \"a\", \"required_nullable\": null}\n200 OK\n{\n  \"dumped\": {\n    \"required_strict\": \"a\",\n    \"required_nullable\": null,\n    \"optional_strict\": \"default\",\n    \"optional_nullable\": null\n  },\n  \"fields_set\": [\n    \"required_nullable\",\n    \"required_strict\"\n  ],\n  \"exclude_unset\": {\n    \"required_strict\": \"a\",\n    \"required_nullable\": null\n  },\n  \"exclude_none\": {\n    \"required_strict\": \"a\",\n    \"optional_strict\": \"default\"\n  }\n}\n",[604,1168,1166],{"__ignoreMap":893},[590,1170,1171,1172,1174,1175,1178,1179,1182,1183,1185,1186,1188,1189,1191],{},"Look carefully at the last two keys. ",[604,1173,640],{}," kept ",[604,1176,1177],{},"required_nullable: null"," because the client actually sent it, and dropped ",[604,1180,1181],{},"optional_nullable"," because they did not. ",[604,1184,644],{}," did the opposite: it threw away the deliberate ",[604,1187,619],{}," and kept a default the client never asked for. If you are writing a PATCH handler, ",[604,1190,644],{}," is almost always the wrong tool.",[590,1193,1194,1200],{},[593,1195,1196,1197,1199],{},"An explicit ",[604,1198,619],{}," on a non-nullable optional field."," Rejected, even though the field has a default:",[888,1202,1205],{"className":1203,"code":1204,"language":694,"meta":893},[1128],"$ POST \u002Ffour-ways\u002F  {\"required_strict\": \"a\", \"required_nullable\": \"b\", \"optional_strict\": null, \"optional_nullable\": null}\n422 Unprocessable Entity\n{\n  \"detail\": [\n    {\n      \"type\": \"string_type\",\n      \"loc\": [\n        \"body\",\n        \"optional_strict\"\n      ],\n      \"msg\": \"Input should be a valid string\",\n      \"input\": null\n    }\n  ]\n}\n",[604,1206,1204],{"__ignoreMap":893},[590,1208,1209,1210,1212,1213,1216,1217,1220,1221,1137,1224,1227,1228,1230],{},"Having a default does not make ",[604,1211,630],{}," a legal ",[650,1214,1215],{},"value",". ",[604,1218,1219],{},"optional_strict"," may be omitted; it may not be nulled. The error ",[604,1222,1223],{},"type",[604,1225,1226],{},"string_type",", not ",[604,1229,634],{}," — a useful distinction when you are branching on error types in a client.",[590,1232,1233],{},[593,1234,1235],{},"Setting the optional-nullable field explicitly:",[888,1237,1240],{"className":1238,"code":1239,"language":694,"meta":893},[1128],"$ POST \u002Ffour-ways\u002F  {\"required_strict\": \"a\", \"required_nullable\": null, \"optional_nullable\": \"set\"}\n200 OK\n{\n  \"dumped\": {\n    \"required_strict\": \"a\",\n    \"required_nullable\": null,\n    \"optional_strict\": \"default\",\n    \"optional_nullable\": \"set\"\n  },\n  \"fields_set\": [\n    \"optional_nullable\",\n    \"required_nullable\",\n    \"required_strict\"\n  ],\n  \"exclude_unset\": {\n    \"required_strict\": \"a\",\n    \"required_nullable\": null,\n    \"optional_nullable\": \"set\"\n  },\n  \"exclude_none\": {\n    \"required_strict\": \"a\",\n    \"optional_strict\": \"default\",\n    \"optional_nullable\": \"set\"\n  }\n}\n",[604,1241,1239],{"__ignoreMap":893},[590,1243,1244,1247,1248,1251,1252,1254,1255,1257,1258,657],{},[604,1245,1246],{},"fields_set"," now has three entries. It tracks exactly which keys the client provided — the only place that information survives, since ",[604,1249,1250],{},"optional_nullable=None"," from a default and ",[604,1253,1250],{}," from an explicit ",[604,1256,619],{}," are indistinguishable in ",[604,1259,1260],{},"dumped",[1262,1263,1265],"h3",{"id":1264},"the-summary-table","The summary table",[1267,1268,1269,1295],"table",{},[1270,1271,1272],"thead",{},[1273,1274,1275,1279,1282,1287,1290],"tr",{},[1276,1277,1278],"th",{},"Declaration",[1276,1280,1281],{},"Required?",[1276,1283,1284,1286],{},[604,1285,619],{}," accepted?",[1276,1288,1289],{},"Omitted",[1276,1291,1292,1293],{},"Explicit ",[604,1294,619],{},[1296,1297,1298,1321,1343,1363],"tbody",{},[1273,1299,1300,1306,1309,1312,1317],{},[1301,1302,1303],"td",{},[604,1304,1305],{},"x: str",[1301,1307,1308],{},"Yes",[1301,1310,1311],{},"No",[1301,1313,1314,1315],{},"422 ",[604,1316,634],{},[1301,1318,1314,1319],{},[604,1320,1226],{},[1273,1322,1323,1328,1332,1334,1338],{},[1301,1324,1325],{},[604,1326,1327],{},"x: str | None",[1301,1329,1330],{},[593,1331,1308],{},[1301,1333,1308],{},[1301,1335,1314,1336],{},[604,1337,634],{},[1301,1339,1340,1341],{},"200, value ",[604,1342,630],{},[1273,1344,1345,1350,1352,1354,1359],{},[1301,1346,1347],{},[604,1348,1349],{},"x: str = \"d\"",[1301,1351,1311],{},[1301,1353,1311],{},[1301,1355,1340,1356],{},[604,1357,1358],{},"\"d\"",[1301,1360,1314,1361],{},[604,1362,1226],{},[1273,1364,1365,1370,1372,1374,1378],{},[1301,1366,1367],{},[604,1368,1369],{},"x: str | None = None",[1301,1371,1311],{},[1301,1373,1308],{},[1301,1375,1340,1376],{},[604,1377,630],{},[1301,1379,1340,1380],{},[604,1381,630],{},[590,1383,1384],{},"Every cell in that table corresponds to a transcript above or in the example file. The second row is the one people write when they mean the fourth.",[1262,1386,1388],{"id":1387},"making-the-intent-explicit","Making the intent explicit",[590,1390,1391,1392,1395],{},"A bare ",[604,1393,1394],{},"required_nullable: str | None"," reads to most people as \"optional\". If you genuinely want required-but-nullable, say so:",[888,1397,1399],{"className":890,"code":1398,"language":892,"meta":893,"style":893},"class Sentinel(BaseModel):\n    \"\"\"Required-but-nullable via Field(...) reads more clearly than a bare annotation.\"\"\"\n\n    nickname: str | None = Field(...)              # required, null allowed\n    avatar_url: str | None = Field(default=None)   # optional, null allowed\n",[604,1400,1401,1414,1419,1423,1448],{"__ignoreMap":893},[897,1402,1403,1405,1408,1410,1412],{"class":899,"line":900},[897,1404,904],{"class":903},[897,1406,1407],{"class":907}," Sentinel",[897,1409,912],{"class":911},[897,1411,916],{"class":915},[897,1413,919],{"class":911},[897,1415,1416],{"class":899,"line":922},[897,1417,1418],{"class":925},"    \"\"\"Required-but-nullable via Field(...) reads more clearly than a bare annotation.\"\"\"\n",[897,1420,1421],{"class":899,"line":929},[897,1422,933],{"emptyLinePlaceholder":932},[897,1424,1425,1428,1430,1432,1434,1436,1439,1442,1445],{"class":899,"line":936},[897,1426,1427],{"class":911},"    nickname: ",[897,1429,708],{"class":915},[897,1431,956],{"class":903},[897,1433,959],{"class":915},[897,1435,973],{"class":903},[897,1437,1438],{"class":911}," Field(",[897,1440,1441],{"class":915},"...",[897,1443,1444],{"class":911},")              ",[897,1446,1447],{"class":944},"# required, null allowed\n",[897,1449,1450,1453,1455,1457,1459,1461,1463,1465,1467,1469,1472],{"class":899,"line":948},[897,1451,1452],{"class":911},"    avatar_url: ",[897,1454,708],{"class":915},[897,1456,956],{"class":903},[897,1458,959],{"class":915},[897,1460,973],{"class":903},[897,1462,1438],{"class":911},[897,1464,839],{"class":907},[897,1466,1090],{"class":903},[897,1468,630],{"class":915},[897,1470,1471],{"class":911},")   ",[897,1473,1474],{"class":944},"# optional, null allowed\n",[888,1476,1479],{"className":1477,"code":1478,"language":694,"meta":893},[1128],"$ POST \u002Fsentinel\u002F  {\"avatar_url\": \"https:\u002F\u002Fx\u002Fy.png\"}\n422 Unprocessable Entity\n{\n  \"detail\": [\n    {\n      \"type\": \"missing\",\n      \"loc\": [\n        \"body\",\n        \"nickname\"\n      ],\n      \"msg\": \"Field required\",\n      \"input\": {\n        \"avatar_url\": \"https:\u002F\u002Fx\u002Fy.png\"\n      }\n    }\n  ]\n}\n\n$ POST \u002Fsentinel\u002F  {\"nickname\": null}\n200 OK\n{\n  \"dumped\": {\n    \"nickname\": null,\n    \"avatar_url\": null\n  },\n  \"fields_set\": [\n    \"nickname\"\n  ]\n}\n",[604,1480,1478],{"__ignoreMap":893},[590,1482,1483,1486,1487,1489],{},[604,1484,1485],{},"Field(...)"," is not decoration. It tells the next reader that the missing default is a decision rather than an oversight, and it survives code review in a way a bare annotation does not. Required-but-nullable is a legitimate contract — \"you must tell me the customer's middle name, and ",[604,1488,619],{}," is a valid answer meaning they have none\" — but it should look deliberate.",[780,1491,1493,1495],{"id":1492},"exclude_unset-and-patch-round-tripping",[604,1494,640],{}," and PATCH Round-Tripping",[590,1497,1498,1499,1501],{},"The canonical use is a partial update where absent means \"leave alone\" and ",[604,1500,619],{}," means \"clear it\":",[888,1503,1505],{"className":890,"code":1504,"language":892,"meta":893,"style":893},"class ProfilePatch(BaseModel):\n    \"\"\"PATCH body: every field optional AND nullable, so absent != explicit null.\"\"\"\n\n    display_name: str | None = None\n    bio: str | None = None\n    timezone: str | None = None\n\n\nSTORED: dict[str, Any] = {\"display_name\": \"Ada\", \"bio\": \"Engineer\", \"timezone\": \"UTC\"}\n\n\n@app.patch(\"\u002Fprofile\")\nasync def patch_profile(patch: ProfilePatch) -> dict[str, Any]:\n    updates = patch.model_dump(exclude_unset=True)   # only keys the client actually sent\n    merged = {**STORED, **updates}\n    return {\"sent_keys\": sorted(updates), \"updates\": updates, \"merged\": merged}\n",[604,1506,1507,1520,1525,1529,1545,1560,1575,1579,1583,1633,1637,1641,1653,1669,1690,1711],{"__ignoreMap":893},[897,1508,1509,1511,1514,1516,1518],{"class":899,"line":900},[897,1510,904],{"class":903},[897,1512,1513],{"class":907}," ProfilePatch",[897,1515,912],{"class":911},[897,1517,916],{"class":915},[897,1519,919],{"class":911},[897,1521,1522],{"class":899,"line":922},[897,1523,1524],{"class":925},"    \"\"\"PATCH body: every field optional AND nullable, so absent != explicit null.\"\"\"\n",[897,1526,1527],{"class":899,"line":929},[897,1528,933],{"emptyLinePlaceholder":932},[897,1530,1531,1534,1536,1538,1540,1542],{"class":899,"line":936},[897,1532,1533],{"class":911},"    display_name: ",[897,1535,708],{"class":915},[897,1537,956],{"class":903},[897,1539,959],{"class":915},[897,1541,973],{"class":903},[897,1543,1544],{"class":915}," None\n",[897,1546,1547,1550,1552,1554,1556,1558],{"class":899,"line":948},[897,1548,1549],{"class":911},"    bio: ",[897,1551,708],{"class":915},[897,1553,956],{"class":903},[897,1555,959],{"class":915},[897,1557,973],{"class":903},[897,1559,1544],{"class":915},[897,1561,1562,1565,1567,1569,1571,1573],{"class":899,"line":965},[897,1563,1564],{"class":911},"    timezone: ",[897,1566,708],{"class":915},[897,1568,956],{"class":903},[897,1570,959],{"class":915},[897,1572,973],{"class":903},[897,1574,1544],{"class":915},[897,1576,1577],{"class":899,"line":982},[897,1578,933],{"emptyLinePlaceholder":932},[897,1580,1581],{"class":899,"line":1001},[897,1582,933],{"emptyLinePlaceholder":932},[897,1584,1585,1588,1591,1593,1596,1598,1601,1604,1606,1609,1612,1615,1617,1620,1622,1625,1627,1630],{"class":899,"line":1006},[897,1586,1587],{"class":915},"STORED",[897,1589,1590],{"class":911},": dict[",[897,1592,708],{"class":915},[897,1594,1595],{"class":911},", Any] ",[897,1597,1090],{"class":903},[897,1599,1600],{"class":911}," {",[897,1602,1603],{"class":925},"\"display_name\"",[897,1605,1070],{"class":911},[897,1607,1608],{"class":925},"\"Ada\"",[897,1610,1611],{"class":911},", ",[897,1613,1614],{"class":925},"\"bio\"",[897,1616,1070],{"class":911},[897,1618,1619],{"class":925},"\"Engineer\"",[897,1621,1611],{"class":911},[897,1623,1624],{"class":925},"\"timezone\"",[897,1626,1070],{"class":911},[897,1628,1629],{"class":925},"\"UTC\"",[897,1631,1632],{"class":911},"}\n",[897,1634,1635],{"class":899,"line":1011},[897,1636,933],{"emptyLinePlaceholder":932},[897,1638,1639],{"class":899,"line":1026},[897,1640,933],{"emptyLinePlaceholder":932},[897,1642,1643,1646,1648,1651],{"class":899,"line":1046},[897,1644,1645],{"class":1014},"@app.patch",[897,1647,912],{"class":911},[897,1649,1650],{"class":925},"\"\u002Fprofile\"",[897,1652,1023],{"class":911},[897,1654,1655,1657,1659,1662,1665,1667],{"class":899,"line":1055},[897,1656,1029],{"class":903},[897,1658,1032],{"class":903},[897,1660,1661],{"class":1014}," patch_profile",[897,1663,1664],{"class":911},"(patch: ProfilePatch) -> dict[",[897,1666,708],{"class":915},[897,1668,1043],{"class":911},[897,1670,1671,1674,1676,1679,1681,1683,1685,1687],{"class":899,"line":1064},[897,1672,1673],{"class":911},"    updates ",[897,1675,1090],{"class":903},[897,1677,1678],{"class":911}," patch.model_dump(",[897,1680,640],{"class":907},[897,1682,1090],{"class":903},[897,1684,1093],{"class":915},[897,1686,1471],{"class":911},[897,1688,1689],{"class":944},"# only keys the client actually sent\n",[897,1691,1692,1695,1697,1699,1702,1704,1706,1708],{"class":899,"line":1079},[897,1693,1694],{"class":911},"    merged ",[897,1696,1090],{"class":903},[897,1698,1600],{"class":911},[897,1700,1701],{"class":903},"**",[897,1703,1587],{"class":915},[897,1705,1611],{"class":911},[897,1707,1701],{"class":903},[897,1709,1710],{"class":911},"updates}\n",[897,1712,1713,1715,1717,1720,1722,1724,1727,1730,1733,1736],{"class":899,"line":1099},[897,1714,1049],{"class":903},[897,1716,1600],{"class":911},[897,1718,1719],{"class":925},"\"sent_keys\"",[897,1721,1070],{"class":911},[897,1723,1073],{"class":915},[897,1725,1726],{"class":911},"(updates), ",[897,1728,1729],{"class":925},"\"updates\"",[897,1731,1732],{"class":911},": updates, ",[897,1734,1735],{"class":925},"\"merged\"",[897,1737,1738],{"class":911},": merged}\n",[590,1740,1741],{},"Three requests, three different meanings, all real:",[888,1743,1746],{"className":1744,"code":1745,"language":694,"meta":893},[1128],"$ PATCH \u002Fprofile  {\"bio\": \"Rewriting my bio\"}\n200 OK\n{\n  \"sent_keys\": [\n    \"bio\"\n  ],\n  \"updates\": {\n    \"bio\": \"Rewriting my bio\"\n  },\n  \"merged\": {\n    \"display_name\": \"Ada\",\n    \"bio\": \"Rewriting my bio\",\n    \"timezone\": \"UTC\"\n  }\n}\n\n$ PATCH \u002Fprofile  {\"bio\": null}\n200 OK\n{\n  \"sent_keys\": [\n    \"bio\"\n  ],\n  \"updates\": {\n    \"bio\": null\n  },\n  \"merged\": {\n    \"display_name\": \"Ada\",\n    \"bio\": null,\n    \"timezone\": \"UTC\"\n  }\n}\n\n$ PATCH \u002Fprofile  {}\n200 OK\n{\n  \"sent_keys\": [],\n  \"updates\": {},\n  \"merged\": {\n    \"display_name\": \"Ada\",\n    \"bio\": \"Engineer\",\n    \"timezone\": \"UTC\"\n  }\n}\n",[604,1747,1745],{"__ignoreMap":893},[590,1749,1750,1751,1754,1755,1758,1759,1762,1763,1766],{},"Set, cleared, untouched — three outcomes from one model, and ",[604,1752,1753],{},"display_name"," and ",[604,1756,1757],{},"timezone"," are never touched in any of them. Swap ",[604,1760,1761],{},"exclude_unset=True"," for ",[604,1764,1765],{},"exclude_none=True"," and the middle case silently becomes a no-op: the clear-my-bio feature stops working, with no error anywhere. That bug is nearly impossible to find by reading the handler, because the code looks right.",[590,1768,1769,1770,1773,1774,1776],{},"The same mechanism drives ",[604,1771,1772],{},"model_copy(update=...)"," and SQLAlchemy partial updates. Whatever the persistence layer, the rule is: compute the update dict with ",[604,1775,1761],{},", and let absent keys simply not appear.",[1262,1778,1780],{"id":1779},"the-limit-of-this-technique","The limit of this technique",[590,1782,1783,1785,1786,1611,1789,1792,1793,1796,1797,1799,1800,1802,1803,1805,1806,1809,1810,1812],{},[604,1784,640],{}," distinguishes two states. Some APIs need three: ",[650,1787,1788],{},"unset",[650,1790,1791],{},"explicit null",", and ",[650,1794,1795],{},"set to a value"," — where ",[604,1798,619],{}," itself is a meaningful value distinct from clearing. If ",[604,1801,630],{}," is a legitimate stored value and clearing means something else again, you need a sentinel type rather than ",[604,1804,630],{},", and a ",[604,1807,1808],{},"Literal"," discriminant or a wrapper model to carry it. That is a real design cost; most APIs are better off deciding that ",[604,1811,619],{}," means \"clear\" and living with two states.",[780,1814,1816],{"id":1815},"verification","Verification",[590,1818,1819,1820,1823],{},"Assert on ",[604,1821,1822],{},"model_fields_set"," in unit tests — it is the property everything else derives from:",[888,1825,1827],{"className":890,"code":1826,"language":892,"meta":893,"style":893},"def test_absent_and_null_are_distinguishable():\n    assert ProfilePatch.model_validate({}).model_fields_set == set()\n    assert ProfilePatch.model_validate({\"bio\": None}).model_fields_set == {\"bio\"}\n    assert ProfilePatch.model_validate({\"bio\": None}).model_dump(exclude_unset=True) == {\"bio\": None}\n",[604,1828,1829,1840,1857,1881],{"__ignoreMap":893},[897,1830,1831,1834,1837],{"class":899,"line":900},[897,1832,1833],{"class":903},"def",[897,1835,1836],{"class":1014}," test_absent_and_null_are_distinguishable",[897,1838,1839],{"class":911},"():\n",[897,1841,1842,1845,1848,1851,1854],{"class":899,"line":922},[897,1843,1844],{"class":903},"    assert",[897,1846,1847],{"class":911}," ProfilePatch.model_validate({}).model_fields_set ",[897,1849,1850],{"class":903},"==",[897,1852,1853],{"class":915}," set",[897,1855,1856],{"class":911},"()\n",[897,1858,1859,1861,1864,1866,1868,1870,1873,1875,1877,1879],{"class":899,"line":929},[897,1860,1844],{"class":903},[897,1862,1863],{"class":911}," ProfilePatch.model_validate({",[897,1865,1614],{"class":925},[897,1867,1070],{"class":911},[897,1869,630],{"class":915},[897,1871,1872],{"class":911},"}).model_fields_set ",[897,1874,1850],{"class":903},[897,1876,1600],{"class":911},[897,1878,1614],{"class":925},[897,1880,1632],{"class":911},[897,1882,1883,1885,1887,1889,1891,1893,1896,1898,1900,1902,1905,1907,1909,1911,1913,1915],{"class":899,"line":936},[897,1884,1844],{"class":903},[897,1886,1863],{"class":911},[897,1888,1614],{"class":925},[897,1890,1070],{"class":911},[897,1892,630],{"class":915},[897,1894,1895],{"class":911},"}).model_dump(",[897,1897,640],{"class":907},[897,1899,1090],{"class":903},[897,1901,1093],{"class":915},[897,1903,1904],{"class":911},") ",[897,1906,1850],{"class":903},[897,1908,1600],{"class":911},[897,1910,1614],{"class":925},[897,1912,1070],{"class":911},[897,1914,630],{"class":915},[897,1916,1632],{"class":911},[590,1918,1919],{},"And guard the required-ness contract against accidental change, which is what actually regresses during refactors:",[888,1921,1923],{"className":890,"code":1922,"language":892,"meta":893,"style":893},"def test_required_fields_are_stable():\n    required = {n for n, f in FourWays.model_fields.items() if f.is_required()}\n    assert required == {\"required_strict\", \"required_nullable\"}\n",[604,1924,1925,1934,1962],{"__ignoreMap":893},[897,1926,1927,1929,1932],{"class":899,"line":900},[897,1928,1833],{"class":903},[897,1930,1931],{"class":1014}," test_required_fields_are_stable",[897,1933,1839],{"class":911},[897,1935,1936,1939,1941,1944,1947,1950,1953,1956,1959],{"class":899,"line":922},[897,1937,1938],{"class":911},"    required ",[897,1940,1090],{"class":903},[897,1942,1943],{"class":911}," {n ",[897,1945,1946],{"class":903},"for",[897,1948,1949],{"class":911}," n, f ",[897,1951,1952],{"class":903},"in",[897,1954,1955],{"class":911}," FourWays.model_fields.items() ",[897,1957,1958],{"class":903},"if",[897,1960,1961],{"class":911}," f.is_required()}\n",[897,1963,1964,1966,1969,1971,1973,1976,1978,1981],{"class":899,"line":929},[897,1965,1844],{"class":903},[897,1967,1968],{"class":911}," required ",[897,1970,1850],{"class":903},[897,1972,1600],{"class":911},[897,1974,1975],{"class":925},"\"required_strict\"",[897,1977,1611],{"class":911},[897,1979,1980],{"class":925},"\"required_nullable\"",[897,1982,1632],{"class":911},[590,1984,1985,1986,1989,1990,1992,1993,1996,1997,2000,2001,657],{},"The schema is the other place to look: a required field appears in the ",[604,1987,1988],{},"required"," array of the model's JSON Schema, and ",[604,1991,619],{}," acceptance shows up as an ",[604,1994,1995],{},"anyOf"," including ",[604,1998,1999],{},"{\"type\": \"null\"}",". An OpenAPI diff in CI catches the accidental flip of either, which is the practice recommended in ",[662,2002,2004],{"href":2003},"\u002Fadvanced-pydantic-validation-serialization\u002Fjson-schema-customization\u002Fcustomizing-openapi-schema-generation-in-fastapi\u002F","customizing OpenAPI schema generation",[780,2006,2008],{"id":2007},"trade-offs-and-when-not-to","Trade-offs and When Not To",[590,2010,2011,2014,2015,2018,2019,2022,2023,2026],{},[593,2012,2013],{},"Do not make everything optional and nullable \"to be safe\"."," A model where every field can be absent cannot express a create operation — you lose the guarantee that a ",[604,2016,2017],{},"User"," has an email, and the check moves into your service layer where it is invisible to clients and to the schema. Use separate models: ",[604,2020,2021],{},"UserCreate"," with required fields, ",[604,2024,2025],{},"UserPatch"," with everything optional.",[590,2028,2029,2032,2033,2035],{},[593,2030,2031],{},"Nullable columns are not nullable API fields."," A database column that permits NULL is an internal storage decision. Exposing that nullability in the response contract forces every client to handle ",[604,2034,619],{}," forever. If the value is always present in practice, serialize a default and keep the API field non-nullable.",[590,2037,2038,2043,2044,2046,2047,2051],{},[593,2039,2040,2042],{},[604,2041,640],{}," on responses is usually wrong."," Omitting keys from a response makes clients defensive about every field. ",[604,2045,640],{}," earns its place on the request side and in update payloads sent onward; response shapes should be stable. Where you do need per-endpoint response shaping, ",[662,2048,2050],{"href":2049},"\u002Fadvanced-pydantic-validation-serialization\u002Fnested-model-serialization\u002Fexcluding-fields-per-endpoint\u002F","excluding fields per endpoint"," covers the sharper tools.",[590,2053,2054,2057],{},[593,2055,2056],{},"Adding a required field is a breaking change; adding an optional one is not."," This is the single most useful consequence of the distinction. New fields go out with defaults, and are only tightened once client telemetry shows everyone sends them.",[780,2059,2061],{"id":2060},"faq","FAQ",[590,2063,2064,2070,2071,2073,2074,2076,2077,2079],{},[593,2065,2066,2067,2069],{},"Does ",[604,2068,606],{}," make a field optional in Pydantic v2?","\nNo. It makes the field nullable. Optionality comes from having a default. A field annotated ",[604,2072,606],{}," with no default is required, and omitting it produces a ",[604,2075,634],{}," error, though sending an explicit ",[604,2078,619],{}," is accepted.",[590,2081,2082,2085,2086,2088,2089,2091,2092,2094],{},[593,2083,2084],{},"How do I tell an omitted field from an explicit null in a PATCH body?","\nDeclare every field as optional and nullable, then call ",[604,2087,656],{},". Fields the client never sent are absent from the result, while a field sent as ",[604,2090,619],{}," appears with the value ",[604,2093,630],{},", which is exactly the distinction a PATCH needs.",[590,2096,2097,2100,2101,2103,2104,2106,2107,2109,2110,2112],{},[593,2098,2099],{},"Why did my v1 model become required when I migrated to v2?","\nPydantic v1 gave ",[604,2102,626],{}," an implicit default of ",[604,2105,630],{},". Pydantic v2 removed that, so an annotation-only ",[604,2108,626],{}," is now required-but-nullable. Adding ",[604,2111,852],{}," restores the v1 behaviour, and it is the single most common source of new 422s after a migration.",[590,2114,2115,2123,2125,2126,1216,2128,2130,2131,2133,2134,2136,2137,657],{},[593,2116,2117,2118,1754,2120,2122],{},"What is the difference between ",[604,2119,640],{},[604,2121,644],{},"?",[604,2124,640],{}," drops fields the client never provided, based on ",[604,2127,1822],{},[604,2129,644],{}," drops any field whose value is ",[604,2132,630],{}," regardless of how it got there, so it discards an explicit ",[604,2135,619],{}," the client deliberately sent. For PATCH semantics you want ",[604,2138,640],{},[590,2140,2141,2147,2148,2150],{},[593,2142,2143,2144,2146],{},"Is ",[604,2145,1485],{}," with an ellipsis still meaningful in Pydantic v2?","\nYes. ",[604,2149,1485],{}," marks a field as required while still letting you attach metadata, and on a nullable annotation it makes the required-but-nullable intent explicit to a reader who would otherwise assume the field was optional.",[780,2152,2154],{"id":2153},"related-reading","Related Reading",[597,2156,2157,2166,2181,2189],{},[600,2158,2159,2162,2163,2165],{},[593,2160,2161],{},"Up to the guide:"," ",[662,2164,665],{"href":664}," for the 422 anatomy behind every transcript here.",[600,2167,2168,2162,2171,2175,2176,2180],{},[593,2169,2170],{},"Sibling pages:",[662,2172,2174],{"href":2173},"\u002Fadvanced-pydantic-validation-serialization\u002Frequest-validation-patterns\u002Fquery-path-and-body-parameter-validation\u002F","Query, Path and Body Parameter Validation"," for constraints on the same fields, and ",[662,2177,2179],{"href":2178},"\u002Fadvanced-pydantic-validation-serialization\u002Frequest-validation-patterns\u002Fvalidating-file-uploads-and-forms\u002F","Validating File Uploads and Forms"," for the multipart case.",[600,2182,2183,2162,2186,2188],{},[593,2184,2185],{},"If you are migrating:",[662,2187,127],{"href":874}," covers the implicit-default removal in its wider context.",[600,2190,2191,2162,2194,2197,2198,657],{},[593,2192,2193],{},"Shaping output:",[662,2195,2196],{"href":2049},"Excluding Fields per Endpoint"," for the response-side counterpart to ",[604,2199,640],{},[2201,2202,2203],"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 .sYEJz, html code.shiki .sYEJz{--shiki-default:#032563}html pre.shiki code .sFeEa, html code.shiki .sFeEa{--shiki-default:#66707B}html pre.shiki code .s3dhs, html code.shiki .s3dhs{--shiki-default:#622CBC}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":893,"searchDepth":922,"depth":922,"links":2205},[2206,2207,2208,2212,2216,2217,2218,2219],{"id":782,"depth":922,"text":783},{"id":819,"depth":922,"text":820},{"id":882,"depth":922,"text":883,"children":2209},[2210,2211],{"id":1264,"depth":929,"text":1265},{"id":1387,"depth":929,"text":1388},{"id":1492,"depth":922,"text":2213,"children":2214},"exclude_unset and PATCH Round-Tripping",[2215],{"id":1779,"depth":929,"text":1780},{"id":1815,"depth":922,"text":1816},{"id":2007,"depth":922,"text":2008},{"id":2060,"depth":922,"text":2061},{"id":2153,"depth":922,"text":2154},"2026-07-20","Optional, nullable and required-but-nullable are three different things in Pydantic v2. See the real 422s, plus exclude_unset for PATCH endpoints that clear.","md",[2224,2227,2229,2231,2234],{"q":2225,"a":2226},"Does str | None make a field optional in Pydantic v2?","No. It makes the field nullable. Optionality comes from having a default. A field annotated str | None with no default is required, and omitting it produces a missing error, though sending an explicit null is accepted.",{"q":2084,"a":2228},"Declare every field as optional and nullable, then call model_dump(exclude_unset=True). Fields the client never sent are absent from the result, while a field sent as null appears with the value None, which is exactly the distinction a PATCH needs.",{"q":2099,"a":2230},"Pydantic v1 gave Optional[x] an implicit default of None. Pydantic v2 removed that, so an annotation-only Optional[x] is now required-but-nullable. Adding = None restores the v1 behaviour, and it is the single most common source of new 422s after a migration.",{"q":2232,"a":2233},"What is the difference between exclude_unset and exclude_none?","exclude_unset drops fields the client never provided, based on model_fields_set. exclude_none drops any field whose value is None regardless of how it got there, so it discards an explicit null the client deliberately sent. For PATCH semantics you want exclude_unset.",{"q":2235,"a":2236},"Is Field(...) with an ellipsis still meaningful in Pydantic v2?","Yes. Field(...) marks a field as required while still letting you attach metadata, and on a nullable annotation it makes the required-but-nullable intent explicit to a reader who would otherwise assume the field was optional.",null,{"slug":2239,"breadcrumb":2240},"optional-vs-nullable-fields",[2241,2244,2247,2248],{"label":2242,"path":2243},"Home","\u002F",{"label":2245,"path":2246},"Advanced Pydantic Validation & Serialization","\u002Fadvanced-pydantic-validation-serialization\u002F",{"label":665,"path":664},{"label":2249,"path":2250},"Optional vs Nullable Fields","\u002Fadvanced-pydantic-validation-serialization\u002Frequest-validation-patterns\u002Foptional-vs-nullable-fields\u002F",{"title":157,"description":2221},"article","gvYf8HWoaegqlez4k1ZIhVD-7J123NRW1rTfvvNz54g",[2237,2237],1784588202620]