[{"data":1,"prerenderedAt":3175},["ShallowReactive",2],{"nav":3,"page-\u002Fcore-architecture-routing-patterns\u002Frequest-response-lifecycle\u002Fstreaming-and-file-responses\u002F":580,"surround-\u002Fcore-architecture-routing-patterns\u002Frequest-response-lifecycle\u002Fstreaming-and-file-responses\u002F":3174},[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":575,"body":582,"dateModified":3139,"datePublished":3139,"description":3140,"extension":3141,"faq":3142,"howto":3157,"meta":3158,"navigation":892,"path":576,"seo":3171,"stem":577,"type":3172,"__hash__":3173},"content\u002Fcore-architecture-routing-patterns\u002Frequest-response-lifecycle\u002Fstreaming-and-file-responses\u002Findex.md",{"type":583,"value":584,"toc":3122},"minimark",[585,589,596,635,647,763,768,774,780,786,808,818,822,831,2310,2317,2324,2328,2354,2365,2369,2383,2396,2418,2424,2445,2453,2456,2601,2609,2613,2635,2638,2670,2676,2705,2714,2720,2724,2735,2905,2908,2912,2915,2973,2979,2983,3011,3023,3032,3045,3070,3074,3118],[586,587,575],"h1",{"id":588},"streaming-and-file-responses-in-fastapi",[590,591,592],"p",{},[593,594,595],"strong",{},"Key takeaways:",[597,598,599,612,615,622,629],"ul",{},[600,601,602,603,607,608,611],"li",{},"Returning any ",[604,605,606],"code",{},"Response"," subclass short-circuits FastAPI's serialization stage entirely — ",[604,609,610],{},"response_model"," is documentation only.",[600,613,614],{},"Status and headers leave before the first byte of the body, so a mid-stream failure cannot become a 500.",[600,616,617,618,621],{},"A ",[604,619,620],{},"BackgroundTask"," attached to a streaming response runs after the generator is exhausted, making it a safe place to clean up.",[600,623,624,625,628],{},"Resources a generator needs should be acquired inside the generator, not injected by a ",[604,626,627],{},"yield"," dependency.",[600,630,631,634],{},[604,632,633],{},"FileResponse"," streams from disk in chunks and handles conditional-request headers for you.",[590,636,637,638,642,643,646],{},"Most FastAPI endpoints build a Python object and let the framework turn it into bytes. Streaming inverts that: you produce the bytes yourself, incrementally, and the framework gets out of the way. Getting out of the way is more literal than people expect — the entire validation and encoding pipeline described in ",[639,640,569],"a",{"href":641},"\u002Fcore-architecture-routing-patterns\u002Frequest-response-lifecycle\u002Fresponse-model-and-serialization-order\u002F"," is skipped. This page covers what that means in practice, and it sits under ",[639,644,557],{"href":645},"\u002Fcore-architecture-routing-patterns\u002Frequest-response-lifecycle\u002F",".",[648,649,655,656,655,660,655,664,655,671,655,679,655,685,655,689,655,693,655,697,655,702,655,705,655,708,655,712,655,716,655,720,655,723,655,727,655,730,655,734,655,738,655,742,655,746,655,750,655,755,655,759],"svg",{"viewBox":650,"role":651,"ariaLabel":652,"xmlns":653,"style":654},"0 0 720 320","img","Timeline comparing a buffered JSON response with a streaming response, showing when headers, chunks, teardown and background tasks occur","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;height:auto;max-width:720px;margin:2rem 0","\n  ",[657,658,659],"title",{},"Buffered response versus streaming response on the wire",[661,662,663],"desc",{},"Two horizontal timelines. The upper one shows a buffered JSON response where validation and encoding complete before a single response start and body message. The lower one shows a streaming response where response start is sent first, followed by several chunks produced by the generator, then the generator finally block and the background task.",[665,666,670],"text",{"x":667,"y":668,"style":669},"16","30","text-anchor:start;fill:currentColor;font:700 13px sans-serif","Buffered JSON response",[672,673],"rect",{"x":667,"y":674,"width":675,"height":676,"rx":677,"style":678},"42","164","40","7","fill:none;stroke:currentColor;stroke-width:1.4",[665,680,684],{"x":681,"y":682,"style":683},"98","67","text-anchor:middle;fill:currentColor;font:400 12px sans-serif","validate + encode",[672,686],{"x":687,"y":674,"width":688,"height":676,"rx":677,"style":678},"190","140",[665,690,692],{"x":691,"y":682,"style":683},"260","response.start",[672,694],{"x":695,"y":674,"width":688,"height":676,"rx":677,"style":696},"340","fill:#00796B;stroke:#00796B;stroke-width:1.4",[665,698,701],{"x":699,"y":682,"style":700},"410","text-anchor:middle;fill:#ffffff;font:600 12px sans-serif","one body msg",[665,703,704],{"x":667,"y":688,"style":669},"Streaming response",[672,706],{"x":667,"y":707,"width":688,"height":676,"rx":677,"style":678},"152",[665,709,692],{"x":710,"y":711,"style":683},"86","177",[672,713],{"x":714,"y":707,"width":715,"height":676,"rx":677,"style":696},"166","88",[665,717,719],{"x":718,"y":711,"style":700},"210","chunk 1",[672,721],{"x":722,"y":707,"width":715,"height":676,"rx":677,"style":696},"264",[665,724,726],{"x":725,"y":711,"style":700},"308","chunk 2",[672,728],{"x":729,"y":707,"width":715,"height":676,"rx":677,"style":696},"362",[665,731,733],{"x":732,"y":711,"style":700},"406","chunk 3",[672,735],{"x":736,"y":707,"width":737,"height":676,"rx":677,"style":678},"460","112",[665,739,741],{"x":740,"y":711,"style":683},"516","finally block",[672,743],{"x":744,"y":707,"width":745,"height":676,"rx":677,"style":678},"582","124",[665,747,749],{"x":748,"y":711,"style":683},"644","background task",[665,751,754],{"x":752,"y":753,"style":683},"360","232","Status and headers are committed at response.start. After that point",[665,756,758],{"x":752,"y":757,"style":683},"254","no failure in the generator can change the status code.",[665,760,762],{"x":752,"y":761,"style":683},"288","No validation stage exists on the streaming path at all.",[764,765,767],"h2",{"id":766},"the-scenario","The scenario",[590,769,770,771,773],{},"You have an export endpoint that assembles a 400 MB CSV. It works, until traffic doubles and the pods start getting OOM-killed, because the whole payload lives in memory twice — once as a Python object, once as encoded bytes. Streaming is the fix, but the moment you switch, three things change that nobody warns you about: your ",[604,772,610],{}," stops doing anything, your error handling stops working, and your database session may or may not still be open when the generator runs.",[764,775,777,778],{"id":776},"why-streaming-bypasses-response_model","Why streaming bypasses ",[604,779,610],{},[590,781,782,783,785],{},"FastAPI's route handler wraps your function and inspects what came back. The check is essentially \"is this already a ",[604,784,606],{},"?\" If yes, it is used as-is. If no, it goes through validation, filtering and encoding.",[590,787,788,791,792,791,794,791,797,800,801,804,805,807],{},[604,789,790],{},"StreamingResponse",", ",[604,793,633],{},[604,795,796],{},"JSONResponse",[604,798,799],{},"PlainTextResponse"," and ",[604,802,803],{},"RedirectResponse"," are all ",[604,806,606],{}," subclasses, so all of them take the bypass. This is not a special case for streaming — it is the general rule, and streaming is simply where it matters most, because a stream has no single object to validate.",[590,809,810,811,813,814,817],{},"The consequence people miss is that ",[604,812,610],{}," does not vanish from your API when it stops being enforced. It still generates the OpenAPI schema. Your documentation, your generated clients and your contract tests all continue to believe the endpoint returns ",[604,815,816],{},"list[Row]",", and nothing checks that it does. The schema becomes a promise you keep by hand.",[764,819,821],{"id":820},"the-demonstration","The demonstration",[590,823,824,825,827,828,646],{},"The app below covers streaming, files, background cleanup, mid-stream failure and the ",[604,826,627],{},"-dependency question in one place. Because the interesting events happen after the response body has been sent, it records them to a module-level list that is read back at ",[604,829,830],{},"\u002Fevents",[832,833,838],"pre",{"className":834,"code":835,"language":836,"meta":837,"style":837},"language-python shiki shiki-themes github-light-high-contrast","\"\"\"StreamingResponse and FileResponse: chunk ordering, response_model bypass, background cleanup.\"\"\"\nimport tempfile\nfrom collections.abc import AsyncIterator\nfrom pathlib import Path\n\nfrom fastapi import Depends, FastAPI\nfrom fastapi.responses import FileResponse, StreamingResponse\nfrom pydantic import BaseModel\nfrom starlette.background import BackgroundTask\n\nEVENTS: list[str] = []\n\nTMP = Path(tempfile.mkdtemp(prefix=\"arch-streaming-\"))\nREPORT = TMP \u002F \"report.csv\"\nREPORT.write_text(\"id,name,tier\\n1,Ada,pro\\n2,Grace,free\\n\")\n\napp = FastAPI()\n\n\nclass Row(BaseModel):\n    id: int\n    name: str\n\n\nasync def rows() -> AsyncIterator[bytes]:\n    EVENTS.append(\"generator: started\")\n    try:\n        for i in range(1, 4):\n            EVENTS.append(f\"generator: yielding chunk {i}\")\n            yield f\"chunk-{i}\\n\".encode()\n    finally:\n        EVENTS.append(\"generator: finally block ran\")\n\n\ndef cleanup() -> None:\n    EVENTS.append(\"background task: ran\")\n\n\n@app.get(\"\u002Fstream\")\nasync def stream() -> StreamingResponse:\n    EVENTS.clear()\n    EVENTS.append(\"path operation: returning StreamingResponse\")\n    return StreamingResponse(rows(), media_type=\"text\u002Fplain\", background=BackgroundTask(cleanup))\n\n\n@app.get(\"\u002Fstream-typed\", response_model=list[Row])\nasync def stream_typed():\n    \"\"\"response_model is declared, but a Response instance is returned as-is: no validation.\"\"\"\n    async def gen() -> AsyncIterator[bytes]:\n        yield b'{\"totally\": \"not a list of Row\"}'\n\n    return StreamingResponse(gen(), media_type=\"application\u002Fjson\")\n\n\n@app.get(\"\u002Fstream-error\")\nasync def stream_error() -> StreamingResponse:\n    async def gen() -> AsyncIterator[bytes]:\n        yield b\"partial-data\\n\"\n        try:\n            raise RuntimeError(\"upstream died mid-stream\")\n        except RuntimeError as exc:\n            # Headers are already on the wire; the only honest option is an in-band marker.\n            yield f\"ERROR: {exc}\\n\".encode()\n\n    return StreamingResponse(gen(), media_type=\"text\u002Fplain\")\n\n\n@app.get(\"\u002Fdownload\")\nasync def download() -> FileResponse:\n    return FileResponse(REPORT, media_type=\"text\u002Fcsv\", filename=\"report.csv\")\n\n\n@app.get(\"\u002Fdownload-temp\")\nasync def download_temp() -> FileResponse:\n    copy = TMP \u002F \"scratch.csv\"\n    copy.write_text(REPORT.read_text())\n\n    def remove() -> None:\n        copy.unlink(missing_ok=True)\n        EVENTS.append(f\"background task: deleted {copy.name}, exists={copy.exists()}\")\n\n    return FileResponse(copy, media_type=\"text\u002Fcsv\", background=BackgroundTask(remove))\n\n\nclass FakeSession:\n    def __init__(self) -> None:\n        self.closed = False\n\n    async def fetch(self, n: int) -> str:\n        return f\"row-{n} (session closed={self.closed})\"\n\n\nasync def get_session() -> AsyncIterator[FakeSession]:\n    EVENTS.clear()\n    session = FakeSession()\n    EVENTS.append(\"dependency: session opened\")\n    try:\n        yield session\n    finally:\n        session.closed = True\n        EVENTS.append(\"dependency: session closed\")\n\n\n@app.get(\"\u002Fstream-with-dep\")\nasync def stream_with_dep(session: FakeSession = Depends(get_session)) -> StreamingResponse:\n    EVENTS.append(\"path operation: returning StreamingResponse that uses the session\")\n\n    async def gen() -> AsyncIterator[bytes]:\n        for n in range(1, 3):\n            row = await session.fetch(n)\n            EVENTS.append(f\"generator: {row}\")\n            yield f\"{row}\\n\".encode()\n\n    return StreamingResponse(gen(), media_type=\"text\u002Fplain\")\n\n\n@app.get(\"\u002Fevents\")\nasync def events() -> dict[str, list[str]]:\n    return {\"events\": list(EVENTS)}\n","python","",[604,839,840,849,860,874,887,894,907,920,933,946,951,973,978,1002,1019,1049,1054,1065,1070,1075,1093,1105,1114,1119,1124,1146,1160,1169,1196,1223,1247,1255,1268,1273,1278,1295,1307,1312,1317,1330,1343,1351,1363,1390,1395,1400,1419,1432,1438,1455,1467,1472,1489,1494,1499,1511,1523,1538,1553,1561,1577,1591,1598,1619,1624,1639,1644,1649,1661,1674,1705,1710,1715,1727,1739,1754,1765,1770,1785,1801,1834,1839,1862,1867,1872,1882,1897,1911,1916,1939,1973,1978,1983,1996,2003,2014,2026,2033,2041,2048,2059,2071,2076,2081,2093,2111,2123,2128,2143,2166,2180,2203,2222,2227,2242,2247,2252,2264,2287],{"__ignoreMap":837},[841,842,845],"span",{"class":843,"line":844},"line",1,[841,846,848],{"class":847},"sYEJz","\"\"\"StreamingResponse and FileResponse: chunk ordering, response_model bypass, background cleanup.\"\"\"\n",[841,850,852,856],{"class":843,"line":851},2,[841,853,855],{"class":854},"sTJeM","import",[841,857,859],{"class":858},"sigWx"," tempfile\n",[841,861,863,866,869,871],{"class":843,"line":862},3,[841,864,865],{"class":854},"from",[841,867,868],{"class":858}," collections.abc ",[841,870,855],{"class":854},[841,872,873],{"class":858}," AsyncIterator\n",[841,875,877,879,882,884],{"class":843,"line":876},4,[841,878,865],{"class":854},[841,880,881],{"class":858}," pathlib ",[841,883,855],{"class":854},[841,885,886],{"class":858}," Path\n",[841,888,890],{"class":843,"line":889},5,[841,891,893],{"emptyLinePlaceholder":892},true,"\n",[841,895,897,899,902,904],{"class":843,"line":896},6,[841,898,865],{"class":854},[841,900,901],{"class":858}," fastapi ",[841,903,855],{"class":854},[841,905,906],{"class":858}," Depends, FastAPI\n",[841,908,910,912,915,917],{"class":843,"line":909},7,[841,911,865],{"class":854},[841,913,914],{"class":858}," fastapi.responses ",[841,916,855],{"class":854},[841,918,919],{"class":858}," FileResponse, StreamingResponse\n",[841,921,923,925,928,930],{"class":843,"line":922},8,[841,924,865],{"class":854},[841,926,927],{"class":858}," pydantic ",[841,929,855],{"class":854},[841,931,932],{"class":858}," BaseModel\n",[841,934,936,938,941,943],{"class":843,"line":935},9,[841,937,865],{"class":854},[841,939,940],{"class":858}," starlette.background ",[841,942,855],{"class":854},[841,944,945],{"class":858}," BackgroundTask\n",[841,947,949],{"class":843,"line":948},10,[841,950,893],{"emptyLinePlaceholder":892},[841,952,954,958,961,964,967,970],{"class":843,"line":953},11,[841,955,957],{"class":956},"sacAq","EVENTS",[841,959,960],{"class":858},": list[",[841,962,963],{"class":956},"str",[841,965,966],{"class":858},"] ",[841,968,969],{"class":854},"=",[841,971,972],{"class":858}," []\n",[841,974,976],{"class":843,"line":975},12,[841,977,893],{"emptyLinePlaceholder":892},[841,979,981,984,987,990,994,996,999],{"class":843,"line":980},13,[841,982,983],{"class":956},"TMP",[841,985,986],{"class":854}," =",[841,988,989],{"class":858}," Path(tempfile.mkdtemp(",[841,991,993],{"class":992},"sV4o_","prefix",[841,995,969],{"class":854},[841,997,998],{"class":847},"\"arch-streaming-\"",[841,1000,1001],{"class":858},"))\n",[841,1003,1005,1008,1010,1013,1016],{"class":843,"line":1004},14,[841,1006,1007],{"class":956},"REPORT",[841,1009,986],{"class":854},[841,1011,1012],{"class":956}," TMP",[841,1014,1015],{"class":854}," \u002F",[841,1017,1018],{"class":847}," \"report.csv\"\n",[841,1020,1022,1024,1027,1030,1033,1036,1038,1041,1043,1046],{"class":843,"line":1021},15,[841,1023,1007],{"class":956},[841,1025,1026],{"class":858},".write_text(",[841,1028,1029],{"class":847},"\"id,name,tier",[841,1031,1032],{"class":854},"\\n",[841,1034,1035],{"class":847},"1,Ada,pro",[841,1037,1032],{"class":854},[841,1039,1040],{"class":847},"2,Grace,free",[841,1042,1032],{"class":854},[841,1044,1045],{"class":847},"\"",[841,1047,1048],{"class":858},")\n",[841,1050,1052],{"class":843,"line":1051},16,[841,1053,893],{"emptyLinePlaceholder":892},[841,1055,1057,1060,1062],{"class":843,"line":1056},17,[841,1058,1059],{"class":858},"app ",[841,1061,969],{"class":854},[841,1063,1064],{"class":858}," FastAPI()\n",[841,1066,1068],{"class":843,"line":1067},18,[841,1069,893],{"emptyLinePlaceholder":892},[841,1071,1073],{"class":843,"line":1072},19,[841,1074,893],{"emptyLinePlaceholder":892},[841,1076,1078,1081,1084,1087,1090],{"class":843,"line":1077},20,[841,1079,1080],{"class":854},"class",[841,1082,1083],{"class":992}," Row",[841,1085,1086],{"class":858},"(",[841,1088,1089],{"class":956},"BaseModel",[841,1091,1092],{"class":858},"):\n",[841,1094,1096,1099,1102],{"class":843,"line":1095},21,[841,1097,1098],{"class":956},"    id",[841,1100,1101],{"class":858},": ",[841,1103,1104],{"class":956},"int\n",[841,1106,1108,1111],{"class":843,"line":1107},22,[841,1109,1110],{"class":858},"    name: ",[841,1112,1113],{"class":956},"str\n",[841,1115,1117],{"class":843,"line":1116},23,[841,1118,893],{"emptyLinePlaceholder":892},[841,1120,1122],{"class":843,"line":1121},24,[841,1123,893],{"emptyLinePlaceholder":892},[841,1125,1127,1130,1133,1137,1140,1143],{"class":843,"line":1126},25,[841,1128,1129],{"class":854},"async",[841,1131,1132],{"class":854}," def",[841,1134,1136],{"class":1135},"s3dhs"," rows",[841,1138,1139],{"class":858},"() -> AsyncIterator[",[841,1141,1142],{"class":956},"bytes",[841,1144,1145],{"class":858},"]:\n",[841,1147,1149,1152,1155,1158],{"class":843,"line":1148},26,[841,1150,1151],{"class":956},"    EVENTS",[841,1153,1154],{"class":858},".append(",[841,1156,1157],{"class":847},"\"generator: started\"",[841,1159,1048],{"class":858},[841,1161,1163,1166],{"class":843,"line":1162},27,[841,1164,1165],{"class":854},"    try",[841,1167,1168],{"class":858},":\n",[841,1170,1172,1175,1178,1181,1184,1186,1189,1191,1194],{"class":843,"line":1171},28,[841,1173,1174],{"class":854},"        for",[841,1176,1177],{"class":858}," i ",[841,1179,1180],{"class":854},"in",[841,1182,1183],{"class":956}," range",[841,1185,1086],{"class":858},[841,1187,1188],{"class":956},"1",[841,1190,791],{"class":858},[841,1192,1193],{"class":956},"4",[841,1195,1092],{"class":858},[841,1197,1199,1202,1204,1207,1210,1213,1216,1219,1221],{"class":843,"line":1198},29,[841,1200,1201],{"class":956},"            EVENTS",[841,1203,1154],{"class":858},[841,1205,1206],{"class":854},"f",[841,1208,1209],{"class":847},"\"generator: yielding chunk ",[841,1211,1212],{"class":854},"{",[841,1214,1215],{"class":858},"i",[841,1217,1218],{"class":854},"}",[841,1220,1045],{"class":847},[841,1222,1048],{"class":858},[841,1224,1226,1229,1232,1235,1237,1239,1242,1244],{"class":843,"line":1225},30,[841,1227,1228],{"class":854},"            yield",[841,1230,1231],{"class":854}," f",[841,1233,1234],{"class":847},"\"chunk-",[841,1236,1212],{"class":854},[841,1238,1215],{"class":858},[841,1240,1241],{"class":854},"}\\n",[841,1243,1045],{"class":847},[841,1245,1246],{"class":858},".encode()\n",[841,1248,1250,1253],{"class":843,"line":1249},31,[841,1251,1252],{"class":854},"    finally",[841,1254,1168],{"class":858},[841,1256,1258,1261,1263,1266],{"class":843,"line":1257},32,[841,1259,1260],{"class":956},"        EVENTS",[841,1262,1154],{"class":858},[841,1264,1265],{"class":847},"\"generator: finally block ran\"",[841,1267,1048],{"class":858},[841,1269,1271],{"class":843,"line":1270},33,[841,1272,893],{"emptyLinePlaceholder":892},[841,1274,1276],{"class":843,"line":1275},34,[841,1277,893],{"emptyLinePlaceholder":892},[841,1279,1281,1284,1287,1290,1293],{"class":843,"line":1280},35,[841,1282,1283],{"class":854},"def",[841,1285,1286],{"class":1135}," cleanup",[841,1288,1289],{"class":858},"() -> ",[841,1291,1292],{"class":956},"None",[841,1294,1168],{"class":858},[841,1296,1298,1300,1302,1305],{"class":843,"line":1297},36,[841,1299,1151],{"class":956},[841,1301,1154],{"class":858},[841,1303,1304],{"class":847},"\"background task: ran\"",[841,1306,1048],{"class":858},[841,1308,1310],{"class":843,"line":1309},37,[841,1311,893],{"emptyLinePlaceholder":892},[841,1313,1315],{"class":843,"line":1314},38,[841,1316,893],{"emptyLinePlaceholder":892},[841,1318,1320,1323,1325,1328],{"class":843,"line":1319},39,[841,1321,1322],{"class":1135},"@app.get",[841,1324,1086],{"class":858},[841,1326,1327],{"class":847},"\"\u002Fstream\"",[841,1329,1048],{"class":858},[841,1331,1333,1335,1337,1340],{"class":843,"line":1332},40,[841,1334,1129],{"class":854},[841,1336,1132],{"class":854},[841,1338,1339],{"class":1135}," stream",[841,1341,1342],{"class":858},"() -> StreamingResponse:\n",[841,1344,1346,1348],{"class":843,"line":1345},41,[841,1347,1151],{"class":956},[841,1349,1350],{"class":858},".clear()\n",[841,1352,1354,1356,1358,1361],{"class":843,"line":1353},42,[841,1355,1151],{"class":956},[841,1357,1154],{"class":858},[841,1359,1360],{"class":847},"\"path operation: returning StreamingResponse\"",[841,1362,1048],{"class":858},[841,1364,1366,1369,1372,1375,1377,1380,1382,1385,1387],{"class":843,"line":1365},43,[841,1367,1368],{"class":854},"    return",[841,1370,1371],{"class":858}," StreamingResponse(rows(), ",[841,1373,1374],{"class":992},"media_type",[841,1376,969],{"class":854},[841,1378,1379],{"class":847},"\"text\u002Fplain\"",[841,1381,791],{"class":858},[841,1383,1384],{"class":992},"background",[841,1386,969],{"class":854},[841,1388,1389],{"class":858},"BackgroundTask(cleanup))\n",[841,1391,1393],{"class":843,"line":1392},44,[841,1394,893],{"emptyLinePlaceholder":892},[841,1396,1398],{"class":843,"line":1397},45,[841,1399,893],{"emptyLinePlaceholder":892},[841,1401,1403,1405,1407,1410,1412,1414,1416],{"class":843,"line":1402},46,[841,1404,1322],{"class":1135},[841,1406,1086],{"class":858},[841,1408,1409],{"class":847},"\"\u002Fstream-typed\"",[841,1411,791],{"class":858},[841,1413,610],{"class":992},[841,1415,969],{"class":854},[841,1417,1418],{"class":858},"list[Row])\n",[841,1420,1422,1424,1426,1429],{"class":843,"line":1421},47,[841,1423,1129],{"class":854},[841,1425,1132],{"class":854},[841,1427,1428],{"class":1135}," stream_typed",[841,1430,1431],{"class":858},"():\n",[841,1433,1435],{"class":843,"line":1434},48,[841,1436,1437],{"class":847},"    \"\"\"response_model is declared, but a Response instance is returned as-is: no validation.\"\"\"\n",[841,1439,1441,1444,1446,1449,1451,1453],{"class":843,"line":1440},49,[841,1442,1443],{"class":854},"    async",[841,1445,1132],{"class":854},[841,1447,1448],{"class":1135}," gen",[841,1450,1139],{"class":858},[841,1452,1142],{"class":956},[841,1454,1145],{"class":858},[841,1456,1458,1461,1464],{"class":843,"line":1457},50,[841,1459,1460],{"class":854},"        yield",[841,1462,1463],{"class":854}," b",[841,1465,1466],{"class":847},"'{\"totally\": \"not a list of Row\"}'\n",[841,1468,1470],{"class":843,"line":1469},51,[841,1471,893],{"emptyLinePlaceholder":892},[841,1473,1475,1477,1480,1482,1484,1487],{"class":843,"line":1474},52,[841,1476,1368],{"class":854},[841,1478,1479],{"class":858}," StreamingResponse(gen(), ",[841,1481,1374],{"class":992},[841,1483,969],{"class":854},[841,1485,1486],{"class":847},"\"application\u002Fjson\"",[841,1488,1048],{"class":858},[841,1490,1492],{"class":843,"line":1491},53,[841,1493,893],{"emptyLinePlaceholder":892},[841,1495,1497],{"class":843,"line":1496},54,[841,1498,893],{"emptyLinePlaceholder":892},[841,1500,1502,1504,1506,1509],{"class":843,"line":1501},55,[841,1503,1322],{"class":1135},[841,1505,1086],{"class":858},[841,1507,1508],{"class":847},"\"\u002Fstream-error\"",[841,1510,1048],{"class":858},[841,1512,1514,1516,1518,1521],{"class":843,"line":1513},56,[841,1515,1129],{"class":854},[841,1517,1132],{"class":854},[841,1519,1520],{"class":1135}," stream_error",[841,1522,1342],{"class":858},[841,1524,1526,1528,1530,1532,1534,1536],{"class":843,"line":1525},57,[841,1527,1443],{"class":854},[841,1529,1132],{"class":854},[841,1531,1448],{"class":1135},[841,1533,1139],{"class":858},[841,1535,1142],{"class":956},[841,1537,1145],{"class":858},[841,1539,1541,1543,1545,1548,1550],{"class":843,"line":1540},58,[841,1542,1460],{"class":854},[841,1544,1463],{"class":854},[841,1546,1547],{"class":847},"\"partial-data",[841,1549,1032],{"class":854},[841,1551,1552],{"class":847},"\"\n",[841,1554,1556,1559],{"class":843,"line":1555},59,[841,1557,1558],{"class":854},"        try",[841,1560,1168],{"class":858},[841,1562,1564,1567,1570,1572,1575],{"class":843,"line":1563},60,[841,1565,1566],{"class":854},"            raise",[841,1568,1569],{"class":956}," RuntimeError",[841,1571,1086],{"class":858},[841,1573,1574],{"class":847},"\"upstream died mid-stream\"",[841,1576,1048],{"class":858},[841,1578,1580,1583,1585,1588],{"class":843,"line":1579},61,[841,1581,1582],{"class":854},"        except",[841,1584,1569],{"class":956},[841,1586,1587],{"class":854}," as",[841,1589,1590],{"class":858}," exc:\n",[841,1592,1594],{"class":843,"line":1593},62,[841,1595,1597],{"class":1596},"sFeEa","            # Headers are already on the wire; the only honest option is an in-band marker.\n",[841,1599,1601,1603,1605,1608,1610,1613,1615,1617],{"class":843,"line":1600},63,[841,1602,1228],{"class":854},[841,1604,1231],{"class":854},[841,1606,1607],{"class":847},"\"ERROR: ",[841,1609,1212],{"class":854},[841,1611,1612],{"class":858},"exc",[841,1614,1241],{"class":854},[841,1616,1045],{"class":847},[841,1618,1246],{"class":858},[841,1620,1622],{"class":843,"line":1621},64,[841,1623,893],{"emptyLinePlaceholder":892},[841,1625,1627,1629,1631,1633,1635,1637],{"class":843,"line":1626},65,[841,1628,1368],{"class":854},[841,1630,1479],{"class":858},[841,1632,1374],{"class":992},[841,1634,969],{"class":854},[841,1636,1379],{"class":847},[841,1638,1048],{"class":858},[841,1640,1642],{"class":843,"line":1641},66,[841,1643,893],{"emptyLinePlaceholder":892},[841,1645,1647],{"class":843,"line":1646},67,[841,1648,893],{"emptyLinePlaceholder":892},[841,1650,1652,1654,1656,1659],{"class":843,"line":1651},68,[841,1653,1322],{"class":1135},[841,1655,1086],{"class":858},[841,1657,1658],{"class":847},"\"\u002Fdownload\"",[841,1660,1048],{"class":858},[841,1662,1664,1666,1668,1671],{"class":843,"line":1663},69,[841,1665,1129],{"class":854},[841,1667,1132],{"class":854},[841,1669,1670],{"class":1135}," download",[841,1672,1673],{"class":858},"() -> FileResponse:\n",[841,1675,1677,1679,1682,1684,1686,1688,1690,1693,1695,1698,1700,1703],{"class":843,"line":1676},70,[841,1678,1368],{"class":854},[841,1680,1681],{"class":858}," FileResponse(",[841,1683,1007],{"class":956},[841,1685,791],{"class":858},[841,1687,1374],{"class":992},[841,1689,969],{"class":854},[841,1691,1692],{"class":847},"\"text\u002Fcsv\"",[841,1694,791],{"class":858},[841,1696,1697],{"class":992},"filename",[841,1699,969],{"class":854},[841,1701,1702],{"class":847},"\"report.csv\"",[841,1704,1048],{"class":858},[841,1706,1708],{"class":843,"line":1707},71,[841,1709,893],{"emptyLinePlaceholder":892},[841,1711,1713],{"class":843,"line":1712},72,[841,1714,893],{"emptyLinePlaceholder":892},[841,1716,1718,1720,1722,1725],{"class":843,"line":1717},73,[841,1719,1322],{"class":1135},[841,1721,1086],{"class":858},[841,1723,1724],{"class":847},"\"\u002Fdownload-temp\"",[841,1726,1048],{"class":858},[841,1728,1730,1732,1734,1737],{"class":843,"line":1729},74,[841,1731,1129],{"class":854},[841,1733,1132],{"class":854},[841,1735,1736],{"class":1135}," download_temp",[841,1738,1673],{"class":858},[841,1740,1742,1745,1747,1749,1751],{"class":843,"line":1741},75,[841,1743,1744],{"class":858},"    copy ",[841,1746,969],{"class":854},[841,1748,1012],{"class":956},[841,1750,1015],{"class":854},[841,1752,1753],{"class":847}," \"scratch.csv\"\n",[841,1755,1757,1760,1762],{"class":843,"line":1756},76,[841,1758,1759],{"class":858},"    copy.write_text(",[841,1761,1007],{"class":956},[841,1763,1764],{"class":858},".read_text())\n",[841,1766,1768],{"class":843,"line":1767},77,[841,1769,893],{"emptyLinePlaceholder":892},[841,1771,1773,1776,1779,1781,1783],{"class":843,"line":1772},78,[841,1774,1775],{"class":854},"    def",[841,1777,1778],{"class":1135}," remove",[841,1780,1289],{"class":858},[841,1782,1292],{"class":956},[841,1784,1168],{"class":858},[841,1786,1788,1791,1794,1796,1799],{"class":843,"line":1787},79,[841,1789,1790],{"class":858},"        copy.unlink(",[841,1792,1793],{"class":992},"missing_ok",[841,1795,969],{"class":854},[841,1797,1798],{"class":956},"True",[841,1800,1048],{"class":858},[841,1802,1804,1806,1808,1810,1813,1815,1818,1820,1823,1825,1828,1830,1832],{"class":843,"line":1803},80,[841,1805,1260],{"class":956},[841,1807,1154],{"class":858},[841,1809,1206],{"class":854},[841,1811,1812],{"class":847},"\"background task: deleted ",[841,1814,1212],{"class":854},[841,1816,1817],{"class":858},"copy.name",[841,1819,1218],{"class":854},[841,1821,1822],{"class":847},", exists=",[841,1824,1212],{"class":854},[841,1826,1827],{"class":858},"copy.exists()",[841,1829,1218],{"class":854},[841,1831,1045],{"class":847},[841,1833,1048],{"class":858},[841,1835,1837],{"class":843,"line":1836},81,[841,1838,893],{"emptyLinePlaceholder":892},[841,1840,1842,1844,1847,1849,1851,1853,1855,1857,1859],{"class":843,"line":1841},82,[841,1843,1368],{"class":854},[841,1845,1846],{"class":858}," FileResponse(copy, ",[841,1848,1374],{"class":992},[841,1850,969],{"class":854},[841,1852,1692],{"class":847},[841,1854,791],{"class":858},[841,1856,1384],{"class":992},[841,1858,969],{"class":854},[841,1860,1861],{"class":858},"BackgroundTask(remove))\n",[841,1863,1865],{"class":843,"line":1864},83,[841,1866,893],{"emptyLinePlaceholder":892},[841,1868,1870],{"class":843,"line":1869},84,[841,1871,893],{"emptyLinePlaceholder":892},[841,1873,1875,1877,1880],{"class":843,"line":1874},85,[841,1876,1080],{"class":854},[841,1878,1879],{"class":992}," FakeSession",[841,1881,1168],{"class":858},[841,1883,1885,1887,1890,1893,1895],{"class":843,"line":1884},86,[841,1886,1775],{"class":854},[841,1888,1889],{"class":956}," __init__",[841,1891,1892],{"class":858},"(self) -> ",[841,1894,1292],{"class":956},[841,1896,1168],{"class":858},[841,1898,1900,1903,1906,1908],{"class":843,"line":1899},87,[841,1901,1902],{"class":956},"        self",[841,1904,1905],{"class":858},".closed ",[841,1907,969],{"class":854},[841,1909,1910],{"class":956}," False\n",[841,1912,1914],{"class":843,"line":1913},88,[841,1915,893],{"emptyLinePlaceholder":892},[841,1917,1919,1921,1923,1926,1929,1932,1935,1937],{"class":843,"line":1918},89,[841,1920,1443],{"class":854},[841,1922,1132],{"class":854},[841,1924,1925],{"class":1135}," fetch",[841,1927,1928],{"class":858},"(self, n: ",[841,1930,1931],{"class":956},"int",[841,1933,1934],{"class":858},") -> ",[841,1936,963],{"class":956},[841,1938,1168],{"class":858},[841,1940,1942,1945,1947,1950,1952,1955,1957,1960,1962,1965,1968,1970],{"class":843,"line":1941},90,[841,1943,1944],{"class":854},"        return",[841,1946,1231],{"class":854},[841,1948,1949],{"class":847},"\"row-",[841,1951,1212],{"class":854},[841,1953,1954],{"class":858},"n",[841,1956,1218],{"class":854},[841,1958,1959],{"class":847}," (session closed=",[841,1961,1212],{"class":854},[841,1963,1964],{"class":956},"self",[841,1966,1967],{"class":858},".closed",[841,1969,1218],{"class":854},[841,1971,1972],{"class":847},")\"\n",[841,1974,1976],{"class":843,"line":1975},91,[841,1977,893],{"emptyLinePlaceholder":892},[841,1979,1981],{"class":843,"line":1980},92,[841,1982,893],{"emptyLinePlaceholder":892},[841,1984,1986,1988,1990,1993],{"class":843,"line":1985},93,[841,1987,1129],{"class":854},[841,1989,1132],{"class":854},[841,1991,1992],{"class":1135}," get_session",[841,1994,1995],{"class":858},"() -> AsyncIterator[FakeSession]:\n",[841,1997,1999,2001],{"class":843,"line":1998},94,[841,2000,1151],{"class":956},[841,2002,1350],{"class":858},[841,2004,2006,2009,2011],{"class":843,"line":2005},95,[841,2007,2008],{"class":858},"    session ",[841,2010,969],{"class":854},[841,2012,2013],{"class":858}," FakeSession()\n",[841,2015,2017,2019,2021,2024],{"class":843,"line":2016},96,[841,2018,1151],{"class":956},[841,2020,1154],{"class":858},[841,2022,2023],{"class":847},"\"dependency: session opened\"",[841,2025,1048],{"class":858},[841,2027,2029,2031],{"class":843,"line":2028},97,[841,2030,1165],{"class":854},[841,2032,1168],{"class":858},[841,2034,2036,2038],{"class":843,"line":2035},98,[841,2037,1460],{"class":854},[841,2039,2040],{"class":858}," session\n",[841,2042,2044,2046],{"class":843,"line":2043},99,[841,2045,1252],{"class":854},[841,2047,1168],{"class":858},[841,2049,2051,2054,2056],{"class":843,"line":2050},100,[841,2052,2053],{"class":858},"        session.closed ",[841,2055,969],{"class":854},[841,2057,2058],{"class":956}," True\n",[841,2060,2062,2064,2066,2069],{"class":843,"line":2061},101,[841,2063,1260],{"class":956},[841,2065,1154],{"class":858},[841,2067,2068],{"class":847},"\"dependency: session closed\"",[841,2070,1048],{"class":858},[841,2072,2074],{"class":843,"line":2073},102,[841,2075,893],{"emptyLinePlaceholder":892},[841,2077,2079],{"class":843,"line":2078},103,[841,2080,893],{"emptyLinePlaceholder":892},[841,2082,2084,2086,2088,2091],{"class":843,"line":2083},104,[841,2085,1322],{"class":1135},[841,2087,1086],{"class":858},[841,2089,2090],{"class":847},"\"\u002Fstream-with-dep\"",[841,2092,1048],{"class":858},[841,2094,2096,2098,2100,2103,2106,2108],{"class":843,"line":2095},105,[841,2097,1129],{"class":854},[841,2099,1132],{"class":854},[841,2101,2102],{"class":1135}," stream_with_dep",[841,2104,2105],{"class":858},"(session: FakeSession ",[841,2107,969],{"class":854},[841,2109,2110],{"class":858}," Depends(get_session)) -> StreamingResponse:\n",[841,2112,2114,2116,2118,2121],{"class":843,"line":2113},106,[841,2115,1151],{"class":956},[841,2117,1154],{"class":858},[841,2119,2120],{"class":847},"\"path operation: returning StreamingResponse that uses the session\"",[841,2122,1048],{"class":858},[841,2124,2126],{"class":843,"line":2125},107,[841,2127,893],{"emptyLinePlaceholder":892},[841,2129,2131,2133,2135,2137,2139,2141],{"class":843,"line":2130},108,[841,2132,1443],{"class":854},[841,2134,1132],{"class":854},[841,2136,1448],{"class":1135},[841,2138,1139],{"class":858},[841,2140,1142],{"class":956},[841,2142,1145],{"class":858},[841,2144,2146,2148,2151,2153,2155,2157,2159,2161,2164],{"class":843,"line":2145},109,[841,2147,1174],{"class":854},[841,2149,2150],{"class":858}," n ",[841,2152,1180],{"class":854},[841,2154,1183],{"class":956},[841,2156,1086],{"class":858},[841,2158,1188],{"class":956},[841,2160,791],{"class":858},[841,2162,2163],{"class":956},"3",[841,2165,1092],{"class":858},[841,2167,2169,2172,2174,2177],{"class":843,"line":2168},110,[841,2170,2171],{"class":858},"            row ",[841,2173,969],{"class":854},[841,2175,2176],{"class":854}," await",[841,2178,2179],{"class":858}," session.fetch(n)\n",[841,2181,2183,2185,2187,2189,2192,2194,2197,2199,2201],{"class":843,"line":2182},111,[841,2184,1201],{"class":956},[841,2186,1154],{"class":858},[841,2188,1206],{"class":854},[841,2190,2191],{"class":847},"\"generator: ",[841,2193,1212],{"class":854},[841,2195,2196],{"class":858},"row",[841,2198,1218],{"class":854},[841,2200,1045],{"class":847},[841,2202,1048],{"class":858},[841,2204,2206,2208,2210,2212,2214,2216,2218,2220],{"class":843,"line":2205},112,[841,2207,1228],{"class":854},[841,2209,1231],{"class":854},[841,2211,1045],{"class":847},[841,2213,1212],{"class":854},[841,2215,2196],{"class":858},[841,2217,1241],{"class":854},[841,2219,1045],{"class":847},[841,2221,1246],{"class":858},[841,2223,2225],{"class":843,"line":2224},113,[841,2226,893],{"emptyLinePlaceholder":892},[841,2228,2230,2232,2234,2236,2238,2240],{"class":843,"line":2229},114,[841,2231,1368],{"class":854},[841,2233,1479],{"class":858},[841,2235,1374],{"class":992},[841,2237,969],{"class":854},[841,2239,1379],{"class":847},[841,2241,1048],{"class":858},[841,2243,2245],{"class":843,"line":2244},115,[841,2246,893],{"emptyLinePlaceholder":892},[841,2248,2250],{"class":843,"line":2249},116,[841,2251,893],{"emptyLinePlaceholder":892},[841,2253,2255,2257,2259,2262],{"class":843,"line":2254},117,[841,2256,1322],{"class":1135},[841,2258,1086],{"class":858},[841,2260,2261],{"class":847},"\"\u002Fevents\"",[841,2263,1048],{"class":858},[841,2265,2267,2269,2271,2274,2277,2279,2282,2284],{"class":843,"line":2266},118,[841,2268,1129],{"class":854},[841,2270,1132],{"class":854},[841,2272,2273],{"class":1135}," events",[841,2275,2276],{"class":858},"() -> dict[",[841,2278,963],{"class":956},[841,2280,2281],{"class":858},", list[",[841,2283,963],{"class":956},[841,2285,2286],{"class":858},"]]:\n",[841,2288,2290,2292,2295,2298,2300,2303,2305,2307],{"class":843,"line":2289},119,[841,2291,1368],{"class":854},[841,2293,2294],{"class":858}," {",[841,2296,2297],{"class":847},"\"events\"",[841,2299,1101],{"class":858},[841,2301,2302],{"class":956},"list",[841,2304,1086],{"class":858},[841,2306,957],{"class":956},[841,2308,2309],{"class":858},")}\n",[590,2311,2312,2313,2316],{},"Real output from ",[604,2314,2315],{},"_verify\u002Fexamples\u002Farch-streaming-responses.py",":",[832,2318,2322],{"className":2319,"code":2321,"language":665,"meta":837},[2320],"language-text","$ GET \u002Fstream\n200 OK\nchunk-1\nchunk-2\nchunk-3\n\n\n$ GET \u002Fevents\n200 OK\n{\n  \"events\": [\n    \"path operation: returning StreamingResponse\",\n    \"generator: started\",\n    \"generator: yielding chunk 1\",\n    \"generator: yielding chunk 2\",\n    \"generator: yielding chunk 3\",\n    \"generator: finally block ran\",\n    \"background task: ran\"\n  ]\n}\n\n$ GET \u002Fstream-with-dep\n200 OK\nrow-1 (session closed=False)\nrow-2 (session closed=False)\n\n\n$ GET \u002Fevents\n200 OK\n{\n  \"events\": [\n    \"dependency: session opened\",\n    \"path operation: returning StreamingResponse that uses the session\",\n    \"generator: row-1 (session closed=False)\",\n    \"generator: row-2 (session closed=False)\",\n    \"dependency: session closed\"\n  ]\n}\n\n$ GET \u002Fstream-typed\n200 OK\n{\n  \"totally\": \"not a list of Row\"\n}\n\n$ GET \u002Fstream-error\n200 OK\npartial-data\nERROR: upstream died mid-stream\n\n\n$ GET \u002Fdownload\n200 OK\nid,name,tier\n1,Ada,pro\n2,Grace,free\n\n\n$ GET \u002Fdownload-temp\n200 OK\nid,name,tier\n1,Ada,pro\n2,Grace,free\n\n\n$ GET \u002Fevents\n200 OK\n{\n  \"events\": [\n    \"dependency: session opened\",\n    \"path operation: returning StreamingResponse that uses the session\",\n    \"generator: row-1 (session closed=False)\",\n    \"generator: row-2 (session closed=False)\",\n    \"dependency: session closed\",\n    \"background task: deleted scratch.csv, exists=False\"\n  ]\n}\n",[604,2323,2321],{"__ignoreMap":837},[764,2325,2327],{"id":2326},"the-generator-does-not-run-inside-your-handler","The generator does not run inside your handler",[590,2329,2330,2331,2333,2334,2337,2338,2342,2343,2346,2347,2349,2350,2353],{},"Look at the first ",[604,2332,830],{}," list. ",[604,2335,2336],{},"path operation: returning StreamingResponse"," is logged ",[2339,2340,2341],"em",{},"before"," ",[604,2344,2345],{},"generator: started",". Constructing a ",[604,2348,790],{}," does not consume the iterator; it stores it. The generator body executes later, while Starlette is pumping the ASGI ",[604,2351,2352],{},"send"," channel.",[590,2355,2356,2357,2360,2361,2364],{},"This is the mechanical reason for most streaming bugs. Anything scoped to the handler's execution — an open transaction, a ",[604,2358,2359],{},"try\u002Fexcept"," around the ",[604,2362,2363],{},"return",", a context variable set locally — has a lifetime that does not obviously extend over the generator. Whatever the generator needs must either be captured in its closure and still be alive, or acquired by the generator itself.",[764,2366,2368],{"id":2367},"background-tasks-are-the-right-cleanup-hook","Background tasks are the right cleanup hook",[590,2370,2371,2372,2375,2376,2379,2380,2382],{},"The ",[604,2373,2374],{},"\u002Fstream"," trace shows the ordering clearly: the generator's ",[604,2377,2378],{},"finally"," block ran, and then the background task ran. Both happened after the third chunk. That makes ",[604,2381,620],{}," a genuinely safe place to clean up resources the stream was consuming — the stream is definitively finished by then.",[590,2384,2385,2388,2389,2392,2393,2395],{},[604,2386,2387],{},"\u002Fdownload-temp"," puts this to work on the pattern everyone eventually needs: serve a temporary file, then delete it. The background task's own log line records ",[604,2390,2391],{},"exists=False",", so the deletion really happened, and the client had already received the full body. Doing the deletion in the handler before returning would have removed the file before ",[604,2394,633],{}," ever opened it.",[590,2397,2398,2399,2402,2403,2406,2407,2410,2411,2413,2414,646],{},"Note the type: this is ",[604,2400,2401],{},"starlette.background.BackgroundTask",", constructed and passed to the response, not FastAPI's ",[604,2404,2405],{},"BackgroundTasks"," dependency. Both end up on the same ",[604,2408,2409],{},"response.background"," slot, but only the former can be attached to a ",[604,2412,606],{}," you construct yourself. The broader question of what belongs in a background task at all is covered in ",[639,2415,2417],{"href":2416},"\u002Fasync-background-tasks-observability\u002Fbackground-task-processing\u002Ffastapi-backgroundtasks-vs-celery-vs-arq\u002F","FastAPI BackgroundTasks vs Celery vs Arq",[764,2419,2371,2421,2423],{"id":2420},"the-yield-dependency-question-measured",[604,2422,627],{}," dependency question, measured",[590,2425,2426,2429,2430,2432,2433,2436,2437,2440,2441,2444],{},[604,2427,2428],{},"\u002Fstream-with-dep"," injects a session through a ",[604,2431,627],{}," dependency and uses it inside the generator. The trace shows ",[604,2434,2435],{},"session closed=False"," on both rows, and ",[604,2438,2439],{},"dependency: session closed"," arriving after the last row. So in FastAPI 0.139.2, the exit stack unwinds ",[2339,2442,2443],{},"after"," the streaming body is consumed, and the pattern works.",[590,2446,2447,2448,2452],{},"That is a useful data point and a bad thing to depend on. The position of dependency teardown relative to response delivery has changed across FastAPI versions, and it is not part of the documented contract — the surrounding rules are set out in ",[639,2449,2451],{"href":2450},"\u002Fcore-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Fyield-dependencies-and-cleanup-order\u002F","Yield Dependencies and Cleanup Order",". If teardown moved earlier in some future release, this endpoint would begin emitting rows from a closed session, and it would do so only under streaming, only in production, and only for large exports.",[590,2454,2455],{},"Write it so the question cannot arise:",[832,2457,2459],{"className":834,"code":2458,"language":836,"meta":837,"style":837},"@app.get(\"\u002Fexport\")\nasync def export(request: Request) -> StreamingResponse:\n    factory = request.app.state.session_factory  # Shared factory, not a live session.\n\n    async def gen() -> AsyncIterator[bytes]:\n        # The generator owns the session for exactly as long as it needs it.\n        async with factory() as session:\n            async for row in session.stream(select(Order)):\n                yield f\"{row.id},{row.total}\\n\".encode()\n\n    return StreamingResponse(gen(), media_type=\"text\u002Fcsv\")\n",[604,2460,2461,2472,2484,2497,2501,2515,2520,2537,2553,2583,2587],{"__ignoreMap":837},[841,2462,2463,2465,2467,2470],{"class":843,"line":844},[841,2464,1322],{"class":1135},[841,2466,1086],{"class":858},[841,2468,2469],{"class":847},"\"\u002Fexport\"",[841,2471,1048],{"class":858},[841,2473,2474,2476,2478,2481],{"class":843,"line":851},[841,2475,1129],{"class":854},[841,2477,1132],{"class":854},[841,2479,2480],{"class":1135}," export",[841,2482,2483],{"class":858},"(request: Request) -> StreamingResponse:\n",[841,2485,2486,2489,2491,2494],{"class":843,"line":862},[841,2487,2488],{"class":858},"    factory ",[841,2490,969],{"class":854},[841,2492,2493],{"class":858}," request.app.state.session_factory  ",[841,2495,2496],{"class":1596},"# Shared factory, not a live session.\n",[841,2498,2499],{"class":843,"line":876},[841,2500,893],{"emptyLinePlaceholder":892},[841,2502,2503,2505,2507,2509,2511,2513],{"class":843,"line":889},[841,2504,1443],{"class":854},[841,2506,1132],{"class":854},[841,2508,1448],{"class":1135},[841,2510,1139],{"class":858},[841,2512,1142],{"class":956},[841,2514,1145],{"class":858},[841,2516,2517],{"class":843,"line":896},[841,2518,2519],{"class":1596},"        # The generator owns the session for exactly as long as it needs it.\n",[841,2521,2522,2525,2528,2531,2534],{"class":843,"line":909},[841,2523,2524],{"class":854},"        async",[841,2526,2527],{"class":854}," with",[841,2529,2530],{"class":858}," factory() ",[841,2532,2533],{"class":854},"as",[841,2535,2536],{"class":858}," session:\n",[841,2538,2539,2542,2545,2548,2550],{"class":843,"line":922},[841,2540,2541],{"class":854},"            async",[841,2543,2544],{"class":854}," for",[841,2546,2547],{"class":858}," row ",[841,2549,1180],{"class":854},[841,2551,2552],{"class":858}," session.stream(select(Order)):\n",[841,2554,2555,2558,2560,2562,2564,2567,2569,2572,2574,2577,2579,2581],{"class":843,"line":935},[841,2556,2557],{"class":854},"                yield",[841,2559,1231],{"class":854},[841,2561,1045],{"class":847},[841,2563,1212],{"class":854},[841,2565,2566],{"class":858},"row.id",[841,2568,1218],{"class":854},[841,2570,2571],{"class":847},",",[841,2573,1212],{"class":854},[841,2575,2576],{"class":858},"row.total",[841,2578,1241],{"class":854},[841,2580,1045],{"class":847},[841,2582,1246],{"class":858},[841,2584,2585],{"class":843,"line":948},[841,2586,893],{"emptyLinePlaceholder":892},[841,2588,2589,2591,2593,2595,2597,2599],{"class":843,"line":953},[841,2590,1368],{"class":854},[841,2592,1479],{"class":858},[841,2594,1374],{"class":992},[841,2596,969],{"class":854},[841,2598,1692],{"class":847},[841,2600,1048],{"class":858},[590,2602,2603,2604,2608],{},"The generator opens and closes its own session, so its lifetime is bounded by the code that uses it rather than by the framework's unwind order. This does mean holding a pooled connection for the duration of the export, which for a slow client is a long time — ",[639,2605,2607],{"href":2606},"\u002Fasync-background-tasks-observability\u002Fasync-database-sessions\u002Ffixing-asyncpg-pool-exhaustion\u002F","Fixing asyncpg Pool Exhaustion"," explains why that matters and what to do about it.",[764,2610,2612],{"id":2611},"errors-after-the-first-byte","Errors after the first byte",[590,2614,2615,2618,2619,2622,2623,2626,2627,2630,2631,2634],{},[604,2616,2617],{},"\u002Fstream-error"," demonstrates the constraint that has no workaround. The response is ",[604,2620,2621],{},"200 OK",", the body contains ",[604,2624,2625],{},"partial-data"," followed by ",[604,2628,2629],{},"ERROR: upstream died mid-stream",", and there is no world in which it could have been a 500. The status line and headers were committed in the ",[604,2632,2633],{},"http.response.start"," message before the generator produced its first chunk.",[590,2636,2637],{},"Your options, in full:",[2639,2640,2641,2651,2657],"ol",{},[600,2642,2643,2646,2647,2650],{},[593,2644,2645],{},"Emit an in-band error marker",", as the example does. For NDJSON this is natural: a final line such as ",[604,2648,2649],{},"{\"error\": \"...\"}"," that clients are contracted to check. For CSV it is ugly but workable. For a single large JSON array it is nearly impossible, which is an argument for NDJSON.",[600,2652,2653,2656],{},[593,2654,2655],{},"Let the exception propagate",", which aborts the connection mid-body. The client sees a truncated response — for HTTP\u002F1.1 chunked encoding, a missing terminal chunk, which well-behaved clients report as an error. This is honest but gives the client no diagnostic.",[600,2658,2659,2662,2663,2665,2666,2669],{},[593,2660,2661],{},"Validate before streaming."," Do the work that can fail — permission checks, query planning, the first row fetch — inside the handler, before you construct the ",[604,2664,790],{},". Failures there are ordinary exceptions handled by the mechanisms in ",[639,2667,485],{"href":2668},"\u002Fcore-architecture-routing-patterns\u002Ferror-handling-global-exceptions\u002Fglobal-exception-handlers-for-consistent-api-responses\u002F",", and they produce a proper status code. This is the technique that actually reduces mid-stream failures rather than dressing them up.",[764,2671,2673,2675],{"id":2672},"fileresponse-specifics",[604,2674,633],{}," specifics",[590,2677,2678,2681,2682,2685,2686,2689,2690,2693,2694,2697,2698,800,2701,2704],{},[604,2679,2680],{},"\u002Fdownload"," returns ",[604,2683,2684],{},"FileResponse(REPORT, media_type=\"text\u002Fcsv\", filename=\"report.csv\")",". Three things happen that a hand-rolled ",[604,2687,2688],{},"Response(open(path,\"rb\").read())"," would not do: the file is streamed in chunks rather than read whole, ",[604,2691,2692],{},"Content-Length"," is set from a ",[604,2695,2696],{},"stat"," call, and ",[604,2699,2700],{},"Last-Modified",[604,2702,2703],{},"ETag"," headers are derived from the file's metadata so conditional requests can return 304.",[590,2706,2371,2707,2709,2710,2713],{},[604,2708,1697],{}," argument sets ",[604,2711,2712],{},"Content-Disposition: attachment",", which is what makes a browser download rather than render. Omit it to serve inline.",[590,2715,2716,2717,2719],{},"Two cautions. First, ",[604,2718,633],{}," opens the file when the response is sent, not when it is constructed, which is exactly why the delete-after-send pattern must use a background task. Second, never build the path from unvalidated user input — a path traversal here reads arbitrary files. Resolve the path and assert it is within the intended directory before constructing the response.",[764,2721,2723],{"id":2722},"verification","Verification",[590,2725,2726,2727,2730,2731,2734],{},"Streaming endpoints need a test that actually streams. A test that calls ",[604,2728,2729],{},"client.get()"," and reads ",[604,2732,2733],{},".text"," buffers the whole body and will happily pass on an endpoint that has silently become non-streaming.",[832,2736,2738],{"className":834,"code":2737,"language":836,"meta":837,"style":837},"def test_export_streams_incrementally(client):\n    with client.stream(\"GET\", \"\u002Fexport\") as resp:\n        assert resp.status_code == 200\n        assert resp.headers[\"content-type\"].startswith(\"text\u002Fcsv\")\n        chunks = list(resp.iter_bytes())\n    # More than one chunk proves the body was not buffered into a single message.\n    assert len(chunks) > 1\n\n\ndef test_temp_file_is_removed_after_download(client, tmp_path):\n    resp = client.get(\"\u002Fdownload-temp\")\n    assert resp.status_code == 200\n    # The background task runs before the response context closes in TestClient.\n    assert not (tmp_path \u002F \"scratch.csv\").exists()\n",[604,2739,2740,2750,2773,2787,2804,2817,2822,2839,2843,2847,2857,2871,2881,2886],{"__ignoreMap":837},[841,2741,2742,2744,2747],{"class":843,"line":844},[841,2743,1283],{"class":854},[841,2745,2746],{"class":1135}," test_export_streams_incrementally",[841,2748,2749],{"class":858},"(client):\n",[841,2751,2752,2755,2758,2761,2763,2765,2768,2770],{"class":843,"line":851},[841,2753,2754],{"class":854},"    with",[841,2756,2757],{"class":858}," client.stream(",[841,2759,2760],{"class":847},"\"GET\"",[841,2762,791],{"class":858},[841,2764,2469],{"class":847},[841,2766,2767],{"class":858},") ",[841,2769,2533],{"class":854},[841,2771,2772],{"class":858}," resp:\n",[841,2774,2775,2778,2781,2784],{"class":843,"line":862},[841,2776,2777],{"class":854},"        assert",[841,2779,2780],{"class":858}," resp.status_code ",[841,2782,2783],{"class":854},"==",[841,2785,2786],{"class":956}," 200\n",[841,2788,2789,2791,2794,2797,2800,2802],{"class":843,"line":876},[841,2790,2777],{"class":854},[841,2792,2793],{"class":858}," resp.headers[",[841,2795,2796],{"class":847},"\"content-type\"",[841,2798,2799],{"class":858},"].startswith(",[841,2801,1692],{"class":847},[841,2803,1048],{"class":858},[841,2805,2806,2809,2811,2814],{"class":843,"line":889},[841,2807,2808],{"class":858},"        chunks ",[841,2810,969],{"class":854},[841,2812,2813],{"class":956}," list",[841,2815,2816],{"class":858},"(resp.iter_bytes())\n",[841,2818,2819],{"class":843,"line":896},[841,2820,2821],{"class":1596},"    # More than one chunk proves the body was not buffered into a single message.\n",[841,2823,2824,2827,2830,2833,2836],{"class":843,"line":909},[841,2825,2826],{"class":854},"    assert",[841,2828,2829],{"class":956}," len",[841,2831,2832],{"class":858},"(chunks) ",[841,2834,2835],{"class":854},">",[841,2837,2838],{"class":956}," 1\n",[841,2840,2841],{"class":843,"line":922},[841,2842,893],{"emptyLinePlaceholder":892},[841,2844,2845],{"class":843,"line":935},[841,2846,893],{"emptyLinePlaceholder":892},[841,2848,2849,2851,2854],{"class":843,"line":948},[841,2850,1283],{"class":854},[841,2852,2853],{"class":1135}," test_temp_file_is_removed_after_download",[841,2855,2856],{"class":858},"(client, tmp_path):\n",[841,2858,2859,2862,2864,2867,2869],{"class":843,"line":953},[841,2860,2861],{"class":858},"    resp ",[841,2863,969],{"class":854},[841,2865,2866],{"class":858}," client.get(",[841,2868,1724],{"class":847},[841,2870,1048],{"class":858},[841,2872,2873,2875,2877,2879],{"class":843,"line":975},[841,2874,2826],{"class":854},[841,2876,2780],{"class":858},[841,2878,2783],{"class":854},[841,2880,2786],{"class":956},[841,2882,2883],{"class":843,"line":980},[841,2884,2885],{"class":1596},"    # The background task runs before the response context closes in TestClient.\n",[841,2887,2888,2890,2893,2896,2899,2902],{"class":843,"line":1004},[841,2889,2826],{"class":854},[841,2891,2892],{"class":854}," not",[841,2894,2895],{"class":858}," (tmp_path ",[841,2897,2898],{"class":854},"\u002F",[841,2900,2901],{"class":847}," \"scratch.csv\"",[841,2903,2904],{"class":858},").exists()\n",[590,2906,2907],{},"Also assert memory: if you can, run the export against a large fixture under a memory cap. Streaming that quietly regressed to buffering shows up as a resident-set spike, not as a failing assertion.",[764,2909,2911],{"id":2910},"trade-offs-and-when-not-to-stream","Trade-offs and when not to stream",[590,2913,2914],{},"Streaming is not free and is not always right.",[597,2916,2917,2935,2941,2957,2967],{},[600,2918,2919,2922,2923,2926,2927,2930,2931,2934],{},[593,2920,2921],{},"You lose the schema guarantee."," As ",[604,2924,2925],{},"\u002Fstream-typed"," proves, a declared ",[604,2928,2929],{},"response_model=list[Row]"," accepted a body that was not a list of ",[604,2932,2933],{},"Row"," at all. If a contract matters more than memory, buffer.",[600,2936,2937,2940],{},[593,2938,2939],{},"You lose clean error semantics"," after the first byte, as shown above.",[600,2942,2943,2949,2950,2952,2953,646],{},[593,2944,2945,2948],{},[604,2946,2947],{},"BaseHTTPMiddleware"," can defeat it."," Middleware built on ",[604,2951,2947],{}," pipes the response through a memory object stream and can buffer it, converting your stream back into a single payload. If you stream, prefer pure ASGI middleware; the performance argument for that choice is made in ",[639,2954,2956],{"href":2955},"\u002Fcore-architecture-routing-patterns\u002Fmiddleware-implementation\u002F","Middleware Implementation",[600,2958,2959,2962,2963,2966],{},[593,2960,2961],{},"Proxies may buffer anyway."," Nginx buffers upstream responses by default; without ",[604,2964,2965],{},"proxy_buffering off"," your chunks arrive in one lump regardless of what the application did.",[600,2968,2969,2972],{},[593,2970,2971],{},"A slow client holds resources for as long as it takes to read."," For a large export drawn from a database, that means a pooled connection pinned for minutes.",[590,2974,2975,2976,2978],{},"For payloads under a few megabytes, a buffered ",[604,2977,610],{}," endpoint is simpler, safer and fully validated. Reach for streaming when the payload is genuinely unbounded, when time-to-first-byte matters, or when the data is already bytes on disk.",[764,2980,2982],{"id":2981},"faq","FAQ",[590,2984,2985,2994,2995,2998,2999,3001,3002,3004,3005,3007,3008,3010],{},[593,2986,2987,2988,2990,2991,2993],{},"Why does ",[604,2989,610],{}," have no effect on a ",[604,2992,790],{},"?","\nBecause FastAPI only runs ",[604,2996,2997],{},"serialize_response"," when the handler returns something that is not already a ",[604,3000,606],{},". A ",[604,3003,790],{}," is a ",[604,3006,606],{},", so FastAPI forwards it untouched and never validates the bytes the generator produces. The declared ",[604,3009,610],{}," still appears in the OpenAPI schema, which means the schema becomes a claim you must uphold yourself.",[590,3012,3013,3019,3020,3022],{},[593,3014,3015,3016,3018],{},"When does a background task attached to a ",[604,3017,790],{}," run?","\nAfter the generator is exhausted and the final body chunk has been sent. In a captured trace the order was generator ",[604,3021,2378],{}," block, then background task, so a background task can safely clean up resources the stream was using.",[590,3024,3025,3028,3029,3031],{},[593,3026,3027],{},"Can I return a 500 if my stream fails halfway through?","\nNo. The status code and headers are sent in the ",[604,3030,2633],{}," message before the first chunk of the body, so by the time an error occurs the client already has a 200. The only options are to emit an in-band error marker in the payload format or to abort the connection, which surfaces to the client as a truncated response.",[590,3033,3034,3040,3041,3044],{},[593,3035,3036,3037,3039],{},"Should the database session for a streaming query come from a ",[604,3038,627],{}," dependency?","\nIt works in FastAPI 0.139.2, where teardown was observed to run after the stream was consumed, but relying on that couples your code to an implementation detail. Acquiring the session inside the generator with an ",[604,3042,3043],{},"async with"," block makes the lifetime explicit and immune to future changes in unwind ordering.",[590,3046,3047,3053,3054,3056,3057,3059,3060,3062,3063,800,3065,3067,3068,646],{},[593,3048,3049,3050,3052],{},"Does ",[604,3051,633],{}," read the whole file into memory?","\nNo. ",[604,3055,633],{}," streams the file in chunks and sets ",[604,3058,2692],{}," from a ",[604,3061,2696],{}," call, so memory use stays flat regardless of file size. It also handles ",[604,3064,2703],{},[604,3066,2700],{}," headers, which is why it is preferable to reading bytes yourself and returning a plain ",[604,3069,606],{},[764,3071,3073],{"id":3072},"related-reading","Related Reading",[597,3075,3076,3084,3091,3100,3111],{},[600,3077,3078,2342,3081,3083],{},[593,3079,3080],{},"Up to the guide:",[639,3082,557],{"href":645}," for where the bypass sits in the wider sequence.",[600,3085,3086,2342,3089,646],{},[593,3087,3088],{},"The stage you are skipping:",[639,3090,569],{"href":641},[600,3092,3093,2342,3096,3099],{},[593,3094,3095],{},"The measured ordering:",[639,3097,563],{"href":3098},"\u002Fcore-architecture-routing-patterns\u002Frequest-response-lifecycle\u002Fhow-a-request-flows-through-fastapi\u002F"," shows exactly when the body messages travel outward.",[600,3101,3102,2342,3105,800,3107,646],{},[593,3103,3104],{},"Middleware that breaks streams:",[639,3106,2956],{"href":2955},[639,3108,3110],{"href":3109},"\u002Fcore-architecture-routing-patterns\u002Fmiddleware-implementation\u002Fmiddleware-execution-order\u002F","Middleware Execution Order",[600,3112,3113,2342,3116,646],{},[593,3114,3115],{},"Holding connections while streaming:",[639,3117,2607],{"href":2606},[3119,3120,3121],"style",{},"html pre.shiki code .sYEJz, html code.shiki .sYEJz{--shiki-default:#032563}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 .sacAq, html code.shiki .sacAq{--shiki-default:#023B95}html pre.shiki code .sV4o_, html code.shiki .sV4o_{--shiki-default:#702C00}html pre.shiki code .s3dhs, html code.shiki .s3dhs{--shiki-default:#622CBC}html pre.shiki code .sFeEa, html code.shiki .sFeEa{--shiki-default:#66707B}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":837,"searchDepth":851,"depth":851,"links":3123},[3124,3125,3127,3128,3129,3130,3132,3133,3135,3136,3137,3138],{"id":766,"depth":851,"text":767},{"id":776,"depth":851,"text":3126},"Why streaming bypasses response_model",{"id":820,"depth":851,"text":821},{"id":2326,"depth":851,"text":2327},{"id":2367,"depth":851,"text":2368},{"id":2420,"depth":851,"text":3131},"The yield dependency question, measured",{"id":2611,"depth":851,"text":2612},{"id":2672,"depth":851,"text":3134},"FileResponse specifics",{"id":2722,"depth":851,"text":2723},{"id":2910,"depth":851,"text":2911},{"id":2981,"depth":851,"text":2982},{"id":3072,"depth":851,"text":3073},"2026-07-20","Use StreamingResponse and FileResponse correctly: async generators, why streaming bypasses response_model, and how background cleanup fits an open stream.","md",[3143,3146,3149,3151,3154],{"q":3144,"a":3145},"Why does response_model have no effect on a StreamingResponse?","Because FastAPI only runs serialize_response when the handler returns something that is not already a Response. A StreamingResponse is a Response, so FastAPI forwards it untouched and never validates the bytes the generator produces. The declared response_model still appears in the OpenAPI schema, which means the schema becomes a claim you must uphold yourself.",{"q":3147,"a":3148},"When does a background task attached to a StreamingResponse run?","After the generator is exhausted and the final body chunk has been sent. In a captured trace the order was generator finally block, then background task, so a background task can safely clean up resources the stream was using.",{"q":3027,"a":3150},"No. The status code and headers are sent in the http.response.start message before the first chunk of the body, so by the time an error occurs the client already has a 200. The only options are to emit an in-band error marker in the payload format or to abort the connection, which surfaces to the client as a truncated response.",{"q":3152,"a":3153},"Should the database session for a streaming query come from a yield dependency?","It works in FastAPI 0.139.2, where teardown was observed to run after the stream was consumed, but relying on that couples your code to an implementation detail. Acquiring the session inside the generator with an async context manager makes the lifetime explicit and immune to future changes in unwind ordering.",{"q":3155,"a":3156},"Does FileResponse read the whole file into memory?","No. FileResponse streams the file in chunks and sets Content-Length from a stat call, so memory use stays flat regardless of file size. It also handles ETag and Last-Modified headers, which is why it is preferable to reading bytes yourself and returning a plain Response.",null,{"slug":3159,"breadcrumb":3160},"streaming-and-file-responses",[3161,3163,3166,3168],{"label":3162,"path":2898},"Home",{"label":3164,"path":3165},"Core Architecture & Routing Patterns","\u002Fcore-architecture-routing-patterns\u002F",{"label":3167,"path":645},"Request\u002FResponse Lifecycle",{"label":3169,"path":3170},"Streaming and File Responses","\u002Fcore-architecture-routing-patterns\u002Frequest-response-lifecycle\u002Fstreaming-and-file-responses\u002F",{"title":575,"description":3140},"article","kp44MUVRak0GPCGk_nc57DiRm_tVhFZYTfP5FtNdF8A",[3157,3157],1784588202739]