Skip to content

Commit 41037c7

Browse files
authored
Resolve protocols without needing to define init (#46)
* fix: resolve protocols without needing to define init * fix: support python 3.8 * fix: support _no_init on python 3.8
1 parent 6043b5b commit 41037c7

2 files changed

Lines changed: 107 additions & 1 deletion

File tree

rodi/__init__.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@
2020
get_type_hints,
2121
)
2222

23+
if sys.version_info >= (3, 9): # pragma: no cover
24+
from typing import _no_init_or_replace_init as _no_init
25+
elif sys.version_info >= (3, 8): # pragma: no cover
26+
from typing import _no_init
27+
2328
try:
2429
from typing import Protocol
2530
except ImportError: # pragma: no cover
@@ -579,6 +584,17 @@ def _ignore_class_attribute(self, key: str, value) -> bool:
579584

580585
return is_classvar or is_initialized
581586

587+
def _has_default_init(self):
588+
init = getattr(self.concrete_type, "__init__", None)
589+
590+
if init is object.__init__:
591+
return True
592+
593+
if sys.version_info >= (3, 8): # pragma: no cover
594+
if init is _no_init:
595+
return True
596+
return False
597+
582598
def _resolve_by_annotations(
583599
self, context: ResolutionContext, annotations: Dict[str, Type]
584600
):
@@ -605,7 +621,7 @@ def __call__(self, context: ResolutionContext):
605621
chain = context.dynamic_chain
606622
chain.append(concrete_type)
607623

608-
if getattr(concrete_type, "__init__") is object.__init__:
624+
if self._has_default_init():
609625
annotations = get_type_hints(
610626
concrete_type,
611627
vars(sys.modules[concrete_type.__module__]),

tests/test_services.py

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from abc import ABC
33
from dataclasses import dataclass
44
from typing import (
5+
Any,
56
ClassVar,
67
Dict,
78
Generic,
@@ -2474,6 +2475,62 @@ class B:
24742475
assert isinstance(value, DynamicResolver)
24752476

24762477

2478+
def test_provide_protocol_with_attribute_dependency() -> None:
2479+
class P(Protocol):
2480+
def foo(self) -> Any:
2481+
...
2482+
2483+
class Dependency:
2484+
pass
2485+
2486+
class Impl(P):
2487+
# attribute dependency
2488+
dependency: Dependency
2489+
2490+
def foo(self) -> Any:
2491+
pass
2492+
2493+
container = Container()
2494+
container.register(Dependency)
2495+
container.register(Impl)
2496+
2497+
try:
2498+
resolved = container.resolve(Impl)
2499+
except CannotResolveParameterException as e:
2500+
pytest.fail(str(e))
2501+
2502+
assert isinstance(resolved, Impl)
2503+
assert isinstance(resolved.dependency, Dependency)
2504+
2505+
2506+
def test_provide_protocol_with_init_dependency() -> None:
2507+
class P(Protocol):
2508+
def foo(self) -> Any:
2509+
...
2510+
2511+
class Dependency:
2512+
pass
2513+
2514+
class Impl(P):
2515+
def __init__(self, dependency: Dependency) -> None:
2516+
self.dependency = dependency
2517+
2518+
def foo(self) -> Any:
2519+
pass
2520+
2521+
container = Container()
2522+
container.register(Dependency)
2523+
container.register(Impl)
2524+
2525+
try:
2526+
resolved = container.resolve(Impl)
2527+
except CannotResolveParameterException as e:
2528+
pytest.fail(str(e))
2529+
2530+
assert isinstance(resolved, Impl)
2531+
assert isinstance(resolved.dependency, Dependency)
2532+
2533+
24772534
def test_provide_protocol_generic() -> None:
24782535
T = TypeVar("T")
24792536

@@ -2500,6 +2557,39 @@ def foo(self, t: A) -> A:
25002557
assert isinstance(resolved, Impl)
25012558

25022559

2560+
def test_provide_protocol_generic_with_inner_dependency() -> None:
2561+
T = TypeVar("T")
2562+
2563+
class P(Protocol[T]):
2564+
def foo(self, t: T) -> T:
2565+
...
2566+
2567+
class A:
2568+
...
2569+
2570+
class Dependency:
2571+
pass
2572+
2573+
class Impl(P[A]):
2574+
dependency: Dependency
2575+
2576+
def foo(self, t: A) -> A:
2577+
return t
2578+
2579+
container = Container()
2580+
2581+
container.register(Impl)
2582+
container.register(Dependency)
2583+
2584+
try:
2585+
resolved = container.resolve(Impl)
2586+
except CannotResolveParameterException as e:
2587+
pytest.fail(str(e))
2588+
2589+
assert isinstance(resolved, Impl)
2590+
assert isinstance(resolved.dependency, Dependency)
2591+
2592+
25032593
def test_ignore_class_var():
25042594
"""
25052595
ClassVar attributes must be ignored, because they are not instance attributes.

0 commit comments

Comments
 (0)