Skip to content

A dependency injection library for Starlette. It supports Scoped, Transient, and Singleton lifetimes, route parameter and request body injection via Pydantic, and seamless integration with Starlette using decorators and middleware.

License

Notifications You must be signed in to change notification settings

daireto/starlette-di

Folders and files

NameName
Last commit message
Last commit date

Latest commit

3abfef2 · Mar 14, 2025

History

24 Commits
Mar 13, 2025
Mar 14, 2025
Mar 13, 2025
Mar 13, 2025
Mar 13, 2025
Mar 13, 2025
Mar 13, 2025
Mar 13, 2025
Mar 13, 2025
Mar 14, 2025
Mar 13, 2025
Mar 14, 2025

Repository files navigation

Starlette DI

Supported Python versions Package version Supported Starlette versions Publish Coverage Status License

Starlette DI is a dependency injection library for Starlette applications. It simplifies dependency management by allowing services to be injected using Scoped, Transient, and Singleton lifetimes (similar to .NET Core). Also, enables automatic injection of route parameters, and request bodies using Pydantic models, making API development more efficient, and structured.

Table of Contents

Features

  • Scoped, Transient, and Singleton: Dependency injection with three service lifetimes, similar to .NET Core.
  • Service Injection: Supports injecting services into functions, methods, and endpoint classes in Starlette.
  • Route Parameter Injection: Automatically extracts URL parameters in controllers.
  • Request Body Injection: Maps JSON request body data directly to Pydantic models.
  • Dependency Injection Middleware: Provides a middleware layer to manage dependency injection throughout the request lifecycle.
  • Pydantic Compatibility: Leverages Pydantic for data validation, and conversion.
  • Decorators for Endpoints: Simplifies injection with @inject, @inject_method, and @inject_class.

Requirements

  • Python>=3.10
  • Starlette>=0.38.0
  • Pydantic>=1.10.21

Installation

You can simply install starlette-di from PyPI:

pip install starlette-di

Tutorial

1. Create a service

Define a service that can be injected:

from abc import ABC, abstractmethod

class IGreeter(ABC):
    @abstractmethod
    def greet(self) -> str: ...


class Greeter(IGreeter):
    def greet(self) -> str:
        return 'Hello!'

Alternatively, use a factory function:

def greeter_factory() -> IGreeter:
    return Greeter()

2. Configure dependency injection

Use a ServiceCollection to register services with different lifetimes:

  • Singleton: one instance for the application lifetime.
  • Scoped: one instance per request.
  • Transient: new instance created each time it's requested.

Example:

from starlette_di import ServiceCollection

services = ServiceCollection()
services.add_transient(IGreeter, Greeter)
# also, services.add_scoped(IGreeter, Greeter)
# or services.add_singleton(IGreeter, Greeter)
provider = services.build_provider()

Using a factory function:

def greeter_factory() -> IGreeter:
    return Greeter()

services.add_transient(IGreeter, greeter_factory)

3. Injecting services

Use the @inject, @inject_method, and @inject_class decorators to inject the service into an endpoint function, method or class respectively.

Warning

Only asynchronous endpoints can be decorated. Trying to decorate a synchronous endpoint will raise a TypeError.

Inject into an endpoint function

Inject the service into an endpoint function using the @inject decorator:

from starlette.requests import Request
from starlette.responses import JSONResponse

from starlette_di import inject

@inject
async def greet(request: Request, greeter: IGreeter):
    return JSONResponse({'message': greeter.greet()})

Inject into an endpoint method

Inject the service into an endpoint method using the @inject_method decorator:

from starlette.requests import Request
from starlette.endpoints import HTTPEndpoint
from starlette.responses import JSONResponse

from starlette_di import inject_method

class GreetEndpoint(HTTPEndpoint):
    @inject_method
    async def get(self, request: Request, greeter: IGreeter):
        return JSONResponse({'message': greeter.greet()})

Note

