Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Restore and improve the annotated dynamic deps support. #208

Merged
merged 6 commits into from
Oct 9, 2024
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
12 changes: 7 additions & 5 deletions scrapy_poet/injection.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from weakref import WeakKeyDictionary

import andi
from andi.typeutils import issubclass_safe
from andi.typeutils import issubclass_safe, strip_annotated
from scrapy import Request, Spider
from scrapy.crawler import Crawler
from scrapy.http import Response
Expand Down Expand Up @@ -224,7 +224,7 @@ def _get_dynamic_deps_factory_text(
# https://github.com/python/cpython/blob/v3.11.9/Lib/dataclasses.py#L413
args = [f"{name}_arg: {name}" for name in type_names]
args_str = ", ".join(args)
result_args = [f"{name}: {name}_arg" for name in type_names]
result_args = [f"strip_annotated({name}): {name}_arg" for name in type_names]
result_args_str = ", ".join(result_args)
create_args_str = ", ".join(type_names)
return (
Expand All @@ -245,12 +245,14 @@ def _get_dynamic_deps_factory(
corresponding args. It has correct type hints so that it can be used as
an ``andi`` custom builder.
"""
ns: Dict[str, type] = {}
type_names: List[str] = []
for type_ in dynamic_types:
type_ = cast(type, strip_annotated(type_))
if not isinstance(type_, type):
raise TypeError(f"Expected a dynamic dependency type, got {type_!r}")
ns[type_.__name__] = type_
txt = Injector._get_dynamic_deps_factory_text(ns.keys())
type_names.append(type_.__name__)
txt = Injector._get_dynamic_deps_factory_text(type_names)
ns: Dict[str, Any] = {}
exec(txt, globals(), ns)
return ns["__create_fn__"](*dynamic_types)

Expand Down
51 changes: 50 additions & 1 deletion tests/test_injection.py
Original file line number Diff line number Diff line change
Expand Up @@ -666,6 +666,35 @@ def callback(dd: DynamicDeps):
instances = yield from injector.build_instances(request, response, plan)
assert set(instances) == {TestItemPage, TestItem, DynamicDeps}

@pytest.mark.skipif(
sys.version_info < (3, 9), reason="No Annotated support in Python < 3.9"
)
@inlineCallbacks
def test_dynamic_deps_annotated(self):
from typing import Annotated

def callback(dd: DynamicDeps):
pass

provider = get_provider({Cls1, Cls2})
injector = get_injector_for_testing({provider: 1})

expected_instances = {
DynamicDeps: DynamicDeps({Cls1: Cls1(), Cls2: Cls2()}),
Annotated[Cls1, 42]: Cls1(),
Annotated[Cls2, "foo"]: Cls2(),
}
expected_kwargs = {
"dd": DynamicDeps({Cls1: Cls1(), Cls2: Cls2()}),
}
yield self._assert_instances(
injector,
callback,
expected_instances,
expected_kwargs,
reqmeta={"inject": [Annotated[Cls1, 42], Annotated[Cls2, "foo"]]},
)


class Html(Injectable):
url = "http://example.com"
Expand Down Expand Up @@ -972,7 +1001,7 @@ def test_dynamic_deps_factory_text():
txt
== """def __create_fn__(int, Cls1):
def dynamic_deps_factory(int_arg: int, Cls1_arg: Cls1) -> DynamicDeps:
return DynamicDeps({int: int_arg, Cls1: Cls1_arg})
return DynamicDeps({strip_annotated(int): int_arg, strip_annotated(Cls1): Cls1_arg})
return dynamic_deps_factory"""
)

Expand All @@ -989,6 +1018,26 @@ def test_dynamic_deps_factory():
assert dd == {int: 42, Cls1: c}


@pytest.mark.skipif(
sys.version_info < (3, 9), reason="No Annotated support in Python < 3.9"
)
def test_dynamic_deps_factory_annotated():
from typing import Annotated

fn = Injector._get_dynamic_deps_factory(
[Annotated[Cls1, 42], Annotated[Cls2, "foo"]]
)
args = andi.inspect(fn)
assert args == {
"Cls1_arg": [Annotated[Cls1, 42]],
"Cls2_arg": [Annotated[Cls2, "foo"]],
}
c1 = Cls1()
c2 = Cls2()
dd = fn(Cls1_arg=c1, Cls2_arg=c2)
assert dd == {Cls1: c1, Cls2: c2}


def test_dynamic_deps_factory_bad_input():
with pytest.raises(
TypeError,
Expand Down
Loading