Injecting Providers in that-depends¶
that-depends uses a decorator-based approach for both synchronous and asynchronous functions. By decorating a function with @inject and marking certain parameters as Provide[...], that-depends will automatically resolve the specified providers at call time.
Overview¶
In that-depends, you define your dependencies as AbstractProvider instances—e.g., Singleton, Factory, Resource, or others. These providers typically live inside a subclass of BaseContainer, making them globally accessible.
When you want to use a provider in a function, you can mark a parameter’s default value as:
You then decorate the function with @inject. This tells that-depends to automatically resolve providers when you call your function.
Quick Start¶
Below is a simple example demonstrating how to define a container, declare a provider, and inject that provider into a function.
1. Define a Container and a Provider¶
from that_depends import BaseContainer
from that_depends.providers import Singleton
class MyContainer(BaseContainer):
greeting_provider = Singleton(lambda: "Hello from MyContainer")
2. Inject the Provider into a Function¶
from that_depends import inject, Provide
@inject
def greet_user(greeting: str = Provide[MyContainer.greeting_provider]) -> str:
return f"Greeting: {greeting}"
Here:
- We used
@injectabovegreet_user. - We declared a parameter
greeting, whose default value isProvide[MyContainer.greeting_provider].
3. Call the Function¶
The @inject Decorator in Detail¶
Synchronous vs Asynchronous Functions¶
@inject works on both sync and async functions. Just note that injecting async providers into sync functions is not supported.
@inject
async def async_greet_user(greeting: str = Provide[MyContainer.greeting_provider]) -> str:
# asynchronous operations...
return f"Greeting: {greeting}"
Using Provide[...] as a Default¶
It is recommended to wrap your provider in Provide[...] when using it as a default in an injected function since it provides correct type resolution:
@inject
def greet_user_direct(
greeting: str = Provide[MyContainer.greeting_provider] # (1)!
) -> str:
return f"Greeting: {greeting}"
- Notice that although
greetingis astr,mypyand you IDE will not complain.
Injection Warnings¶
If @inject finds no parameters whose default values are providers, it will issue a warning:
Expected injection, but nothing found. Remove @inject decorator.
This is to avoid accidentally decorating a function that doesn’t actually require injection.
Specifying a Scope¶
By default, @inject uses the ContextScopes.INJECT scope. If you want to override that, do:
from that_depends import inject
from that_depends.providers.context_resources import ContextScopes
@inject(scope=ContextScopes.REQUEST)
def greet_user(greeting: str = Provide[MyContainer.greeting_provider]):
...
When greet_user is called, that-depends:
- Initializes the context for all
REQUEST(orANY) scopedargsandkwargs. - Resolves all providers in the
argsandkwargsof the function. - Calls your function with the resolved dependencies.
For more details regarding scopes and context management, see the Context Resources documentation and the Scopes documentation.
Overriding Providers¶
In tests or specialized scenarios, you may want to override a provider’s value temporarily. You can do so with the container’s override_providers() method or the provider’s own override_context():
def test_greet_override():
# Override the greeting_provider with a mock value
with MyContainer.override_providers_sync({"greeting_provider": "TestHello"}):
result = greet_user()
assert result == "Greeting: TestHello"
This is especially helpful for unit tests where you want to substitute real dependencies (e.g., database connections) with mocks or stubs.
For more details on overring providers, see the Overriding Providers documentation.
Frequently Asked Questions¶
-
Do I need to call
@injectevery time I reference a provider?
No—only when you want automatic injection of providers into function parameters. If you are resolving dependencies manually (e.g.,MyContainer.greeting_provider.resolve_sync()), then@injectis not needed. -
What if I provide a custom argument to a parameter that has a default provider?
If you explicitly pass a value, that value overrides the injected default: -
Can I combine
@injectwith other decorators?
Yes, you can. Generally, put@injectbelow others, depending on the order you need. If you run into issues, experiment with the order or handle context manually.