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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ class UserRepository:


class Dependencies(Group):
settings = providers.Factory(scope=Scope.APP, creator=Settings)
user_repository = providers.Factory(scope=Scope.REQUEST, creator=UserRepository)
settings = providers.Factory(Settings, scope=Scope.APP)
user_repository = providers.Factory(UserRepository, scope=Scope.REQUEST)


with Container(groups=[Dependencies], validate=True) as container:
Expand Down
15 changes: 9 additions & 6 deletions architecture/providers.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ attributes:
from modern_di import providers, Group, Scope

class AppProviders(Group):
db_pool = providers.Factory(scope=Scope.APP, creator=create_pool)
user_repo = providers.Factory(scope=Scope.REQUEST, creator=UserRepository)
db_pool = providers.Factory(create_pool, scope=Scope.APP)
user_repo = providers.Factory(UserRepository, scope=Scope.REQUEST)
```

`Group.get_named_providers()` walks the MRO and returns a `dict[str, AbstractProvider]` mapping each declared
Expand All @@ -28,15 +28,18 @@ non-provider override mask the parent provider of the same name. `Group.get_prov

## `Factory` — the universal provider

`Factory` is the main building block. Every provider that calls a creator callable (a constructor or factory function passed as `creator=`) is a `Factory`.
`Factory` is the main building block. Every provider that calls a creator callable (a constructor or factory
function passed as its `creator` argument) is a `Factory`. Each registerable provider's subject argument —
`Factory.creator`, `ContextProvider.context_type`, `Alias.source_type` — is positional-or-keyword and leads its
`__init__`; every other parameter stays keyword-only.

### Signature

```python
Factory(
creator: Callable[..., T],
*,
scope: IntEnum = Scope.APP,
creator: Callable[..., T],
bound_type: type | None = UNSET,
kwargs: dict[str, Any] | None = None,
cache: bool | CacheSettings[T] | None = None,
Expand Down Expand Up @@ -101,7 +104,7 @@ each time.
being constructed by a factory:

```python
providers.ContextProvider(scope=Scope.REQUEST, context_type=HttpRequest)
providers.ContextProvider(HttpRequest, scope=Scope.REQUEST)
```

At resolution time it looks the value up in the container's `context_registry` for the matching scope. What
Expand Down Expand Up @@ -129,7 +132,7 @@ and the two paths are independent:
`Alias` delegates resolution to another registered provider, located by the source type:

```python
providers.Alias(source_type=ConcreteDatabase, bound_type=DatabaseProtocol)
providers.Alias(ConcreteDatabase, bound_type=DatabaseProtocol)
```

`Alias.resolve` calls `container.resolve_provider(source_provider)` — it holds no cache of its own — and also
Expand Down
10 changes: 5 additions & 5 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ class Settings:


class Dependencies(Group):
settings = providers.Factory(creator=Settings)
settings = providers.Factory(Settings)


# Pass validate=True to detect cycles and scope-chain errors at startup
Expand Down Expand Up @@ -100,7 +100,7 @@ def close_settings(settings: Settings) -> None:

class Dependencies(Group):
settings = providers.Factory(
creator=Settings,
Settings,
cache=providers.CacheSettings(finalizer=close_settings),
)

Expand Down Expand Up @@ -150,11 +150,11 @@ class UserRepository:

class Dependencies(Group):
settings = providers.Factory(
creator=Settings,
Settings,
cache=providers.CacheSettings(finalizer=close_settings),
)
request_id = providers.ContextProvider(scope=Scope.REQUEST, context_type=RequestId)
user_repository = providers.Factory(scope=Scope.REQUEST, creator=UserRepository)
request_id = providers.ContextProvider(RequestId, scope=Scope.REQUEST)
user_repository = providers.Factory(UserRepository, scope=Scope.REQUEST)


with Container(groups=[Dependencies], validate=True) as container:
Expand Down
2 changes: 1 addition & 1 deletion docs/integrations/aiohttp.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class Settings:


class AppGroup(Group):
settings = providers.Factory(scope=Scope.APP, creator=Settings)
settings = providers.Factory(Settings, scope=Scope.APP)


@inject
Expand Down
6 changes: 3 additions & 3 deletions docs/integrations/fastapi.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ def create_singleton() -> datetime.datetime:

class AppGroup(Group):
singleton = providers.Factory(
create_singleton,
scope=Scope.APP,
creator=create_singleton,
cache=True
)

Expand Down Expand Up @@ -130,8 +130,8 @@ def create_request_info(request: fastapi.Request) -> dict[str, str]:
class AppGroup(Group):
# Factory automatically resolves the request dependency based on type annotation
request_info = providers.Factory(
create_request_info,
scope=Scope.REQUEST,
creator=create_request_info,
)
```

