Skip to content

FastAPI Extension

You can use AnyDI with FastAPI easily. Since FastAPI has its own dependency injection, you can use Provide annotation or Inject marker instead of the standard Depends.

from typing import Annotated

from fastapi import FastAPI, Path

import anydi.ext.fastapi
from anydi import Container, Provide


class HelloService:
    async def say_hello(self, name: str) -> str:
        return f"Hello, {name}"


container = Container()
container.register(HelloService)

app = FastAPI()


@app.get("/hello/{name}")
async def say_hello(
    name: Annotated[str, Path()],
    hello_service: Provide[HelloService],
) -> str:
    return await hello_service.say_hello(name=name)


anydi.ext.fastapi.install(app, container)

Note

To detect a dependency type, provide a valid type annotation.

Provide[Service] is equivalent to Annotated[Service, Inject()].

You can also use Inject() marker:

@app.get("/hello/{name}")
async def say_hello(
    name: str = Path(),
    hello_service: HelloService = Inject(),
) -> str:
    return await hello_service.say_hello(name=name)

Lifespan support

If you need to use AnyDI resources in your FastAPI application, you can add AnyDI startup and shutdown events to the FastAPI lifecycle events.

You can do this like this:

from fastapi import FastAPI

from anydi import Container

container = Container()

app = FastAPI(on_startup=[container.astart], on_shutdown=[container.aclose])

or using lifespan handler:

import contextlib
from typing import AsyncIterator

from fastapi import FastAPI

from anydi import Container

container = Container()


@contextlib.asynccontextmanager
async def lifespan(app: FastAPI) -> AsyncIterator[None]:
    await container.astart()
    yield
    await container.aclose()


app = FastAPI(lifespan=lifespan)

Request Scope

To use request-scoped dependencies in your FastAPI application, use the RequestScopedMiddleware. This middleware creates dependencies that are tied to each request lifecycle. You can also access the Request object.

Here is an example:

from dataclasses import dataclass

from fastapi import FastAPI, Path, Request
from starlette.middleware import Middleware

import anydi.ext.fastapi
from anydi import Container, Provide
from anydi.ext.fastapi import RequestScopedMiddleware


@dataclass
class User:
    id: str
    email: str


class UserService:
    def __init__(self, request: Request) -> None:
        self.request = request

    async def get_user(self, user_id: str) -> User:
        # Use request headers, IP, etc., from the Request object as needed
        client_ip = self.request.client.host
        return User(id=user_id, email=f"user-{client_ip}@mail.com")


container = Container()


@container.provider(scope="request")
def user_service(request: Request) -> UserService:
    return UserService(request=request)


app = FastAPI(
    middleware=[
        Middleware(RequestScopedMiddleware, container=container),
    ],
)


@app.get("/user/{user_id}")
async def get_user(
    user_id: str,
    user_service: Provide[UserService],
) -> dict:
    user = await user_service.get_user(user_id=user_id)
    return {"id": user.id, "email": user.email}


# Install `AnyDI` support in FastAPI
anydi.ext.fastapi.install(app, container)

With this setup, you can use request-scoped dependencies in your application. Request is automatically available in request-scoped providers, so you can access the request object and its data.

WebSocket Support

AnyDI supports WebSocket endpoints in FastAPI with a dedicated websocket scope for connection-specific state. The websocket scope is registered automatically when you call anydi.ext.fastapi.install().

WebSocket scope

The websocket scope has request as parent scope. The hierarchy is: singletonrequestwebsocket. This means websocket-scoped dependencies can use both request-scoped and singleton dependencies.

The websocket scope lets you create dependencies that:

  • Are created one time per WebSocket connection
  • Stay alive for all messages in a connection
  • Are cleaned up automatically when the connection closes
  • Are isolated between different connections
  • Can use request-scoped and singleton dependencies

Basic WebSocket Example

Here's a simple example of using dependency injection in a WebSocket endpoint:

from fastapi import FastAPI, WebSocket
from starlette.middleware import Middleware

import anydi.ext.fastapi
from anydi import Container, Provide
from anydi.ext.fastapi import RequestScopedMiddleware


class ConnectionState:
    def __init__(self) -> None:
        self.message_count = 0

    def process(self, message: str) -> str:
        self.message_count += 1
        return f"Message #{self.message_count}: {message}"


container = Container()


@container.provider(scope="websocket")
def connection_state() -> ConnectionState:
    return ConnectionState()


app = FastAPI(
    middleware=[
        Middleware(RequestScopedMiddleware, container=container),
    ]
)


@app.websocket("/ws/echo")
async def websocket_echo(
    websocket: WebSocket, state: Provide[ConnectionState],
) -> None:
    await websocket.accept()
    try:
        while True:
            data = await websocket.receive_text()
            if data == "quit":
                break
            response = state.process(data)
            await websocket.send_text(response)
    finally:
        await websocket.close()


anydi.ext.fastapi.install(app, container)

In this example, each WebSocket connection gets its own ConnectionState instance that stays alive for all messages in that connection. When the connection closes, the instance is cleaned up automatically.

Resource Cleanup

WebSocket-scoped dependencies support automatic cleanup using generator-based providers:

from collections.abc import Iterator


@container.provider(scope="websocket")
def database_connection() -> Iterator[DatabaseConnection]:
    # Setup: Create connection
    conn = DatabaseConnection()
    print(f"WebSocket connection opened: {conn.id}")

    yield conn

    # Cleanup: Close connection when WebSocket disconnects
    conn.close()
    print(f"WebSocket connection closed: {conn.id}")


@app.websocket("/ws/database")
async def websocket_database(
    websocket: WebSocket,
    db: Provide[DatabaseConnection],
) -> None:
    await websocket.accept()
    # Use the database connection
    data = await websocket.receive_text()
    result = await db.query(data)
    await websocket.send_json(result)
    await websocket.close()
    # Cleanup automatically happens here

Accessing WebSocket Object

The WebSocket object is automatically available in both request and websocket scoped providers:

from dataclasses import dataclass
from typing import Any

from starlette.websockets import WebSocket


@dataclass
class ConnectionInfo:
    client: str
    headers: dict[str, Any]


@container.provider(scope="websocket")
def connection_info(websocket: WebSocket) -> ConnectionInfo:
    return ConnectionInfo(
        client=websocket.client.host if websocket.client else "unknown",
        headers=dict(websocket.headers),
    )


@app.websocket("/ws/info")
async def websocket_info(
    websocket: WebSocket,
    info: Provide[ConnectionInfo],
) -> None:
    await websocket.accept()
    await websocket.send_json(info)
    await websocket.close()

Concurrent Connections

Each WebSocket connection maintains its own isolated scope:

# Connection 1 gets its own ConnectionState instance
# Connection 2 gets a different ConnectionState instance
# They don't interfere with each other

@app.websocket("/ws/concurrent")
async def websocket_concurrent(
    websocket: WebSocket,
    state: Provide[ConnectionState],
) -> None:
    await websocket.accept()
    # Each connection has isolated state
    while True:
        data = await websocket.receive_text()
        # state.message_count is independent per connection
        response = state.process(data)
        await websocket.send_text(response)

Scope Hierarchy

Since websocket inherits from request scope, websocket-scoped providers can inject request-scoped dependencies:

import uuid


@container.provider(scope="request")
def request_id() -> str:
    return str(uuid.uuid4())


@container.provider(scope="websocket")
def connection_tracker(request_id: str) -> ConnectionTracker:
    return ConnectionTracker(connection_id=request_id)