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
32 changes: 32 additions & 0 deletions docs/providers/context_local_resource.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
.. _context-local-resource-provider:

Context Local Resource provider
================================

.. meta::
:keywords: Python,DI,Dependency injection,IoC,Inversion of Control,Resource,Context Local,
Context Variables,Singleton,Per-context
:description: Context Local Resource provider provides a component with initialization and shutdown
that is scoped to execution context using contextvars. This page demonstrates how to
use context local resource provider.

.. currentmodule:: dependency_injector.providers

``ContextLocalResource`` inherits from :ref:`resource-provider` and uses the same initialization and shutdown logic
as the standard ``Resource`` provider.
It extends it with context-local storage using Python's ``contextvars`` module.
This means that objects are context local singletons - the same context will
receive the same instance, but different execution contexts will have their own separate instances.

This is particularly useful in asynchronous applications where you need per-request resource instances
(such as database sessions) that are automatically cleaned up when the request context ends.
Example:

.. literalinclude:: ../../examples/providers/context_local_resource.py
:language: python
:lines: 3-



.. disqus::

1 change: 1 addition & 0 deletions docs/providers/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ Providers module API docs - :py:mod:`dependency_injector.providers`
dict
configuration
resource
context_local_resource
aggregate
selector
dependency
Expand Down
3 changes: 3 additions & 0 deletions docs/providers/resource.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ Resource provider
Resource providers help to initialize and configure logging, event loop, thread or process pool, etc.

Resource provider is similar to ``Singleton``. Resource initialization happens only once.
If you need a context local singleton (where each execution context has its own instance),
see :ref:`context-local-resource-provider`.

You can make injections and use provided instance the same way like you do with any other provider.

.. code-block:: python
Expand Down
49 changes: 49 additions & 0 deletions examples/providers/context_local_resource.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
from uuid import uuid4

from fastapi import Depends, FastAPI

from dependency_injector import containers, providers
from dependency_injector.wiring import Closing, Provide, inject

global_list = []


class AsyncSessionLocal:
def __init__(self):
self.id = uuid4()

async def __aenter__(self):
print("Entering session !")
return self

async def __aexit__(self, exc_type, exc_val, exc_tb):
print("Closing session !")

async def execute(self, user_input):
return f"Executing {user_input} in session {self.id}"


app = FastAPI()


class Container(containers.DeclarativeContainer):
db_session = providers.ContextLocalResource(AsyncSessionLocal)


@app.get("/")
@inject
async def index(db: AsyncSessionLocal = Depends(Closing[Provide["db_session"]])):
if db.id in global_list:
raise Exception("The db session was already used") # never reaches here
global_list.append(db.id)
res = await db.execute("SELECT 1")
return str(res)


if __name__ == "__main__":
import uvicorn

container = Container()
container.wire(modules=["__main__"])
uvicorn.run(app, host="localhost", port=8000)
container.unwire()
6 changes: 3 additions & 3 deletions src/dependency_injector/_cwiring.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ from collections.abc import Awaitable
from inspect import CO_ITERABLE_COROUTINE
from types import CoroutineType, GeneratorType

from .providers cimport Provider, Resource
from .providers cimport Provider, BaseResource
from .wiring import _Marker


Expand Down Expand Up @@ -54,15 +54,15 @@ cdef class DependencyResolver:
cdef Provider provider

for name, provider in self.closings.items():
if _is_injectable(self.kwargs, name) and isinstance(provider, Resource):
if _is_injectable(self.kwargs, name) and isinstance(provider, BaseResource):
provider.shutdown()

cdef list _handle_closings_async(self):
cdef list to_await = []
cdef Provider provider

for name, provider in self.closings.items():
if _is_injectable(self.kwargs, name) and isinstance(provider, Resource):
if _is_injectable(self.kwargs, name) and isinstance(provider, BaseResource):
if _isawaitable(shutdown := provider.shutdown()):
to_await.append(shutdown)

Expand Down
6 changes: 3 additions & 3 deletions src/dependency_injector/containers.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ try:
except ImportError:
from typing_extensions import Self as _Self