If you are implementing a custom starlette.routing.Route class for endpoints that do not expect the request object to be passed, you can set the pass_request argument to False:

from starlette.responses import JSONResponse
from starlette.endpoints import HTTPEndpoint

from starlette_di import inject_method

class GreetEndpoint(HTTPEndpoint):
    @inject_method(pass_request=False)
    async def get(self, greeter: IGreeter):
        return JSONResponse({'message': greeter.greet()})

Inject into an endpoint class

Inject the service into an endpoint class using the @inject_class decorator:

from starlette.responses import JSONResponse
from starlette.endpoints import HTTPEndpoint

from starlette_di import inject_class

@inject_class
class GreetEndpoint(HTTPEndpoint):
    def __init__(self, request: Request, greeter: IGreeter):
        super().__init__(request)
        self.greeter = greeter

    async def get(self, request: Request):
        return JSONResponse({'message': self.greeter.greet()})

Warning

The decorated class must be a subclass of starlette.endpoints.HTTPEndpoint. Otherwise, it will raise a TypeError. To learn more about endpoints, see the Starlette documentation.

4. Inject path params

You can inject request path parameters:

from starlette.requests import Request
from starlette.responses import JSONResponse
from starlette.routing import Route

from starlette_di import inject

@inject
async def greet_person(self, request: Request, name: str):
    return JSONResponse({'message': f'Hello {name}!'})

routes = [
    Route('/greet/{name:str}', greet_person),
]

5. Inject request body

Also, you can inject the request body using Pydantic models. If there's only one Pydantic model parameter, the whole JSON body is injected. Otherwise, each parameter is extracted from the JSON body using its name.

Only one parameter:

from pydantic import BaseModel
from starlette.requests import Request
from starlette.responses import JSONResponse

class User(BaseModel):
    name: str
    age: int

@inject
async def create_user(request: Request, user: User):
    return JSONResponse({'name': user.name, 'age': user.age})

# Example request
# {'name': 'Jane Doe', 'age': 25}

Two or more parameters

from pydantic import BaseModel
from starlette.requests import Request
from starlette.responses import JSONResponse

class User(BaseModel):
    name: str
    age: int

class Product(BaseModel):
    name: str
    price: float

@inject
async def update_product(request: Request, user: User, product: Product):
    return JSONResponse({'user_name': user.name, 'product_name': product.name})

# Example request
# {
#     'user': {'name': 'Jane Doe', 'age': 25},
#     'product': {'name': 'Computer', 'price': 225.0},
# }

Warning

The request body must be a JSON dict. Otherwise, it will raise a ValueError.

6. Use the DependencyInjectionMiddleware

Use the DependencyInjectionMiddleware to handle dependency injection.

This middleware sets up the request scope for dependency injection by creating a scoped service provider, and adding it to the request scope.

Pass the service provider built in here to the service_provider argument of the middleware:

from starlette.applications import Starlette
from starlette.middleware import Middleware
from starlette.routing import Route
from starlette_di import DependencyInjectionMiddleware

app = Starlette(
    routes=[Route('/greet', GreetEndpoint)],
    middleware=[
        Middleware(DependencyInjectionMiddleware, service_provider=provider),
    ]
)

Note

You can access the scoped service provider from the request scope using the SERVICE_PROVIDER_ARG_NAME constant:

from starlette_di.definitions import SERVICE_PROVIDER_ARG_NAME

request.scope[SERVICE_PROVIDER_ARG_NAME]
# <starlette_di.service_provider.ScopedServiceProvider object at 0x00000...>

Full example

Find the full tutorial example here.

Contributing

See the contribution guidelines.

License

This project is licensed under the MIT License. See the LICENSE file for details.

Support

If you find this project useful, give it a ⭐ on GitHub!

About

A dependency injection library for Starlette. It supports Scoped, Transient, and Singleton lifetimes, route parameter and request body injection via Pydantic, and seamless integration with Starlette using decorators and middleware.

Topics

Resources

License

Stars

Watchers

Forks

Languages