Expand All @@ -154,8 +154,8 @@ def create_request_info(request: fastapi.Request) -> dict[str, str]:
class AppGroup(Group):
# Factory explicitly uses the request provider from the integration
request_info = providers.Factory(
create_request_info,
scope=Scope.REQUEST,
creator=create_request_info,
kwargs={"request": modern_di_fastapi.fastapi_request_provider}
)
```
Expand Down
8 changes: 4 additions & 4 deletions docs/integrations/faststream.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,13 @@ class OrderProcessor:

class AppGroup(Group):
settings = providers.Factory(
Settings,
scope=Scope.APP,
creator=Settings,
cache=True,
)
order_processor = providers.Factory(
OrderProcessor,
scope=Scope.REQUEST,
creator=OrderProcessor,
)


Expand Down Expand Up @@ -107,8 +107,8 @@ def create_message_info(message: faststream.StreamMessage) -> dict[str, str]:
class AppGroup(Group):
# The message dependency is resolved by type annotation
message_info = providers.Factory(
create_message_info,
scope=Scope.REQUEST,
creator=create_message_info,
)
```

Expand All @@ -126,8 +126,8 @@ def create_message_info(message: faststream.StreamMessage) -> dict[str, str]:

class AppGroup(Group):
message_info = providers.Factory(
create_message_info,
scope=Scope.REQUEST,
creator=create_message_info,
kwargs={"message": modern_di_faststream.faststream_message_provider},
)
```
Expand Down
10 changes: 5 additions & 5 deletions docs/integrations/litestar.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ def create_singleton() -> datetime.datetime:

class AppGroup(Group):
singleton = providers.Factory(
create_singleton,
scope=Scope.APP,
creator=create_singleton,
cache=True
)

Expand Down Expand Up @@ -77,7 +77,7 @@ class UserRepository:


class AppGroup(Group):
user_repo = providers.Factory(scope=Scope.REQUEST, creator=UserRepository)
user_repo = providers.Factory(UserRepository, scope=Scope.REQUEST)


ALL_GROUPS = [AppGroup]
Expand Down Expand Up @@ -114,7 +114,7 @@ class MyService:


class Dependencies(Group):
my_service = providers.Factory(scope=Scope.REQUEST, creator=MyService)
my_service = providers.Factory(MyService, scope=Scope.REQUEST)


ALL_GROUPS = [Dependencies]
Expand Down Expand Up @@ -168,8 +168,8 @@ def create_request_info(request: litestar.Request) -> dict[str, str]:
class AppGroup(Group):
# Factory automatically resolves the request dependency based on type annotation
request_info = providers.Factory(
create_request_info,
scope=Scope.REQUEST,
creator=create_request_info,
)
```

Expand All @@ -192,8 +192,8 @@ def create_request_info(request: litestar.Request) -> dict[str, str]:
class AppGroup(Group):
# Factory explicitly uses the request provider from the integration
request_info = providers.Factory(
create_request_info,
scope=Scope.REQUEST,
creator=create_request_info,
kwargs={"request": modern_di_litestar.litestar_request_provider}
)
```
Expand Down
6 changes: 3 additions & 3 deletions docs/integrations/starlette.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class Settings:


class AppGroup(Group):
settings = providers.Factory(scope=Scope.APP, creator=Settings)
settings = providers.Factory(Settings, scope=Scope.APP)


@inject
Expand Down Expand Up @@ -112,7 +112,7 @@ def create_request_info(request: Request) -> dict[str, str]:


class AppGroup(Group):
request_info = providers.Factory(scope=Scope.REQUEST, creator=create_request_info)
request_info = providers.Factory(create_request_info, scope=Scope.REQUEST)
```

### Explicit Usage (Provider-based Resolution)
Expand All @@ -129,8 +129,8 @@ def create_request_info(request: Request) -> dict[str, str]:

class AppGroup(Group):
request_info = providers.Factory(
create_request_info,
scope=Scope.REQUEST,
creator=create_request_info,
kwargs={"request": modern_di_starlette.starlette_request_provider},
)
```
Expand Down
6 changes: 3 additions & 3 deletions docs/integrations/typer.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,13 @@ class HealthReporter:

class AppGroup(Group):
settings = providers.Factory(
Settings,
scope=Scope.APP,
creator=Settings,
cache=True,
)
health_reporter = providers.Factory(
HealthReporter,
scope=Scope.REQUEST,
creator=HealthReporter,
)


Expand Down Expand Up @@ -101,7 +101,7 @@ class Job:


class AppGroup(Group):
job = providers.Factory(scope=Scope.ACTION, creator=Job, bound_type=None)
job = providers.Factory(Job, scope=Scope.ACTION, bound_type=None)


@app.command()
Expand Down
4 changes: 2 additions & 2 deletions docs/integrations/writing-integrations.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ dispatches off them.
```python
from modern_di import Scope, providers

myfw_request_provider = providers.ContextProvider(scope=Scope.REQUEST, context_type=myfw.Request)
myfw_websocket_provider = providers.ContextProvider(scope=Scope.SESSION, context_type=myfw.WebSocket)
myfw_request_provider = providers.ContextProvider(myfw.Request, scope=Scope.REQUEST)
myfw_websocket_provider = providers.ContextProvider(myfw.WebSocket, scope=Scope.SESSION)