from .providers import Provider, Resource, Self, ProviderParent
from .providers import Provider, BaseResource, Self, ProviderParent

C_Base = TypeVar("C_Base", bound="Container")
C = TypeVar("C", bound="DeclarativeContainer")
Expand Down Expand Up @@ -77,8 +77,8 @@ class Container:
warn_unresolved: bool = False,
) -> None: ...
def unwire(self) -> None: ...
def init_resources(self, resource_type: Type[Resource[Any]] = Resource) -> Optional[Awaitable[None]]: ...
def shutdown_resources(self, resource_type: Type[Resource[Any]] = Resource) -> Optional[Awaitable[None]]: ...
def init_resources(self, resource_type: Type[BaseResource[Any]] = BaseResource) -> Optional[Awaitable[None]]: ...
def shutdown_resources(self, resource_type: Type[BaseResource[Any]] = BaseResource) -> Optional[Awaitable[None]]: ...
def load_config(self) -> None: ...
def apply_container_providers_overridings(self) -> None: ...
def reset_singletons(self) -> SingletonResetContext[C_Base]: ...
Expand Down
8 changes: 4 additions & 4 deletions src/dependency_injector/containers.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -339,10 +339,10 @@ class DynamicContainer(Container):
self.wired_to_modules.clear()
self.wired_to_packages.clear()

def init_resources(self, resource_type=providers.Resource):
def init_resources(self, resource_type=providers.BaseResource):
"""Initialize all container resources."""

if not issubclass(resource_type, providers.Resource):
if not issubclass(resource_type, providers.BaseResource):
raise TypeError("resource_type must be a subclass of Resource provider")

futures = []
Expand All @@ -356,10 +356,10 @@ class DynamicContainer(Container):
if futures:
return asyncio.gather(*futures)

def shutdown_resources(self, resource_type=providers.Resource):
def shutdown_resources(self, resource_type=providers.BaseResource):
"""Shutdown all container resources."""

if not issubclass(resource_type, providers.Resource):
if not issubclass(resource_type, providers.BaseResource):
raise TypeError("resource_type must be a subclass of Resource provider")

def _independent_resources(resources):
Expand Down
26 changes: 22 additions & 4 deletions src/dependency_injector/providers.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -224,11 +224,19 @@ cdef class Dict(Provider):
cpdef object _provide(self, tuple args, dict kwargs)


cdef class Resource(Provider):
cdef class ResourceState:
cdef object resource
cdef object shutdowner
cdef bint is_async
cdef bint async_done

cdef void from_coro(self, coro, error_callback)
cdef void from_async_context_manager(self, acm, error_callback)
cdef object from_context_manager(self, cm, error_callback)


cdef class BaseResource(Provider):
cdef object _provides
cdef bint _initialized
cdef object _shutdowner
cdef object _resource

cdef tuple _args
cdef int _args_len
Expand All @@ -237,6 +245,16 @@ cdef class Resource(Provider):
cdef int _kwargs_len

cpdef object _provide(self, tuple args, dict kwargs)
cdef void set_state(self, ResourceState state)
cdef ResourceState get_state(self)


cdef class Resource(BaseResource):
cdef ResourceState _state


cdef class ContextLocalResource(BaseResource):
cdef object _cvar


cdef class Container(Provider):
Expand Down
5 changes: 4 additions & 1 deletion src/dependency_injector/providers.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -449,7 +449,7 @@ class Dict(Provider[_Dict]):
) -> _Self: ...
def clear_kwargs(self) -> _Self: ...

class Resource(Provider[T]):
class BaseResource(Provider[T]):
@overload
def __init__(
self,
Expand Down Expand Up @@ -524,6 +524,9 @@ class Resource(Provider[T]):
def init(self) -> Optional[Awaitable[T]]: ...
def shutdown(self) -> Optional[Awaitable]: ...

class Resource(BaseResource[T]): ...
class ContextLocalResource(BaseResource[T]):...

class Container(Provider[T]):
def __init__(
self,
Expand Down
Loading
Loading