Skip to content

Commit

Permalink
Merge pull request #208 from scrapinghub/dynamic-check-annotated
Browse files Browse the repository at this point in the history
Restore and improve the annotated dynamic deps support.
  • Loading branch information
wRAR authored Oct 9, 2024
2 parents c43e150 + 0e5cc93 commit d67bb06
Show file tree
Hide file tree
Showing 2 changed files with 57 additions and 6 deletions.
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

0 comments on commit d67bb06

Please sign in to comment.