[{"data":1,"prerenderedAt":1016},["ShallowReactive",2],{"nav":3,"page-\u002Fadvanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002Fpydantic-v2-async-custom-validator\u002F":310,"surround-\u002Fadvanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002Fpydantic-v2-async-custom-validator\u002F":1014},[4,96,212],{"title":5,"path":6,"stem":7,"children":8},"Advanced Pydantic Validation Serialization","\u002Fadvanced-pydantic-validation-serialization","advanced-pydantic-validation-serialization",[9,12,30,42,54,66,90],{"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],{"title":13,"path":14,"stem":15},{"title":19,"path":20,"stem":21,"children":22},"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",[23],{"title":19,"path":20,"stem":21},{"title":25,"path":26,"stem":27,"children":28},"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",[29],{"title":25,"path":26,"stem":27},{"title":31,"path":32,"stem":33,"children":34},"JSON Schema Customization in Pydantic and FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Fjson-schema-customization","advanced-pydantic-validation-serialization\u002Fjson-schema-customization\u002Findex",[35,36],{"title":31,"path":32,"stem":33},{"title":37,"path":38,"stem":39,"children":40},"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",[41],{"title":37,"path":38,"stem":39},{"title":43,"path":44,"stem":45,"children":46},"Nested Model Serialization in FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Fnested-model-serialization","advanced-pydantic-validation-serialization\u002Fnested-model-serialization\u002Findex",[47,48],{"title":43,"path":44,"stem":45},{"title":49,"path":50,"stem":51,"children":52},"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",[53],{"title":49,"path":50,"stem":51},{"title":55,"path":56,"stem":57,"children":58},"Performance Optimization for Pydantic Models in FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Fperformance-optimization-for-models","advanced-pydantic-validation-serialization\u002Fperformance-optimization-for-models\u002Findex",[59,60],{"title":55,"path":56,"stem":57},{"title":61,"path":62,"stem":63,"children":64},"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",[65],{"title":61,"path":62,"stem":63},{"title":67,"path":68,"stem":69,"children":70},"Pydantic V2 Migration Guide for FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide","advanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Findex",[71,72,78,84],{"title":67,"path":68,"stem":69},{"title":73,"path":74,"stem":75,"children":76},"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",[77],{"title":73,"path":74,"stem":75},{"title":79,"path":80,"stem":81,"children":82},"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",[83],{"title":79,"path":80,"stem":81},{"title":85,"path":86,"stem":87,"children":88},"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",[89],{"title":85,"path":86,"stem":87},{"title":91,"path":92,"stem":93,"children":94},"Type Hinting and IDE Integration in FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Ftype-hinting-ide-integration","advanced-pydantic-validation-serialization\u002Ftype-hinting-ide-integration\u002Findex",[95],{"title":91,"path":92,"stem":93},{"title":97,"path":98,"stem":99,"children":100},"Async Background Tasks Observability","\u002Fasync-background-tasks-observability","async-background-tasks-observability",[101,104,122,140,158,176,194],{"title":102,"path":98,"stem":103},"Async, Background Tasks, and Observability in FastAPI","async-background-tasks-observability\u002Findex",{"title":105,"path":106,"stem":107,"children":108},"Async Correctness and Concurrency in FastAPI","\u002Fasync-background-tasks-observability\u002Fasync-correctness-concurrency","async-background-tasks-observability\u002Fasync-correctness-concurrency\u002Findex",[109,110,116],{"title":105,"path":106,"stem":107},{"title":111,"path":112,"stem":113,"children":114},"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",[115],{"title":111,"path":112,"stem":113},{"title":117,"path":118,"stem":119,"children":120},"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",[121],{"title":117,"path":118,"stem":119},{"title":123,"path":124,"stem":125,"children":126},"Async Database Sessions in FastAPI","\u002Fasync-background-tasks-observability\u002Fasync-database-sessions","async-background-tasks-observability\u002Fasync-database-sessions\u002Findex",[127,128,134],{"title":123,"path":124,"stem":125},{"title":129,"path":130,"stem":131,"children":132},"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",[133],{"title":129,"path":130,"stem":131},{"title":135,"path":136,"stem":137,"children":138},"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",[139],{"title":135,"path":136,"stem":137},{"title":141,"path":142,"stem":143,"children":144},"Background Task Processing in FastAPI","\u002Fasync-background-tasks-observability\u002Fbackground-task-processing","async-background-tasks-observability\u002Fbackground-task-processing\u002Findex",[145,146,152],{"title":141,"path":142,"stem":143},{"title":147,"path":148,"stem":149,"children":150},"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",[151],{"title":147,"path":148,"stem":149},{"title":153,"path":154,"stem":155,"children":156},"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",[157],{"title":153,"path":154,"stem":155},{"title":159,"path":160,"stem":161,"children":162},"Caching Strategies in FastAPI","\u002Fasync-background-tasks-observability\u002Fcaching-strategies","async-background-tasks-observability\u002Fcaching-strategies\u002Findex",[163,164,170],{"title":159,"path":160,"stem":161},{"title":165,"path":166,"stem":167,"children":168},"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",[169],{"title":165,"path":166,"stem":167},{"title":171,"path":172,"stem":173,"children":174},"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",[175],{"title":171,"path":172,"stem":173},{"title":177,"path":178,"stem":179,"children":180},"Observability and Tracing in FastAPI","\u002Fasync-background-tasks-observability\u002Fobservability-and-tracing","async-background-tasks-observability\u002Fobservability-and-tracing\u002Findex",[181,182,188],{"title":177,"path":178,"stem":179},{"title":183,"path":184,"stem":185,"children":186},"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",[187],{"title":183,"path":184,"stem":185},{"title":189,"path":190,"stem":191,"children":192},"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",[193],{"title":189,"path":190,"stem":191},{"title":195,"path":196,"stem":197,"children":198},"Rate Limiting and Throttling in FastAPI","\u002Fasync-background-tasks-observability\u002Frate-limiting-throttling","async-background-tasks-observability\u002Frate-limiting-throttling\u002Findex",[199,200,206],{"title":195,"path":196,"stem":197},{"title":201,"path":202,"stem":203,"children":204},"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",[205],{"title":201,"path":202,"stem":203},{"title":207,"path":208,"stem":209,"children":210},"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",[211],{"title":207,"path":208,"stem":209},{"title":213,"path":214,"stem":215,"children":216},"Core Architecture Routing Patterns","\u002Fcore-architecture-routing-patterns","core-architecture-routing-patterns",[217,220,232,250,268,280,292],{"title":218,"path":214,"stem":219},"FastAPI Core Architecture and Routing Patterns","core-architecture-routing-patterns\u002Findex",{"title":221,"path":222,"stem":223,"children":224},"Application Factory Patterns in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fapplication-factory-patterns","core-architecture-routing-patterns\u002Fapplication-factory-patterns\u002Findex",[225,226],{"title":221,"path":222,"stem":223},{"title":227,"path":228,"stem":229,"children":230},"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",[231],{"title":227,"path":228,"stem":229},{"title":233,"path":234,"stem":235,"children":236},"Configuration Management in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fconfiguration-management","core-architecture-routing-patterns\u002Fconfiguration-management\u002Findex",[237,238,244],{"title":233,"path":234,"stem":235},{"title":239,"path":240,"stem":241,"children":242},"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",[243],{"title":239,"path":240,"stem":241},{"title":245,"path":246,"stem":247,"children":248},"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",[249],{"title":245,"path":246,"stem":247},{"title":251,"path":252,"stem":253,"children":254},"Dependency Injection Strategies in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fdependency-injection-strategies","core-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Findex",[255,256,262],{"title":251,"path":252,"stem":253},{"title":257,"path":258,"stem":259,"children":260},"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",[261],{"title":257,"path":258,"stem":259},{"title":263,"path":264,"stem":265,"children":266},"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",[267],{"title":263,"path":264,"stem":265},{"title":269,"path":270,"stem":271,"children":272},"Error Handling and Global Exceptions in FastAPI","\u002Fcore-architecture-routing-patterns\u002Ferror-handling-global-exceptions","core-architecture-routing-patterns\u002Ferror-handling-global-exceptions\u002Findex",[273,274],{"title":269,"path":270,"stem":271},{"title":275,"path":276,"stem":277,"children":278},"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",[279],{"title":275,"path":276,"stem":277},{"title":281,"path":282,"stem":283,"children":284},"Middleware Implementation in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fmiddleware-implementation","core-architecture-routing-patterns\u002Fmiddleware-implementation\u002Findex",[285,286],{"title":281,"path":282,"stem":283},{"title":287,"path":288,"stem":289,"children":290},"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",[291],{"title":287,"path":288,"stem":289},{"title":293,"path":294,"stem":295,"children":296},"Modular Router Organization in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fmodular-router-organization","core-architecture-routing-patterns\u002Fmodular-router-organization\u002Findex",[297,298,304],{"title":293,"path":294,"stem":295},{"title":299,"path":300,"stem":301,"children":302},"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",[303],{"title":299,"path":300,"stem":301},{"title":305,"path":306,"stem":307,"children":308},"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",[309],{"title":305,"path":306,"stem":307},{"id":311,"title":25,"body":312,"description":965,"extension":966,"meta":967,"navigation":441,"path":26,"seo":1012,"stem":27,"__hash__":1013},"content\u002Fadvanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002Fpydantic-v2-async-custom-validator\u002Findex.md",{"type":313,"value":314,"toc":953},"minimark",[315,319,326,362,371,376,383,387,399,403,408,539,543,643,647,803,807,835,839,923,927,949],[316,317,25],"h1",{"id":318},"pydantic-v2-async-custom-validator-what-to-do-instead",[320,321,322],"p",{},[323,324,325],"strong",{},"Key takeaways:",[327,328,329,342,353,356,359],"ul",{},[330,331,332,333,337,338,341],"li",{},"Pydantic v2 validators are synchronous — they cannot be ",[334,335,336],"code",{},"async"," or ",[334,339,340],{},"await",".",[330,343,344,345,348,349,352],{},"Keep ",[334,346,347],{},"field_validator"," and ",[334,350,351],{},"model_validator"," for pure shape checks.",[330,354,355],{},"Run async rules such as uniqueness in the service layer or a dependency.",[330,357,358],{},"Express request-time async preconditions as FastAPI dependencies.",[330,360,361],{},"Test the synchronous model rules and the async service rules separately.",[320,363,364,365,370],{},"This guide corrects a common expectation around ",[366,367,369],"a",{"href":368},"\u002Fadvanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002F","Custom Validators and Field Constraints"," and routes async checks to where they belong.",[372,373,375],"h2",{"id":374},"the-problem-this-solves","The Problem This Solves",[320,377,378,379,382],{},"Developers reach for an ",[334,380,381],{},"async def field_validator"," to check uniqueness or call an external service during validation, and it silently does not work — Pydantic does not await it. The real need is legitimate; it just belongs outside the model. This guide shows the correct placement.",[372,384,386],{"id":385},"prerequisites","Prerequisites",[327,388,389,392],{},[330,390,391],{},"Pydantic v2 and an async FastAPI stack.",[330,393,394,395,341],{},"An async database session available via ",[366,396,398],{"href":397},"\u002Fcore-architecture-routing-patterns\u002Fdependency-injection-strategies\u002F","dependency injection",[372,400,402],{"id":401},"step-by-step-implementation","Step-by-Step Implementation",[404,405,407],"h3",{"id":406},"_1-keep-the-model-synchronous","1. Keep the model synchronous",[409,410,415],"pre",{"className":411,"code":412,"language":413,"meta":414,"style":414},"language-python shiki shiki-themes github-light","from pydantic import BaseModel, EmailStr, field_validator\n\n\nclass SignupRequest(BaseModel):\n    email: EmailStr\n\n    @field_validator(\"email\")\n    @classmethod\n    def normalize(cls, v: str) -> str:\n        return v.lower()          # Pure, synchronous shape normalization only.\n","python","",[334,416,417,436,443,448,467,473,478,493,503,526],{"__ignoreMap":414},[418,419,422,426,430,433],"span",{"class":420,"line":421},"line",1,[418,423,425],{"class":424},"sD7c4","from",[418,427,429],{"class":428},"sgsFI"," pydantic ",[418,431,432],{"class":424},"import",[418,434,435],{"class":428}," BaseModel, EmailStr, field_validator\n",[418,437,439],{"class":420,"line":438},2,[418,440,442],{"emptyLinePlaceholder":441},true,"\n",[418,444,446],{"class":420,"line":445},3,[418,447,442],{"emptyLinePlaceholder":441},[418,449,451,454,458,461,464],{"class":420,"line":450},4,[418,452,453],{"class":424},"class",[418,455,457],{"class":456},"s7eDp"," SignupRequest",[418,459,460],{"class":428},"(",[418,462,463],{"class":456},"BaseModel",[418,465,466],{"class":428},"):\n",[418,468,470],{"class":420,"line":469},5,[418,471,472],{"class":428},"    email: EmailStr\n",[418,474,476],{"class":420,"line":475},6,[418,477,442],{"emptyLinePlaceholder":441},[418,479,481,484,486,490],{"class":420,"line":480},7,[418,482,483],{"class":456},"    @field_validator",[418,485,460],{"class":428},[418,487,489],{"class":488},"sYBdl","\"email\"",[418,491,492],{"class":428},")\n",[418,494,496,499],{"class":420,"line":495},8,[418,497,498],{"class":456},"    @",[418,500,502],{"class":501},"sYu0t","classmethod\n",[418,504,506,509,512,515,518,521,523],{"class":420,"line":505},9,[418,507,508],{"class":424},"    def",[418,510,511],{"class":456}," normalize",[418,513,514],{"class":428},"(cls, v: ",[418,516,517],{"class":501},"str",[418,519,520],{"class":428},") -> ",[418,522,517],{"class":501},[418,524,525],{"class":428},":\n",[418,527,529,532,535],{"class":420,"line":528},10,[418,530,531],{"class":424},"        return",[418,533,534],{"class":428}," v.lower()          ",[418,536,538],{"class":537},"sAwPA","# Pure, synchronous shape normalization only.\n",[404,540,542],{"id":541},"_2-put-the-async-rule-in-the-service","2. Put the async rule in the service",[409,544,546],{"className":411,"code":545,"language":413,"meta":414,"style":414},"from fastapi import HTTPException\nfrom sqlalchemy.ext.asyncio import AsyncSession\n\n\nasync def register(session: AsyncSession, data: SignupRequest) -> int:\n    # The async uniqueness check belongs here, with a real session.\n    if await email_exists(session, data.email):\n        raise HTTPException(409, \"email already registered\")\n    return await insert_user(session, data.email)\n",[334,547,548,560,572,576,580,598,603,614,633],{"__ignoreMap":414},[418,549,550,552,555,557],{"class":420,"line":421},[418,551,425],{"class":424},[418,553,554],{"class":428}," fastapi ",[418,556,432],{"class":424},[418,558,559],{"class":428}," HTTPException\n",[418,561,562,564,567,569],{"class":420,"line":438},[418,563,425],{"class":424},[418,565,566],{"class":428}," sqlalchemy.ext.asyncio ",[418,568,432],{"class":424},[418,570,571],{"class":428}," AsyncSession\n",[418,573,574],{"class":420,"line":445},[418,575,442],{"emptyLinePlaceholder":441},[418,577,578],{"class":420,"line":450},[418,579,442],{"emptyLinePlaceholder":441},[418,581,582,584,587,590,593,596],{"class":420,"line":469},[418,583,336],{"class":424},[418,585,586],{"class":424}," def",[418,588,589],{"class":456}," register",[418,591,592],{"class":428},"(session: AsyncSession, data: SignupRequest) -> ",[418,594,595],{"class":501},"int",[418,597,525],{"class":428},[418,599,600],{"class":420,"line":475},[418,601,602],{"class":537},"    # The async uniqueness check belongs here, with a real session.\n",[418,604,605,608,611],{"class":420,"line":480},[418,606,607],{"class":424},"    if",[418,609,610],{"class":424}," await",[418,612,613],{"class":428}," email_exists(session, data.email):\n",[418,615,616,619,622,625,628,631],{"class":420,"line":495},[418,617,618],{"class":424},"        raise",[418,620,621],{"class":428}," HTTPException(",[418,623,624],{"class":501},"409",[418,626,627],{"class":428},", ",[418,629,630],{"class":488},"\"email already registered\"",[418,632,492],{"class":428},[418,634,635,638,640],{"class":420,"line":505},[418,636,637],{"class":424},"    return",[418,639,610],{"class":424},[418,641,642],{"class":428}," insert_user(session, data.email)\n",[404,644,646],{"id":645},"_3-or-express-it-as-a-dependency","3. Or express it as a dependency",[409,648,650],{"className":411,"code":649,"language":413,"meta":414,"style":414},"from typing import Annotated\n\nfrom fastapi import Depends\n\n\nasync def unique_email(data: SignupRequest, session: SessionDep) -> SignupRequest:\n    # Runs before the handler; raises a structured error on conflict.\n    if await email_exists(session, data.email):\n        raise HTTPException(409, \"email already registered\")\n    return data\n\n\n@router.post(\"\u002Fsignup\")\nasync def signup(data: Annotated[SignupRequest, Depends(unique_email)],\n                 session: SessionDep) -> dict[str, int]:\n    return {\"id\": await register(session, data)}\n",[334,651,652,664,668,679,683,687,699,704,712,726,733,738,743,756,769,784],{"__ignoreMap":414},[418,653,654,656,659,661],{"class":420,"line":421},[418,655,425],{"class":424},[418,657,658],{"class":428}," typing ",[418,660,432],{"class":424},[418,662,663],{"class":428}," Annotated\n",[418,665,666],{"class":420,"line":438},[418,667,442],{"emptyLinePlaceholder":441},[418,669,670,672,674,676],{"class":420,"line":445},[418,671,425],{"class":424},[418,673,554],{"class":428},[418,675,432],{"class":424},[418,677,678],{"class":428}," Depends\n",[418,680,681],{"class":420,"line":450},[418,682,442],{"emptyLinePlaceholder":441},[418,684,685],{"class":420,"line":469},[418,686,442],{"emptyLinePlaceholder":441},[418,688,689,691,693,696],{"class":420,"line":475},[418,690,336],{"class":424},[418,692,586],{"class":424},[418,694,695],{"class":456}," unique_email",[418,697,698],{"class":428},"(data: SignupRequest, session: SessionDep) -> SignupRequest:\n",[418,700,701],{"class":420,"line":480},[418,702,703],{"class":537},"    # Runs before the handler; raises a structured error on conflict.\n",[418,705,706,708,710],{"class":420,"line":495},[418,707,607],{"class":424},[418,709,610],{"class":424},[418,711,613],{"class":428},[418,713,714,716,718,720,722,724],{"class":420,"line":505},[418,715,618],{"class":424},[418,717,621],{"class":428},[418,719,624],{"class":501},[418,721,627],{"class":428},[418,723,630],{"class":488},[418,725,492],{"class":428},[418,727,728,730],{"class":420,"line":528},[418,729,637],{"class":424},[418,731,732],{"class":428}," data\n",[418,734,736],{"class":420,"line":735},11,[418,737,442],{"emptyLinePlaceholder":441},[418,739,741],{"class":420,"line":740},12,[418,742,442],{"emptyLinePlaceholder":441},[418,744,746,749,751,754],{"class":420,"line":745},13,[418,747,748],{"class":456},"@router.post",[418,750,460],{"class":428},[418,752,753],{"class":488},"\"\u002Fsignup\"",[418,755,492],{"class":428},[418,757,759,761,763,766],{"class":420,"line":758},14,[418,760,336],{"class":424},[418,762,586],{"class":424},[418,764,765],{"class":456}," signup",[418,767,768],{"class":428},"(data: Annotated[SignupRequest, Depends(unique_email)],\n",[418,770,772,775,777,779,781],{"class":420,"line":771},15,[418,773,774],{"class":428},"                 session: SessionDep) -> dict[",[418,776,517],{"class":501},[418,778,627],{"class":428},[418,780,595],{"class":501},[418,782,783],{"class":428},"]:\n",[418,785,787,789,792,795,798,800],{"class":420,"line":786},16,[418,788,637],{"class":424},[418,790,791],{"class":428}," {",[418,793,794],{"class":488},"\"id\"",[418,796,797],{"class":428},": ",[418,799,340],{"class":424},[418,801,802],{"class":428}," register(session, data)}\n",[372,804,806],{"id":805},"edge-cases-and-gotchas","Edge Cases and Gotchas",[327,808,809,819,825],{},[330,810,811,814,815,818],{},[323,812,813],{},"Silent no-op."," An ",[334,816,817],{},"async def"," validator is not awaited by Pydantic; do not rely on it.",[330,820,821,824],{},[323,822,823],{},"Validation vs authorization."," Async checks that depend on the caller's identity are authorization, not validation; keep them in dependencies.",[330,826,827,830,831,341],{},[323,828,829],{},"Error consistency."," Raise a domain error so the response uses your ",[366,832,834],{"href":833},"\u002Fcore-architecture-routing-patterns\u002Ferror-handling-global-exceptions\u002F","error envelope",[372,836,838],{"id":837},"verification","Verification",[409,840,842],{"className":411,"code":841,"language":413,"meta":414,"style":414},"import pytest\nfrom fastapi import HTTPException\n\n\nasync def test_duplicate_email_rejected(session):\n    await insert_user(session, \"ada@example.com\")\n    with pytest.raises(HTTPException):\n        await register(session, SignupRequest(email=\"ADA@example.com\"))\n",[334,843,844,851,861,865,869,881,894,902],{"__ignoreMap":414},[418,845,846,848],{"class":420,"line":421},[418,847,432],{"class":424},[418,849,850],{"class":428}," pytest\n",[418,852,853,855,857,859],{"class":420,"line":438},[418,854,425],{"class":424},[418,856,554],{"class":428},[418,858,432],{"class":424},[418,860,559],{"class":428},[418,862,863],{"class":420,"line":445},[418,864,442],{"emptyLinePlaceholder":441},[418,866,867],{"class":420,"line":450},[418,868,442],{"emptyLinePlaceholder":441},[418,870,871,873,875,878],{"class":420,"line":469},[418,872,336],{"class":424},[418,874,586],{"class":424},[418,876,877],{"class":456}," test_duplicate_email_rejected",[418,879,880],{"class":428},"(session):\n",[418,882,883,886,889,892],{"class":420,"line":475},[418,884,885],{"class":424},"    await",[418,887,888],{"class":428}," insert_user(session, ",[418,890,891],{"class":488},"\"ada@example.com\"",[418,893,492],{"class":428},[418,895,896,899],{"class":420,"line":480},[418,897,898],{"class":424},"    with",[418,900,901],{"class":428}," pytest.raises(HTTPException):\n",[418,903,904,907,910,914,917,920],{"class":420,"line":495},[418,905,906],{"class":424},"        await",[418,908,909],{"class":428}," register(session, SignupRequest(",[418,911,913],{"class":912},"sqxcx","email",[418,915,916],{"class":424},"=",[418,918,919],{"class":488},"\"ADA@example.com\"",[418,921,922],{"class":428},"))\n",[372,924,926],{"id":925},"related-reading","Related Reading",[327,928,929,937],{},[330,930,931,934,935,341],{},[323,932,933],{},"Up to the topic:"," ",[366,936,369],{"href":368},[330,938,939,934,942,348,945,341],{},[323,940,941],{},"Related guides:",[366,943,944],{"href":397},"Dependency Injection Strategies",[366,946,948],{"href":947},"\u002Fasync-background-tasks-observability\u002Fasync-database-sessions\u002F","Async Database Sessions",[950,951,952],"style",{},"html pre.shiki code .sD7c4, html code.shiki .sD7c4{--shiki-default:#D73A49}html pre.shiki code .sgsFI, html code.shiki .sgsFI{--shiki-default:#24292E}html pre.shiki code .s7eDp, html code.shiki .s7eDp{--shiki-default:#6F42C1}html pre.shiki code .sYBdl, html code.shiki .sYBdl{--shiki-default:#032F62}html pre.shiki code .sYu0t, html code.shiki .sYu0t{--shiki-default:#005CC5}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sqxcx, html code.shiki .sqxcx{--shiki-default:#E36209}",{"title":414,"searchDepth":438,"depth":438,"links":954},[955,956,957,962,963,964],{"id":374,"depth":438,"text":375},{"id":385,"depth":438,"text":386},{"id":401,"depth":438,"text":402,"children":958},[959,960,961],{"id":406,"depth":445,"text":407},{"id":541,"depth":445,"text":542},{"id":645,"depth":445,"text":646},{"id":805,"depth":438,"text":806},{"id":837,"depth":438,"text":838},{"id":925,"depth":438,"text":926},"Pydantic v2 validators run synchronously and cannot be async. Learn why, and the correct patterns for async checks in FastAPI: validate at the boundary, enforce rules in the service layer.","md",{"slug":968,"type":969,"breadcrumb":970,"datePublished":982,"dateModified":983,"howto":984,"faq":1002},"pydantic-v2-async-custom-validator","long_tail",[971,974,977,979],{"label":972,"path":973},"Home","\u002F",{"label":975,"path":976},"Advanced Pydantic Validation & Serialization","\u002Fadvanced-pydantic-validation-serialization\u002F",{"label":978,"path":368},"Custom Validators & Field Constraints",{"label":980,"path":981},"Pydantic v2 Async Custom Validator","\u002Fadvanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002Fpydantic-v2-async-custom-validator\u002F","2026-03-06","2026-06-18",{"name":985,"steps":986},"Handle async validation needs with Pydantic v2 and FastAPI",[987,990,993,996,999],{"name":988,"text":989},"Keep model validators synchronous","Use field_validator and model_validator only for pure, synchronous checks on the data's shape.",{"name":991,"text":992},"Move I\u002FO rules to the service","Perform async checks such as uniqueness in the service layer with an injected session.",{"name":994,"text":995},"Use dependencies for request-time async checks","Express async preconditions as FastAPI dependencies that run before the handler.",{"name":997,"text":998},"Return structured errors","Raise HTTPException or a domain error so the failure uses your error envelope.",{"name":1000,"text":1001},"Test both layers","Unit-test the sync model rules and the async service rules separately.",[1003,1006,1009],{"q":1004,"a":1005},"Can a Pydantic v2 validator be async?","No. Pydantic v2 runs validation on its synchronous Rust core, so field_validator and model_validator functions must be synchronous and cannot await. Attempting an async validator does not give you awaited validation; the coroutine is not awaited as you expect. Async checks belong outside the model.",{"q":1007,"a":1008},"How do I validate something that requires a database call, like uniqueness?","Do it in the service layer or a FastAPI dependency, not in the model. The model validates the shape of the input synchronously; the async rule — does this email already exist — runs with an injected async session before or during the handler, raising a structured error if it fails. This keeps the validation boundary pure and fast.",{"q":1010,"a":1011},"Why does Pydantic keep validators synchronous?","Because validation runs in a hot, synchronous core for speed and because models are constructed in many non-async contexts. Mixing awaited I\u002FO into validation would make every model construction potentially async and slow, and would blur the line between validating data shape and enforcing system state. Keeping it synchronous preserves a fast, predictable boundary.",{"title":25,"description":965},"_lB8E3qPg0OoYCk4XZfpDK7cAe4m0lBAjPLwjCUtE-8",[1015,1015],null,1781809863436]