Skip to content

Commit 2b88182

Browse files
authored
Merge branch 'master' into multibind-for-plugins
2 parents cc4b8d0 + d6e0ea4 commit 2b88182

File tree

3 files changed

+208
-34
lines changed

3 files changed

+208
-34
lines changed

.github/workflows/ci.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,6 @@ jobs:
4242
if which black; then black --check . ; fi
4343
check-manifest
4444
- name: Report coverage to Codecov
45-
uses: codecov/codecov-action@v1
45+
uses: codecov/codecov-action@v5
46+
env:
47+
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

injector/__init__.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1263,6 +1263,16 @@ def _is_injection_annotation(annotation: Any) -> bool:
12631263
_inject_marker in annotation.__metadata__ or _noinject_marker in annotation.__metadata__
12641264
)
12651265

1266+
def _recreate_annotated_origin(annotated_type: Any) -> Any:
1267+
# Creates `Annotated[type, annotation]` from `Inject[Annotated[type, annotation]]`,
1268+
# to support the injection of annotated types with the `Inject[]` annotation.
1269+
origin = annotated_type.__origin__
1270+
for metadata in annotated_type.__metadata__: # pragma: no branch
1271+
if metadata in (_inject_marker, _noinject_marker):
1272+
break
1273+
origin = Annotated[origin, metadata]
1274+
return origin
1275+
12661276
spec = inspect.getfullargspec(callable)
12671277

12681278
try:
@@ -1295,7 +1305,7 @@ def _is_injection_annotation(annotation: Any) -> bool:
12951305
for k, v in list(bindings.items()):
12961306
# extract metadata only from Inject and NonInject
12971307
if _is_injection_annotation(v):
1298-
v, metadata = v.__origin__, v.__metadata__
1308+
v, metadata = _recreate_annotated_origin(v), v.__metadata__
12991309
bindings[k] = v
13001310
else:
13011311
metadata = tuple()

injector_test.py

Lines changed: 194 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1654,89 +1654,176 @@ def test_binder_has_implicit_binding_for_implicitly_bound_type():
16541654
assert not injector.binder.has_explicit_binding_for(int)
16551655

16561656

1657-
def test_get_bindings():
1658-
def function1(a: int) -> None:
1657+
def test_gets_no_bindings_without_injection() -> None:
1658+
def function(a: int) -> None:
16591659
pass
16601660

1661-
assert get_bindings(function1) == {}
1661+
assert get_bindings(function) == {}
16621662

1663+
1664+
def test_gets_bindings_with_inject_decorator() -> None:
16631665
@inject
1664-
def function2(a: int) -> None:
1666+
def function(a: int) -> None:
16651667
pass
16661668

1667-
assert get_bindings(function2) == {'a': int}
1669+
assert get_bindings(function) == {'a': int}
1670+
16681671

1672+
def test_gets_multiple_bindings_with_inject_decorator() -> None:
16691673
@inject
1670-
@noninjectable('b')
1671-
def function3(a: int, b: str) -> None:
1674+
def function(a: int, b: str) -> None:
16721675
pass
16731676

1674-
assert get_bindings(function3) == {'a': int}
1677+
assert get_bindings(function) == {'a': int, 'b': str}
1678+
1679+
1680+
def test_only_gets_injectable_bindings_without_noninjectable_decorator() -> None:
1681+
@inject
1682+
@noninjectable('b')
1683+
def function1(a: int, b: str) -> None:
1684+
pass
16751685

16761686
# Let's verify that the inject/noninjectable ordering doesn't matter
16771687
@noninjectable('b')
16781688
@inject
1679-
def function3b(a: int, b: str) -> None:
1689+
def function2(a: int, b: str) -> None:
16801690
pass
16811691

1682-
assert get_bindings(function3b) == {'a': int}
1692+
assert get_bindings(function1) == {'a': int} == get_bindings(function2)
1693+
16831694

1684-
# The simple case of no @inject but injection requested with Inject[...]
1685-
def function4(a: Inject[int], b: str) -> None:
1695+
def test_gets_bindings_with_inject_annotation() -> None:
1696+
def function(a: Inject[int], b: str) -> None:
16861697
pass
16871698

1688-
assert get_bindings(function4) == {'a': int}
1699+
assert get_bindings(function) == {'a': int}
1700+
1701+
1702+
def test_gets_multiple_bindings_with_inject_annotation() -> None:
1703+
def function(a: Inject[int], b: Inject[str]) -> None:
1704+
pass
1705+
1706+
assert get_bindings(function) == {'a': int, 'b': str}
1707+
16891708

