Skip to content

Commit 6d84f56

Browse files
authored
Merge pull request #157 from modern-python/fix-required-but-missing-context
raise error for required but missing context
2 parents fd12e1e + 68ee379 commit 6d84f56

3 files changed

Lines changed: 45 additions & 13 deletions

File tree

modern_di/providers/context_provider.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,16 @@
1010

1111

1212
class ContextProvider(AbstractProvider[types.T_co]):
13-
__slots__ = AbstractProvider.BASE_SLOTS
14-
15-
def __init__(self, *, scope: Scope = Scope.APP, context_type: type[types.T_co]) -> None:
16-
super().__init__(scope=scope, bound_type=context_type)
13+
__slots__ = [*AbstractProvider.BASE_SLOTS, "_context_type"]
14+
15+
def __init__(
16+
self,
17+
*,
18+
scope: Scope = Scope.APP,
19+
context_type: type[types.T_co],
20+
bound_type: type | None = types.UNSET, # type: ignore[assignment]
21+
) -> None:
22+
super().__init__(scope=scope, bound_type=bound_type if bound_type != types.UNSET else context_type)
1723

1824
def validate(self, container: "Container") -> dict[str, typing.Any]: # noqa: ARG002
1925
return {"bound_type": self.bound_type, "self": self}

modern_di/providers/factory.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import typing
44

55
from modern_di import errors, types
6+
from modern_di.providers import ContextProvider
67
from modern_di.providers.abstract import AbstractProvider
78
from modern_di.scope import Scope
89
from modern_di.types_parser import SignatureItem, parse_creator
@@ -63,11 +64,18 @@ def _compile_kwargs(self, container: "Container") -> dict[str, typing.Any]:
6364
if provider:
6465
break
6566

67+
is_kwarg_not_found = not self._kwargs or k not in self._kwargs
6668
if provider:
6769
result[k] = provider
70+
if is_kwarg_not_found and isinstance(provider, ContextProvider) and provider.resolve(container) is None:
71+
raise RuntimeError(
72+
errors.FACTORY_ARGUMENT_RESOLUTION_ERROR.format(
73+
arg_name=k, arg_type=v.arg_type, bound_type=self.bound_type or self._creator
74+
)
75+
)
6876
continue
6977

70-
if (not self._kwargs or k not in self._kwargs) and v.default == types.UNSET:
78+
if v.default == types.UNSET and is_kwarg_not_found:
7179
raise RuntimeError(
7280
errors.FACTORY_ARGUMENT_RESOLUTION_ERROR.format(
7381
arg_name=k, arg_type=v.arg_type, bound_type=self.bound_type or self._creator

tests/providers/test_context_provider.py

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,51 @@
1+
import dataclasses
12
import datetime
23

3-
from modern_di import Container, Scope, providers
4+
import pytest
5+
6+
from modern_di import Container, Group, Scope, providers
47

58

6-
context_provider = providers.ContextProvider(scope=Scope.APP, context_type=datetime.datetime)
79
request_context_provider = providers.ContextProvider(scope=Scope.REQUEST, context_type=datetime.datetime)
810

911

12+
@dataclasses.dataclass(kw_only=True, slots=True, frozen=True)
13+
class SomeFactory:
14+
arg1: datetime.datetime
15+
16+
17+
class MyGroup(Group):
18+
context_provider = providers.ContextProvider(scope=Scope.APP, context_type=datetime.datetime)
19+
some_factory = providers.Factory(creator=SomeFactory)
20+
21+
1022
def test_context_provider() -> None:
1123
now = datetime.datetime.now(tz=datetime.timezone.utc)
1224
app_container = Container(context={datetime.datetime: now})
13-
app_container.validate_provider(context_provider)
14-
instance1 = app_container.resolve_provider(context_provider)
15-
instance2 = app_container.resolve_provider(context_provider)
25+
app_container.validate_provider(MyGroup.context_provider)
26+
instance1 = app_container.resolve_provider(MyGroup.context_provider)
27+
instance2 = app_container.resolve_provider(MyGroup.context_provider)
1628
assert instance1 is instance2 is now
1729

1830

1931
def test_context_provider_set_context_after_creation() -> None:
2032
now = datetime.datetime.now(tz=datetime.timezone.utc)
2133
app_container = Container()
2234
app_container.set_context(datetime.datetime, now)
23-
instance1 = app_container.resolve_provider(context_provider)
24-
instance2 = app_container.resolve_provider(context_provider)
35+
instance1 = app_container.resolve_provider(MyGroup.context_provider)
36+
instance2 = app_container.resolve_provider(MyGroup.context_provider)
2537
assert instance1 is instance2 is now
2638

2739

2840
def test_context_provider_not_found() -> None:
2941
app_container = Container()
30-
assert app_container.resolve_provider(context_provider) is None
42+
assert app_container.resolve_provider(MyGroup.context_provider) is None
43+
44+
45+
def test_context_provider_not_found_but_required() -> None:
46+
app_container = Container(groups=[MyGroup])
47+
with pytest.raises(RuntimeError, match=r"Argument arg1 of type <class 'datetime.datetime'> cannot be resolved"):
48+
app_container.resolve(SomeFactory)
3149

3250

3351
def test_context_provider_in_request_scope() -> None:

0 commit comments

Comments
 (0)