Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
184 changes: 184 additions & 0 deletions docs/integrations/flask.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
# Usage with `Flask`

Flask has no dependency-injection system of its own, so `modern-di-flask` uses
the `@inject` decorator with `FromDI` markers (there is no `Depends`). `setup_di`
installs a `before_request`/`teardown_appcontext` pair that opens a per-request
`Scope.REQUEST` child container and closes it once the request finishes.
Resolution is **sync-only** — the child container is closed with `close_sync()`.

## How to use

### 1. Install `modern-di-flask`

=== "uv"

```bash
uv add modern-di-flask
```

=== "pip"

```bash
pip install modern-di-flask
```

### 2. Apply to your application

```python
import typing

from flask import Flask
from modern_di import Container, Group, Scope, providers
from modern_di_flask import FromDI, inject, setup_di


class Settings:
def __init__(self) -> None:
self.greeting = "hello"


class Dependencies(Group):
settings = providers.Factory(scope=Scope.APP, creator=Settings)


app = Flask(__name__)


@app.route("/hello/<name>")
@inject
def hello(name: str, settings: typing.Annotated[Settings, FromDI(Dependencies.settings)]) -> str:
return f"{settings.greeting}, {name}"


# call setup_di AFTER registering routes — required when using auto_inject
setup_di(app, Container(groups=[Dependencies], validate=True))
```

`FromDI(dependency)` accepts either a provider reference (as above) or a plain
type, resolved from the per-request child container the middleware built.

### 3. `auto_inject`

Pass `auto_inject=True` to `setup_di` to wire every registered view (app routes
and blueprint routes alike) without a per-view `@inject`. Because it walks
`app.view_functions` at call time, `setup_di` must run **after** all routes —
including blueprint routes — have been registered:

```python
import typing

from flask import Flask
from modern_di import Container, Group, Scope, providers
from modern_di_flask import FromDI, setup_di


class Settings:
def __init__(self) -> None:
self.greeting = "hello"


class Dependencies(Group):
settings = providers.Factory(scope=Scope.APP, creator=Settings)


app = Flask(__name__)


@app.route("/hello/<name>")
def hello(name: str, settings: typing.Annotated[Settings, FromDI(Dependencies.settings)]) -> str:
return f"{settings.greeting}, {name}"


# no @inject needed on individual views
setup_di(app, Container(groups=[Dependencies], validate=True), auto_inject=True)
```

A view that already carries `@inject` is left alone — `auto_inject` only wraps
views that weren't injected yet.

### 4. Scopes and request lifecycle

See [the scope hierarchy](../providers/scopes.md#the-scope-dependency-rule).
Flask has no websocket concept, so the integration only ever opens one child
scope: `before_request` builds a `Scope.REQUEST` child of the root container
and stores it on `flask.g`; `teardown_appcontext` closes it with `close_sync()`
once the request (including error handling) is done.

### 5. Root container teardown

`setup_di` does not close the root container for you — Flask has no
application-shutdown hook to run it from. You own root teardown, typically at
your own process-shutdown point:

```python
from flask import Flask
from modern_di import Container, Group, Scope, providers
from modern_di_flask import fetch_di_container, setup_di


class Dependencies(Group):
pass


app = Flask(__name__)
setup_di(app, Container(groups=[Dependencies], validate=True))

# ... register an atexit hook, a CLI teardown command, or call this
# explicitly wherever your process shuts down:
fetch_di_container(app).close_sync()
```

## Framework Context Objects

`flask.Request` is automatically made available by the integration — see
[Framework Context Objects](../providers/context.md#framework-context-objects)
for how implicit and explicit resolution work.

The following context provider is available for import:

- `flask_request_provider` — `ContextProvider` for the current `flask.Request` (REQUEST scope), auto-registered by type.

### Implicit Usage (Type-based Resolution)

```python
from flask import Request
from modern_di import Group, Scope, providers


def create_request_info(request: Request) -> dict[str, str]:
return {"method": request.method, "url": request.url}


class AppGroup(Group):
request_info = providers.Factory(create_request_info, scope=Scope.REQUEST)
```

### Explicit Usage (Provider-based Resolution)

```python
from flask import Request
from modern_di import Group, Scope, providers
from modern_di_flask import flask_request_provider


def create_request_info(request: Request) -> dict[str, str]:
return {"method": request.method, "url": request.url}


class AppGroup(Group):
request_info = providers.Factory(
create_request_info,
scope=Scope.REQUEST,
kwargs={"request": flask_request_provider},
)
```

## API

| Symbol | Description |
|---|---|
| `setup_di(app, container, *, auto_inject=False)` | Registers the container on `app.extensions`, installs the `before_request`/`teardown_appcontext` pair that builds and closes a per-request `Scope.REQUEST` child container, and — if `auto_inject=True` — wraps every currently-registered view with `inject`; returns the container. |
| `FromDI(dependency)` | Marker (used with `@inject`) that resolves a provider or type from the per-request child container. |
| `inject` | Decorator for a view function; resolves its `FromDI`-annotated parameters without rewriting the function's signature. |
| `fetch_di_container(app)` | Returns the root `Container` stored on `app.extensions`. |
| `flask_request_provider` | `ContextProvider` for `flask.Request` (REQUEST scope), auto-registered by type. |
2 changes: 2 additions & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ nav:
- celery: integrations/celery.md
- FastAPI: integrations/fastapi.md
- FastStream: integrations/faststream.md
- Flask: integrations/flask.md
- Litestar: integrations/litestar.md
- Starlette: integrations/starlette.md
- taskiq: integrations/taskiq.md
Expand Down Expand Up @@ -166,6 +167,7 @@ plugins:
- integrations/celery.md: Usage with Celery
- integrations/fastapi.md: Usage with FastAPI
- integrations/faststream.md: Usage with FastStream
- integrations/flask.md: Usage with Flask
- integrations/litestar.md: Usage with Litestar
- integrations/starlette.md: Usage with Starlette
- integrations/taskiq.md: Usage with taskiq
Expand Down