1690-
# Using @inject with Inject is redundant but it should not break anything
1709+
def test_gets_bindings_inject_with_redundant_inject_annotation() -> None:
16911710
@inject
1692-
def function5(a: Inject[int], b: str) -> None:
1711+
def function(a: Inject[int], b: str) -> None:
16931712
pass
16941713

1695-
assert get_bindings(function5) == {'a': int, 'b': str}
1714+
assert get_bindings(function) == {'a': int, 'b': str}
16961715

1697-
# We need to be able to exclude a parameter from injection with NoInject
1716+
1717+
def test_only_gets_bindings_without_noinject_annotation() -> None:
16981718
@inject
1699-
def function6(a: int, b: NoInject[str]) -> None:
1719+
def function(a: int, b: NoInject[str]) -> None:
17001720
pass
17011721

1702-
assert get_bindings(function6) == {'a': int}
1722+
assert get_bindings(function) == {'a': int}
1723+
17031724

1704-
# The presence of NoInject should not trigger anything on its own
1705-
def function7(a: int, b: NoInject[str]) -> None:
1725+
def test_gets_no_bindings_for_noinject_annotation_only() -> None:
1726+
def function(a: int, b: NoInject[str]) -> None:
17061727
pass
17071728

1708-
assert get_bindings(function7) == {}
1729+
assert get_bindings(function) == {}
17091730

1731+
1732+
def test_gets_no_bindings_for_multiple_noinject_annotations() -> None:
17101733
# There was a bug where in case of multiple NoInject-decorated parameters only the first one was
17111734
# actually made noninjectable and we tried to inject something we couldn't possibly provide
17121735
# into the second one.
17131736
@inject
1714-
def function8(a: NoInject[int], b: NoInject[int]) -> None:
1737+
def function(a: NoInject[int], b: NoInject[int]) -> None:
17151738
pass
17161739

1717-
assert get_bindings(function8) == {}
1740+
assert get_bindings(function) == {}
1741+
17181742

1719-
# Default arguments to NoInject annotations should behave the same as noninjectable decorator w.r.t 'None'
1743+
def test_get_bindings_noinject_with_default_should_behave_identically() -> None:
17201744
@inject
17211745
@noninjectable('b')
1722-
def function9(self, a: int, b: Optional[str] = None):
1746+
def function1(self, a: int, b: Optional[str] = None) -> None:
17231747
pass
17241748

17251749
@inject
1726-
def function10(self, a: int, b: NoInject[Optional[str]] = None):
1727-
# b:s type is Union[NoInject[Union[str, None]], None]
1750+
def function2(self, a: int, b: NoInject[Optional[str]] = None) -> None:
1751+
# b's type is Union[NoInject[Union[str, None]], None]
17281752
pass
17291753

1730-
assert get_bindings(function9) == {'a': int} == get_bindings(function10)
1754+
assert get_bindings(function1) == {'a': int} == get_bindings(function2)
17311755

1756+
1757+
def test_get_bindings_with_an_invalid_forward_reference_return_type() -> None:
17321758
# If there's a return type annottion that contains an a forward reference that can't be
17331759
# resolved (for whatever reason) we don't want that to break things for us – return types
17341760
# don't matter for the purpose of dependency injection.
17351761
@inject
1736-
def function11(a: int) -> 'InvalidForwardReference':
1762+
def function(a: int) -> 'InvalidForwardReference':
1763+
pass
1764+
1765+
assert get_bindings(function) == {'a': int}
1766+
1767+
1768+
def test_gets_bindings_for_annotated_type_with_inject_decorator() -> None:
1769+
UserID = Annotated[int, 'user_id']
1770+
1771+
@inject
1772+
def function(a: UserID, b: str) -> None:
1773+
pass
1774+
1775+
assert get_bindings(function) == {'a': UserID, 'b': str}
1776+
1777+
1778+
def test_gets_bindings_of_annotated_type_with_inject_annotation() -> None:
1779+
UserID = Annotated[int, 'user_id']
1780+
1781+
def function(a: Inject[UserID], b: Inject[str]) -> None:
1782+
pass
1783+
1784+
assert get_bindings(function) == {'a': UserID, 'b': str}
1785+
1786+
1787+
def test_gets_bindings_of_new_type_with_inject_annotation() -> None:
1788+
Name = NewType('Name', str)
1789+
1790+
@inject
1791+
def function(a: Name, b: str) -> None:
1792+
pass
1793+
1794+
assert get_bindings(function) == {'a': Name, 'b': str}
1795+
1796+
1797+
def test_gets_bindings_of_inject_annotation_with_new_type() -> None:
1798+
def function(a: Inject[Name], b: str) -> None:
1799+
pass
1800+
1801+
assert get_bindings(function) == {'a': Name}
1802+
1803+
1804+
def test_get_bindings_of_nested_noinject_inject_annotation() -> None:
1805+
# This is not how this is intended to be used
1806+
def function(a: Inject[NoInject[int]], b: NoInject[Inject[str]]) -> None:
1807+
pass
1808+
1809+
assert get_bindings(function) == {}
1810+
1811+
1812+
def test_get_bindings_of_nested_noinject_inject_annotation_and_inject_decorator() -> None:
1813+
# This is not how this is intended to be used
1814+
@inject
1815+
def function(a: Inject[NoInject[int]], b: NoInject[Inject[str]]) -> None:
17371816
pass
17381817

