Metadata-Version: 2.5
Name: fastapi-cache-di
Version: 0.4.0
Summary: Speed up FastAPI startup by caching dependency-tree introspection
Project-URL: Homepage, https://github.com/Toilal/fastapi-cache-di
Project-URL: Repository, https://github.com/Toilal/fastapi-cache-di
Project-URL: Changelog, https://github.com/Toilal/fastapi-cache-di/blob/develop/CHANGELOG.md
Project-URL: Issues, https://github.com/Toilal/fastapi-cache-di/issues
Author-email: Rémi Alvergnat <toilal.dev@gmail.com>
License-Expression: MIT
License-File: LICENSE
Keywords: cache,dependency-injection,fastapi,performance,startup
Classifier: Development Status :: 4 - Beta
Classifier: Framework :: FastAPI
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.11
Requires-Dist: fastapi<1.0,>=0.112.4
Description-Content-Type: text/markdown

# fastapi-cache-di

[![Latest Version](https://img.shields.io/pypi/v/fastapi-cache-di.svg)](https://pypi.python.org/pypi/fastapi-cache-di)
[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/Toilal/fastapi-cache-di/blob/develop/LICENSE)
[![Build Status](https://img.shields.io/github/actions/workflow/status/Toilal/fastapi-cache-di/ci.yml?branch=develop)](https://github.com/Toilal/fastapi-cache-di/actions/workflows/ci.yml)
[![Codecov](https://img.shields.io/codecov/c/github/Toilal/fastapi-cache-di)](https://codecov.io/gh/Toilal/fastapi-cache-di)
[![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/relekang/python-semantic-release)

Speed up **FastAPI startup** by caching its dependency-tree introspection.

At startup, FastAPI re-introspects every route's dependency tree with no caching,
so dependencies shared across many routes (auth checks, DB handles, role guards …)
are parsed from scratch again and again. `fastapi-cache-di` memoizes that work for
the duration of route loading, turning `O(routes × tree-depth)` into
`O(routes + unique-deps)`, then restores FastAPI's originals.

## Install

```bash
pip install fastapi-cache-di
# or
uv add fastapi-cache-di
```

## Usage

Wrap the code that loads your routes with the context manager:

```python
from fastapi import APIRouter, FastAPI

from fastapi_cache_di import fastapi_deps_cache

app = FastAPI()
users = APIRouter()
orders = APIRouter()
billing = APIRouter()

with fastapi_deps_cache(warm_up=app):
    # Registering routes introspects each dependency tree; caching is active here.
    app.include_router(users)
    app.include_router(orders)
    app.include_router(billing)
# included routes built, originals restored here; cache memory freed
```

`warm_up=app` matters from FastAPI 0.137: `include_router` no longer builds the
included routes, FastAPI builds them on the first request through each included
router (or the first `/openapi.json`), after the block has exited and without
the cache. `warm_up` builds them when the block exits normally, while the cache
is still active. It has no effect before FastAPI 0.137.2, and does not cover
routes added after the block, mounted sub-applications or `frontend()` routes.

Manual patch/unpatch, sharing and inspecting the cache, and the cache-key details
are covered in the documentation.

## Calling loop-safe dependencies inline

At **request time**, FastAPI calls every dependency that is neither a coroutine
nor a generator through the threadpool: every `Depends(SomeClass)` and every
plain `def` dependency costs a round trip to a worker thread, often far more
than a class whose `__init__` only stores its arguments.

`patch_fastapi_run_in_threadpool()` lets such dependencies run directly on the
event loop. It is opt-in, because the library cannot tell whether a call blocks:

- `loop_safe` marks one sync function or class (it returns the same object, so
  `Depends(...)` and `dependency_overrides` keep working);
- `inline_classes=True` treats every class as loop-safe;
- everything else still goes through the threadpool, as do sync generator
  dependencies and sync endpoints.

```python
from typing import Annotated

from fastapi import Depends, FastAPI

from fastapi_cache_di import loop_safe, patch_fastapi_run_in_threadpool

patch_fastapi_run_in_threadpool(inline_classes=True)


@loop_safe
def get_tenant_id() -> str:
    return "acme"


class UserRepository:
    def __init__(self, tenant_id: Annotated[str, Depends(get_tenant_id)]) -> None:
        self.tenant_id = tenant_id


app = FastAPI()


@app.get("/users")
async def list_users(repo: Annotated[UserRepository, Depends()]) -> str:
    return repo.tenant_id
```

**A loop-safe callable must never block**: no I/O, no lock, no heavy
computation. A blocking call on the event loop stalls every in-flight request.
See the documentation for overrides and the marker shared with
[fastapi-standalone-di](https://github.com/Toilal/fastapi-standalone-di).

## Skipping the sub-tree of cached dependencies

At **request time**, FastAPI solves the whole sub-tree of a dependency before
checking whether its value is already in the request's cache, so a dependency
shared across a deep tree is solved again at every occurrence.
`patch_fastapi_solve_dependencies()` stops that walk as soon as the value is
cached. It is opt-in and stays active for the app's lifetime:

```python
from fastapi import FastAPI

from fastapi_cache_di import patch_fastapi_solve_dependencies

patch_fastapi_solve_dependencies()

app = FastAPI()
```

The values injected into endpoints are unchanged; see the documentation for the
details.

## Solving routes from a precompiled plan

Even with the sub-tree skip, FastAPI still walks the dependency tree
recursively at every request, and recomputes for each dependency what only
depends on the route: its cache key, the kind of its callable, the request
parameters it reads. `patch_fastapi_resolution_plan()` compiles each route's
dependency tree once, at the route's first request, into a flat plan in
FastAPI's evaluation order, then solves every request of that route from the
plan in a single loop. It is opt-in and stays active for the app's lifetime:

```python
from fastapi import FastAPI

from fastapi_cache_di import patch_fastapi_resolution_plan

patch_fastapi_resolution_plan()

app = FastAPI()
```

On a route of 100 dependencies and 211 edges, solving a request takes about a
tenth of the time it takes with the sub-tree skip alone. The injected values,
the validation errors, the request's dependency cache and the exit order of
`yield` dependencies are the ones FastAPI produces, with `dependency_overrides`
too; see the documentation for the details.

## Documentation

Full documentation is available at
[toilal.github.io/fastapi-cache-di](https://toilal.github.io/fastapi-cache-di/)
(usage guide, internals, and API reference).

## Requirements

- Python ≥ 3.12
- FastAPI ≥ 0.112.4

## License

[MIT](./LICENSE) © Rémi Alvergnat


# CHANGELOG

<!-- version list -->

## v0.4.0 (2026-10-20)

### Bug Fixes

- Build included routers' routes under the deps cache with warm_up
  ([#19](https://github.com/Toilal/fastapi-cache-di/pull/19),
  [`24e5c75`](https://github.com/Toilal/fastapi-cache-di/commit/24e5c7563f52ec4202baaa908255758817aedda8))

### Features

- Solve route dependencies from a plan compiled once per route
  ([#20](https://github.com/Toilal/fastapi-cache-di/pull/20),
  [`913674b`](https://github.com/Toilal/fastapi-cache-di/commit/913674b296a1ba2b177ec0d4dd9a4cc86906a017))


## v0.3.0 (2026-10-16)

### Bug Fixes

- Patch get_flat_dependant only where FastAPI still defines it
  ([#14](https://github.com/Toilal/fastapi-cache-di/pull/14),
  [`30fbb28`](https://github.com/Toilal/fastapi-cache-di/commit/30fbb2898aa08e1163b493fb3a7833f9cd4b6e12))

- Support FastAPI 0.141+ where get_flat_dependant no longer exists
  ([#14](https://github.com/Toilal/fastapi-cache-di/pull/14),
  [`30fbb28`](https://github.com/Toilal/fastapi-cache-di/commit/30fbb2898aa08e1163b493fb3a7833f9cd4b6e12))

### Features

- Call loop-safe dependencies on the event loop instead of a threadpool hop
  ([#16](https://github.com/Toilal/fastapi-cache-di/pull/16),
  [`a37d214`](https://github.com/Toilal/fastapi-cache-di/commit/a37d214e0c31012062d0edca3184aa49cbffb28a))

- Skip cached sub-trees when dependency_overrides is set too
  ([#15](https://github.com/Toilal/fastapi-cache-di/pull/15),
  [`bb8a2f7`](https://github.com/Toilal/fastapi-cache-di/commit/bb8a2f7d61764740a79b7ce0fe9b0d4bec016cb3))

- Skip re-walking the sub-tree of already-cached dependencies at request time
  ([#15](https://github.com/Toilal/fastapi-cache-di/pull/15),
  [`bb8a2f7`](https://github.com/Toilal/fastapi-cache-di/commit/bb8a2f7d61764740a79b7ce0fe9b0d4bec016cb3))


## v0.2.1 (2026-07-06)

### Bug Fixes

- Ship py.typed marker for PEP 561 type information
  ([#11](https://github.com/Toilal/fastapi-cache-di/pull/11),
  [`1a7eb6f`](https://github.com/Toilal/fastapi-cache-di/commit/1a7eb6f89c79b17910ad7e6642f2f229532cf98d))


## v0.2.0 (2026-07-05)

### Features

- Support Python 3.11 ([#10](https://github.com/Toilal/fastapi-cache-di/pull/10),
  [`d593d6f`](https://github.com/Toilal/fastapi-cache-di/commit/d593d6f55f56ebff80af0b4608066c8f426dcaad))


## v0.1.0 (2026-07-05)

- Initial Release

Changelog entries are generated automatically by
[python-semantic-release](https://python-semantic-release.readthedocs.io/) from
[Conventional Commits](https://www.conventionalcommits.org/) on release.
