[{"data":1,"prerenderedAt":2663},["ShallowReactive",2],{"nav":3,"page-\u002Fasync-background-tasks-observability\u002Fbackground-task-processing\u002Fwhen-backgroundtasks-silently-fails\u002F":580,"surround-\u002Fasync-background-tasks-observability\u002Fbackground-task-processing\u002Fwhen-backgroundtasks-silently-fails\u002F":2662},[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":279,"body":582,"dateModified":2631,"datePublished":2631,"description":2632,"extension":2633,"faq":2634,"howto":2645,"meta":2646,"navigation":913,"path":280,"seo":2659,"stem":281,"type":2660,"__hash__":2661},"content\u002Fasync-background-tasks-observability\u002Fbackground-task-processing\u002Fwhen-backgroundtasks-silently-fails\u002Findex.md",{"type":583,"value":584,"toc":2622},"minimark",[585,589,596,628,644,782,787,819,829,842,853,872,876,879,1792,1795,2075,2078,2085,2102,2106,2109,2344,2347,2364,2375,2379,2382,2444,2447,2494,2504,2510,2514,2520,2534,2541,2545,2551,2557,2563,2569,2575,2579,2618],[586,587,279],"h1",{"id":588},"when-fastapi-backgroundtasks-silently-fails",[590,591,592],"p",{},[593,594,595],"strong",{},"Key takeaways:",[597,598,599,612,619,622,625],"ul",{},[600,601,602,606,607,611],"li",{},[603,604,605],"code",{},"BackgroundTasks"," runs ",[608,609,610],"em",{},"after"," the response is sent, so a failing task cannot change the status code the client already received.",[600,613,614,615,618],{},"The exception escapes into the ASGI server, where it is logged by ",[603,616,617],{},"uvicorn.error"," — and nowhere near your application logger.",[600,620,621],{},"One raising task cancels every task queued behind it, because Starlette awaits them sequentially in one coroutine.",[600,623,624],{},"Wrap every task callable in a guard that logs the exception with its arguments, and increment a failure metric.",[600,626,627],{},"If the work must not be lost, the guard is not enough — you need a durable queue.",[590,629,630,631,633,634,637,638,643],{},"You shipped a signup endpoint that sends a welcome email through ",[603,632,605],{},". Support reports that some users never receive it. The API returned ",[603,635,636],{},"200"," every time, there is no error in your dashboards, and the endpoint's own logs look perfectly healthy. This page is part of ",[639,640,642],"a",{"href":641},"\u002Fasync-background-tasks-observability\u002Fbackground-task-processing\u002F","Background Task Processing",", and it covers the specific failure mode where the task raised and nothing told you.",[645,646,647,778],"figure",{},[648,649,657,658,657,662,657,666,657,673,657,678,657,683,657,691,657,697,657,702,657,707,657,712,657,716,657,719,657,723,657,726,657,731,657,734,657,738,657,741,657,746,657,749,657,755,657,759,657,763,657,766,657,769,657,771,657,775],"svg",{"viewBox":650,"role":651,"ariaLabelledBy":652,"xmlns":655,"style":656},"0 0 720 300","img",[653,654],"bgfail-title","bgfail-desc","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;height:auto;max-width:720px;margin:2rem 0","\n  ",[659,660,661],"title",{"id":653},"Where a background task exception goes",[663,664,665],"desc",{"id":654},"A timeline showing the response leaving with status 200, then the background task running and raising, with the exception travelling to the server error log rather than to the client.",[667,668],"line",{"x1":669,"y1":670,"x2":671,"y2":670,"style":672},"40","60","680","stroke:currentColor;stroke-width:1.4px",[674,675],"polygon",{"points":676,"style":677},"680,56 690,60 680,64","fill:currentColor",[679,680,682],"text",{"x":669,"y":669,"style":681},"text-anchor:start;fill:currentColor;font:600 13px sans-serif","time",[684,685],"rect",{"x":669,"y":686,"width":687,"height":688,"rx":689,"style":690},"80","150","52","8","fill:none;stroke:currentColor;stroke-width:1.4px",[679,692,696],{"x":693,"y":694,"style":695},"115","103","text-anchor:middle;fill:currentColor;font:600 13px sans-serif","handler runs",[679,698,701],{"x":693,"y":699,"style":700},"121","text-anchor:middle;fill:currentColor;font:400 12px sans-serif","add_task(...)",[684,703],{"x":704,"y":686,"width":705,"height":688,"rx":689,"style":706},"210","170","fill:#00796B;stroke:#00796B;stroke-width:1.4px",[679,708,711],{"x":709,"y":694,"style":710},"295","text-anchor:middle;fill:#ffffff;font:600 13px sans-serif","200 OK sent",[679,713,715],{"x":709,"y":699,"style":714},"text-anchor:middle;fill:#ffffff;font:400 12px sans-serif","client is done",[684,717],{"x":718,"y":686,"width":687,"height":688,"rx":689,"style":690},"400",[679,720,722],{"x":721,"y":694,"style":695},"475","task runs",[679,724,725],{"x":721,"y":699,"style":700},"raises",[667,727],{"x1":728,"y1":729,"x2":730,"y2":729,"style":672},"190","106","206",[674,732],{"points":733,"style":677},"206,102 214,106 206,110",[667,735],{"x1":736,"y1":729,"x2":737,"y2":729,"style":672},"380","396",[674,739],{"points":740,"style":677},"396,102 404,106 396,110",[667,742],{"x1":721,"y1":743,"x2":721,"y2":744,"style":745},"132","196","stroke:currentColor;stroke-width:1.4px;stroke-dasharray:5 4",[674,747],{"points":748,"style":677},"471,196 475,206 479,196",[684,750],{"x":751,"y":704,"width":752,"height":753,"rx":689,"style":754},"360","230","48","fill:none;stroke:#00796B;stroke-width:1.6px",[679,756,758],{"x":721,"y":757,"style":695},"231","uvicorn.error logger",[679,760,762],{"x":721,"y":761,"style":700},"249","not your app logger",[667,764],{"x1":709,"y1":743,"x2":687,"y2":744,"style":765},"stroke:currentColor;stroke-width:1.2px;stroke-dasharray:4 4",[674,767],{"points":768,"style":677},"152,192 143,200 148,190",[684,770],{"x":669,"y":704,"width":752,"height":753,"rx":689,"style":690},[679,772,774],{"x":773,"y":757,"style":695},"155","client sees success",[679,776,777],{"x":773,"y":761,"style":700},"no error reaches it",[779,780,781],"figcaption",{},"The response is committed before the task runs, so the exception can only travel upward into the server, never back to the caller.",[783,784,786],"h2",{"id":785},"why-it-happens","Why It Happens",[590,788,789,791,792,794,795,798,799,802,803,806,807,810,811,814,815,818],{},[603,790,605],{}," is a parameter-shaped wrapper around Starlette's ",[603,793,605],{}," object. When your path operation returns, FastAPI attaches that object to the ",[603,796,797],{},"Response"," as ",[603,800,801],{},"response.background",". Starlette's ",[603,804,805],{},"Response.__call__"," then does three things in order: it sends the ",[603,808,809],{},"http.response.start"," message with the status and headers, it sends the ",[603,812,813],{},"http.response.body"," message with the payload, and only then it awaits ",[603,816,817],{},"self.background()",".",[590,820,821,822,824,825,828],{},"That ordering is the whole story. By the time your task's first line executes, the ASGI server has already flushed the response. The status code is committed. The body is committed. There is no mechanism by which a later exception could turn the ",[603,823,636],{}," into a ",[603,826,827],{},"500",", because HTTP does not allow retracting a response you have already written to the socket.",[590,830,831,832,834,835,838,839,841],{},"So where does the exception go? It propagates out of the ",[603,833,805],{}," coroutine, up through the router, through your middleware stack, and into the ASGI server. Uvicorn catches it at the top of its protocol handler and logs ",[603,836,837],{},"Exception in ASGI application"," with a traceback on the ",[603,840,617],{}," logger. That is a different logger from the one your application code uses, and in a lot of production setups it is either not JSON-formatted, not shipped to the same index, or filtered out by a log level rule. The failure exists but it is not where anyone is looking.",[590,843,844,845,848,849,852],{},"There is a second consequence that surprises people more than the first. ",[603,846,847],{},"BackgroundTasks.__call__"," iterates the queued tasks in a plain ",[603,850,851],{},"for"," loop and awaits each one. An exception in task one means tasks two and three never start. A noisy analytics call that fails intermittently will silently stop your audit writes from happening at all.",[590,854,855,856,859,860,863,864,867,868,871],{},"Note also that exception handlers registered with ",[603,857,858],{},"@app.exception_handler(...)"," cannot help here. Those handlers are installed by ",[603,861,862],{},"ExceptionMiddleware"," and ",[603,865,866],{},"ServerErrorMiddleware",", which wrap the ",[608,869,870],{},"request"," — and by the time the background task runs, the response has already travelled back out through that middleware. The stack that would have caught the error has unwound.",[783,873,875],{"id":874},"proving-it","Proving It",[590,877,878],{},"Here is a self-contained app with two endpoints: one that queues an unguarded task and one that queues a guarded one. To make the invisible visible, a small pure-ASGI middleware plays the role uvicorn plays — it records the response messages as they leave and catches whatever the application raises afterwards.",[880,881,886],"pre",{"className":882,"code":883,"language":884,"meta":885,"style":885},"language-python shiki shiki-themes github-light-high-contrast","import logging\nimport traceback\n\nfrom fastapi import BackgroundTasks, FastAPI\n\nTIMELINE: list[str] = []\n\n\nclass ListHandler(logging.Handler):\n    \"\"\"Stand-in for a log backend so the transcript can show what was actually logged.\"\"\"\n\n    def emit(self, record: logging.LogRecord) -> None:\n        line = f\"log {record.levelname} {record.name}: {record.getMessage()}\"\n        if record.exc_info:\n            line += \" | \" + traceback.format_exception_only(record.exc_info[1])[0].strip()\n        TIMELINE.append(line)\n\n\nlogger = logging.getLogger(\"app.tasks\")\nlogger.setLevel(logging.INFO)\nlogger.addHandler(ListHandler())\nlogger.propagate = False\n\n\nclass ServerBoundary:\n    \"\"\"Plays the role uvicorn plays: it sees the response leave, then the task explode.\"\"\"\n\n    def __init__(self, app) -> None:\n        self.app = app\n\n    async def __call__(self, scope, receive, send) -> None:\n        if scope[\"type\"] != \"http\" or scope[\"path\"] == \"\u002Fevidence\":\n            await self.app(scope, receive, send)\n            return\n\n        async def send_wrapper(message) -> None:\n            if message[\"type\"] == \"http.response.start\":\n                TIMELINE.append(f\"response status {message['status']} sent to client\")\n            elif message[\"type\"] == \"http.response.body\" and not message.get(\"more_body\"):\n                TIMELINE.append(\"response body flushed: client request is complete\")\n            await send(message)\n\n        try:\n            await self.app(scope, receive, send_wrapper)\n        except Exception as exc:\n            # uvicorn logs \"Exception in ASGI application\" here. The client saw 200 long ago.\n            TIMELINE.append(f\"server caught {type(exc).__name__} AFTER the response: {exc}\")\n\n\napp = FastAPI()\napp.add_middleware(ServerBoundary)\n\nDELIVERED: list[str] = []\n\n\nasync def send_receipt(order_id: str) -> None:\n    \"\"\"The task as most people write it: it can raise, and nothing here handles that.\"\"\"\n    TIMELINE.append(f\"task send_receipt({order_id}) started\")\n    if order_id == \"bad\":\n        raise RuntimeError(f\"receipt provider rejected order {order_id}\")\n    DELIVERED.append(order_id)\n    TIMELINE.append(f\"task send_receipt({order_id}) finished\")\n\n\n@app.post(\"\u002Forders\u002F{order_id}\u002Funsafe\")\nasync def create_order_unsafe(order_id: str, tasks: BackgroundTasks) -> dict[str, str]:\n    tasks.add_task(send_receipt, order_id)\n    return {\"order_id\": order_id, \"status\": \"created\"}\n","python","",[603,887,888,900,908,915,929,934,956,961,966,990,997,1002,1021,1065,1074,1104,1113,1118,1123,1140,1151,1157,1168,1173,1178,1188,1194,1199,1214,1228,1233,1252,1289,1301,1307,1312,1330,1350,1383,1414,1426,1434,1439,1447,1457,1472,1479,1520,1525,1530,1541,1547,1552,1568,1573,1578,1601,1607,1632,1648,1674,1683,1705,1710,1715,1734,1761,1767],{"__ignoreMap":885},[889,890,892,896],"span",{"class":667,"line":891},1,[889,893,895],{"class":894},"sTJeM","import",[889,897,899],{"class":898},"sigWx"," logging\n",[889,901,903,905],{"class":667,"line":902},2,[889,904,895],{"class":894},[889,906,907],{"class":898}," traceback\n",[889,909,911],{"class":667,"line":910},3,[889,912,914],{"emptyLinePlaceholder":913},true,"\n",[889,916,918,921,924,926],{"class":667,"line":917},4,[889,919,920],{"class":894},"from",[889,922,923],{"class":898}," fastapi ",[889,925,895],{"class":894},[889,927,928],{"class":898}," BackgroundTasks, FastAPI\n",[889,930,932],{"class":667,"line":931},5,[889,933,914],{"emptyLinePlaceholder":913},[889,935,937,941,944,947,950,953],{"class":667,"line":936},6,[889,938,940],{"class":939},"sacAq","TIMELINE",[889,942,943],{"class":898},": list[",[889,945,946],{"class":939},"str",[889,948,949],{"class":898},"] ",[889,951,952],{"class":894},"=",[889,954,955],{"class":898}," []\n",[889,957,959],{"class":667,"line":958},7,[889,960,914],{"emptyLinePlaceholder":913},[889,962,964],{"class":667,"line":963},8,[889,965,914],{"emptyLinePlaceholder":913},[889,967,969,972,976,979,982,984,987],{"class":667,"line":968},9,[889,970,971],{"class":894},"class",[889,973,975],{"class":974},"sV4o_"," ListHandler",[889,977,978],{"class":898},"(",[889,980,981],{"class":939},"logging",[889,983,818],{"class":898},[889,985,986],{"class":939},"Handler",[889,988,989],{"class":898},"):\n",[889,991,993],{"class":667,"line":992},10,[889,994,996],{"class":995},"sYEJz","    \"\"\"Stand-in for a log backend so the transcript can show what was actually logged.\"\"\"\n",[889,998,1000],{"class":667,"line":999},11,[889,1001,914],{"emptyLinePlaceholder":913},[889,1003,1005,1008,1012,1015,1018],{"class":667,"line":1004},12,[889,1006,1007],{"class":894},"    def",[889,1009,1011],{"class":1010},"s3dhs"," emit",[889,1013,1014],{"class":898},"(self, record: logging.LogRecord) -> ",[889,1016,1017],{"class":939},"None",[889,1019,1020],{"class":898},":\n",[889,1022,1024,1027,1029,1032,1035,1038,1041,1044,1047,1050,1052,1055,1057,1060,1062],{"class":667,"line":1023},13,[889,1025,1026],{"class":898},"        line ",[889,1028,952],{"class":894},[889,1030,1031],{"class":894}," f",[889,1033,1034],{"class":995},"\"log ",[889,1036,1037],{"class":894},"{",[889,1039,1040],{"class":898},"record.levelname",[889,1042,1043],{"class":894},"}",[889,1045,1046],{"class":894}," {",[889,1048,1049],{"class":898},"record.name",[889,1051,1043],{"class":894},[889,1053,1054],{"class":995},": ",[889,1056,1037],{"class":894},[889,1058,1059],{"class":898},"record.getMessage()",[889,1061,1043],{"class":894},[889,1063,1064],{"class":995},"\"\n",[889,1066,1068,1071],{"class":667,"line":1067},14,[889,1069,1070],{"class":894},"        if",[889,1072,1073],{"class":898}," record.exc_info:\n",[889,1075,1077,1080,1083,1086,1089,1092,1095,1098,1101],{"class":667,"line":1076},15,[889,1078,1079],{"class":898},"            line ",[889,1081,1082],{"class":894},"+=",[889,1084,1085],{"class":995}," \" | \"",[889,1087,1088],{"class":894}," +",[889,1090,1091],{"class":898}," traceback.format_exception_only(record.exc_info[",[889,1093,1094],{"class":939},"1",[889,1096,1097],{"class":898},"])[",[889,1099,1100],{"class":939},"0",[889,1102,1103],{"class":898},"].strip()\n",[889,1105,1107,1110],{"class":667,"line":1106},16,[889,1108,1109],{"class":939},"        TIMELINE",[889,1111,1112],{"class":898},".append(line)\n",[889,1114,1116],{"class":667,"line":1115},17,[889,1117,914],{"emptyLinePlaceholder":913},[889,1119,1121],{"class":667,"line":1120},18,[889,1122,914],{"emptyLinePlaceholder":913},[889,1124,1126,1129,1131,1134,1137],{"class":667,"line":1125},19,[889,1127,1128],{"class":898},"logger ",[889,1130,952],{"class":894},[889,1132,1133],{"class":898}," logging.getLogger(",[889,1135,1136],{"class":995},"\"app.tasks\"",[889,1138,1139],{"class":898},")\n",[889,1141,1143,1146,1149],{"class":667,"line":1142},20,[889,1144,1145],{"class":898},"logger.setLevel(logging.",[889,1147,1148],{"class":939},"INFO",[889,1150,1139],{"class":898},[889,1152,1154],{"class":667,"line":1153},21,[889,1155,1156],{"class":898},"logger.addHandler(ListHandler())\n",[889,1158,1160,1163,1165],{"class":667,"line":1159},22,[889,1161,1162],{"class":898},"logger.propagate ",[889,1164,952],{"class":894},[889,1166,1167],{"class":939}," False\n",[889,1169,1171],{"class":667,"line":1170},23,[889,1172,914],{"emptyLinePlaceholder":913},[889,1174,1176],{"class":667,"line":1175},24,[889,1177,914],{"emptyLinePlaceholder":913},[889,1179,1181,1183,1186],{"class":667,"line":1180},25,[889,1182,971],{"class":894},[889,1184,1185],{"class":974}," ServerBoundary",[889,1187,1020],{"class":898},[889,1189,1191],{"class":667,"line":1190},26,[889,1192,1193],{"class":995},"    \"\"\"Plays the role uvicorn plays: it sees the response leave, then the task explode.\"\"\"\n",[889,1195,1197],{"class":667,"line":1196},27,[889,1198,914],{"emptyLinePlaceholder":913},[889,1200,1202,1204,1207,1210,1212],{"class":667,"line":1201},28,[889,1203,1007],{"class":894},[889,1205,1206],{"class":939}," __init__",[889,1208,1209],{"class":898},"(self, app) -> ",[889,1211,1017],{"class":939},[889,1213,1020],{"class":898},[889,1215,1217,1220,1223,1225],{"class":667,"line":1216},29,[889,1218,1219],{"class":939},"        self",[889,1221,1222],{"class":898},".app ",[889,1224,952],{"class":894},[889,1226,1227],{"class":898}," app\n",[889,1229,1231],{"class":667,"line":1230},30,[889,1232,914],{"emptyLinePlaceholder":913},[889,1234,1236,1239,1242,1245,1248,1250],{"class":667,"line":1235},31,[889,1237,1238],{"class":894},"    async",[889,1240,1241],{"class":894}," def",[889,1243,1244],{"class":939}," __call__",[889,1246,1247],{"class":898},"(self, scope, receive, send) -> ",[889,1249,1017],{"class":939},[889,1251,1020],{"class":898},[889,1253,1255,1257,1260,1263,1265,1268,1271,1274,1276,1279,1281,1284,1287],{"class":667,"line":1254},32,[889,1256,1070],{"class":894},[889,1258,1259],{"class":898}," scope[",[889,1261,1262],{"class":995},"\"type\"",[889,1264,949],{"class":898},[889,1266,1267],{"class":894},"!=",[889,1269,1270],{"class":995}," \"http\"",[889,1272,1273],{"class":894}," or",[889,1275,1259],{"class":898},[889,1277,1278],{"class":995},"\"path\"",[889,1280,949],{"class":898},[889,1282,1283],{"class":894},"==",[889,1285,1286],{"class":995}," \"\u002Fevidence\"",[889,1288,1020],{"class":898},[889,1290,1292,1295,1298],{"class":667,"line":1291},33,[889,1293,1294],{"class":894},"            await",[889,1296,1297],{"class":939}," self",[889,1299,1300],{"class":898},".app(scope, receive, send)\n",[889,1302,1304],{"class":667,"line":1303},34,[889,1305,1306],{"class":894},"            return\n",[889,1308,1310],{"class":667,"line":1309},35,[889,1311,914],{"emptyLinePlaceholder":913},[889,1313,1315,1318,1320,1323,1326,1328],{"class":667,"line":1314},36,[889,1316,1317],{"class":894},"        async",[889,1319,1241],{"class":894},[889,1321,1322],{"class":1010}," send_wrapper",[889,1324,1325],{"class":898},"(message) -> ",[889,1327,1017],{"class":939},[889,1329,1020],{"class":898},[889,1331,1333,1336,1339,1341,1343,1345,1348],{"class":667,"line":1332},37,[889,1334,1335],{"class":894},"            if",[889,1337,1338],{"class":898}," message[",[889,1340,1262],{"class":995},[889,1342,949],{"class":898},[889,1344,1283],{"class":894},[889,1346,1347],{"class":995}," \"http.response.start\"",[889,1349,1020],{"class":898},[889,1351,1353,1356,1359,1362,1365,1367,1370,1373,1376,1378,1381],{"class":667,"line":1352},38,[889,1354,1355],{"class":939},"                TIMELINE",[889,1357,1358],{"class":898},".append(",[889,1360,1361],{"class":894},"f",[889,1363,1364],{"class":995},"\"response status ",[889,1366,1037],{"class":894},[889,1368,1369],{"class":898},"message[",[889,1371,1372],{"class":995},"'status'",[889,1374,1375],{"class":898},"]",[889,1377,1043],{"class":894},[889,1379,1380],{"class":995}," sent to client\"",[889,1382,1139],{"class":898},[889,1384,1386,1389,1391,1393,1395,1397,1400,1403,1406,1409,1412],{"class":667,"line":1385},39,[889,1387,1388],{"class":894},"            elif",[889,1390,1338],{"class":898},[889,1392,1262],{"class":995},[889,1394,949],{"class":898},[889,1396,1283],{"class":894},[889,1398,1399],{"class":995}," \"http.response.body\"",[889,1401,1402],{"class":894}," and",[889,1404,1405],{"class":894}," not",[889,1407,1408],{"class":898}," message.get(",[889,1410,1411],{"class":995},"\"more_body\"",[889,1413,989],{"class":898},[889,1415,1417,1419,1421,1424],{"class":667,"line":1416},40,[889,1418,1355],{"class":939},[889,1420,1358],{"class":898},[889,1422,1423],{"class":995},"\"response body flushed: client request is complete\"",[889,1425,1139],{"class":898},[889,1427,1429,1431],{"class":667,"line":1428},41,[889,1430,1294],{"class":894},[889,1432,1433],{"class":898}," send(message)\n",[889,1435,1437],{"class":667,"line":1436},42,[889,1438,914],{"emptyLinePlaceholder":913},[889,1440,1442,1445],{"class":667,"line":1441},43,[889,1443,1444],{"class":894},"        try",[889,1446,1020],{"class":898},[889,1448,1450,1452,1454],{"class":667,"line":1449},44,[889,1451,1294],{"class":894},[889,1453,1297],{"class":939},[889,1455,1456],{"class":898},".app(scope, receive, send_wrapper)\n",[889,1458,1460,1463,1466,1469],{"class":667,"line":1459},45,[889,1461,1462],{"class":894},"        except",[889,1464,1465],{"class":939}," Exception",[889,1467,1468],{"class":894}," as",[889,1470,1471],{"class":898}," exc:\n",[889,1473,1475],{"class":667,"line":1474},46,[889,1476,1478],{"class":1477},"sFeEa","            # uvicorn logs \"Exception in ASGI application\" here. The client saw 200 long ago.\n",[889,1480,1482,1485,1487,1489,1492,1494,1497,1500,1503,1505,1508,1510,1513,1515,1518],{"class":667,"line":1481},47,[889,1483,1484],{"class":939},"            TIMELINE",[889,1486,1358],{"class":898},[889,1488,1361],{"class":894},[889,1490,1491],{"class":995},"\"server caught ",[889,1493,1037],{"class":894},[889,1495,1496],{"class":939},"type",[889,1498,1499],{"class":898},"(exc).",[889,1501,1502],{"class":939},"__name__",[889,1504,1043],{"class":894},[889,1506,1507],{"class":995}," AFTER the response: ",[889,1509,1037],{"class":894},[889,1511,1512],{"class":898},"exc",[889,1514,1043],{"class":894},[889,1516,1517],{"class":995},"\"",[889,1519,1139],{"class":898},[889,1521,1523],{"class":667,"line":1522},48,[889,1524,914],{"emptyLinePlaceholder":913},[889,1526,1528],{"class":667,"line":1527},49,[889,1529,914],{"emptyLinePlaceholder":913},[889,1531,1533,1536,1538],{"class":667,"line":1532},50,[889,1534,1535],{"class":898},"app ",[889,1537,952],{"class":894},[889,1539,1540],{"class":898}," FastAPI()\n",[889,1542,1544],{"class":667,"line":1543},51,[889,1545,1546],{"class":898},"app.add_middleware(ServerBoundary)\n",[889,1548,1550],{"class":667,"line":1549},52,[889,1551,914],{"emptyLinePlaceholder":913},[889,1553,1555,1558,1560,1562,1564,1566],{"class":667,"line":1554},53,[889,1556,1557],{"class":939},"DELIVERED",[889,1559,943],{"class":898},[889,1561,946],{"class":939},[889,1563,949],{"class":898},[889,1565,952],{"class":894},[889,1567,955],{"class":898},[889,1569,1571],{"class":667,"line":1570},54,[889,1572,914],{"emptyLinePlaceholder":913},[889,1574,1576],{"class":667,"line":1575},55,[889,1577,914],{"emptyLinePlaceholder":913},[889,1579,1581,1584,1586,1589,1592,1594,1597,1599],{"class":667,"line":1580},56,[889,1582,1583],{"class":894},"async",[889,1585,1241],{"class":894},[889,1587,1588],{"class":1010}," send_receipt",[889,1590,1591],{"class":898},"(order_id: ",[889,1593,946],{"class":939},[889,1595,1596],{"class":898},") -> ",[889,1598,1017],{"class":939},[889,1600,1020],{"class":898},[889,1602,1604],{"class":667,"line":1603},57,[889,1605,1606],{"class":995},"    \"\"\"The task as most people write it: it can raise, and nothing here handles that.\"\"\"\n",[889,1608,1610,1613,1615,1617,1620,1622,1625,1627,1630],{"class":667,"line":1609},58,[889,1611,1612],{"class":939},"    TIMELINE",[889,1614,1358],{"class":898},[889,1616,1361],{"class":894},[889,1618,1619],{"class":995},"\"task send_receipt(",[889,1621,1037],{"class":894},[889,1623,1624],{"class":898},"order_id",[889,1626,1043],{"class":894},[889,1628,1629],{"class":995},") started\"",[889,1631,1139],{"class":898},[889,1633,1635,1638,1641,1643,1646],{"class":667,"line":1634},59,[889,1636,1637],{"class":894},"    if",[889,1639,1640],{"class":898}," order_id ",[889,1642,1283],{"class":894},[889,1644,1645],{"class":995}," \"bad\"",[889,1647,1020],{"class":898},[889,1649,1651,1654,1657,1659,1661,1664,1666,1668,1670,1672],{"class":667,"line":1650},60,[889,1652,1653],{"class":894},"        raise",[889,1655,1656],{"class":939}," RuntimeError",[889,1658,978],{"class":898},[889,1660,1361],{"class":894},[889,1662,1663],{"class":995},"\"receipt provider rejected order ",[889,1665,1037],{"class":894},[889,1667,1624],{"class":898},[889,1669,1043],{"class":894},[889,1671,1517],{"class":995},[889,1673,1139],{"class":898},[889,1675,1677,1680],{"class":667,"line":1676},61,[889,1678,1679],{"class":939},"    DELIVERED",[889,1681,1682],{"class":898},".append(order_id)\n",[889,1684,1686,1688,1690,1692,1694,1696,1698,1700,1703],{"class":667,"line":1685},62,[889,1687,1612],{"class":939},[889,1689,1358],{"class":898},[889,1691,1361],{"class":894},[889,1693,1619],{"class":995},[889,1695,1037],{"class":894},[889,1697,1624],{"class":898},[889,1699,1043],{"class":894},[889,1701,1702],{"class":995},") finished\"",[889,1704,1139],{"class":898},[889,1706,1708],{"class":667,"line":1707},63,[889,1709,914],{"emptyLinePlaceholder":913},[889,1711,1713],{"class":667,"line":1712},64,[889,1714,914],{"emptyLinePlaceholder":913},[889,1716,1718,1721,1723,1726,1729,1732],{"class":667,"line":1717},65,[889,1719,1720],{"class":1010},"@app.post",[889,1722,978],{"class":898},[889,1724,1725],{"class":995},"\"\u002Forders\u002F",[889,1727,1728],{"class":894},"{order_id}",[889,1730,1731],{"class":995},"\u002Funsafe\"",[889,1733,1139],{"class":898},[889,1735,1737,1739,1741,1744,1746,1748,1751,1753,1756,1758],{"class":667,"line":1736},66,[889,1738,1583],{"class":894},[889,1740,1241],{"class":894},[889,1742,1743],{"class":1010}," create_order_unsafe",[889,1745,1591],{"class":898},[889,1747,946],{"class":939},[889,1749,1750],{"class":898},", tasks: BackgroundTasks) -> dict[",[889,1752,946],{"class":939},[889,1754,1755],{"class":898},", ",[889,1757,946],{"class":939},[889,1759,1760],{"class":898},"]:\n",[889,1762,1764],{"class":667,"line":1763},67,[889,1765,1766],{"class":898},"    tasks.add_task(send_receipt, order_id)\n",[889,1768,1770,1773,1775,1778,1781,1784,1786,1789],{"class":667,"line":1769},68,[889,1771,1772],{"class":894},"    return",[889,1774,1046],{"class":898},[889,1776,1777],{"class":995},"\"order_id\"",[889,1779,1780],{"class":898},": order_id, ",[889,1782,1783],{"class":995},"\"status\"",[889,1785,1054],{"class":898},[889,1787,1788],{"class":995},"\"created\"",[889,1790,1791],{"class":898},"}\n",[590,1793,1794],{},"The guarded variant wraps the same callable:",[880,1796,1798],{"className":882,"code":1797,"language":884,"meta":885,"style":885},"def guarded(fn):\n    \"\"\"Wrap a task so a failure is logged with its traceback instead of vanishing.\"\"\"\n\n    async def runner(*args, **kwargs) -> None:\n        try:\n            await fn(*args, **kwargs)\n        except Exception:\n            logger.exception(\"background task %s failed args=%r\", fn.__name__, args)\n\n    runner.__name__ = f\"guarded_{fn.__name__}\"\n    return runner\n\n\n@app.post(\"\u002Forders\u002F{order_id}\u002Fsafe\")\nasync def create_order_safe(order_id: str, tasks: BackgroundTasks) -> dict[str, str]:\n    tasks.add_task(guarded(send_receipt), order_id)\n    return {\"order_id\": order_id, \"status\": \"created\"}\n\n\n@app.get(\"\u002Fevidence\")\nasync def evidence() -> dict[str, object]:\n    return {\"delivered\": DELIVERED, \"timeline\": TIMELINE}\n",[603,1799,1800,1811,1816,1820,1847,1853,1869,1877,1904,1908,1934,1941,1945,1949,1964,1987,1992,2010,2014,2018,2030,2051],{"__ignoreMap":885},[889,1801,1802,1805,1808],{"class":667,"line":891},[889,1803,1804],{"class":894},"def",[889,1806,1807],{"class":1010}," guarded",[889,1809,1810],{"class":898},"(fn):\n",[889,1812,1813],{"class":667,"line":902},[889,1814,1815],{"class":995},"    \"\"\"Wrap a task so a failure is logged with its traceback instead of vanishing.\"\"\"\n",[889,1817,1818],{"class":667,"line":910},[889,1819,914],{"emptyLinePlaceholder":913},[889,1821,1822,1824,1826,1829,1831,1834,1837,1840,1843,1845],{"class":667,"line":917},[889,1823,1238],{"class":894},[889,1825,1241],{"class":894},[889,1827,1828],{"class":1010}," runner",[889,1830,978],{"class":898},[889,1832,1833],{"class":894},"*",[889,1835,1836],{"class":898},"args, ",[889,1838,1839],{"class":894},"**",[889,1841,1842],{"class":898},"kwargs) -> ",[889,1844,1017],{"class":939},[889,1846,1020],{"class":898},[889,1848,1849,1851],{"class":667,"line":931},[889,1850,1444],{"class":894},[889,1852,1020],{"class":898},[889,1854,1855,1857,1860,1862,1864,1866],{"class":667,"line":936},[889,1856,1294],{"class":894},[889,1858,1859],{"class":898}," fn(",[889,1861,1833],{"class":894},[889,1863,1836],{"class":898},[889,1865,1839],{"class":894},[889,1867,1868],{"class":898},"kwargs)\n",[889,1870,1871,1873,1875],{"class":667,"line":958},[889,1872,1462],{"class":894},[889,1874,1465],{"class":939},[889,1876,1020],{"class":898},[889,1878,1879,1882,1885,1888,1891,1894,1896,1899,1901],{"class":667,"line":963},[889,1880,1881],{"class":898},"            logger.exception(",[889,1883,1884],{"class":995},"\"background task ",[889,1886,1887],{"class":894},"%s",[889,1889,1890],{"class":995}," failed args=",[889,1892,1893],{"class":894},"%r",[889,1895,1517],{"class":995},[889,1897,1898],{"class":898},", fn.",[889,1900,1502],{"class":939},[889,1902,1903],{"class":898},", args)\n",[889,1905,1906],{"class":667,"line":968},[889,1907,914],{"emptyLinePlaceholder":913},[889,1909,1910,1913,1915,1918,1920,1923,1925,1928,1930,1932],{"class":667,"line":992},[889,1911,1912],{"class":898},"    runner.",[889,1914,1502],{"class":939},[889,1916,1917],{"class":894}," =",[889,1919,1031],{"class":894},[889,1921,1922],{"class":995},"\"guarded_",[889,1924,1037],{"class":894},[889,1926,1927],{"class":898},"fn.",[889,1929,1502],{"class":939},[889,1931,1043],{"class":894},[889,1933,1064],{"class":995},[889,1935,1936,1938],{"class":667,"line":999},[889,1937,1772],{"class":894},[889,1939,1940],{"class":898}," runner\n",[889,1942,1943],{"class":667,"line":1004},[889,1944,914],{"emptyLinePlaceholder":913},[889,1946,1947],{"class":667,"line":1023},[889,1948,914],{"emptyLinePlaceholder":913},[889,1950,1951,1953,1955,1957,1959,1962],{"class":667,"line":1067},[889,1952,1720],{"class":1010},[889,1954,978],{"class":898},[889,1956,1725],{"class":995},[889,1958,1728],{"class":894},[889,1960,1961],{"class":995},"\u002Fsafe\"",[889,1963,1139],{"class":898},[889,1965,1966,1968,1970,1973,1975,1977,1979,1981,1983,1985],{"class":667,"line":1076},[889,1967,1583],{"class":894},[889,1969,1241],{"class":894},[889,1971,1972],{"class":1010}," create_order_safe",[889,1974,1591],{"class":898},[889,1976,946],{"class":939},[889,1978,1750],{"class":898},[889,1980,946],{"class":939},[889,1982,1755],{"class":898},[889,1984,946],{"class":939},[889,1986,1760],{"class":898},[889,1988,1989],{"class":667,"line":1106},[889,1990,1991],{"class":898},"    tasks.add_task(guarded(send_receipt), order_id)\n",[889,1993,1994,1996,1998,2000,2002,2004,2006,2008],{"class":667,"line":1115},[889,1995,1772],{"class":894},[889,1997,1046],{"class":898},[889,1999,1777],{"class":995},[889,2001,1780],{"class":898},[889,2003,1783],{"class":995},[889,2005,1054],{"class":898},[889,2007,1788],{"class":995},[889,2009,1791],{"class":898},[889,2011,2012],{"class":667,"line":1120},[889,2013,914],{"emptyLinePlaceholder":913},[889,2015,2016],{"class":667,"line":1125},[889,2017,914],{"emptyLinePlaceholder":913},[889,2019,2020,2023,2025,2028],{"class":667,"line":1142},[889,2021,2022],{"class":1010},"@app.get",[889,2024,978],{"class":898},[889,2026,2027],{"class":995},"\"\u002Fevidence\"",[889,2029,1139],{"class":898},[889,2031,2032,2034,2036,2039,2042,2044,2046,2049],{"class":667,"line":1153},[889,2033,1583],{"class":894},[889,2035,1241],{"class":894},[889,2037,2038],{"class":1010}," evidence",[889,2040,2041],{"class":898},"() -> dict[",[889,2043,946],{"class":939},[889,2045,1755],{"class":898},[889,2047,2048],{"class":939},"object",[889,2050,1760],{"class":898},[889,2052,2053,2055,2057,2060,2062,2064,2066,2069,2071,2073],{"class":667,"line":1159},[889,2054,1772],{"class":894},[889,2056,1046],{"class":898},[889,2058,2059],{"class":995},"\"delivered\"",[889,2061,1054],{"class":898},[889,2063,1557],{"class":939},[889,2065,1755],{"class":898},[889,2067,2068],{"class":995},"\"timeline\"",[889,2070,1054],{"class":898},[889,2072,940],{"class":939},[889,2074,1791],{"class":898},[590,2076,2077],{},"This is the real output of running that app against a successful order, a failing unguarded order, and a failing guarded order:",[880,2079,2083],{"className":2080,"code":2082,"language":679,"meta":885},[2081],"language-text","$ POST \u002Forders\u002Fok-1\u002Funsafe\n200 OK\n{\n  \"order_id\": \"ok-1\",\n  \"status\": \"created\"\n}\n\n$ POST \u002Forders\u002Fbad\u002Funsafe\n200 OK\n{\n  \"order_id\": \"bad\",\n  \"status\": \"created\"\n}\n\n$ POST \u002Forders\u002Fbad\u002Fsafe\n200 OK\n{\n  \"order_id\": \"bad\",\n  \"status\": \"created\"\n}\n\n$ GET \u002Fevidence\n200 OK\n{\n  \"delivered\": [\n    \"ok-1\"\n  ],\n  \"timeline\": [\n    \"response status 200 sent to client\",\n    \"response body flushed: client request is complete\",\n    \"task send_receipt(ok-1) started\",\n    \"task send_receipt(ok-1) finished\",\n    \"response status 200 sent to client\",\n    \"response body flushed: client request is complete\",\n    \"task send_receipt(bad) started\",\n    \"server caught RuntimeError AFTER the response: receipt provider rejected order bad\",\n    \"response status 200 sent to client\",\n    \"response body flushed: client request is complete\",\n    \"task send_receipt(bad) started\",\n    \"log ERROR app.tasks: background task send_receipt failed args=('bad',) | RuntimeError: receipt provider rejected order bad\"\n  ]\n}\n",[603,2084,2082],{"__ignoreMap":885},[590,2086,2087,2088,2091,2092,2094,2095,2098,2099,2101],{},"Read the timeline carefully. In every case the status line and the body are flushed ",[608,2089,2090],{},"before"," the task starts. The failing order still answered ",[603,2093,636],{},". The only difference between the second and third requests is where the evidence ended up: in the unguarded case the ",[603,2096,2097],{},"RuntimeError"," reached the server boundary, where in production it becomes a ",[603,2100,617],{}," traceback nobody has alerted on; in the guarded case it reached the application logger with the task name and its arguments attached.",[783,2103,2105],{"id":2104},"the-fix","The Fix",[590,2107,2108],{},"The decorator above is the minimum. In a real service, make the guard do three jobs: log with context, count the failure, and decide whether the work needs a durable home.",[880,2110,2112],{"className":882,"code":2111,"language":884,"meta":885,"style":885},"import logging\n\nlogger = logging.getLogger(\"app.tasks\")\n\n\ndef background_safe(fn):\n    \"\"\"Decorate task callables so a failure is observable and cannot cancel sibling tasks.\"\"\"\n\n    async def runner(*args, **kwargs):\n        try:\n            return await fn(*args, **kwargs)\n        except Exception:\n            logger.exception(\n                \"background_task_failed\",\n                extra={\"task\": fn.__name__, \"args\": repr(args)},\n            )\n            TASK_FAILURES.labels(task=fn.__name__).inc()   # Alert on this, not on the log.\n\n    runner.__name__ = fn.__name__\n    return runner\n\n\n@background_safe\nasync def send_receipt(order_id: str) -> None:\n    ...\n",[603,2113,2114,2120,2124,2136,2140,2144,2153,2158,2162,2181,2187,2205,2213,2218,2226,2256,2261,2284,2288,2302,2308,2312,2316,2321,2339],{"__ignoreMap":885},[889,2115,2116,2118],{"class":667,"line":891},[889,2117,895],{"class":894},[889,2119,899],{"class":898},[889,2121,2122],{"class":667,"line":902},[889,2123,914],{"emptyLinePlaceholder":913},[889,2125,2126,2128,2130,2132,2134],{"class":667,"line":910},[889,2127,1128],{"class":898},[889,2129,952],{"class":894},[889,2131,1133],{"class":898},[889,2133,1136],{"class":995},[889,2135,1139],{"class":898},[889,2137,2138],{"class":667,"line":917},[889,2139,914],{"emptyLinePlaceholder":913},[889,2141,2142],{"class":667,"line":931},[889,2143,914],{"emptyLinePlaceholder":913},[889,2145,2146,2148,2151],{"class":667,"line":936},[889,2147,1804],{"class":894},[889,2149,2150],{"class":1010}," background_safe",[889,2152,1810],{"class":898},[889,2154,2155],{"class":667,"line":958},[889,2156,2157],{"class":995},"    \"\"\"Decorate task callables so a failure is observable and cannot cancel sibling tasks.\"\"\"\n",[889,2159,2160],{"class":667,"line":963},[889,2161,914],{"emptyLinePlaceholder":913},[889,2163,2164,2166,2168,2170,2172,2174,2176,2178],{"class":667,"line":968},[889,2165,1238],{"class":894},[889,2167,1241],{"class":894},[889,2169,1828],{"class":1010},[889,2171,978],{"class":898},[889,2173,1833],{"class":894},[889,2175,1836],{"class":898},[889,2177,1839],{"class":894},[889,2179,2180],{"class":898},"kwargs):\n",[889,2182,2183,2185],{"class":667,"line":992},[889,2184,1444],{"class":894},[889,2186,1020],{"class":898},[889,2188,2189,2192,2195,2197,2199,2201,2203],{"class":667,"line":999},[889,2190,2191],{"class":894},"            return",[889,2193,2194],{"class":894}," await",[889,2196,1859],{"class":898},[889,2198,1833],{"class":894},[889,2200,1836],{"class":898},[889,2202,1839],{"class":894},[889,2204,1868],{"class":898},[889,2206,2207,2209,2211],{"class":667,"line":1004},[889,2208,1462],{"class":894},[889,2210,1465],{"class":939},[889,2212,1020],{"class":898},[889,2214,2215],{"class":667,"line":1023},[889,2216,2217],{"class":898},"            logger.exception(\n",[889,2219,2220,2223],{"class":667,"line":1067},[889,2221,2222],{"class":995},"                \"background_task_failed\"",[889,2224,2225],{"class":898},",\n",[889,2227,2228,2231,2233,2235,2238,2241,2243,2245,2248,2250,2253],{"class":667,"line":1076},[889,2229,2230],{"class":974},"                extra",[889,2232,952],{"class":894},[889,2234,1037],{"class":898},[889,2236,2237],{"class":995},"\"task\"",[889,2239,2240],{"class":898},": fn.",[889,2242,1502],{"class":939},[889,2244,1755],{"class":898},[889,2246,2247],{"class":995},"\"args\"",[889,2249,1054],{"class":898},[889,2251,2252],{"class":939},"repr",[889,2254,2255],{"class":898},"(args)},\n",[889,2257,2258],{"class":667,"line":1106},[889,2259,2260],{"class":898},"            )\n",[889,2262,2263,2266,2269,2272,2274,2276,2278,2281],{"class":667,"line":1115},[889,2264,2265],{"class":939},"            TASK_FAILURES",[889,2267,2268],{"class":898},".labels(",[889,2270,2271],{"class":974},"task",[889,2273,952],{"class":894},[889,2275,1927],{"class":898},[889,2277,1502],{"class":939},[889,2279,2280],{"class":898},").inc()   ",[889,2282,2283],{"class":1477},"# Alert on this, not on the log.\n",[889,2285,2286],{"class":667,"line":1120},[889,2287,914],{"emptyLinePlaceholder":913},[889,2289,2290,2292,2294,2296,2299],{"class":667,"line":1125},[889,2291,1912],{"class":898},[889,2293,1502],{"class":939},[889,2295,1917],{"class":894},[889,2297,2298],{"class":898}," fn.",[889,2300,2301],{"class":939},"__name__\n",[889,2303,2304,2306],{"class":667,"line":1142},[889,2305,1772],{"class":894},[889,2307,1940],{"class":898},[889,2309,2310],{"class":667,"line":1153},[889,2311,914],{"emptyLinePlaceholder":913},[889,2313,2314],{"class":667,"line":1159},[889,2315,914],{"emptyLinePlaceholder":913},[889,2317,2318],{"class":667,"line":1170},[889,2319,2320],{"class":1010},"@background_safe\n",[889,2322,2323,2325,2327,2329,2331,2333,2335,2337],{"class":667,"line":1175},[889,2324,1583],{"class":894},[889,2326,1241],{"class":894},[889,2328,1588],{"class":1010},[889,2330,1591],{"class":898},[889,2332,946],{"class":939},[889,2334,1596],{"class":898},[889,2336,1017],{"class":939},[889,2338,1020],{"class":898},[889,2340,2341],{"class":667,"line":1180},[889,2342,2343],{"class":939},"    ...\n",[590,2345,2346],{},"Because the guard swallows the exception, the tasks queued behind it still run — that alone fixes a class of bug where a flaky third-party call quietly disabled the rest of your post-response work.",[590,2348,2349,2350,2353,2354,2358,2359,2363],{},"The counter matters more than the log line. A log you have to go looking for is not observability; a ",[603,2351,2352],{},"task_failures_total"," counter with a rate alert is. Wiring that up is covered in ",[639,2355,2357],{"href":2356},"\u002Fasync-background-tasks-observability\u002Fobservability-and-tracing\u002Fprometheus-metrics-for-fastapi\u002F","Prometheus metrics for FastAPI",", and giving the log line the same request id as the originating request is covered in ",[639,2360,2362],{"href":2361},"\u002Fasync-background-tasks-observability\u002Fobservability-and-tracing\u002Fcorrelating-logs-traces-and-errors\u002F","correlating logs, traces and errors"," — worth doing, because a background task failure with no request id is nearly impossible to trace back to the user it affected.",[590,2365,2366,2367,2370,2371,2374],{},"Do not forget the second half of the guard's contract: swallowing the exception means the work is now ",[608,2368,2369],{},"definitely"," lost rather than ",[608,2372,2373],{},"probably"," lost. That is fine for a metrics ping and wrong for a payment capture.",[783,2376,2378],{"id":2377},"verification","Verification",[590,2380,2381],{},"Two checks belong in your test suite. The first asserts the endpoint responds successfully even when the task will fail, which documents the behaviour rather than leaving it as a surprise:",[880,2383,2385],{"className":882,"code":2384,"language":884,"meta":885,"style":885},"def test_response_succeeds_even_when_task_fails(client, caplog):\n    response = client.post(\"\u002Forders\u002Fbad\u002Fsafe\")\n    assert response.status_code == 200            # The client is never told.\n    assert \"background_task_failed\" in caplog.text  # But the failure is recorded.\n",[603,2386,2387,2397,2412,2428],{"__ignoreMap":885},[889,2388,2389,2391,2394],{"class":667,"line":891},[889,2390,1804],{"class":894},[889,2392,2393],{"class":1010}," test_response_succeeds_even_when_task_fails",[889,2395,2396],{"class":898},"(client, caplog):\n",[889,2398,2399,2402,2404,2407,2410],{"class":667,"line":902},[889,2400,2401],{"class":898},"    response ",[889,2403,952],{"class":894},[889,2405,2406],{"class":898}," client.post(",[889,2408,2409],{"class":995},"\"\u002Forders\u002Fbad\u002Fsafe\"",[889,2411,1139],{"class":898},[889,2413,2414,2417,2420,2422,2425],{"class":667,"line":910},[889,2415,2416],{"class":894},"    assert",[889,2418,2419],{"class":898}," response.status_code ",[889,2421,1283],{"class":894},[889,2423,2424],{"class":939}," 200",[889,2426,2427],{"class":1477},"            # The client is never told.\n",[889,2429,2430,2432,2435,2438,2441],{"class":667,"line":917},[889,2431,2416],{"class":894},[889,2433,2434],{"class":995}," \"background_task_failed\"",[889,2436,2437],{"class":894}," in",[889,2439,2440],{"class":898}," caplog.text  ",[889,2442,2443],{"class":1477},"# But the failure is recorded.\n",[590,2445,2446],{},"The second asserts the guard actually protects siblings:",[880,2448,2450],{"className":882,"code":2449,"language":884,"meta":885,"style":885},"def test_failing_task_does_not_cancel_the_next_one(client):\n    client.post(\"\u002Forders\u002Fbad\u002Ftwo-tasks\")\n    assert AUDIT_WRITES == [\"order bad\"]   # Ran despite the earlier task raising.\n",[603,2451,2452,2462,2472],{"__ignoreMap":885},[889,2453,2454,2456,2459],{"class":667,"line":891},[889,2455,1804],{"class":894},[889,2457,2458],{"class":1010}," test_failing_task_does_not_cancel_the_next_one",[889,2460,2461],{"class":898},"(client):\n",[889,2463,2464,2467,2470],{"class":667,"line":902},[889,2465,2466],{"class":898},"    client.post(",[889,2468,2469],{"class":995},"\"\u002Forders\u002Fbad\u002Ftwo-tasks\"",[889,2471,1139],{"class":898},[889,2473,2474,2476,2479,2482,2485,2488,2491],{"class":667,"line":910},[889,2475,2416],{"class":894},[889,2477,2478],{"class":939}," AUDIT_WRITES",[889,2480,2481],{"class":894}," ==",[889,2483,2484],{"class":898}," [",[889,2486,2487],{"class":995},"\"order bad\"",[889,2489,2490],{"class":898},"]   ",[889,2492,2493],{"class":1477},"# Ran despite the earlier task raising.\n",[590,2495,2496,2497,2500,2501,2503],{},"Note that ",[603,2498,2499],{},"TestClient"," re-raises exceptions from background tasks by default, which is why the unguarded version of this test fails loudly in CI while passing silently in production. That asymmetry is itself a useful signal: if a background-task test blows up under ",[603,2502,2499],{},", you have found an unguarded task.",[590,2505,2506,2507,2509],{},"In production, the check is a dashboard panel: request rate for the endpoint next to ",[603,2508,2352],{}," for its task. If the first is flat and the second is climbing, you are returning success while doing nothing.",[783,2511,2513],{"id":2512},"trade-offs-and-when-not-to-use-this","Trade-offs and When Not To Use This",[590,2515,2516,2517,2519],{},"Guarding tasks makes failures visible, but it does not make them recoverable. ",[603,2518,605],{}," has no persistence: the queue lives in a Python object attached to a response in one process. A rolling deploy that terminates the worker between the response and the task drops everything queued, and no amount of exception handling changes that.",[590,2521,2522,2523,2525,2526,2529,2530,818],{},"Use ",[603,2524,605],{}," when the work is short, idempotent-by-accident, and genuinely optional — cache warming, a best-effort notification, an analytics ping. Move to a durable queue when losing the work costs money or trust. The comparison of the options is in ",[639,2527,261],{"href":2528},"\u002Fasync-background-tasks-observability\u002Fbackground-task-processing\u002Ffastapi-backgroundtasks-vs-celery-vs-arq\u002F",", and once you are on a queue the next problem is retry safety, covered in ",[639,2531,2533],{"href":2532},"\u002Fasync-background-tasks-observability\u002Fbackground-task-processing\u002Fretry-and-idempotency-for-tasks\u002F","retry and idempotency for tasks",[590,2535,2536,2537,2540],{},"There is also a capacity trade-off. Because the task runs in the same process on the same event loop, a slow task holds that worker's concurrency slot after the client has gone. Under load you can end up with a queue of invisible post-response work that shows up as latency on ",[608,2538,2539],{},"other"," requests, with nothing in your request metrics to explain it. If your tasks are not sub-second, they do not belong here regardless of how well you log them.",[783,2542,2544],{"id":2543},"faq","FAQ",[590,2546,2547,2550],{},[593,2548,2549],{},"Why does my client get a 200 when the background task raised?","\nBecause the response is already sent before the task runs. Starlette returns the response to the ASGI server first, then awaits the background tasks, so by the time your task raises there is no status code left to change and no body left to write.",[590,2552,2553,2556],{},[593,2554,2555],{},"Where does the exception from a BackgroundTask actually go?","\nIt propagates out of the ASGI application into the server. Uvicorn logs it as 'Exception in ASGI application' on the uvicorn.error logger. If that logger is not configured, filtered out, or shipped, the failure is invisible.",[590,2558,2559,2562],{},[593,2560,2561],{},"Do the remaining background tasks run if one of them raises?","\nNo. Starlette awaits the tasks in order in a single coroutine, so the first exception aborts the rest of the list. A failing audit-log task will stop a later email task from ever running.",[590,2564,2565,2568],{},[593,2566,2567],{},"Should I add a global exception handler to catch background task failures?","\nIt will not help. Exception handlers wrap the request\u002Fresponse cycle, and background tasks run after that cycle has completed, so the handler is no longer in the call stack. Wrap the task callable itself instead.",[590,2570,2571,2574],{},[593,2572,2573],{},"When should I move off BackgroundTasks entirely?","\nAs soon as the work must not be lost. BackgroundTasks has no persistence, no retry, and no dead-letter destination, so a deploy or a crash between the response and the task drops the work with no record of it.",[783,2576,2578],{"id":2577},"related-reading","Related Reading",[597,2580,2581,2590,2598,2607],{},[600,2582,2583,2586,2587,2589],{},[593,2584,2585],{},"Up to the topic:"," ",[639,2588,642],{"href":641}," explains where in-process tasks sit relative to real queues.",[600,2591,2592,2586,2595,2597],{},[593,2593,2594],{},"Choosing a runner:",[639,2596,261],{"href":2528}," compares durability, retry and operational cost.",[600,2599,2600,2586,2603,2606],{},[593,2601,2602],{},"Once tasks retry:",[639,2604,2605],{"href":2532},"Retry and idempotency for tasks"," covers making a re-run harmless.",[600,2608,2609,2586,2612,863,2614,818],{},[593,2610,2611],{},"Making failures visible:",[639,2613,2357],{"href":2356},[639,2615,2617],{"href":2616},"\u002Fasync-background-tasks-observability\u002Fobservability-and-tracing\u002Fstructured-json-logging-with-request-ids\u002F","structured JSON logging with request IDs",[2619,2620,2621],"style",{},"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 .sYEJz, html code.shiki .sYEJz{--shiki-default:#032563}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":885,"searchDepth":902,"depth":902,"links":2623},[2624,2625,2626,2627,2628,2629,2630],{"id":785,"depth":902,"text":786},{"id":874,"depth":902,"text":875},{"id":2104,"depth":902,"text":2105},{"id":2377,"depth":902,"text":2378},{"id":2512,"depth":902,"text":2513},{"id":2543,"depth":902,"text":2544},{"id":2577,"depth":902,"text":2578},"2026-07-20","A raising BackgroundTask still returns 200 and can vanish from your logs. See the real timeline, why Starlette swallows it, and how to log task failures.","md",[2635,2637,2639,2641,2643],{"q":2549,"a":2636},"Because the response is already sent before the task runs. Starlette returns the response to the ASGI server first, then awaits the background tasks, so by the time your task raises there is no status code left to change and no body left to write.",{"q":2555,"a":2638},"It propagates out of the ASGI application into the server. Uvicorn logs it as 'Exception in ASGI application' on the uvicorn.error logger. If that logger is not configured, filtered out, or shipped, the failure is invisible.",{"q":2561,"a":2640},"No. Starlette awaits the tasks in order in a single coroutine, so the first exception aborts the rest of the list. A failing audit-log task will stop a later email task from ever running.",{"q":2567,"a":2642},"It will not help. Exception handlers wrap the request\u002Fresponse cycle, and background tasks run after that cycle has completed, so the handler is no longer in the call stack. Wrap the task callable itself instead.",{"q":2573,"a":2644},"As soon as the work must not be lost. BackgroundTasks has no persistence, no retry, and no dead-letter destination, so a deploy or a crash between the response and the task drops the work with no record of it.",null,{"slug":2647,"breadcrumb":2648},"when-backgroundtasks-silently-fails",[2649,2652,2655,2656],{"label":2650,"path":2651},"Home","\u002F",{"label":2653,"path":2654},"Async, Background Tasks & Observability","\u002Fasync-background-tasks-observability\u002F",{"label":642,"path":641},{"label":2657,"path":2658},"When BackgroundTasks Silently Fails","\u002Fasync-background-tasks-observability\u002Fbackground-task-processing\u002Fwhen-backgroundtasks-silently-fails\u002F",{"title":279,"description":2632},"article","l5KF22NeH7WjFF0YZ_4RrRiZ_ZQ0P-VqGbHXGfp7KSw",[2645,2645],1784588203038]