[{"data":1,"prerenderedAt":2362},["ShallowReactive",2],{"nav":3,"page-\u002Fasync-background-tasks-observability\u002Fasync-database-sessions\u002Ftesting-with-async-database-fixtures\u002F":580,"surround-\u002Fasync-background-tasks-observability\u002Fasync-database-sessions\u002Ftesting-with-async-database-fixtures\u002F":2361},[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":243,"body":582,"dateModified":2328,"datePublished":2328,"description":2329,"extension":2330,"faq":2331,"howto":2344,"meta":2345,"navigation":903,"path":244,"seo":2358,"stem":245,"type":2359,"__hash__":2360},"content\u002Fasync-background-tasks-observability\u002Fasync-database-sessions\u002Ftesting-with-async-database-fixtures\u002Findex.md",{"type":583,"value":584,"toc":2317},"minimark",[585,589,596,627,641,730,735,738,746,750,757,783,786,795,798,802,805,832,835,1323,1326,1346,1362,1375,1384,1388,1398,1870,1873,1879,1882,1917,1920,1924,1930,2025,2039,2043,2046,2049,2088,2091,2123,2126,2130,2139,2149,2164,2174,2194,2198,2215,2221,2234,2244,2256,2262,2266,2313],[586,587,243],"h1",{"id":588},"testing-with-async-database-fixtures-in-fastapi",[590,591,592],"p",{},[593,594,595],"strong",{},"Key takeaways:",[597,598,599,603,606,617,620],"ul",{},[600,601,602],"li",{},"One session-scoped engine, one function-scoped transaction: build the schema once and undo each test.",[600,604,605],{},"Bind the session to an already-open connection and roll that connection's transaction back — never drop and recreate tables per test.",[600,607,608,612,613,616],{},[609,610,611],"code",{},"join_transaction_mode=\"create_savepoint\""," is what lets the endpoint call ",[609,614,615],{},"commit()"," without escaping your rollback.",[600,618,619],{},"Override the session dependency, and clear the overrides in teardown so nothing leaks between tests.",[600,621,622,623,626],{},"Match ",[609,624,625],{},"asyncio_default_fixture_loop_scope"," to the lifetime of your longest-lived async fixture.",[590,628,629,630,635,636,640],{},"This page applies the boundaries from ",[631,632,634],"a",{"href":633},"\u002Fasync-background-tasks-observability\u002Fasync-database-sessions\u002Ftransaction-management-and-rollback\u002F","Transaction Management and Rollback"," to a test suite, and sits under ",[631,637,639],{"href":638},"\u002Fasync-background-tasks-observability\u002Fasync-database-sessions\u002F","Async Database Sessions",".",[642,643,644,726],"figure",{},[645,646,654,655,654,659,654,663,654,670,654,679,654,685,654,692,654,698,654,705,654,710,654,715,654,721],"svg",{"viewBox":647,"role":648,"ariaLabelledBy":649,"xmlns":652,"style":653},"0 0 720 300","img",[650,651],"fx-title","fx-desc","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;height:auto;max-width:720px;margin:2rem 0","\n  ",[656,657,658],"title",{"id":650},"Nesting of the engine, transaction and session fixtures",[660,661,662],"desc",{"id":651},"A session-scoped engine fixture contains a per-test connection and outer transaction, which contains the session bound to it and the app's savepoint. Rolling back the outer transaction discards everything the test wrote.",[664,665,669],"text",{"x":666,"y":667,"style":668},"360","28","text-anchor:middle;fill:currentColor;font:600 15px sans-serif","Fixture nesting, outermost first",[671,672],"rect",{"x":673,"y":674,"width":675,"height":676,"rx":677,"style":678},"20","44","680","200","10","fill:none;stroke:currentColor;stroke-width:1.6",[664,680,684],{"x":681,"y":682,"style":683},"40","68","text-anchor:start;fill:currentColor;font:600 13px sans-serif","engine (scope=session) - schema created once",[671,686],{"x":674,"y":687,"width":688,"height":689,"rx":690,"style":691},"82","632","146","9","fill:none;stroke:#00796B;stroke-width:1.8",[664,693,697],{"x":694,"y":695,"style":696},"64","106","text-anchor:start;fill:#00796B;font:600 13px sans-serif","connection + outer transaction (per test)",[671,699],{"x":682,"y":700,"width":701,"height":702,"rx":703,"style":704},"120","584","92","8","fill:none;stroke:currentColor;stroke-width:1.4",[664,706,709],{"x":707,"y":708,"style":683},"88","144","db_session bound to that connection",[671,711],{"x":702,"y":712,"width":713,"height":681,"rx":714,"style":704},"158","536","7",[664,716,720],{"x":717,"y":718,"style":719},"112","183","text-anchor:start;fill:currentColor;font:12px monospace","endpoint commit() -> releases a SAVEPOINT",[664,722,725],{"x":666,"y":723,"style":724},"272","text-anchor:middle;fill:#00796B;font:600 13px sans-serif","Roll back the outer transaction and every layer above vanishes.",[727,728,729],"figcaption",{},"Isolation comes from the outer transaction, so tests never wait for tables to be dropped and rebuilt.",[731,732,734],"h2",{"id":733},"the-problem-this-solves","The Problem This Solves",[590,736,737],{},"Database tests fail in two characteristic ways. Either they are slow, because every test recreates the schema, or they are flaky, because they share state and the outcome depends on ordering. Both are solved by the same idea: give each test a transaction and never let it commit anything you cannot take back.",[590,739,740,741,745],{},"Doing that with an ",[742,743,744],"em",{},"async"," session adds a second problem that has nothing to do with databases — event loops. An async fixture and the test that uses it must run on the same loop, and pytest-asyncio's defaults will happily give them different ones.",[731,747,749],{"id":748},"why-it-happens-two-independent-lifetimes","Why It Happens: Two Independent Lifetimes",[590,751,752,753,756],{},"An async SQLAlchemy engine owns a connection pool, and every pooled connection is created on some event loop. If a session-scoped ",[609,754,755],{},"engine"," fixture is created on loop A and a test then runs on loop B, the first query fails at the driver layer, not at the ORM layer, which makes the error message unhelpful.",[590,758,759,760,763,764,767,768,770,771,774,775,778,779,782],{},"pytest-asyncio governs this with two settings. ",[609,761,762],{},"asyncio_mode = auto"," makes every ",[609,765,766],{},"async def"," test runnable without a per-test marker. ",[609,769,625],{}," decides which loop async ",[742,772,773],{},"fixtures"," get. The rule to internalise: ",[593,776,777],{},"the loop must live at least as long as the longest-lived async fixture."," A session-scoped engine therefore needs a session-scoped loop, and the tests using it need ",[609,780,781],{},"loop_scope=\"session\""," to match.",[590,784,785],{},"Without any plugin at all, the failure is unmistakable:",[787,788,793],"pre",{"className":789,"code":791,"language":664,"meta":792},[790],"language-text","$ GET \u002Fpytest\u002Fno-asyncio-mode\n200 OK\nF                                                                        [100%]\n=================================== FAILURES ===================================\n____________________ test_without_pytest_asyncio_configured ____________________\nasync def functions are not natively supported.\nYou need to install a suitable plugin for your async framework, for example:\n  - anyio\n  - pytest-asyncio\n  - pytest-tornasync\n  - pytest-trio\n  - pytest-twisted\n=========================== short test summary info ============================\nFAILED test_broken.py::test_without_pytest_asyncio_configured - Failed: async...\n1 failed in 0.00s\n","",[609,794,791],{"__ignoreMap":792},[590,796,797],{},"That is a real pytest run against a file containing one trivially-passing async test and no plugin configuration. It fails not because the assertion is wrong but because nobody awaited the coroutine.",[731,799,801],{"id":800},"the-fix-three-fixtures-that-nest","The Fix: Three Fixtures That Nest",[590,803,804],{},"Start with the configuration, because everything else depends on it:",[787,806,810],{"className":807,"code":808,"language":809,"meta":792,"style":792},"language-ini shiki shiki-themes github-light-high-contrast","[pytest]\nasyncio_mode = auto\nasyncio_default_fixture_loop_scope = session\n","ini",[609,811,812,820,826],{"__ignoreMap":792},[813,814,817],"span",{"class":815,"line":816},"line",1,[813,818,819],{},"[pytest]\n",[813,821,823],{"class":815,"line":822},2,[813,824,825],{},"asyncio_mode = auto\n",[813,827,829],{"class":815,"line":828},3,[813,830,831],{},"asyncio_default_fixture_loop_scope = session\n",[590,833,834],{},"Then the fixtures. Each one is deliberately narrow: the engine builds the schema, the session owns a rollback, and the client wires the app to that session.",[787,836,840],{"className":837,"code":838,"language":839,"meta":792,"style":792},"language-python shiki shiki-themes github-light-high-contrast","import pytest\nimport pytest_asyncio\nfrom httpx import ASGITransport, AsyncClient\nfrom sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine\nfrom sqlalchemy.pool import StaticPool\n\nfrom myapp import Base, app, get_session\n\n\n@pytest_asyncio.fixture(scope=\"session\")\nasync def engine():\n    \"\"\"One in-memory database for the whole session; schema created once.\"\"\"\n    engine = create_async_engine(\n        \"sqlite+aiosqlite:\u002F\u002F\", poolclass=StaticPool, connect_args={\"check_same_thread\": False}\n    )\n    async with engine.begin() as conn:\n        await conn.run_sync(Base.metadata.create_all)\n    yield engine\n    await engine.dispose()\n\n\n@pytest_asyncio.fixture\nasync def db_session(engine):\n    \"\"\"Bind the session to an OUTER transaction and roll it back, whatever the test committed.\"\"\"\n    async with engine.connect() as connection:\n        transaction = await connection.begin()\n        maker = async_sessionmaker(bind=connection, expire_on_commit=False, join_transaction_mode=\"create_savepoint\")\n        async with maker() as session:\n            yield session\n        await transaction.rollback()\n\n\n@pytest_asyncio.fixture\nasync def client(db_session: AsyncSession):\n    \"\"\"Point the app's session dependency at the test's transaction, then undo the override.\"\"\"\n    app.dependency_overrides[get_session] = lambda: db_session\n    transport = ASGITransport(app=app)\n    async with AsyncClient(transport=transport, base_url=\"http:\u002F\u002Ftest\") as c:\n        yield c\n    app.dependency_overrides.clear()\n","python",[609,841,842,852,859,872,885,898,905,918,923,928,952,966,972,983,1021,1027,1045,1054,1063,1072,1077,1082,1088,1101,1107,1122,1136,1174,1190,1199,1207,1212,1217,1222,1235,1241,1255,1274,1308,1317],{"__ignoreMap":792},[813,843,844,848],{"class":815,"line":816},[813,845,847],{"class":846},"sTJeM","import",[813,849,851],{"class":850},"sigWx"," pytest\n",[813,853,854,856],{"class":815,"line":822},[813,855,847],{"class":846},[813,857,858],{"class":850}," pytest_asyncio\n",[813,860,861,864,867,869],{"class":815,"line":828},[813,862,863],{"class":846},"from",[813,865,866],{"class":850}," httpx ",[813,868,847],{"class":846},[813,870,871],{"class":850}," ASGITransport, AsyncClient\n",[813,873,875,877,880,882],{"class":815,"line":874},4,[813,876,863],{"class":846},[813,878,879],{"class":850}," sqlalchemy.ext.asyncio ",[813,881,847],{"class":846},[813,883,884],{"class":850}," AsyncSession, async_sessionmaker, create_async_engine\n",[813,886,888,890,893,895],{"class":815,"line":887},5,[813,889,863],{"class":846},[813,891,892],{"class":850}," sqlalchemy.pool ",[813,894,847],{"class":846},[813,896,897],{"class":850}," StaticPool\n",[813,899,901],{"class":815,"line":900},6,[813,902,904],{"emptyLinePlaceholder":903},true,"\n",[813,906,908,910,913,915],{"class":815,"line":907},7,[813,909,863],{"class":846},[813,911,912],{"class":850}," myapp ",[813,914,847],{"class":846},[813,916,917],{"class":850}," Base, app, get_session\n",[813,919,921],{"class":815,"line":920},8,[813,922,904],{"emptyLinePlaceholder":903},[813,924,926],{"class":815,"line":925},9,[813,927,904],{"emptyLinePlaceholder":903},[813,929,931,935,938,942,945,949],{"class":815,"line":930},10,[813,932,934],{"class":933},"s3dhs","@pytest_asyncio.fixture",[813,936,937],{"class":850},"(",[813,939,941],{"class":940},"sV4o_","scope",[813,943,944],{"class":846},"=",[813,946,948],{"class":947},"sYEJz","\"session\"",[813,950,951],{"class":850},")\n",[813,953,955,957,960,963],{"class":815,"line":954},11,[813,956,744],{"class":846},[813,958,959],{"class":846}," def",[813,961,962],{"class":933}," engine",[813,964,965],{"class":850},"():\n",[813,967,969],{"class":815,"line":968},12,[813,970,971],{"class":947},"    \"\"\"One in-memory database for the whole session; schema created once.\"\"\"\n",[813,973,975,978,980],{"class":815,"line":974},13,[813,976,977],{"class":850},"    engine ",[813,979,944],{"class":846},[813,981,982],{"class":850}," create_async_engine(\n",[813,984,986,989,992,995,997,1000,1003,1005,1008,1011,1014,1018],{"class":815,"line":985},14,[813,987,988],{"class":947},"        \"sqlite+aiosqlite:\u002F\u002F\"",[813,990,991],{"class":850},", ",[813,993,994],{"class":940},"poolclass",[813,996,944],{"class":846},[813,998,999],{"class":850},"StaticPool, ",[813,1001,1002],{"class":940},"connect_args",[813,1004,944],{"class":846},[813,1006,1007],{"class":850},"{",[813,1009,1010],{"class":947},"\"check_same_thread\"",[813,1012,1013],{"class":850},": ",[813,1015,1017],{"class":1016},"sacAq","False",[813,1019,1020],{"class":850},"}\n",[813,1022,1024],{"class":815,"line":1023},15,[813,1025,1026],{"class":850},"    )\n",[813,1028,1030,1033,1036,1039,1042],{"class":815,"line":1029},16,[813,1031,1032],{"class":846},"    async",[813,1034,1035],{"class":846}," with",[813,1037,1038],{"class":850}," engine.begin() ",[813,1040,1041],{"class":846},"as",[813,1043,1044],{"class":850}," conn:\n",[813,1046,1048,1051],{"class":815,"line":1047},17,[813,1049,1050],{"class":846},"        await",[813,1052,1053],{"class":850}," conn.run_sync(Base.metadata.create_all)\n",[813,1055,1057,1060],{"class":815,"line":1056},18,[813,1058,1059],{"class":846},"    yield",[813,1061,1062],{"class":850}," engine\n",[813,1064,1066,1069],{"class":815,"line":1065},19,[813,1067,1068],{"class":846},"    await",[813,1070,1071],{"class":850}," engine.dispose()\n",[813,1073,1075],{"class":815,"line":1074},20,[813,1076,904],{"emptyLinePlaceholder":903},[813,1078,1080],{"class":815,"line":1079},21,[813,1081,904],{"emptyLinePlaceholder":903},[813,1083,1085],{"class":815,"line":1084},22,[813,1086,1087],{"class":933},"@pytest_asyncio.fixture\n",[813,1089,1091,1093,1095,1098],{"class":815,"line":1090},23,[813,1092,744],{"class":846},[813,1094,959],{"class":846},[813,1096,1097],{"class":933}," db_session",[813,1099,1100],{"class":850},"(engine):\n",[813,1102,1104],{"class":815,"line":1103},24,[813,1105,1106],{"class":947},"    \"\"\"Bind the session to an OUTER transaction and roll it back, whatever the test committed.\"\"\"\n",[813,1108,1110,1112,1114,1117,1119],{"class":815,"line":1109},25,[813,1111,1032],{"class":846},[813,1113,1035],{"class":846},[813,1115,1116],{"class":850}," engine.connect() ",[813,1118,1041],{"class":846},[813,1120,1121],{"class":850}," connection:\n",[813,1123,1125,1128,1130,1133],{"class":815,"line":1124},26,[813,1126,1127],{"class":850},"        transaction ",[813,1129,944],{"class":846},[813,1131,1132],{"class":846}," await",[813,1134,1135],{"class":850}," connection.begin()\n",[813,1137,1139,1142,1144,1147,1150,1152,1155,1158,1160,1162,1164,1167,1169,1172],{"class":815,"line":1138},27,[813,1140,1141],{"class":850},"        maker ",[813,1143,944],{"class":846},[813,1145,1146],{"class":850}," async_sessionmaker(",[813,1148,1149],{"class":940},"bind",[813,1151,944],{"class":846},[813,1153,1154],{"class":850},"connection, ",[813,1156,1157],{"class":940},"expire_on_commit",[813,1159,944],{"class":846},[813,1161,1017],{"class":1016},[813,1163,991],{"class":850},[813,1165,1166],{"class":940},"join_transaction_mode",[813,1168,944],{"class":846},[813,1170,1171],{"class":947},"\"create_savepoint\"",[813,1173,951],{"class":850},[813,1175,1177,1180,1182,1185,1187],{"class":815,"line":1176},28,[813,1178,1179],{"class":846},"        async",[813,1181,1035],{"class":846},[813,1183,1184],{"class":850}," maker() ",[813,1186,1041],{"class":846},[813,1188,1189],{"class":850}," session:\n",[813,1191,1193,1196],{"class":815,"line":1192},29,[813,1194,1195],{"class":846},"            yield",[813,1197,1198],{"class":850}," session\n",[813,1200,1202,1204],{"class":815,"line":1201},30,[813,1203,1050],{"class":846},[813,1205,1206],{"class":850}," transaction.rollback()\n",[813,1208,1210],{"class":815,"line":1209},31,[813,1211,904],{"emptyLinePlaceholder":903},[813,1213,1215],{"class":815,"line":1214},32,[813,1216,904],{"emptyLinePlaceholder":903},[813,1218,1220],{"class":815,"line":1219},33,[813,1221,1087],{"class":933},[813,1223,1225,1227,1229,1232],{"class":815,"line":1224},34,[813,1226,744],{"class":846},[813,1228,959],{"class":846},[813,1230,1231],{"class":933}," client",[813,1233,1234],{"class":850},"(db_session: AsyncSession):\n",[813,1236,1238],{"class":815,"line":1237},35,[813,1239,1240],{"class":947},"    \"\"\"Point the app's session dependency at the test's transaction, then undo the override.\"\"\"\n",[813,1242,1244,1247,1249,1252],{"class":815,"line":1243},36,[813,1245,1246],{"class":850},"    app.dependency_overrides[get_session] ",[813,1248,944],{"class":846},[813,1250,1251],{"class":846}," lambda",[813,1253,1254],{"class":850},": db_session\n",[813,1256,1258,1261,1263,1266,1269,1271],{"class":815,"line":1257},37,[813,1259,1260],{"class":850},"    transport ",[813,1262,944],{"class":846},[813,1264,1265],{"class":850}," ASGITransport(",[813,1267,1268],{"class":940},"app",[813,1270,944],{"class":846},[813,1272,1273],{"class":850},"app)\n",[813,1275,1277,1279,1281,1284,1287,1289,1292,1295,1297,1300,1303,1305],{"class":815,"line":1276},38,[813,1278,1032],{"class":846},[813,1280,1035],{"class":846},[813,1282,1283],{"class":850}," AsyncClient(",[813,1285,1286],{"class":940},"transport",[813,1288,944],{"class":846},[813,1290,1291],{"class":850},"transport, ",[813,1293,1294],{"class":940},"base_url",[813,1296,944],{"class":846},[813,1298,1299],{"class":947},"\"http:\u002F\u002Ftest\"",[813,1301,1302],{"class":850},") ",[813,1304,1041],{"class":846},[813,1306,1307],{"class":850}," c:\n",[813,1309,1311,1314],{"class":815,"line":1310},39,[813,1312,1313],{"class":846},"        yield",[813,1315,1316],{"class":850}," c\n",[813,1318,1320],{"class":815,"line":1319},40,[813,1321,1322],{"class":850},"    app.dependency_overrides.clear()\n",[590,1324,1325],{},"Four decisions in there deserve explanation.",[590,1327,1328,1334,1335,1338,1339,1342,1343,1345],{},[593,1329,1330,1333],{},[609,1331,1332],{},"StaticPool"," with an in-memory URL."," ",[609,1336,1337],{},"sqlite+aiosqlite:\u002F\u002F"," creates a database that lives inside one connection. A normal pool would hand out a ",[742,1340,1341],{},"second",", empty connection to the next caller and your tables would appear to vanish. ",[609,1344,1332],{}," reuses the single connection so the whole suite sees one database.",[590,1347,1348,1357,1358,1361],{},[593,1349,1350,1351,1354,1355,640],{},"Binding the sessionmaker to ",[609,1352,1353],{},"connection",", not ",[609,1356,755],{}," This is what puts the session ",[742,1359,1360],{},"inside"," a transaction that the fixture, not the session, controls.",[590,1363,1364,1368,1369,1371,1372,1374],{},[593,1365,1366,640],{},[609,1367,611],{}," Without it, the application's ",[609,1370,615],{}," would end your outer transaction and there would be nothing left to roll back. With it, SQLAlchemy opens a savepoint for the session's work, so a ",[609,1373,615],{}," in the app resolves to releasing that savepoint. The data stays visible to the test and stays inside the outer transaction.",[590,1376,1377,1383],{},[593,1378,1379,1382],{},[609,1380,1381],{},"app.dependency_overrides.clear()"," after the yield."," Overrides live on the app object, which outlives the test. Clearing in teardown is the difference between an isolated suite and a suite where test ordering matters.",[731,1385,1387],{"id":1386},"what-the-fixtures-guarantee","What the Fixtures Guarantee",[590,1389,1390,1391,1394,1395,640],{},"The tests below are written to prove the isolation rather than to exercise business logic. The app under test exposes ",[609,1392,1393],{},"POST \u002Fwidgets"," (which flushes but never commits) and ",[609,1396,1397],{},"GET \u002Fwidgets",[787,1399,1401],{"className":837,"code":1400,"language":839,"meta":792,"style":792},"import pytest\n\npytestmark = pytest.mark.asyncio(loop_scope=\"session\")\n\n\nasync def test_create_widget_returns_the_row(client):\n    response = await client.post(\"\u002Fwidgets\", params={\"name\": \"sprocket\"})\n    assert response.status_code == 200\n    assert response.json() == {\"id\": 1, \"name\": \"sprocket\"}\n\n\nasync def test_previous_test_left_nothing_behind(client):\n    \"\"\"Proves the rollback: this is a separate test, so the sprocket is gone.\"\"\"\n    response = await client.get(\"\u002Fwidgets\")\n    assert response.json() == []\n\n\nasync def test_ids_restart_because_the_insert_was_undone(client):\n    created = await client.post(\"\u002Fwidgets\", params={\"name\": \"gasket\"})\n    assert created.json()[\"id\"] == 1\n\n\nasync def test_validation_failure_writes_nothing(client):\n    bad = await client.post(\"\u002Fwidgets\", params={\"name\": \"   \"})\n    assert bad.status_code == 422\n    assert bad.json() == {\"detail\": \"name must not be blank\"}\n    assert (await client.get(\"\u002Fwidgets\")).json() == []\n\n\nasync def test_the_session_fixture_and_the_endpoint_share_a_transaction(client, db_session):\n    from sqlalchemy import select\n\n    from myapp import Widget\n\n    await client.post(\"\u002Fwidgets\", params={\"name\": \"flange\"})\n    rows = (await db_session.execute(select(Widget))).scalars().all()\n    assert [w.name for w in rows] == [\"flange\"]\n",[609,1402,1403,1409,1413,1432,1436,1440,1452,1487,1501,1531,1535,1539,1550,1555,1570,1581,1585,1589,1600,1630,1647,1651,1655,1666,1696,1708,1729,1750,1754,1758,1770,1783,1787,1798,1802,1827,1841],{"__ignoreMap":792},[813,1404,1405,1407],{"class":815,"line":816},[813,1406,847],{"class":846},[813,1408,851],{"class":850},[813,1410,1411],{"class":815,"line":822},[813,1412,904],{"emptyLinePlaceholder":903},[813,1414,1415,1418,1420,1423,1426,1428,1430],{"class":815,"line":828},[813,1416,1417],{"class":850},"pytestmark ",[813,1419,944],{"class":846},[813,1421,1422],{"class":850}," pytest.mark.asyncio(",[813,1424,1425],{"class":940},"loop_scope",[813,1427,944],{"class":846},[813,1429,948],{"class":947},[813,1431,951],{"class":850},[813,1433,1434],{"class":815,"line":874},[813,1435,904],{"emptyLinePlaceholder":903},[813,1437,1438],{"class":815,"line":887},[813,1439,904],{"emptyLinePlaceholder":903},[813,1441,1442,1444,1446,1449],{"class":815,"line":900},[813,1443,744],{"class":846},[813,1445,959],{"class":846},[813,1447,1448],{"class":933}," test_create_widget_returns_the_row",[813,1450,1451],{"class":850},"(client):\n",[813,1453,1454,1457,1459,1461,1464,1467,1469,1472,1474,1476,1479,1481,1484],{"class":815,"line":907},[813,1455,1456],{"class":850},"    response ",[813,1458,944],{"class":846},[813,1460,1132],{"class":846},[813,1462,1463],{"class":850}," client.post(",[813,1465,1466],{"class":947},"\"\u002Fwidgets\"",[813,1468,991],{"class":850},[813,1470,1471],{"class":940},"params",[813,1473,944],{"class":846},[813,1475,1007],{"class":850},[813,1477,1478],{"class":947},"\"name\"",[813,1480,1013],{"class":850},[813,1482,1483],{"class":947},"\"sprocket\"",[813,1485,1486],{"class":850},"})\n",[813,1488,1489,1492,1495,1498],{"class":815,"line":920},[813,1490,1491],{"class":846},"    assert",[813,1493,1494],{"class":850}," response.status_code ",[813,1496,1497],{"class":846},"==",[813,1499,1500],{"class":1016}," 200\n",[813,1502,1503,1505,1508,1510,1513,1516,1518,1521,1523,1525,1527,1529],{"class":815,"line":925},[813,1504,1491],{"class":846},[813,1506,1507],{"class":850}," response.json() ",[813,1509,1497],{"class":846},[813,1511,1512],{"class":850}," {",[813,1514,1515],{"class":947},"\"id\"",[813,1517,1013],{"class":850},[813,1519,1520],{"class":1016},"1",[813,1522,991],{"class":850},[813,1524,1478],{"class":947},[813,1526,1013],{"class":850},[813,1528,1483],{"class":947},[813,1530,1020],{"class":850},[813,1532,1533],{"class":815,"line":930},[813,1534,904],{"emptyLinePlaceholder":903},[813,1536,1537],{"class":815,"line":954},[813,1538,904],{"emptyLinePlaceholder":903},[813,1540,1541,1543,1545,1548],{"class":815,"line":968},[813,1542,744],{"class":846},[813,1544,959],{"class":846},[813,1546,1547],{"class":933}," test_previous_test_left_nothing_behind",[813,1549,1451],{"class":850},[813,1551,1552],{"class":815,"line":974},[813,1553,1554],{"class":947},"    \"\"\"Proves the rollback: this is a separate test, so the sprocket is gone.\"\"\"\n",[813,1556,1557,1559,1561,1563,1566,1568],{"class":815,"line":985},[813,1558,1456],{"class":850},[813,1560,944],{"class":846},[813,1562,1132],{"class":846},[813,1564,1565],{"class":850}," client.get(",[813,1567,1466],{"class":947},[813,1569,951],{"class":850},[813,1571,1572,1574,1576,1578],{"class":815,"line":1023},[813,1573,1491],{"class":846},[813,1575,1507],{"class":850},[813,1577,1497],{"class":846},[813,1579,1580],{"class":850}," []\n",[813,1582,1583],{"class":815,"line":1029},[813,1584,904],{"emptyLinePlaceholder":903},[813,1586,1587],{"class":815,"line":1047},[813,1588,904],{"emptyLinePlaceholder":903},[813,1590,1591,1593,1595,1598],{"class":815,"line":1056},[813,1592,744],{"class":846},[813,1594,959],{"class":846},[813,1596,1597],{"class":933}," test_ids_restart_because_the_insert_was_undone",[813,1599,1451],{"class":850},[813,1601,1602,1605,1607,1609,1611,1613,1615,1617,1619,1621,1623,1625,1628],{"class":815,"line":1065},[813,1603,1604],{"class":850},"    created ",[813,1606,944],{"class":846},[813,1608,1132],{"class":846},[813,1610,1463],{"class":850},[813,1612,1466],{"class":947},[813,1614,991],{"class":850},[813,1616,1471],{"class":940},[813,1618,944],{"class":846},[813,1620,1007],{"class":850},[813,1622,1478],{"class":947},[813,1624,1013],{"class":850},[813,1626,1627],{"class":947},"\"gasket\"",[813,1629,1486],{"class":850},[813,1631,1632,1634,1637,1639,1642,1644],{"class":815,"line":1074},[813,1633,1491],{"class":846},[813,1635,1636],{"class":850}," created.json()[",[813,1638,1515],{"class":947},[813,1640,1641],{"class":850},"] ",[813,1643,1497],{"class":846},[813,1645,1646],{"class":1016}," 1\n",[813,1648,1649],{"class":815,"line":1079},[813,1650,904],{"emptyLinePlaceholder":903},[813,1652,1653],{"class":815,"line":1084},[813,1654,904],{"emptyLinePlaceholder":903},[813,1656,1657,1659,1661,1664],{"class":815,"line":1090},[813,1658,744],{"class":846},[813,1660,959],{"class":846},[813,1662,1663],{"class":933}," test_validation_failure_writes_nothing",[813,1665,1451],{"class":850},[813,1667,1668,1671,1673,1675,1677,1679,1681,1683,1685,1687,1689,1691,1694],{"class":815,"line":1103},[813,1669,1670],{"class":850},"    bad ",[813,1672,944],{"class":846},[813,1674,1132],{"class":846},[813,1676,1463],{"class":850},[813,1678,1466],{"class":947},[813,1680,991],{"class":850},[813,1682,1471],{"class":940},[813,1684,944],{"class":846},[813,1686,1007],{"class":850},[813,1688,1478],{"class":947},[813,1690,1013],{"class":850},[813,1692,1693],{"class":947},"\"   \"",[813,1695,1486],{"class":850},[813,1697,1698,1700,1703,1705],{"class":815,"line":1109},[813,1699,1491],{"class":846},[813,1701,1702],{"class":850}," bad.status_code ",[813,1704,1497],{"class":846},[813,1706,1707],{"class":1016}," 422\n",[813,1709,1710,1712,1715,1717,1719,1722,1724,1727],{"class":815,"line":1124},[813,1711,1491],{"class":846},[813,1713,1714],{"class":850}," bad.json() ",[813,1716,1497],{"class":846},[813,1718,1512],{"class":850},[813,1720,1721],{"class":947},"\"detail\"",[813,1723,1013],{"class":850},[813,1725,1726],{"class":947},"\"name must not be blank\"",[813,1728,1020],{"class":850},[813,1730,1731,1733,1736,1739,1741,1743,1746,1748],{"class":815,"line":1138},[813,1732,1491],{"class":846},[813,1734,1735],{"class":850}," (",[813,1737,1738],{"class":846},"await",[813,1740,1565],{"class":850},[813,1742,1466],{"class":947},[813,1744,1745],{"class":850},")).json() ",[813,1747,1497],{"class":846},[813,1749,1580],{"class":850},[813,1751,1752],{"class":815,"line":1176},[813,1753,904],{"emptyLinePlaceholder":903},[813,1755,1756],{"class":815,"line":1192},[813,1757,904],{"emptyLinePlaceholder":903},[813,1759,1760,1762,1764,1767],{"class":815,"line":1201},[813,1761,744],{"class":846},[813,1763,959],{"class":846},[813,1765,1766],{"class":933}," test_the_session_fixture_and_the_endpoint_share_a_transaction",[813,1768,1769],{"class":850},"(client, db_session):\n",[813,1771,1772,1775,1778,1780],{"class":815,"line":1209},[813,1773,1774],{"class":846},"    from",[813,1776,1777],{"class":850}," sqlalchemy ",[813,1779,847],{"class":846},[813,1781,1782],{"class":850}," select\n",[813,1784,1785],{"class":815,"line":1214},[813,1786,904],{"emptyLinePlaceholder":903},[813,1788,1789,1791,1793,1795],{"class":815,"line":1219},[813,1790,1774],{"class":846},[813,1792,912],{"class":850},[813,1794,847],{"class":846},[813,1796,1797],{"class":850}," Widget\n",[813,1799,1800],{"class":815,"line":1224},[813,1801,904],{"emptyLinePlaceholder":903},[813,1803,1804,1806,1808,1810,1812,1814,1816,1818,1820,1822,1825],{"class":815,"line":1237},[813,1805,1068],{"class":846},[813,1807,1463],{"class":850},[813,1809,1466],{"class":947},[813,1811,991],{"class":850},[813,1813,1471],{"class":940},[813,1815,944],{"class":846},[813,1817,1007],{"class":850},[813,1819,1478],{"class":947},[813,1821,1013],{"class":850},[813,1823,1824],{"class":947},"\"flange\"",[813,1826,1486],{"class":850},[813,1828,1829,1832,1834,1836,1838],{"class":815,"line":1243},[813,1830,1831],{"class":850},"    rows ",[813,1833,944],{"class":846},[813,1835,1735],{"class":850},[813,1837,1738],{"class":846},[813,1839,1840],{"class":850}," db_session.execute(select(Widget))).scalars().all()\n",[813,1842,1843,1845,1848,1851,1854,1857,1860,1862,1865,1867],{"class":815,"line":1257},[813,1844,1491],{"class":846},[813,1846,1847],{"class":850}," [w.name ",[813,1849,1850],{"class":846},"for",[813,1852,1853],{"class":850}," w ",[813,1855,1856],{"class":846},"in",[813,1858,1859],{"class":850}," rows] ",[813,1861,1497],{"class":846},[813,1863,1864],{"class":850}," [",[813,1866,1824],{"class":947},[813,1868,1869],{"class":850},"]\n",[590,1871,1872],{},"Running that suite really does pass, in a real subprocess:",[787,1874,1877],{"className":1875,"code":1876,"language":664,"meta":792},[790],"$ GET \u002Fpytest\u002Fdb-fixtures\n200 OK\n.....                                                                    [100%]\n5 passed in 0.00s\n",[609,1878,1876],{"__ignoreMap":792},[590,1880,1881],{},"Each of those five dots is load-bearing:",[597,1883,1884,1887,1894,1901,1906],{},[600,1885,1886],{},"The first test creates widget id 1.",[600,1888,1889,1890,1893],{},"The second sees an ",[593,1891,1892],{},"empty"," list. Nothing was dropped or truncated between them; the outer transaction was simply rolled back.",[600,1895,1896,1897,1900],{},"The third gets id 1 ",[742,1898,1899],{},"again",", which is a stronger claim than \"the row is gone\" — the autoincrement sequence was rolled back too, so the tests are genuinely order-independent.",[600,1902,1903,1904,640],{},"The fourth confirms a 422 leaves no residue, closing the loop with ",[631,1905,634],{"href":633},[600,1907,1908,1909,1912,1913,1916],{},"The fifth is the one that proves the wiring: the test queries ",[609,1910,1911],{},"db_session"," directly and sees the row the ",[742,1914,1915],{},"endpoint"," inserted. If the override had failed, the app would have used its production engine and this assertion would find nothing.",[590,1918,1919],{},"That last test is the one to write first when adopting this pattern, because a broken override is otherwise invisible — the tests still pass, they just pass against the wrong database.",[731,1921,1923],{"id":1922},"adapting-the-pattern-to-postgres","Adapting the Pattern to Postgres",[590,1925,1926,1927,1929],{},"The fixture shape is unchanged; only the engine setup differs. Point the engine at a real database, drop ",[609,1928,1332],{},", and create the schema against a per-run database or schema name:",[787,1931,1933],{"className":837,"code":1932,"language":839,"meta":792,"style":792},"@pytest_asyncio.fixture(scope=\"session\")\nasync def engine():\n    url = os.environ[\"TEST_DATABASE_URL\"]  # e.g. postgresql+asyncpg:\u002F\u002F...\u002Fapp_test\n    engine = create_async_engine(url)\n    async with engine.begin() as conn:\n        await conn.run_sync(Base.metadata.drop_all)\n        await conn.run_sync(Base.metadata.create_all)\n    yield engine\n    await engine.dispose()\n",[609,1934,1935,1949,1959,1979,1988,2000,2007,2013,2019],{"__ignoreMap":792},[813,1936,1937,1939,1941,1943,1945,1947],{"class":815,"line":816},[813,1938,934],{"class":933},[813,1940,937],{"class":850},[813,1942,941],{"class":940},[813,1944,944],{"class":846},[813,1946,948],{"class":947},[813,1948,951],{"class":850},[813,1950,1951,1953,1955,1957],{"class":815,"line":822},[813,1952,744],{"class":846},[813,1954,959],{"class":846},[813,1956,962],{"class":933},[813,1958,965],{"class":850},[813,1960,1961,1964,1966,1969,1972,1975],{"class":815,"line":828},[813,1962,1963],{"class":850},"    url ",[813,1965,944],{"class":846},[813,1967,1968],{"class":850}," os.environ[",[813,1970,1971],{"class":947},"\"TEST_DATABASE_URL\"",[813,1973,1974],{"class":850},"]  ",[813,1976,1978],{"class":1977},"sFeEa","# e.g. postgresql+asyncpg:\u002F\u002F...\u002Fapp_test\n",[813,1980,1981,1983,1985],{"class":815,"line":874},[813,1982,977],{"class":850},[813,1984,944],{"class":846},[813,1986,1987],{"class":850}," create_async_engine(url)\n",[813,1989,1990,1992,1994,1996,1998],{"class":815,"line":887},[813,1991,1032],{"class":846},[813,1993,1035],{"class":846},[813,1995,1038],{"class":850},[813,1997,1041],{"class":846},[813,1999,1044],{"class":850},[813,2001,2002,2004],{"class":815,"line":900},[813,2003,1050],{"class":846},[813,2005,2006],{"class":850}," conn.run_sync(Base.metadata.drop_all)\n",[813,2008,2009,2011],{"class":815,"line":907},[813,2010,1050],{"class":846},[813,2012,1053],{"class":850},[813,2014,2015,2017],{"class":815,"line":920},[813,2016,1059],{"class":846},[813,2018,1062],{"class":850},[813,2020,2021,2023],{"class":815,"line":925},[813,2022,1068],{"class":846},[813,2024,1071],{"class":850},[590,2026,2027,2028,2030,2031,2034,2035,2038],{},"The ",[609,2029,1911],{}," and ",[609,2032,2033],{},"client"," fixtures need no changes at all — the connection-plus-rollback trick is a SQLAlchemy feature, not a SQLite one. If your project uses migrations, run those instead of ",[609,2036,2037],{},"create_all"," so the tests exercise the schema you will actually deploy.",[731,2040,2042],{"id":2041},"verification","Verification",[590,2044,2045],{},"The pattern is only trustworthy if you can detect it silently breaking, so keep two guards.",[590,2047,2048],{},"Assert that the app is not talking to the production engine:",[787,2050,2052],{"className":837,"code":2051,"language":839,"meta":792,"style":792},"async def test_override_is_actually_installed(client):\n    from myapp import get_session\n    assert get_session in app.dependency_overrides\n",[609,2053,2054,2065,2076],{"__ignoreMap":792},[813,2055,2056,2058,2060,2063],{"class":815,"line":816},[813,2057,744],{"class":846},[813,2059,959],{"class":846},[813,2061,2062],{"class":933}," test_override_is_actually_installed",[813,2064,1451],{"class":850},[813,2066,2067,2069,2071,2073],{"class":815,"line":822},[813,2068,1774],{"class":846},[813,2070,912],{"class":850},[813,2072,847],{"class":846},[813,2074,2075],{"class":850}," get_session\n",[813,2077,2078,2080,2083,2085],{"class":815,"line":828},[813,2079,1491],{"class":846},[813,2081,2082],{"class":850}," get_session ",[813,2084,1856],{"class":846},[813,2086,2087],{"class":850}," app.dependency_overrides\n",[590,2089,2090],{},"And assert that overrides do not survive the test:",[787,2092,2094],{"className":837,"code":2093,"language":839,"meta":792,"style":792},"def test_no_overrides_leak_between_tests():\n    # Runs after the async tests above; the client fixture must have cleaned up.\n    assert app.dependency_overrides == {}\n",[609,2095,2096,2106,2111],{"__ignoreMap":792},[813,2097,2098,2101,2104],{"class":815,"line":816},[813,2099,2100],{"class":846},"def",[813,2102,2103],{"class":933}," test_no_overrides_leak_between_tests",[813,2105,965],{"class":850},[813,2107,2108],{"class":815,"line":822},[813,2109,2110],{"class":1977},"    # Runs after the async tests above; the client fixture must have cleaned up.\n",[813,2112,2113,2115,2118,2120],{"class":815,"line":828},[813,2114,1491],{"class":846},[813,2116,2117],{"class":850}," app.dependency_overrides ",[813,2119,1497],{"class":846},[813,2121,2122],{"class":850}," {}\n",[590,2124,2125],{},"Beyond that, the diagnostic habit is to make one test deliberately commit and confirm a later test cannot see it — which is exactly what the first three tests above encode.",[731,2127,2129],{"id":2128},"trade-offs-and-when-not-to","Trade-offs and When Not To",[590,2131,2132,2135,2136,2138],{},[593,2133,2134],{},"The savepoint trick hides some real behaviour."," Because the app's ",[609,2137,615],{}," is not a true commit, deferred constraints and commit-time triggers do not fire the way they would in production. For those specific behaviours, write a smaller suite that commits for real and cleans up with truncation.",[590,2140,2141,2144,2145,2148],{},[593,2142,2143],{},"Session-scoped engines and parallel tests interact badly."," Under ",[609,2146,2147],{},"pytest-xdist"," each worker is a separate process and needs its own database name, or workers will roll back each other's data. Derive the database name from the worker id.",[590,2150,2151,2154,2155,2158,2159,2163],{},[593,2152,2153],{},"In-memory SQLite is not your database."," It is fast and fine for wiring, validation and transaction-boundary tests. It will not reproduce Postgres locking, ",[609,2156,2157],{},"JSONB"," behaviour, dialect-specific SQL or the pool exhaustion covered in ",[631,2160,2162],{"href":2161},"\u002Fasync-background-tasks-observability\u002Fasync-database-sessions\u002Ffixing-asyncpg-pool-exhaustion\u002F","Fixing asyncpg Pool Exhaustion",". Keep an integration tier on the real engine.",[590,2165,2166,2169,2170,640],{},[593,2167,2168],{},"Fixtures that share a session hide concurrency bugs."," Every test here runs one request at a time against one session. Code that only breaks under concurrent requests to the same row needs a test that issues concurrent requests — see ",[631,2171,2173],{"href":2172},"\u002Fasync-background-tasks-observability\u002Fasync-correctness-concurrency\u002Fconcurrent-requests-with-asyncio-gather\u002F","Concurrent Requests with asyncio.gather",[590,2175,2176,2186,2187,2189,2190,640],{},[593,2177,2178,2179,1354,2182,2185],{},"Use ",[609,2180,2181],{},"AsyncClient",[609,2183,2184],{},"TestClient",", with these fixtures."," The fixtures are async and live on the test's event loop; ",[609,2188,2184],{}," runs the app on a different loop of its own. The consequences are laid out in ",[631,2191,2193],{"href":2192},"\u002Fasync-background-tasks-observability\u002Ftesting-fastapi-applications\u002Ftestclient-vs-httpx-asyncclient\u002F","TestClient vs httpx AsyncClient",[731,2195,2197],{"id":2196},"faq","FAQ",[590,2199,2200,2203,2204,2207,2208,2211,2212,640],{},[593,2201,2202],{},"Why do my async tests fail with async def functions are not natively supported?","\npytest cannot run a coroutine on its own, so without an async plugin it collects the test, refuses to await it and fails. Install pytest-asyncio and set ",[609,2205,2206],{},"asyncio_mode"," to ",[609,2209,2210],{},"auto",", or mark each test with ",[609,2213,2214],{},"pytest.mark.asyncio",[590,2216,2217,2220],{},[593,2218,2219],{},"How do I make each test start from a clean database?","\nOpen a connection, begin a transaction on it, bind the session to that connection, and roll the transaction back after the test. Everything the test wrote, including its commits, disappears without dropping or recreating any tables.",[590,2222,2223,2226,2227,2229,2230,2233],{},[593,2224,2225],{},"How can the test still see rows after the endpoint commits?","\nBind the session maker to the open connection with ",[609,2228,1166],{}," set to ",[609,2231,2232],{},"create_savepoint",". The endpoint's commit then resolves to releasing a savepoint inside your outer transaction rather than ending it, so the data stays visible and stays undoable.",[590,2235,2236,2239,2240,2243],{},[593,2237,2238],{},"How do I point the app at the test database?","\nOverride the session dependency in ",[609,2241,2242],{},"app.dependency_overrides"," with a callable returning the test session, then clear the overrides in the fixture teardown so the override cannot leak into another test.",[590,2245,2246,2249,2250,2207,2252,2255],{},[593,2247,2248],{},"Why does my session-scoped async fixture raise a different event loop error?","\nA fixture that lives longer than one test must run on a loop that also lives that long. Set ",[609,2251,625],{},[609,2253,2254],{},"session"," and give the tests a matching loop scope, otherwise pytest-asyncio creates a fresh loop per test while the engine is still bound to the first one.",[590,2257,2258,2261],{},[593,2259,2260],{},"Is in-memory SQLite good enough for testing?","\nIt is excellent for fast tests of application logic, wiring and transaction boundaries, but it does not reproduce Postgres dialect features, concurrency or locking. Run the fast suite on SQLite and a smaller integration suite against the real engine.",[731,2263,2265],{"id":2264},"related","Related",[597,2267,2268,2276,2284,2293,2303],{},[600,2269,2270,1334,2273,2275],{},[593,2271,2272],{},"Up to the topic:",[631,2274,639],{"href":638}," covers the production session these fixtures replace.",[600,2277,2278,1334,2281,2283],{},[593,2279,2280],{},"The boundary being tested:",[631,2282,634],{"href":633}," explains commit placement and savepoints.",[600,2285,2286,1334,2289,2292],{},[593,2287,2288],{},"Wider testing story:",[631,2290,363],{"href":2291},"\u002Fasync-background-tasks-observability\u002Ftesting-fastapi-applications\u002F"," puts these fixtures in context alongside clients and mocks.",[600,2294,2295,1334,2298,2302],{},[593,2296,2297],{},"Async test mechanics:",[631,2299,2301],{"href":2300},"\u002Fasync-background-tasks-observability\u002Ftesting-fastapi-applications\u002Ftesting-async-endpoints-with-pytest-asyncio\u002F","Testing Async Endpoints with pytest-asyncio"," goes deeper on loop scopes and markers.",[600,2304,2305,1334,2308,2312],{},[593,2306,2307],{},"Overrides in general:",[631,2309,2311],{"href":2310},"\u002Fcore-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Foverriding-dependencies-in-tests\u002F","Overriding Dependencies in Tests"," covers the substitution mechanism beyond sessions.",[2314,2315,2316],"style",{},"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 .sTJeM, html code.shiki .sTJeM{--shiki-default:#A0111F}html pre.shiki code .sigWx, html code.shiki .sigWx{--shiki-default:#0E1116}html pre.shiki code .s3dhs, html code.shiki .s3dhs{--shiki-default:#622CBC}html pre.shiki code .sV4o_, html code.shiki .sV4o_{--shiki-default:#702C00}html pre.shiki code .sYEJz, html code.shiki .sYEJz{--shiki-default:#032563}html pre.shiki code .sacAq, html code.shiki .sacAq{--shiki-default:#023B95}html pre.shiki code .sFeEa, html code.shiki .sFeEa{--shiki-default:#66707B}",{"title":792,"searchDepth":822,"depth":822,"links":2318},[2319,2320,2321,2322,2323,2324,2325,2326,2327],{"id":733,"depth":822,"text":734},{"id":748,"depth":822,"text":749},{"id":800,"depth":822,"text":801},{"id":1386,"depth":822,"text":1387},{"id":1922,"depth":822,"text":1923},{"id":2041,"depth":822,"text":2042},{"id":2128,"depth":822,"text":2129},{"id":2196,"depth":822,"text":2197},{"id":2264,"depth":822,"text":2265},"2026-07-20","Build pytest-asyncio fixtures for async SQLAlchemy: a session-scoped engine, a per-test transaction that rolls back, and a session dependency override.","md",[2332,2334,2336,2338,2340,2342],{"q":2202,"a":2333},"pytest cannot run a coroutine on its own, so without an async plugin it collects the test, refuses to await it and fails. Install pytest-asyncio and set asyncio_mode to auto, or mark each test with pytest.mark.asyncio.",{"q":2219,"a":2335},"Open a connection, begin a transaction on it, bind the session to that connection, and roll the transaction back after the test. Everything the test wrote, including its commits, disappears without dropping or recreating any tables.",{"q":2225,"a":2337},"Bind the session maker to the open connection with join_transaction_mode set to create_savepoint. The endpoint's commit then resolves to releasing a savepoint inside your outer transaction rather than ending it, so the data stays visible and stays undoable.",{"q":2238,"a":2339},"Override the session dependency in app.dependency_overrides with a callable returning the test session, then clear the overrides in the fixture teardown so the override cannot leak into another test.",{"q":2248,"a":2341},"A fixture that lives longer than one test must run on a loop that also lives that long. Set asyncio_default_fixture_loop_scope to session and give the tests a matching loop scope, otherwise pytest-asyncio creates a fresh loop per test while the engine is still bound to the first one.",{"q":2260,"a":2343},"It is excellent for fast tests of application logic, wiring and transaction boundaries, but it does not reproduce Postgres dialect features, concurrency or locking. Run the fast suite on SQLite and a smaller integration suite against the real engine.",null,{"slug":2346,"breadcrumb":2347},"testing-with-async-database-fixtures",[2348,2351,2354,2355],{"label":2349,"path":2350},"Home","\u002F",{"label":2352,"path":2353},"Async, Background Tasks & Observability","\u002Fasync-background-tasks-observability\u002F",{"label":639,"path":638},{"label":2356,"path":2357},"Testing with Async Database Fixtures","\u002Fasync-background-tasks-observability\u002Fasync-database-sessions\u002Ftesting-with-async-database-fixtures\u002F",{"title":243,"description":2329},"article","QCN5fzvXyxMAhCeiix64rCDI2Y_98is-_6m3ReU4ySg",[2344,2344],1784588203038]