_CONNECTION_PROVIDERS = (myfw_request_provider, myfw_websocket_provider)
```
Expand Down
8 changes: 4 additions & 4 deletions docs/introduction/comparison.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,11 +132,11 @@ lifetime dialect, so here is how the same six concepts translate:

| Concept | dependency-injector | dishka | wireup | svcs | FastAPI `Depends` | modern-di |
|---|---|---|---|---|---|---|
| Singleton (create once, share) | `providers.Singleton(...)` | `provide(Impl, scope=Scope.APP)` — cached by default within its scope | `@injectable` — default `lifetime="singleton"` | `registry.register_value(Type, value)` at startup | a dependency wrapped in `@lru_cache` | [`Factory(scope=Scope.APP, cache=True)`](../providers/factories.md#cached-factories) |
| Singleton (create once, share) | `providers.Singleton(...)` | `provide(Impl, scope=Scope.APP)` — cached by default within its scope | `@injectable` — default `lifetime="singleton"` | `registry.register_value(Type, value)` at startup | a dependency wrapped in `@lru_cache` | [`Factory(..., scope=Scope.APP, cache=True)`](../providers/factories.md#cached-factories) |
| Transient (fresh instance every time) | `providers.Factory(...)` | `provide(Impl, cache=False)` | `@injectable(lifetime="transient")` | no dedicated provider — call the plain factory directly | `Depends(fn, use_cache=False)` | a plain [`Factory(...)`](../providers/factories.md) with no `cache` |
| Request-scoped | `providers.Resource` + the `Closing` wiring marker | `provide(Impl, scope=Scope.REQUEST)` | `@injectable(lifetime="scoped")` | one instance per `svcs.Container` (built per request) | bare `Depends(fn)` — computed once per request by default | [`Factory(scope=Scope.REQUEST, cache=True)`](../providers/scopes.md) |
| Runtime value (request object, etc.) | `providers.Configuration` / `.from_value()` | `from_context(provides=Type, scope=...)` declared, then `context={Type: value}` at scope entry | a typed constructor parameter resolved from the active scope's context | `registry.register_value(Type, value)`, or a per-container local factory | the framework injects `Request`/`WebSocket` directly by type | [`ContextProvider(context_type=...)`](../providers/context.md) + `context={...}` |
| Interface binding (concrete → abstract type) | `providers.AbstractFactory` — must be overridden with a concrete `Factory` before use | `alias(source=Impl, provides=Interface)` | `@injectable(as_type=Interface)` | `register_factory(Interface, factory)` — svcs keys by whatever type you register under | n/a — `Depends` is callable-keyed, not type-keyed | [`Alias(source_type=Impl, bound_type=Interface)`](../providers/alias.md) |
| Request-scoped | `providers.Resource` + the `Closing` wiring marker | `provide(Impl, scope=Scope.REQUEST)` | `@injectable(lifetime="scoped")` | one instance per `svcs.Container` (built per request) | bare `Depends(fn)` — computed once per request by default | [`Factory(..., scope=Scope.REQUEST, cache=True)`](../providers/scopes.md) |
| Runtime value (request object, etc.) | `providers.Configuration` / `.from_value()` | `from_context(provides=Type, scope=...)` declared, then `context={Type: value}` at scope entry | a typed constructor parameter resolved from the active scope's context | `registry.register_value(Type, value)`, or a per-container local factory | the framework injects `Request`/`WebSocket` directly by type | [`ContextProvider(...)`](../providers/context.md) + `context={...}` |
| Interface binding (concrete → abstract type) | `providers.AbstractFactory` — must be overridden with a concrete `Factory` before use | `alias(source=Impl, provides=Interface)` | `@injectable(as_type=Interface)` | `register_factory(Interface, factory)` — svcs keys by whatever type you register under | n/a — `Depends` is callable-keyed, not type-keyed | [`Alias(Impl, bound_type=Interface)`](../providers/alias.md) |
| Test override | `provider.override(...)`, or `with provider.override(...):` | no dedicated API — build a separate container from mock providers | `with container.override.injectable(Target, new=fake):` | re-call `register_value()`/`register_factory()`; `container.close()` first if already cached | `app.dependency_overrides[dep] = fake` | [`container.override(provider, mock)`](../recipes/testing-overrides.md) |

## See also
Expand Down
10 changes: 5 additions & 5 deletions docs/introduction/for-fastapi-users.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ translates the `Depends` idioms you already know into their modern-di equivalent

| FastAPI `Depends` | modern-di | Notes |
|---|---|---|
| `Depends(fn)` | `Factory(creator=fn)` | Both auto-wire the callable's parameters; modern-di matches by type annotation instead of by the callable's own parameter defaults. |
| bare `Depends(fn)` (`use_cache=True`, the default) | `Factory(scope=Scope.REQUEST, creator=fn, cache=True)` | FastAPI memoizes a dependency for the rest of the *same request* once it's been called; the REQUEST-scoped cached `Factory` is the equivalent — one shared instance per request container. |
| `Depends(fn, use_cache=False)` | a bare `Factory(creator=fn)` — no `cache` | Without `cache`, a `Factory` builds a fresh instance on every resolve, matching `use_cache=False`. |
| `Depends(fn)` | `Factory(fn)` | Both auto-wire the callable's parameters; modern-di matches by type annotation instead of by the callable's own parameter defaults. |
| bare `Depends(fn)` (`use_cache=True`, the default) | `Factory(fn, scope=Scope.REQUEST, cache=True)` | FastAPI memoizes a dependency for the rest of the *same request* once it's been called; the REQUEST-scoped cached `Factory` is the equivalent — one shared instance per request container. |
| `Depends(fn, use_cache=False)` | a bare `Factory(fn)` — no `cache` | Without `cache`, a `Factory` builds a fresh instance on every resolve, matching `use_cache=False`. |
| `yield`-based teardown (`def fn(): ...; yield x; ...cleanup...`) | `cache=CacheSettings(finalizer=cleanup_fn)` | modern-di has no generator-creator form (see [Design decisions](design-decisions.md)); teardown is a second, explicit object instead of code after `yield`. `finalizer` may be sync or async — see [Lifecycle](../providers/lifecycle.md). |
| `@lru_cache`-wrapped dependency (process-wide singleton) | `Factory(scope=Scope.APP, creator=fn, cache=True)`, optionally with a `finalizer` | `lru_cache` has no cleanup hook; the APP-scoped cached `Factory` adds one via `CacheSettings(finalizer=...)` if the singleton needs to release anything on shutdown. |
| `@lru_cache`-wrapped dependency (process-wide singleton) | `Factory(fn, scope=Scope.APP, cache=True)`, optionally with a `finalizer` | `lru_cache` has no cleanup hook; the APP-scoped cached `Factory` adds one via `CacheSettings(finalizer=...)` if the singleton needs to release anything on shutdown. |
| `app.dependency_overrides[fn] = fake` | `container.override(provider, fake)` | modern-di overrides are keyed by **provider reference**, not by callable, and apply across the whole container tree — see [Testing with overrides](../recipes/testing-overrides.md). Reset with `container.reset_override(provider)`. |

## Two meanings of "scope"
Expand Down Expand Up @@ -53,8 +53,8 @@ def close_session(session: Session) -> None:

class Dependencies(Group):
session = providers.Factory(
create_session,
scope=Scope.REQUEST,
creator=create_session,
cache=providers.CacheSettings(finalizer=close_session),
)
```
Expand Down
4 changes: 2 additions & 2 deletions docs/introduction/resolving.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,11 @@ class DatabaseConnection:

class Dependencies(Group):
db_config = providers.Factory(
DatabaseConfig,
scope=Scope.APP,
creator=DatabaseConfig,
kwargs={"host": "localhost", "port": 5432},
)
db_connection = providers.Factory(scope=Scope.APP, creator=DatabaseConnection)
db_connection = providers.Factory(DatabaseConnection, scope=Scope.APP)


container = Container(groups=[Dependencies], validate=True)
Expand Down
Loading