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.
- 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
.
Python>=3.10
Starlette>=0.38.0
Pydantic>=1.10.21
You can simply install starlette-di from PyPI:
pip install starlette-di
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()
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)
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.
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),
]
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
.
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...>
Find the full tutorial example here.
See the contribution guidelines.
This project is licensed under the MIT License. See the LICENSE file for details.
If you find this project useful, give it a ⭐ on GitHub!