1739-
assert get_bindings(function11) == {'a': int}
1818+
assert get_bindings(function) == {}
1819+
1820+
1821+
def test_get_bindings_of_nested_inject_annotations() -> None:
1822+
# This is not how this is intended to be used
1823+
def function(a: Inject[Inject[int]]) -> None:
1824+
pass
1825+
1826+
assert get_bindings(function) == {'a': int}
17401827

17411828

17421829
# Tests https://github.com/alecthomas/injector/issues/202
@@ -1824,19 +1911,78 @@ def configure(binder):
18241911

18251912
def test_annotated_integration_with_annotated():
18261913
UserID = Annotated[int, 'user_id']
1914+
UserAge = Annotated[int, 'user_age']
18271915

18281916
@inject
18291917
class TestClass:
1830-
def __init__(self, user_id: UserID):
1918+
def __init__(self, user_id: UserID, user_age: UserAge):
18311919
self.user_id = user_id
1920+
self.user_age = user_age
18321921

18331922
def configure(binder):
18341923
binder.bind(UserID, to=123)
1924+
binder.bind(UserAge, to=32)
18351925

18361926
injector = Injector([configure])
18371927

18381928
test_class = injector.get(TestClass)
18391929
assert test_class.user_id == 123
1930+
assert test_class.user_age == 32
1931+
1932+
1933+
def test_inject_annotation_with_annotated_type():
1934+
UserID = Annotated[int, 'user_id']
1935+
UserAge = Annotated[int, 'user_age']
1936+
1937+
class TestClass:
1938+
def __init__(self, user_id: Inject[UserID], user_age: Inject[UserAge]):
1939+
self.user_id = user_id
1940+
self.user_age = user_age
1941+
1942+
def configure(binder):
1943+
binder.bind(UserID, to=123)
1944+
binder.bind(UserAge, to=32)
1945+
binder.bind(int, to=456)
1946+
1947+
injector = Injector([configure])
1948+
1949+
test_class = injector.get(TestClass)
1950+
assert test_class.user_id == 123
1951+
assert test_class.user_age == 32
1952+
1953+
1954+
def test_inject_annotation_with_nested_annotated_type():
1955+
UserID = Annotated[int, 'user_id']
1956+
SpecialUserID = Annotated[UserID, 'special_user_id']
1957+
1958+
class TestClass:
1959+
def __init__(self, user_id: Inject[SpecialUserID]):
1960+
self.user_id = user_id
1961+
1962+
def configure(binder):
1963+
binder.bind(SpecialUserID, to=123)
1964+
1965+
injector = Injector([configure])
1966+
1967+
test_class = injector.get(TestClass)
1968+
assert test_class.user_id == 123
1969+
1970+
1971+
def test_noinject_annotation_with_annotated_type():
1972+
UserID = Annotated[int, 'user_id']
1973+
1974+
@inject
1975+
class TestClass:
1976+
def __init__(self, user_id: NoInject[UserID] = None):
1977+
self.user_id = user_id
1978+
1979+
def configure(binder):
1980+
binder.bind(UserID, to=123)
1981+
1982+
injector = Injector([configure])
1983+
1984+
test_class = injector.get(TestClass)
1985+
assert test_class.user_id is None
18401986

18411987

18421988
def test_newtype_integration_with_annotated():
@@ -1856,6 +2002,22 @@ def configure(binder):
18562002
assert test_class.user_id == 123
18572003

18582004

2005+
def test_newtype_with_injection_annotation():
2006+
UserID = NewType('UserID', int)
2007+
2008+
class TestClass:
2009+
def __init__(self, user_id: Inject[UserID]):
2010+
self.user_id = user_id
2011+
2012+
def configure(binder):
2013+
binder.bind(UserID, to=123)
2014+
2015+
injector = Injector([configure])
2016+
2017+
test_class = injector.get(TestClass)
2018+
assert test_class.user_id == 123
2019+
2020+
18592021
def test_dataclass_annotated_parameter():
18602022
Foo = Annotated[int, object()]
18612023

0 commit comments

Comments
 (0)