[{"data":1,"prerenderedAt":2723},["ShallowReactive",2],{"nav":3,"page-\u002Fasync-background-tasks-observability\u002Fcaching-strategies\u002Fcaching-dependency-results\u002F":580,"surround-\u002Fasync-background-tasks-observability\u002Fcaching-strategies\u002Fcaching-dependency-results\u002F":2722},[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":297,"body":582,"dateModified":2691,"datePublished":2691,"description":2692,"extension":2693,"faq":2694,"howto":2705,"meta":2706,"navigation":853,"path":298,"seo":2719,"stem":299,"type":2720,"__hash__":2721},"content\u002Fasync-background-tasks-observability\u002Fcaching-strategies\u002Fcaching-dependency-results\u002Findex.md",{"type":583,"value":584,"toc":2680},"minimark",[585,589,596,626,635,770,775,790,806,809,813,820,1333,1339,1570,1573,1580,1583,1587,1590,1701,1708,1711,1869,1880,1886,1903,1906,2109,2120,2126,2150,2154,2157,2160,2308,2325,2335,2339,2355,2358,2408,2419,2423,2426,2571,2574,2578,2581,2584,2603,2607,2613,2619,2625,2631,2637,2641,2676],[586,587,297],"h1",{"id":588},"caching-dependency-results-in-fastapi",[590,591,592],"p",{},[593,594,595],"strong",{},"Key takeaways:",[597,598,599,603,610,613,616],"ul",{},[600,601,602],"li",{},"FastAPI already caches a dependency per request; a value used by five sub-dependencies is computed once.",[600,604,605,609],{},[606,607,608],"code",{},"use_cache=False"," is per declaration site — you must set it everywhere in the graph to actually force re-execution.",[600,611,612],{},"Process-scoped caches survive across requests but are per worker, so invalidation is only ever local.",[600,614,615],{},"A cache key that omits the user is a data leak: the first caller's permissions get served to everyone.",[600,617,618,621,622,625],{},[606,619,620],{},"functools.lru_cache"," on an ",[606,623,624],{},"async def"," dependency caches the coroutine, not the result, and breaks on the second await.",[590,627,628,629,634],{},"A permissions lookup shows up three times in one request's dependency graph and you want to know whether that is three database round trips. Then you want to know whether you can keep the answer between requests, and what it costs you when you do. This page is part of ",[630,631,633],"a",{"href":632},"\u002Fasync-background-tasks-observability\u002Fcaching-strategies\u002F","Caching Strategies",", and it deals with the dependency layer specifically — the cache that sits closest to your business logic and is the easiest one to get subtly wrong.",[636,637,638,766],"figure",{},[639,640,648,649,648,653,648,657,648,666,648,673,648,678,648,682,648,690,648,696,648,699,648,704,648,708,648,712,648,715,648,719,648,723,648,727,648,731,648,736,648,738,648,741,648,745,648,749,648,753,648,757,648,761],"svg",{"viewBox":641,"role":642,"ariaLabelledBy":643,"xmlns":646,"style":647},"0 0 720 300","img",[644,645],"cachedep-title","cachedep-desc","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;height:auto;max-width:720px;margin:2rem 0","\n  ",[650,651,652],"title",{"id":644},"Three cache scopes for a dependency result",[654,655,656],"desc",{"id":645},"Three stacked bands showing request scope lasting one request, process scope shared by requests within one worker, and Redis shared across all workers.",[658,659],"rect",{"x":660,"y":661,"width":662,"height":663,"rx":664,"style":665},"20","24","680","76","10","fill:none;stroke:currentColor;stroke-width:1.4px",[667,668,672],"text",{"x":669,"y":670,"style":671},"36","48","text-anchor:start;fill:currentColor;font:600 13px sans-serif","request scope",[667,674,677],{"x":669,"y":675,"style":676},"66","text-anchor:start;fill:currentColor;font:400 12px sans-serif","built in, use_cache",[667,679,681],{"x":669,"y":680,"style":676},"84","dies with the request",[658,683],{"x":684,"y":685,"width":686,"height":687,"rx":688,"style":689},"230","40","130","44","7","fill:#00796B;stroke:#00796B;stroke-width:1.4px",[667,691,695],{"x":692,"y":693,"style":694},"295","67","text-anchor:middle;fill:#ffffff;font:600 13px sans-serif","request A",[658,697],{"x":698,"y":685,"width":686,"height":687,"rx":688,"style":665},"380",[667,700,703],{"x":701,"y":693,"style":702},"445","text-anchor:middle;fill:currentColor;font:600 13px sans-serif","request B",[658,705],{"x":706,"y":685,"width":707,"height":687,"rx":688,"style":665},"530","150",[667,709,711],{"x":710,"y":693,"style":702},"605","no sharing",[658,713],{"x":660,"y":714,"width":662,"height":663,"rx":664,"style":665},"112",[667,716,718],{"x":669,"y":717,"style":671},"136","process scope",[667,720,722],{"x":669,"y":721,"style":676},"154","module global",[667,724,726],{"x":669,"y":725,"style":676},"172","one copy per worker",[658,728],{"x":684,"y":729,"width":730,"height":687,"rx":688,"style":689},"128","280",[667,732,735],{"x":733,"y":734,"style":694},"370","155","worker 1 requests",[658,737],{"x":706,"y":729,"width":707,"height":687,"rx":688,"style":665},[667,739,740],{"x":710,"y":734,"style":702},"worker 2: cold",[658,742],{"x":660,"y":743,"width":662,"height":663,"rx":664,"style":744},"200","fill:none;stroke:#00796B;stroke-width:1.6px",[667,746,748],{"x":669,"y":747,"style":671},"224","Redis scope",[667,750,752],{"x":669,"y":751,"style":676},"242","shared, invalidatable",[667,754,756],{"x":669,"y":755,"style":676},"260","costs a round trip",[658,758],{"x":684,"y":759,"width":760,"height":687,"rx":688,"style":689},"216","450",[667,762,765],{"x":763,"y":764,"style":694},"455","243","every worker, one copy",[767,768,769],"figcaption",{},"The scope you pick decides who can see a stale value and who can invalidate it.",[771,772,774],"h2",{"id":773},"what-fastapi-already-does","What FastAPI Already Does",[590,776,777,778,781,782,785,786,789],{},"FastAPI keeps a per-request cache of dependency results, keyed by the pair ",[606,779,780],{},"(callable, security_scopes)",". It lives in the ",[606,783,784],{},"dependency_cache"," dict that ",[606,787,788],{},"solve_dependencies"," threads through the resolution of one request's graph. When the same callable is reached again — from a second sub-dependency, from a router-level dependency, from the path operation itself — the stored value is returned instead of calling it again.",[590,791,792,793,796,797,801,802,805],{},"Two conditions control that. The dependency must be declared with ",[606,794,795],{},"use_cache=True",", which is the default, and it must be the ",[798,799,800],"em",{},"same callable object",". Two separately defined functions with identical bodies are different keys; so is the same function wrapped twice by different decorators. This is why a dependency you refactored into a factory (",[606,803,804],{},"def make_dep(x): def dep(): ...; return dep",") suddenly starts running many times per request — every call to the factory produces a new object.",[590,807,808],{},"The important limit is scope. This cache is created when the request starts and discarded when it ends. It exists to stop redundant work inside one request, not to save work across requests. Anything longer-lived is yours to build.",[771,810,812],{"id":811},"measuring-the-three-scopes","Measuring the Three Scopes",[590,814,815,816,819],{},"The example below counts loads for real. ",[606,817,818],{},"load_permissions"," increments a counter on every call, so the transcript shows exactly how many times each strategy hit the \"database\".",[821,822,827],"pre",{"className":823,"code":824,"language":825,"meta":826,"style":826},"language-python shiki shiki-themes github-light-high-contrast","from typing import Annotated\n\nfrom fastapi import Depends, FastAPI\n\nSTATS = {\"db_loads\": 0, \"settings_loads\": 0, \"process_cache_hits\": 0, \"process_cache_misses\": 0}\n\n\nasync def load_permissions(user_id: str) -> list[str]:\n    \"\"\"Stand-in for the expensive call: one 'query' per invocation, counted.\"\"\"\n    STATS[\"db_loads\"] += 1\n    return [\"admin\"] if user_id == \"u1\" else [\"read\"]\n\n\nasync def current_user(user: str = \"u1\") -> str:\n    \"\"\"In a real app this reads a token; here it is a query param so the run is reproducible.\"\"\"\n    return user\n\n\nasync def permissions(user: Annotated[str, Depends(current_user)]) -> list[str]:\n    return await load_permissions(user)\n\n\nasync def can_write(perms: Annotated[list[str], Depends(permissions)]) -> bool:\n    return \"admin\" in perms\n\n\nasync def can_delete(perms: Annotated[list[str], Depends(permissions)]) -> bool:\n    return \"admin\" in perms\n\n\n@app.get(\"\u002Frequest-scoped\")\nasync def request_scoped(\n    perms: Annotated[list[str], Depends(permissions)],\n    write: Annotated[bool, Depends(can_write)],\n    delete: Annotated[bool, Depends(can_delete)],\n) -> dict[str, object]:\n    return {\"perms\": perms, \"write\": write, \"delete\": delete, \"db_loads\": STATS[\"db_loads\"]}\n","python","",[606,828,829,848,855,868,873,927,932,937,964,970,990,1027,1032,1037,1064,1070,1078,1083,1088,1110,1121,1126,1131,1154,1168,1173,1178,1198,1209,1214,1219,1234,1247,1258,1269,1280,1295],{"__ignoreMap":826},[830,831,834,838,842,845],"span",{"class":832,"line":833},"line",1,[830,835,837],{"class":836},"sTJeM","from",[830,839,841],{"class":840},"sigWx"," typing ",[830,843,844],{"class":836},"import",[830,846,847],{"class":840}," Annotated\n",[830,849,851],{"class":832,"line":850},2,[830,852,854],{"emptyLinePlaceholder":853},true,"\n",[830,856,858,860,863,865],{"class":832,"line":857},3,[830,859,837],{"class":836},[830,861,862],{"class":840}," fastapi ",[830,864,844],{"class":836},[830,866,867],{"class":840}," Depends, FastAPI\n",[830,869,871],{"class":832,"line":870},4,[830,872,854],{"emptyLinePlaceholder":853},[830,874,876,880,883,886,890,893,896,899,902,904,906,908,911,913,915,917,920,922,924],{"class":832,"line":875},5,[830,877,879],{"class":878},"sacAq","STATS",[830,881,882],{"class":836}," =",[830,884,885],{"class":840}," {",[830,887,889],{"class":888},"sYEJz","\"db_loads\"",[830,891,892],{"class":840},": ",[830,894,895],{"class":878},"0",[830,897,898],{"class":840},", ",[830,900,901],{"class":888},"\"settings_loads\"",[830,903,892],{"class":840},[830,905,895],{"class":878},[830,907,898],{"class":840},[830,909,910],{"class":888},"\"process_cache_hits\"",[830,912,892],{"class":840},[830,914,895],{"class":878},[830,916,898],{"class":840},[830,918,919],{"class":888},"\"process_cache_misses\"",[830,921,892],{"class":840},[830,923,895],{"class":878},[830,925,926],{"class":840},"}\n",[830,928,930],{"class":832,"line":929},6,[830,931,854],{"emptyLinePlaceholder":853},[830,933,935],{"class":832,"line":934},7,[830,936,854],{"emptyLinePlaceholder":853},[830,938,940,943,946,950,953,956,959,961],{"class":832,"line":939},8,[830,941,942],{"class":836},"async",[830,944,945],{"class":836}," def",[830,947,949],{"class":948},"s3dhs"," load_permissions",[830,951,952],{"class":840},"(user_id: ",[830,954,955],{"class":878},"str",[830,957,958],{"class":840},") -> list[",[830,960,955],{"class":878},[830,962,963],{"class":840},"]:\n",[830,965,967],{"class":832,"line":966},9,[830,968,969],{"class":888},"    \"\"\"Stand-in for the expensive call: one 'query' per invocation, counted.\"\"\"\n",[830,971,973,976,979,981,984,987],{"class":832,"line":972},10,[830,974,975],{"class":878},"    STATS",[830,977,978],{"class":840},"[",[830,980,889],{"class":888},[830,982,983],{"class":840},"] ",[830,985,986],{"class":836},"+=",[830,988,989],{"class":878}," 1\n",[830,991,993,996,999,1002,1004,1007,1010,1013,1016,1019,1021,1024],{"class":832,"line":992},11,[830,994,995],{"class":836},"    return",[830,997,998],{"class":840}," [",[830,1000,1001],{"class":888},"\"admin\"",[830,1003,983],{"class":840},[830,1005,1006],{"class":836},"if",[830,1008,1009],{"class":840}," user_id ",[830,1011,1012],{"class":836},"==",[830,1014,1015],{"class":888}," \"u1\"",[830,1017,1018],{"class":836}," else",[830,1020,998],{"class":840},[830,1022,1023],{"class":888},"\"read\"",[830,1025,1026],{"class":840},"]\n",[830,1028,1030],{"class":832,"line":1029},12,[830,1031,854],{"emptyLinePlaceholder":853},[830,1033,1035],{"class":832,"line":1034},13,[830,1036,854],{"emptyLinePlaceholder":853},[830,1038,1040,1042,1044,1047,1050,1052,1054,1056,1059,1061],{"class":832,"line":1039},14,[830,1041,942],{"class":836},[830,1043,945],{"class":836},[830,1045,1046],{"class":948}," current_user",[830,1048,1049],{"class":840},"(user: ",[830,1051,955],{"class":878},[830,1053,882],{"class":836},[830,1055,1015],{"class":888},[830,1057,1058],{"class":840},") -> ",[830,1060,955],{"class":878},[830,1062,1063],{"class":840},":\n",[830,1065,1067],{"class":832,"line":1066},15,[830,1068,1069],{"class":888},"    \"\"\"In a real app this reads a token; here it is a query param so the run is reproducible.\"\"\"\n",[830,1071,1073,1075],{"class":832,"line":1072},16,[830,1074,995],{"class":836},[830,1076,1077],{"class":840}," user\n",[830,1079,1081],{"class":832,"line":1080},17,[830,1082,854],{"emptyLinePlaceholder":853},[830,1084,1086],{"class":832,"line":1085},18,[830,1087,854],{"emptyLinePlaceholder":853},[830,1089,1091,1093,1095,1098,1101,1103,1106,1108],{"class":832,"line":1090},19,[830,1092,942],{"class":836},[830,1094,945],{"class":836},[830,1096,1097],{"class":948}," permissions",[830,1099,1100],{"class":840},"(user: Annotated[",[830,1102,955],{"class":878},[830,1104,1105],{"class":840},", Depends(current_user)]) -> list[",[830,1107,955],{"class":878},[830,1109,963],{"class":840},[830,1111,1113,1115,1118],{"class":832,"line":1112},20,[830,1114,995],{"class":836},[830,1116,1117],{"class":836}," await",[830,1119,1120],{"class":840}," load_permissions(user)\n",[830,1122,1124],{"class":832,"line":1123},21,[830,1125,854],{"emptyLinePlaceholder":853},[830,1127,1129],{"class":832,"line":1128},22,[830,1130,854],{"emptyLinePlaceholder":853},[830,1132,1134,1136,1138,1141,1144,1146,1149,1152],{"class":832,"line":1133},23,[830,1135,942],{"class":836},[830,1137,945],{"class":836},[830,1139,1140],{"class":948}," can_write",[830,1142,1143],{"class":840},"(perms: Annotated[list[",[830,1145,955],{"class":878},[830,1147,1148],{"class":840},"], Depends(permissions)]) -> ",[830,1150,1151],{"class":878},"bool",[830,1153,1063],{"class":840},[830,1155,1157,1159,1162,1165],{"class":832,"line":1156},24,[830,1158,995],{"class":836},[830,1160,1161],{"class":888}," \"admin\"",[830,1163,1164],{"class":836}," in",[830,1166,1167],{"class":840}," perms\n",[830,1169,1171],{"class":832,"line":1170},25,[830,1172,854],{"emptyLinePlaceholder":853},[830,1174,1176],{"class":832,"line":1175},26,[830,1177,854],{"emptyLinePlaceholder":853},[830,1179,1181,1183,1185,1188,1190,1192,1194,1196],{"class":832,"line":1180},27,[830,1182,942],{"class":836},[830,1184,945],{"class":836},[830,1186,1187],{"class":948}," can_delete",[830,1189,1143],{"class":840},[830,1191,955],{"class":878},[830,1193,1148],{"class":840},[830,1195,1151],{"class":878},[830,1197,1063],{"class":840},[830,1199,1201,1203,1205,1207],{"class":832,"line":1200},28,[830,1202,995],{"class":836},[830,1204,1161],{"class":888},[830,1206,1164],{"class":836},[830,1208,1167],{"class":840},[830,1210,1212],{"class":832,"line":1211},29,[830,1213,854],{"emptyLinePlaceholder":853},[830,1215,1217],{"class":832,"line":1216},30,[830,1218,854],{"emptyLinePlaceholder":853},[830,1220,1222,1225,1228,1231],{"class":832,"line":1221},31,[830,1223,1224],{"class":948},"@app.get",[830,1226,1227],{"class":840},"(",[830,1229,1230],{"class":888},"\"\u002Frequest-scoped\"",[830,1232,1233],{"class":840},")\n",[830,1235,1237,1239,1241,1244],{"class":832,"line":1236},32,[830,1238,942],{"class":836},[830,1240,945],{"class":836},[830,1242,1243],{"class":948}," request_scoped",[830,1245,1246],{"class":840},"(\n",[830,1248,1250,1253,1255],{"class":832,"line":1249},33,[830,1251,1252],{"class":840},"    perms: Annotated[list[",[830,1254,955],{"class":878},[830,1256,1257],{"class":840},"], Depends(permissions)],\n",[830,1259,1261,1264,1266],{"class":832,"line":1260},34,[830,1262,1263],{"class":840},"    write: Annotated[",[830,1265,1151],{"class":878},[830,1267,1268],{"class":840},", Depends(can_write)],\n",[830,1270,1272,1275,1277],{"class":832,"line":1271},35,[830,1273,1274],{"class":840},"    delete: Annotated[",[830,1276,1151],{"class":878},[830,1278,1279],{"class":840},", Depends(can_delete)],\n",[830,1281,1283,1286,1288,1290,1293],{"class":832,"line":1282},36,[830,1284,1285],{"class":840},") -> dict[",[830,1287,955],{"class":878},[830,1289,898],{"class":840},[830,1291,1292],{"class":878},"object",[830,1294,963],{"class":840},[830,1296,1298,1300,1302,1305,1308,1311,1314,1317,1320,1322,1324,1326,1328,1330],{"class":832,"line":1297},37,[830,1299,995],{"class":836},[830,1301,885],{"class":840},[830,1303,1304],{"class":888},"\"perms\"",[830,1306,1307],{"class":840},": perms, ",[830,1309,1310],{"class":888},"\"write\"",[830,1312,1313],{"class":840},": write, ",[830,1315,1316],{"class":888},"\"delete\"",[830,1318,1319],{"class":840},": delete, ",[830,1321,889],{"class":888},[830,1323,892],{"class":840},[830,1325,879],{"class":878},[830,1327,978],{"class":840},[830,1329,889],{"class":888},[830,1331,1332],{"class":840},"]}\n",[590,1334,1335,1336,1338],{},"The uncached comparison needs ",[606,1337,608],{}," at every site, including inside the sub-dependencies — that is the part people get wrong:",[821,1340,1342],{"className":823,"code":1341,"language":825,"meta":826,"style":826},"async def can_write_uncached(\n    perms: Annotated[list[str], Depends(permissions, use_cache=False)],\n) -> bool:\n    return \"admin\" in perms\n\n\nasync def can_delete_uncached(\n    perms: Annotated[list[str], Depends(permissions, use_cache=False)],\n) -> bool:\n    return \"admin\" in perms\n\n\n@app.get(\"\u002Fno-cache\")\nasync def no_cache(\n    perms: Annotated[list[str], Depends(permissions, use_cache=False)],\n    write: Annotated[bool, Depends(can_write_uncached, use_cache=False)],\n    delete: Annotated[bool, Depends(can_delete_uncached, use_cache=False)],\n) -> dict[str, object]:\n    return {\"perms\": perms, \"write\": write, \"delete\": delete, \"db_loads\": STATS[\"db_loads\"]}\n",[606,1343,1344,1355,1377,1385,1395,1399,1403,1414,1430,1438,1448,1452,1456,1467,1478,1494,1511,1528,1540],{"__ignoreMap":826},[830,1345,1346,1348,1350,1353],{"class":832,"line":833},[830,1347,942],{"class":836},[830,1349,945],{"class":836},[830,1351,1352],{"class":948}," can_write_uncached",[830,1354,1246],{"class":840},[830,1356,1357,1359,1361,1364,1368,1371,1374],{"class":832,"line":850},[830,1358,1252],{"class":840},[830,1360,955],{"class":878},[830,1362,1363],{"class":840},"], Depends(permissions, ",[830,1365,1367],{"class":1366},"sV4o_","use_cache",[830,1369,1370],{"class":836},"=",[830,1372,1373],{"class":878},"False",[830,1375,1376],{"class":840},")],\n",[830,1378,1379,1381,1383],{"class":832,"line":857},[830,1380,1058],{"class":840},[830,1382,1151],{"class":878},[830,1384,1063],{"class":840},[830,1386,1387,1389,1391,1393],{"class":832,"line":870},[830,1388,995],{"class":836},[830,1390,1161],{"class":888},[830,1392,1164],{"class":836},[830,1394,1167],{"class":840},[830,1396,1397],{"class":832,"line":875},[830,1398,854],{"emptyLinePlaceholder":853},[830,1400,1401],{"class":832,"line":929},[830,1402,854],{"emptyLinePlaceholder":853},[830,1404,1405,1407,1409,1412],{"class":832,"line":934},[830,1406,942],{"class":836},[830,1408,945],{"class":836},[830,1410,1411],{"class":948}," can_delete_uncached",[830,1413,1246],{"class":840},[830,1415,1416,1418,1420,1422,1424,1426,1428],{"class":832,"line":939},[830,1417,1252],{"class":840},[830,1419,955],{"class":878},[830,1421,1363],{"class":840},[830,1423,1367],{"class":1366},[830,1425,1370],{"class":836},[830,1427,1373],{"class":878},[830,1429,1376],{"class":840},[830,1431,1432,1434,1436],{"class":832,"line":966},[830,1433,1058],{"class":840},[830,1435,1151],{"class":878},[830,1437,1063],{"class":840},[830,1439,1440,1442,1444,1446],{"class":832,"line":972},[830,1441,995],{"class":836},[830,1443,1161],{"class":888},[830,1445,1164],{"class":836},[830,1447,1167],{"class":840},[830,1449,1450],{"class":832,"line":992},[830,1451,854],{"emptyLinePlaceholder":853},[830,1453,1454],{"class":832,"line":1029},[830,1455,854],{"emptyLinePlaceholder":853},[830,1457,1458,1460,1462,1465],{"class":832,"line":1034},[830,1459,1224],{"class":948},[830,1461,1227],{"class":840},[830,1463,1464],{"class":888},"\"\u002Fno-cache\"",[830,1466,1233],{"class":840},[830,1468,1469,1471,1473,1476],{"class":832,"line":1039},[830,1470,942],{"class":836},[830,1472,945],{"class":836},[830,1474,1475],{"class":948}," no_cache",[830,1477,1246],{"class":840},[830,1479,1480,1482,1484,1486,1488,1490,1492],{"class":832,"line":1066},[830,1481,1252],{"class":840},[830,1483,955],{"class":878},[830,1485,1363],{"class":840},[830,1487,1367],{"class":1366},[830,1489,1370],{"class":836},[830,1491,1373],{"class":878},[830,1493,1376],{"class":840},[830,1495,1496,1498,1500,1503,1505,1507,1509],{"class":832,"line":1072},[830,1497,1263],{"class":840},[830,1499,1151],{"class":878},[830,1501,1502],{"class":840},", Depends(can_write_uncached, ",[830,1504,1367],{"class":1366},[830,1506,1370],{"class":836},[830,1508,1373],{"class":878},[830,1510,1376],{"class":840},[830,1512,1513,1515,1517,1520,1522,1524,1526],{"class":832,"line":1080},[830,1514,1274],{"class":840},[830,1516,1151],{"class":878},[830,1518,1519],{"class":840},", Depends(can_delete_uncached, ",[830,1521,1367],{"class":1366},[830,1523,1370],{"class":836},[830,1525,1373],{"class":878},[830,1527,1376],{"class":840},[830,1529,1530,1532,1534,1536,1538],{"class":832,"line":1085},[830,1531,1285],{"class":840},[830,1533,955],{"class":878},[830,1535,898],{"class":840},[830,1537,1292],{"class":878},[830,1539,963],{"class":840},[830,1541,1542,1544,1546,1548,1550,1552,1554,1556,1558,1560,1562,1564,1566,1568],{"class":832,"line":1090},[830,1543,995],{"class":836},[830,1545,885],{"class":840},[830,1547,1304],{"class":888},[830,1549,1307],{"class":840},[830,1551,1310],{"class":888},[830,1553,1313],{"class":840},[830,1555,1316],{"class":888},[830,1557,1319],{"class":840},[830,1559,889],{"class":888},[830,1561,892],{"class":840},[830,1563,879],{"class":878},[830,1565,978],{"class":840},[830,1567,889],{"class":888},[830,1569,1332],{"class":840},[590,1571,1572],{},"Real output from two requests to the cached route followed by one to the uncached route:",[821,1574,1578],{"className":1575,"code":1577,"language":667,"meta":826},[1576],"language-text","$ GET \u002Frequest-scoped\n200 OK\n{\n  \"perms\": [\n    \"admin\"\n  ],\n  \"write\": true,\n  \"delete\": true,\n  \"db_loads\": 1\n}\n\n$ GET \u002Frequest-scoped\n200 OK\n{\n  \"perms\": [\n    \"admin\"\n  ],\n  \"write\": true,\n  \"delete\": true,\n  \"db_loads\": 2\n}\n\n$ GET \u002Fno-cache\n200 OK\n{\n  \"perms\": [\n    \"admin\"\n  ],\n  \"write\": true,\n  \"delete\": true,\n  \"db_loads\": 5\n}\n",[606,1579,1577],{"__ignoreMap":826},[590,1581,1582],{},"Three declarations, one load. The second request starts from a cold cache and adds exactly one more load — proof that nothing carries over. The uncached route jumps the counter from 2 to 5: three loads in a single request, one per declaration site.",[771,1584,1586],{"id":1585},"process-scope-right-for-config-dangerous-for-data","Process Scope: Right for Config, Dangerous for Data",[590,1588,1589],{},"A module-level value that outlives the request is the cheapest possible cache — a dict lookup, no serialisation, no network. For genuinely global data such as configuration it is exactly right:",[821,1591,1593],{"className":823,"code":1592,"language":825,"meta":826,"style":826},"_settings: dict | None = None\n\n\nasync def get_settings() -> dict:\n    global _settings\n    if _settings is None:\n        STATS[\"settings_loads\"] += 1\n        _settings = {\"feature_x\": True}\n    return _settings\n",[606,1594,1595,1614,1618,1622,1638,1646,1661,1676,1695],{"__ignoreMap":826},[830,1596,1597,1600,1603,1606,1609,1611],{"class":832,"line":833},[830,1598,1599],{"class":840},"_settings: ",[830,1601,1602],{"class":878},"dict",[830,1604,1605],{"class":836}," |",[830,1607,1608],{"class":878}," None",[830,1610,882],{"class":836},[830,1612,1613],{"class":878}," None\n",[830,1615,1616],{"class":832,"line":850},[830,1617,854],{"emptyLinePlaceholder":853},[830,1619,1620],{"class":832,"line":857},[830,1621,854],{"emptyLinePlaceholder":853},[830,1623,1624,1626,1628,1631,1634,1636],{"class":832,"line":870},[830,1625,942],{"class":836},[830,1627,945],{"class":836},[830,1629,1630],{"class":948}," get_settings",[830,1632,1633],{"class":840},"() -> ",[830,1635,1602],{"class":878},[830,1637,1063],{"class":840},[830,1639,1640,1643],{"class":832,"line":875},[830,1641,1642],{"class":836},"    global",[830,1644,1645],{"class":840}," _settings\n",[830,1647,1648,1651,1654,1657,1659],{"class":832,"line":929},[830,1649,1650],{"class":836},"    if",[830,1652,1653],{"class":840}," _settings ",[830,1655,1656],{"class":836},"is",[830,1658,1608],{"class":878},[830,1660,1063],{"class":840},[830,1662,1663,1666,1668,1670,1672,1674],{"class":832,"line":934},[830,1664,1665],{"class":878},"        STATS",[830,1667,978],{"class":840},[830,1669,901],{"class":888},[830,1671,983],{"class":840},[830,1673,986],{"class":836},[830,1675,989],{"class":878},[830,1677,1678,1681,1683,1685,1688,1690,1693],{"class":832,"line":939},[830,1679,1680],{"class":840},"        _settings ",[830,1682,1370],{"class":836},[830,1684,885],{"class":840},[830,1686,1687],{"class":888},"\"feature_x\"",[830,1689,892],{"class":840},[830,1691,1692],{"class":878},"True",[830,1694,926],{"class":840},[830,1696,1697,1699],{"class":832,"line":966},[830,1698,995],{"class":836},[830,1700,1645],{"class":840},[590,1702,1703,1704,1707],{},"Two requests hit that endpoint and the transcript shows ",[606,1705,1706],{},"\"settings_loads\": 1"," both times. Nothing subtle happens because the value does not depend on who is asking.",[590,1709,1710],{},"Now the trap. Here is the same pattern applied to per-user permissions, with a key that ignores the user:",[821,1712,1714],{"className":823,"code":1713,"language":825,"meta":826,"style":826},"_BAD_CACHE: dict[str, object] = {}\n\n\nasync def permissions_process_cached_wrong(\n    user: Annotated[str, Depends(current_user)],\n) -> list[str]:\n    if \"perms\" in _BAD_CACHE:                      # BUG: one slot for every user.\n        STATS[\"process_cache_hits\"] += 1\n        return _BAD_CACHE[\"perms\"]\n    STATS[\"process_cache_misses\"] += 1\n    _BAD_CACHE[\"perms\"] = await load_permissions(user)\n    return _BAD_CACHE[\"perms\"]\n",[606,1715,1716,1737,1741,1745,1756,1766,1774,1799,1813,1826,1840,1857],{"__ignoreMap":826},[830,1717,1718,1721,1724,1726,1728,1730,1732,1734],{"class":832,"line":833},[830,1719,1720],{"class":878},"_BAD_CACHE",[830,1722,1723],{"class":840},": dict[",[830,1725,955],{"class":878},[830,1727,898],{"class":840},[830,1729,1292],{"class":878},[830,1731,983],{"class":840},[830,1733,1370],{"class":836},[830,1735,1736],{"class":840}," {}\n",[830,1738,1739],{"class":832,"line":850},[830,1740,854],{"emptyLinePlaceholder":853},[830,1742,1743],{"class":832,"line":857},[830,1744,854],{"emptyLinePlaceholder":853},[830,1746,1747,1749,1751,1754],{"class":832,"line":870},[830,1748,942],{"class":836},[830,1750,945],{"class":836},[830,1752,1753],{"class":948}," permissions_process_cached_wrong",[830,1755,1246],{"class":840},[830,1757,1758,1761,1763],{"class":832,"line":875},[830,1759,1760],{"class":840},"    user: Annotated[",[830,1762,955],{"class":878},[830,1764,1765],{"class":840},", Depends(current_user)],\n",[830,1767,1768,1770,1772],{"class":832,"line":929},[830,1769,958],{"class":840},[830,1771,955],{"class":878},[830,1773,963],{"class":840},[830,1775,1776,1778,1781,1783,1786,1789,1793,1796],{"class":832,"line":934},[830,1777,1650],{"class":836},[830,1779,1780],{"class":888}," \"perms\"",[830,1782,1164],{"class":836},[830,1784,1785],{"class":878}," _BAD_CACHE",[830,1787,1788],{"class":840},":                      ",[830,1790,1792],{"class":1791},"sFeEa","# ",[830,1794,1795],{"class":836},"BUG",[830,1797,1798],{"class":1791},": one slot for every user.\n",[830,1800,1801,1803,1805,1807,1809,1811],{"class":832,"line":939},[830,1802,1665],{"class":878},[830,1804,978],{"class":840},[830,1806,910],{"class":888},[830,1808,983],{"class":840},[830,1810,986],{"class":836},[830,1812,989],{"class":878},[830,1814,1815,1818,1820,1822,1824],{"class":832,"line":966},[830,1816,1817],{"class":836},"        return",[830,1819,1785],{"class":878},[830,1821,978],{"class":840},[830,1823,1304],{"class":888},[830,1825,1026],{"class":840},[830,1827,1828,1830,1832,1834,1836,1838],{"class":832,"line":972},[830,1829,975],{"class":878},[830,1831,978],{"class":840},[830,1833,919],{"class":888},[830,1835,983],{"class":840},[830,1837,986],{"class":836},[830,1839,989],{"class":878},[830,1841,1842,1845,1847,1849,1851,1853,1855],{"class":832,"line":992},[830,1843,1844],{"class":878},"    _BAD_CACHE",[830,1846,978],{"class":840},[830,1848,1304],{"class":888},[830,1850,983],{"class":840},[830,1852,1370],{"class":836},[830,1854,1117],{"class":836},[830,1856,1120],{"class":840},[830,1858,1859,1861,1863,1865,1867],{"class":832,"line":1029},[830,1860,995],{"class":836},[830,1862,1785],{"class":878},[830,1864,978],{"class":840},[830,1866,1304],{"class":888},[830,1868,1026],{"class":840},[590,1870,1871,1872,1875,1876,1879],{},"The real output, with ",[606,1873,1874],{},"u1"," (an admin) arriving first and ",[606,1877,1878],{},"u2"," (read-only) second:",[821,1881,1884],{"className":1882,"code":1883,"language":667,"meta":826},[1576],"$ GET \u002Fperms-wrong?user=u1\n200 OK\n{\n  \"user\": \"u1\",\n  \"perms\": [\n    \"admin\"\n  ]\n}\n\n$ GET \u002Fperms-wrong?user=u2\n200 OK\n{\n  \"user\": \"u2\",\n  \"perms\": [\n    \"admin\"\n  ]\n}\n",[606,1885,1883],{"__ignoreMap":826},[590,1887,1888,1890,1891,1894,1895,1898,1899,1902],{},[606,1889,1878],{}," was granted ",[606,1892,1893],{},"admin",". Not stale data — ",[798,1896,1897],{},"someone else's"," data, used for an authorisation decision. This bug survives code review easily because the dependency signature takes ",[606,1900,1901],{},"user",", so it looks user-aware; only the cache key is not. And it never shows up in local testing, where you are always the same user.",[590,1904,1905],{},"The fix is to key on every input the value depends on, and to add an expiry so a permission change eventually propagates:",[821,1907,1909],{"className":823,"code":1908,"language":825,"meta":826,"style":826},"import time\n\n_GOOD_CACHE: dict[str, tuple[float, list[str]]] = {}\nTTL_SECONDS = 30.0\n\n\nasync def permissions_process_cached(\n    user: Annotated[str, Depends(current_user)],\n) -> list[str]:\n    entry = _GOOD_CACHE.get(user)\n    if entry and (time.monotonic() - entry[0]) \u003C TTL_SECONDS:\n        STATS[\"process_cache_hits\"] += 1\n        return entry[1]\n    STATS[\"process_cache_misses\"] += 1\n    value = await load_permissions(user)\n    _GOOD_CACHE[user] = (time.monotonic(), value)\n    return value\n",[606,1910,1911,1918,1922,1949,1959,1963,1967,1978,1986,1994,2007,2039,2053,2064,2078,2089,2102],{"__ignoreMap":826},[830,1912,1913,1915],{"class":832,"line":833},[830,1914,844],{"class":836},[830,1916,1917],{"class":840}," time\n",[830,1919,1920],{"class":832,"line":850},[830,1921,854],{"emptyLinePlaceholder":853},[830,1923,1924,1927,1929,1931,1934,1937,1940,1942,1945,1947],{"class":832,"line":857},[830,1925,1926],{"class":878},"_GOOD_CACHE",[830,1928,1723],{"class":840},[830,1930,955],{"class":878},[830,1932,1933],{"class":840},", tuple[",[830,1935,1936],{"class":878},"float",[830,1938,1939],{"class":840},", list[",[830,1941,955],{"class":878},[830,1943,1944],{"class":840},"]]] ",[830,1946,1370],{"class":836},[830,1948,1736],{"class":840},[830,1950,1951,1954,1956],{"class":832,"line":870},[830,1952,1953],{"class":878},"TTL_SECONDS",[830,1955,882],{"class":836},[830,1957,1958],{"class":878}," 30.0\n",[830,1960,1961],{"class":832,"line":875},[830,1962,854],{"emptyLinePlaceholder":853},[830,1964,1965],{"class":832,"line":929},[830,1966,854],{"emptyLinePlaceholder":853},[830,1968,1969,1971,1973,1976],{"class":832,"line":934},[830,1970,942],{"class":836},[830,1972,945],{"class":836},[830,1974,1975],{"class":948}," permissions_process_cached",[830,1977,1246],{"class":840},[830,1979,1980,1982,1984],{"class":832,"line":939},[830,1981,1760],{"class":840},[830,1983,955],{"class":878},[830,1985,1765],{"class":840},[830,1987,1988,1990,1992],{"class":832,"line":966},[830,1989,958],{"class":840},[830,1991,955],{"class":878},[830,1993,963],{"class":840},[830,1995,1996,1999,2001,2004],{"class":832,"line":972},[830,1997,1998],{"class":840},"    entry ",[830,2000,1370],{"class":836},[830,2002,2003],{"class":878}," _GOOD_CACHE",[830,2005,2006],{"class":840},".get(user)\n",[830,2008,2009,2011,2014,2017,2020,2023,2026,2028,2031,2034,2037],{"class":832,"line":992},[830,2010,1650],{"class":836},[830,2012,2013],{"class":840}," entry ",[830,2015,2016],{"class":836},"and",[830,2018,2019],{"class":840}," (time.monotonic() ",[830,2021,2022],{"class":836},"-",[830,2024,2025],{"class":840}," entry[",[830,2027,895],{"class":878},[830,2029,2030],{"class":840},"]) ",[830,2032,2033],{"class":836},"\u003C",[830,2035,2036],{"class":878}," TTL_SECONDS",[830,2038,1063],{"class":840},[830,2040,2041,2043,2045,2047,2049,2051],{"class":832,"line":1029},[830,2042,1665],{"class":878},[830,2044,978],{"class":840},[830,2046,910],{"class":888},[830,2048,983],{"class":840},[830,2050,986],{"class":836},[830,2052,989],{"class":878},[830,2054,2055,2057,2059,2062],{"class":832,"line":1034},[830,2056,1817],{"class":836},[830,2058,2025],{"class":840},[830,2060,2061],{"class":878},"1",[830,2063,1026],{"class":840},[830,2065,2066,2068,2070,2072,2074,2076],{"class":832,"line":1039},[830,2067,975],{"class":878},[830,2069,978],{"class":840},[830,2071,919],{"class":888},[830,2073,983],{"class":840},[830,2075,986],{"class":836},[830,2077,989],{"class":878},[830,2079,2080,2083,2085,2087],{"class":832,"line":1066},[830,2081,2082],{"class":840},"    value ",[830,2084,1370],{"class":836},[830,2086,1117],{"class":836},[830,2088,1120],{"class":840},[830,2090,2091,2094,2097,2099],{"class":832,"line":1072},[830,2092,2093],{"class":878},"    _GOOD_CACHE",[830,2095,2096],{"class":840},"[user] ",[830,2098,1370],{"class":836},[830,2100,2101],{"class":840}," (time.monotonic(), value)\n",[830,2103,2104,2106],{"class":832,"line":1080},[830,2105,995],{"class":836},[830,2107,2108],{"class":840}," value\n",[590,2110,2111,2112,898,2114,2116,2117,2119],{},"Real hit and miss counts across ",[606,2113,1874],{},[606,2115,1878],{},", then ",[606,2118,1874],{}," again:",[821,2121,2124],{"className":2122,"code":2123,"language":667,"meta":826},[1576],"$ GET \u002Fperms-keyed?user=u1\n200 OK\n{\n  \"user\": \"u1\",\n  \"perms\": [\n    \"admin\"\n  ],\n  \"cache\": {\n    \"db_loads\": 7,\n    \"settings_loads\": 1,\n    \"process_cache_hits\": 1,\n    \"process_cache_misses\": 2\n  }\n}\n\n$ GET \u002Fperms-keyed?user=u2\n200 OK\n{\n  \"user\": \"u2\",\n  \"perms\": [\n    \"read\"\n  ],\n  \"cache\": {\n    \"db_loads\": 8,\n    \"settings_loads\": 1,\n    \"process_cache_hits\": 1,\n    \"process_cache_misses\": 3\n  }\n}\n\n$ GET \u002Fperms-keyed?user=u1\n200 OK\n{\n  \"user\": \"u1\",\n  \"perms\": [\n    \"admin\"\n  ],\n  \"cache\": {\n    \"db_loads\": 8,\n    \"settings_loads\": 1,\n    \"process_cache_hits\": 2,\n    \"process_cache_misses\": 3\n  }\n}\n\n",[606,2125,2123],{"__ignoreMap":826},[590,2127,2128,2130,2131,2134,2135,2137,2138,2141,2142,2145,2146,2149],{},[606,2129,1878],{}," gets ",[606,2132,2133],{},"read",", correctly, at the cost of one miss. The repeat visit from ",[606,2136,1874],{}," is a hit and ",[606,2139,2140],{},"db_loads"," does not move. Note ",[606,2143,2144],{},"time.monotonic()"," rather than ",[606,2147,2148],{},"time.time()",": a monotonic clock cannot jump backwards when NTP adjusts the system time, which would otherwise make an entry look valid for hours.",[771,2151,2153],{"id":2152},"when-to-move-the-cache-to-redis","When to Move the Cache to Redis",[590,2155,2156],{},"Process scope has one structural limit that no amount of careful keying fixes: each uvicorn worker has its own dict. With four workers behind a load balancer, one user's request can hit a worker with a fresh value and the next hit a worker with a two-minute-old one, and there is no way to invalidate all four when the underlying data changes. You can wait out a TTL, but you cannot make a write take effect immediately.",[590,2158,2159],{},"Move to Redis when either of those matters — when the value must be consistent across workers, or when a write must invalidate the cache now rather than in thirty seconds. The dependency shape barely changes:",[821,2161,2163],{"className":823,"code":2162,"language":825,"meta":826,"style":826},"import json\n\n\nasync def permissions_cached(\n    user: Annotated[str, Depends(current_user)],\n    redis: Annotated[Redis, Depends(get_redis)],\n) -> list[str]:\n    key = f\"perms:v2:{user}\"                  # Version in the key: a schema change is a new key.\n    cached = await redis.get(key)\n    if cached is not None:\n        return json.loads(cached)\n    value = await load_permissions(user)\n    await redis.set(key, json.dumps(value), ex=300)\n    return value\n",[606,2164,2165,2172,2176,2180,2191,2199,2204,2212,2239,2251,2267,2274,2284,2302],{"__ignoreMap":826},[830,2166,2167,2169],{"class":832,"line":833},[830,2168,844],{"class":836},[830,2170,2171],{"class":840}," json\n",[830,2173,2174],{"class":832,"line":850},[830,2175,854],{"emptyLinePlaceholder":853},[830,2177,2178],{"class":832,"line":857},[830,2179,854],{"emptyLinePlaceholder":853},[830,2181,2182,2184,2186,2189],{"class":832,"line":870},[830,2183,942],{"class":836},[830,2185,945],{"class":836},[830,2187,2188],{"class":948}," permissions_cached",[830,2190,1246],{"class":840},[830,2192,2193,2195,2197],{"class":832,"line":875},[830,2194,1760],{"class":840},[830,2196,955],{"class":878},[830,2198,1765],{"class":840},[830,2200,2201],{"class":832,"line":929},[830,2202,2203],{"class":840},"    redis: Annotated[Redis, Depends(get_redis)],\n",[830,2205,2206,2208,2210],{"class":832,"line":934},[830,2207,958],{"class":840},[830,2209,955],{"class":878},[830,2211,963],{"class":840},[830,2213,2214,2217,2219,2222,2225,2228,2230,2233,2236],{"class":832,"line":939},[830,2215,2216],{"class":840},"    key ",[830,2218,1370],{"class":836},[830,2220,2221],{"class":836}," f",[830,2223,2224],{"class":888},"\"perms:v2:",[830,2226,2227],{"class":836},"{",[830,2229,1901],{"class":840},[830,2231,2232],{"class":836},"}",[830,2234,2235],{"class":888},"\"",[830,2237,2238],{"class":1791},"                  # Version in the key: a schema change is a new key.\n",[830,2240,2241,2244,2246,2248],{"class":832,"line":966},[830,2242,2243],{"class":840},"    cached ",[830,2245,1370],{"class":836},[830,2247,1117],{"class":836},[830,2249,2250],{"class":840}," redis.get(key)\n",[830,2252,2253,2255,2258,2260,2263,2265],{"class":832,"line":972},[830,2254,1650],{"class":836},[830,2256,2257],{"class":840}," cached ",[830,2259,1656],{"class":836},[830,2261,2262],{"class":836}," not",[830,2264,1608],{"class":878},[830,2266,1063],{"class":840},[830,2268,2269,2271],{"class":832,"line":992},[830,2270,1817],{"class":836},[830,2272,2273],{"class":840}," json.loads(cached)\n",[830,2275,2276,2278,2280,2282],{"class":832,"line":1029},[830,2277,2082],{"class":840},[830,2279,1370],{"class":836},[830,2281,1117],{"class":836},[830,2283,1120],{"class":840},[830,2285,2286,2289,2292,2295,2297,2300],{"class":832,"line":1034},[830,2287,2288],{"class":836},"    await",[830,2290,2291],{"class":840}," redis.set(key, json.dumps(value), ",[830,2293,2294],{"class":1366},"ex",[830,2296,1370],{"class":836},[830,2298,2299],{"class":878},"300",[830,2301,1233],{"class":840},[830,2303,2304,2306],{"class":832,"line":1039},[830,2305,995],{"class":836},[830,2307,2108],{"class":840},[590,2309,2310,2311,2314,2315,2319,2320,2324],{},"The ",[606,2312,2313],{},"v2"," segment is worth the character cost. When the shape of the cached value changes, bumping the version invalidates everything atomically without a flush and without a deploy-order problem where old and new code disagree about what is stored. The write-side invalidation patterns — delete on write, tag-based grouping, and why they are harder than they look — are covered in ",[630,2316,2318],{"href":2317},"\u002Fasync-background-tasks-observability\u002Fcaching-strategies\u002Fcache-invalidation-patterns-in-fastapi\u002F","cache invalidation patterns in FastAPI",", and caching whole responses rather than dependency values is ",[630,2321,2323],{"href":2322},"\u002Fasync-background-tasks-observability\u002Fcaching-strategies\u002Fredis-response-caching-in-fastapi\u002F","Redis response caching in FastAPI",".",[590,2326,2327,2328,2330,2331,2324],{},"One correctness note that applies to every shared cache: a Redis round trip is I\u002FO, so a cached dependency is still ",[606,2329,624],{}," and still awaits. If your cache lookup is synchronous (a blocking Redis client, a file read) you have moved a blocking call into the dependency layer where it will stall the event loop for every request, not just the ones that miss — see ",[630,2332,2334],{"href":2333},"\u002Fasync-background-tasks-observability\u002Fasync-correctness-concurrency\u002Ffixing-blocking-calls-in-async-routes\u002F","fixing blocking calls in async routes",[771,2336,2338],{"id":2337},"the-lru_cache-trap","The lru_cache Trap",[590,2340,2341,2343,2344,2346,2347,2350,2351,2354],{},[606,2342,620],{}," is the obvious tool and it does not work on async dependencies. Calling an ",[606,2345,624],{}," returns a coroutine object; ",[606,2348,2349],{},"lru_cache"," stores that object and hands the same one back on the next call. Awaiting an already-awaited coroutine raises ",[606,2352,2353],{},"RuntimeError: cannot reuse already awaited coroutine",". The first request succeeds and the second fails, which makes it a genuinely nasty production bug — it passes any test that makes one call.",[590,2356,2357],{},"On a synchronous dependency it is fine and idiomatic, which is why the pattern is so common for settings:",[821,2359,2361],{"className":823,"code":2360,"language":825,"meta":826,"style":826},"from functools import lru_cache\n\n\n@lru_cache\ndef get_settings() -> Settings:\n    return Settings()          # Sync, no I\u002FO, one instance for the process lifetime.\n",[606,2362,2363,2375,2379,2383,2388,2398],{"__ignoreMap":826},[830,2364,2365,2367,2370,2372],{"class":832,"line":833},[830,2366,837],{"class":836},[830,2368,2369],{"class":840}," functools ",[830,2371,844],{"class":836},[830,2373,2374],{"class":840}," lru_cache\n",[830,2376,2377],{"class":832,"line":850},[830,2378,854],{"emptyLinePlaceholder":853},[830,2380,2381],{"class":832,"line":857},[830,2382,854],{"emptyLinePlaceholder":853},[830,2384,2385],{"class":832,"line":870},[830,2386,2387],{"class":948},"@lru_cache\n",[830,2389,2390,2393,2395],{"class":832,"line":875},[830,2391,2392],{"class":836},"def",[830,2394,1630],{"class":948},[830,2396,2397],{"class":840},"() -> Settings:\n",[830,2399,2400,2402,2405],{"class":832,"line":929},[830,2401,995],{"class":836},[830,2403,2404],{"class":840}," Settings()          ",[830,2406,2407],{"class":1791},"# Sync, no I\u002FO, one instance for the process lifetime.\n",[590,2409,2410,2411,2415,2416,2418],{},"That is the standard approach described in ",[630,2412,2414],{"href":2413},"\u002Fcore-architecture-routing-patterns\u002Fconfiguration-management\u002Fmanaging-environment-variables-with-pydantic-settings\u002F","managing environment variables with pydantic-settings",". Keep ",[606,2417,2349],{}," for that case and use an explicit dict with an expiry for anything awaited.",[771,2420,2422],{"id":2421},"verification","Verification",[590,2424,2425],{},"Count calls rather than trusting the code to be doing what you think:",[821,2427,2429],{"className":823,"code":2428,"language":825,"meta":826,"style":826},"def test_dependency_runs_once_per_request(client, monkeypatch):\n    calls = []\n    monkeypatch.setattr(\"app.deps.load_permissions\", lambda u: calls.append(u) or [\"admin\"])\n    client.get(\"\u002Frequest-scoped\")\n    assert len(calls) == 1        # Three declarations, one call.\n\n\ndef test_cache_is_keyed_by_user(client):\n    assert client.get(\"\u002Fperms-keyed?user=u1\").json()[\"perms\"] == [\"admin\"]\n    assert client.get(\"\u002Fperms-keyed?user=u2\").json()[\"perms\"] == [\"read\"]\n",[606,2430,2431,2441,2451,2477,2486,2505,2509,2513,2523,2548],{"__ignoreMap":826},[830,2432,2433,2435,2438],{"class":832,"line":833},[830,2434,2392],{"class":836},[830,2436,2437],{"class":948}," test_dependency_runs_once_per_request",[830,2439,2440],{"class":840},"(client, monkeypatch):\n",[830,2442,2443,2446,2448],{"class":832,"line":850},[830,2444,2445],{"class":840},"    calls ",[830,2447,1370],{"class":836},[830,2449,2450],{"class":840}," []\n",[830,2452,2453,2456,2459,2461,2464,2467,2470,2472,2474],{"class":832,"line":857},[830,2454,2455],{"class":840},"    monkeypatch.setattr(",[830,2457,2458],{"class":888},"\"app.deps.load_permissions\"",[830,2460,898],{"class":840},[830,2462,2463],{"class":836},"lambda",[830,2465,2466],{"class":840}," u: calls.append(u) ",[830,2468,2469],{"class":836},"or",[830,2471,998],{"class":840},[830,2473,1001],{"class":888},[830,2475,2476],{"class":840},"])\n",[830,2478,2479,2482,2484],{"class":832,"line":870},[830,2480,2481],{"class":840},"    client.get(",[830,2483,1230],{"class":888},[830,2485,1233],{"class":840},[830,2487,2488,2491,2494,2497,2499,2502],{"class":832,"line":875},[830,2489,2490],{"class":836},"    assert",[830,2492,2493],{"class":878}," len",[830,2495,2496],{"class":840},"(calls) ",[830,2498,1012],{"class":836},[830,2500,2501],{"class":878}," 1",[830,2503,2504],{"class":1791},"        # Three declarations, one call.\n",[830,2506,2507],{"class":832,"line":929},[830,2508,854],{"emptyLinePlaceholder":853},[830,2510,2511],{"class":832,"line":934},[830,2512,854],{"emptyLinePlaceholder":853},[830,2514,2515,2517,2520],{"class":832,"line":939},[830,2516,2392],{"class":836},[830,2518,2519],{"class":948}," test_cache_is_keyed_by_user",[830,2521,2522],{"class":840},"(client):\n",[830,2524,2525,2527,2530,2533,2536,2538,2540,2542,2544,2546],{"class":832,"line":966},[830,2526,2490],{"class":836},[830,2528,2529],{"class":840}," client.get(",[830,2531,2532],{"class":888},"\"\u002Fperms-keyed?user=u1\"",[830,2534,2535],{"class":840},").json()[",[830,2537,1304],{"class":888},[830,2539,983],{"class":840},[830,2541,1012],{"class":836},[830,2543,998],{"class":840},[830,2545,1001],{"class":888},[830,2547,1026],{"class":840},[830,2549,2550,2552,2554,2557,2559,2561,2563,2565,2567,2569],{"class":832,"line":972},[830,2551,2490],{"class":836},[830,2553,2529],{"class":840},[830,2555,2556],{"class":888},"\"\u002Fperms-keyed?user=u2\"",[830,2558,2535],{"class":840},[830,2560,1304],{"class":888},[830,2562,983],{"class":840},[830,2564,1012],{"class":836},[830,2566,998],{"class":840},[830,2568,1023],{"class":888},[830,2570,1026],{"class":840},[590,2572,2573],{},"The second test is the one that catches the leak, and it is worth writing for every cached dependency whose value varies by caller. In production, export hit and miss counters per cache name; a hit rate that is suspiciously close to 100% on user-specific data is usually a missing key segment rather than good luck.",[771,2575,2577],{"id":2576},"trade-offs-and-when-not-to","Trade-offs and When Not To",[590,2579,2580],{},"Caching a dependency trades freshness for latency, and at the dependency layer the thing you are usually caching is an authorisation decision. That makes the staleness window a security property: a revoked permission stays live for the length of your TTL. Thirty seconds is defensible; five minutes on an admin flag is how a fired employee keeps write access through their exit interview.",[590,2582,2583],{},"Do not cache when the underlying call is already fast. A primary-key lookup on a warm connection can be cheaper than a Redis round trip, and the cache then adds a network hop, a serialisation step, and a whole class of consistency bugs in exchange for nothing.",[590,2585,2586,2587,2590,2591,2593,2594,2598,2599,2324],{},"And do not reach for a process-scoped cache to fix a per-request problem. If the same value is being computed repeatedly ",[798,2588,2589],{},"inside"," one request, the built-in ",[606,2592,1367],{}," behaviour already handles it — the fix is to share one dependency object, not to add a global. Getting that structure right is the subject of ",[630,2595,2597],{"href":2596},"\u002Fcore-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Fbest-practices-for-fastapi-dependency-injection\u002F","best practices for FastAPI dependency injection"," and ",[630,2600,2602],{"href":2601},"\u002Fcore-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Fdependency-caching-and-use-cache\u002F","dependency caching and use_cache",[771,2604,2606],{"id":2605},"faq","FAQ",[590,2608,2609,2612],{},[593,2610,2611],{},"Does FastAPI already cache dependency results?","\nYes, within a single request. A dependency declared many times in one request's graph is called once and the value reused, as long as use_cache is True (the default) and it is the same callable object. Nothing is shared between requests.",[590,2614,2615,2618],{},[593,2616,2617],{},"Why does use_cache=False on the top-level dependency not stop the caching?","\nBecause the flag applies per declaration site. Sub-dependencies that still declare the callable with the default use_cache=True keep sharing the cached value, so you must set the flag at every site in the graph to force a re-run.",[590,2620,2621,2624],{},[593,2622,2623],{},"What is wrong with caching per-user data in a module-level dict?","\nIf the cache key does not include the user, the first user's value is served to everyone until it expires. It is a data leak, not just a stale read, and it is invisible in single-user testing because you never see another user's data come back.",[590,2626,2627,2630],{},[593,2628,2629],{},"When should a dependency cache live in Redis rather than in the process?","\nWhen the value must be consistent across workers or invalidated on write. A process-local cache means each uvicorn worker has its own copy with its own expiry, so an invalidation on one worker leaves the others serving stale data.",[590,2632,2633,2636],{},[593,2634,2635],{},"Is lru_cache safe on an async dependency?","\nNo. functools.lru_cache caches the coroutine object rather than its result, and awaiting the same coroutine twice raises a RuntimeError. Cache the awaited value in a dict, or apply lru_cache only to synchronous dependencies such as settings loaders.",[771,2638,2640],{"id":2639},"related-reading","Related Reading",[597,2642,2643,2651,2658,2666],{},[600,2644,2645,2648,2649,2324],{},[593,2646,2647],{},"Up to the topic:"," ",[630,2650,633],{"href":632},[600,2652,2653,2648,2656,2324],{},[593,2654,2655],{},"Caching whole responses:",[630,2657,2323],{"href":2322},[600,2659,2660,2648,2663,2324],{},[593,2661,2662],{},"Keeping caches honest:",[630,2664,2665],{"href":2317},"Cache invalidation patterns in FastAPI",[600,2667,2668,2648,2671,2598,2674,2324],{},[593,2669,2670],{},"Dependency mechanics:",[630,2672,2673],{"href":2601},"Dependency caching and use_cache",[630,2675,2597],{"href":2596},[2677,2678,2679],"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 .sYEJz, html code.shiki .sYEJz{--shiki-default:#032563}html pre.shiki code .s3dhs, html code.shiki .s3dhs{--shiki-default:#622CBC}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sV4o_, html code.shiki .sV4o_{--shiki-default:#702C00}html pre.shiki code .sFeEa, html code.shiki .sFeEa{--shiki-default:#66707B}",{"title":826,"searchDepth":850,"depth":850,"links":2681},[2682,2683,2684,2685,2686,2687,2688,2689,2690],{"id":773,"depth":850,"text":774},{"id":811,"depth":850,"text":812},{"id":1585,"depth":850,"text":1586},{"id":2152,"depth":850,"text":2153},{"id":2337,"depth":850,"text":2338},{"id":2421,"depth":850,"text":2422},{"id":2576,"depth":850,"text":2577},{"id":2605,"depth":850,"text":2606},{"id":2639,"depth":850,"text":2640},"2026-07-20","Request-scoped, process-scoped and Redis caching for FastAPI dependencies, with verified hit and miss counts and the per-user leak process caches cause.","md",[2695,2697,2699,2701,2703],{"q":2611,"a":2696},"Yes, within a single request. A dependency declared many times in one request's graph is called once and the value reused, as long as use_cache is True (the default) and it is the same callable object. Nothing is shared between requests.",{"q":2617,"a":2698},"Because the flag applies per declaration site. Sub-dependencies that still declare the callable with the default use_cache=True keep sharing the cached value, so you must set the flag at every site in the graph to force a re-run.",{"q":2623,"a":2700},"If the cache key does not include the user, the first user's value is served to everyone until it expires. It is a data leak, not just a stale read, and it is invisible in single-user testing because you never see another user's data come back.",{"q":2629,"a":2702},"When the value must be consistent across workers or invalidated on write. A process-local cache means each uvicorn worker has its own copy with its own expiry, so an invalidation on one worker leaves the others serving stale data.",{"q":2635,"a":2704},"No. functools.lru_cache caches the coroutine object rather than its result, and awaiting the same coroutine twice raises a RuntimeError. Cache the awaited value in a dict, or apply lru_cache only to synchronous dependencies such as settings loaders.",null,{"slug":2707,"breadcrumb":2708},"caching-dependency-results",[2709,2712,2715,2716],{"label":2710,"path":2711},"Home","\u002F",{"label":2713,"path":2714},"Async, Background Tasks & Observability","\u002Fasync-background-tasks-observability\u002F",{"label":633,"path":632},{"label":2717,"path":2718},"Caching Dependency Results","\u002Fasync-background-tasks-observability\u002Fcaching-strategies\u002Fcaching-dependency-results\u002F",{"title":297,"description":2692},"article","kNzkxpIgxNGdAMrW_XAAJuURet-3XrMfs76V9BT6DhQ",[2705,2705],1784588203038]