Skip to content

Commit 910c990

Browse files
authored
Merge pull request #188 from Gallaecio/future-annotations
Fix DummyResponse detection when using strings as annotations
2 parents f27c827 + 6621b71 commit 910c990

File tree

2 files changed

+149
-3
lines changed

2 files changed

+149
-3
lines changed

scrapy_poet/injection.py

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,18 @@
44
import os
55
import pprint
66
import warnings
7-
from typing import Any, Callable, Dict, List, Mapping, Optional, Set, Type, cast
7+
from typing import (
8+
Any,
9+
Callable,
10+
Dict,
11+
List,
12+
Mapping,
13+
Optional,
14+
Set,
15+
Type,
16+
cast,
17+
get_type_hints,
18+
)
819
from weakref import WeakKeyDictionary
920

1021
import andi
@@ -38,6 +49,10 @@
3849
logger = logging.getLogger(__name__)
3950

4051

52+
class _UNDEFINED:
53+
pass
54+
55+
4156
class Injector:
4257
"""
4358
Keep all the logic required to do dependency injection in Scrapy callbacks.
@@ -387,11 +402,13 @@ def is_callback_requiring_scrapy_response(
387402
# Let's assume response is going to be used.
388403
return True
389404

390-
if first_parameter.annotation is first_parameter.empty:
405+
callback_type_hints = get_type_hints(callback)
406+
first_parameter_type_hint = callback_type_hints.get(first_parameter_key, _UNDEFINED)
407+
if first_parameter_type_hint is _UNDEFINED:
391408
# There's no type annotation, so we're probably using response here.
392409
return True
393410

394-
if issubclass_safe(first_parameter.annotation, DummyResponse):
411+
if issubclass_safe(first_parameter_type_hint, DummyResponse):
395412
# See: https://github.com/scrapinghub/scrapy-poet/issues/48
396413
# See: https://github.com/scrapinghub/scrapy-poet/issues/118
397414
if raw_callback is None and not is_min_scrapy_version("2.8.0"):

tests/test_response_required_logic.py

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,40 @@ def parse11(self, response: TextResponse):
157157
def parse12(self, response: TextResponse, book_page: DummyProductPage):
158158
pass
159159

160+
# Strings as type hints (which in addition to something users may do, is
161+
# also functionally-equivalent to having from __future__ import annotations
162+
# in your code, see https://peps.python.org/pep-0649/).
163+
164+
def parse13(self, response: "DummyResponse"):
165+
pass
166+
167+
def parse14(self, res: "DummyResponse"):
168+
pass
169+
170+
def parse15(self, response, book_page: "BookPage"):
171+
pass
172+
173+
def parse16(self, response: "DummyResponse", book_page: "BookPage"):
174+
pass
175+
176+
def parse17(self, response, book_page: "DummyProductPage"):
177+
pass
178+
179+
def parse18(self, response: "DummyResponse", book_page: "DummyProductPage"):
180+
pass
181+
182+
def parse19(self, response, book_page: "FakeProductPage"):
183+
pass
184+
185+
def parse20(self, response: "DummyResponse", book_page: "FakeProductPage"):
186+
pass
187+
188+
def parse21(self, response: "TextResponse"):
189+
pass
190+
191+
def parse22(self, response: "TextResponse", book_page: "DummyProductPage"):
192+
pass
193+
160194

161195
def test_get_callback():
162196
spider = MySpider()
@@ -226,6 +260,36 @@ def test_is_callback_using_response_for_scrapy28_below() -> None:
226260
assert (
227261
is_callback_requiring_scrapy_response(spider.parse12, request.callback) is True
228262
)
263+
assert (
264+
is_callback_requiring_scrapy_response(spider.parse13, request.callback) is False
265+
)
266+
assert (
267+
is_callback_requiring_scrapy_response(spider.parse14, request.callback) is False
268+
)
269+
assert (
270+
is_callback_requiring_scrapy_response(spider.parse15, request.callback) is True
271+
)
272+
assert (
273+
is_callback_requiring_scrapy_response(spider.parse16, request.callback) is False
274+
)
275+
assert (
276+
is_callback_requiring_scrapy_response(spider.parse17, request.callback) is True
277+
)
278+
assert (
279+
is_callback_requiring_scrapy_response(spider.parse18, request.callback) is False
280+
)
281+
assert (
282+
is_callback_requiring_scrapy_response(spider.parse19, request.callback) is True
283+
)
284+
assert (
285+
is_callback_requiring_scrapy_response(spider.parse20, request.callback) is False
286+
)
287+
assert (
288+
is_callback_requiring_scrapy_response(spider.parse21, request.callback) is True
289+
)
290+
assert (
291+
is_callback_requiring_scrapy_response(spider.parse22, request.callback) is True
292+
)
229293
# Callbacks created with the callback_for function won't make use of
230294
# the response, but their providers might use them.
231295
assert (
@@ -263,6 +327,21 @@ def test_is_callback_using_response_for_scrapy28_below() -> None:
263327
assert (
264328
is_callback_requiring_scrapy_response(spider.parse12, request.callback) is True
265329
)
330+
assert (
331+
is_callback_requiring_scrapy_response(spider.parse15, request.callback) is True
332+
)
333+
assert (
334+
is_callback_requiring_scrapy_response(spider.parse17, request.callback) is True
335+
)
336+
assert (
337+
is_callback_requiring_scrapy_response(spider.parse19, request.callback) is True
338+
)
339+
assert (
340+
is_callback_requiring_scrapy_response(spider.parse21, request.callback) is True
341+
)
342+
assert (
343+
is_callback_requiring_scrapy_response(spider.parse22, request.callback) is True
344+
)
266345

267346
for method in (
268347
spider.parse3,
@@ -337,6 +416,46 @@ def test_is_callback_using_response_for_scrapy28_and_above() -> None:
337416
is_callback_requiring_scrapy_response(spider.parse12, request.callback)
338417
is True
339418
)
419+
assert (
420+
is_callback_requiring_scrapy_response(spider.parse13, request.callback)
421+
is False
422+
)
423+
assert (
424+
is_callback_requiring_scrapy_response(spider.parse14, request.callback)
425+
is False
426+
)
427+
assert (
428+
is_callback_requiring_scrapy_response(spider.parse15, request.callback)
429+
is True
430+
)
431+
assert (
432+
is_callback_requiring_scrapy_response(spider.parse16, request.callback)
433+
is False
434+
)
435+
assert (
436+
is_callback_requiring_scrapy_response(spider.parse17, request.callback)
437+
is True
438+
)
439+
assert (
440+
is_callback_requiring_scrapy_response(spider.parse18, request.callback)
441+
is False
442+
)
443+
assert (
444+
is_callback_requiring_scrapy_response(spider.parse19, request.callback)
445+
is True
446+
)
447+
assert (
448+
is_callback_requiring_scrapy_response(spider.parse20, request.callback)
449+
is False
450+
)
451+
assert (
452+
is_callback_requiring_scrapy_response(spider.parse21, request.callback)
453+
is True
454+
)
455+
assert (
456+
is_callback_requiring_scrapy_response(spider.parse22, request.callback)
457+
is True
458+
)
340459
# Callbacks created with the callback_for function won't make use of
341460
# the response, but their providers might use them.
342461
assert (
@@ -382,3 +501,13 @@ def check_response_required(expected, callback):
382501
yield from check_response_required(False, spider.parse10)
383502
yield from check_response_required(True, spider.parse11)
384503
yield from check_response_required(True, spider.parse12)
504+
yield from check_response_required(False, spider.parse13)
505+
yield from check_response_required(False, spider.parse14)
506+
yield from check_response_required(True, spider.parse15)
507+
yield from check_response_required(True, spider.parse16)
508+
yield from check_response_required(True, spider.parse17)
509+
yield from check_response_required(False, spider.parse18)
510+
yield from check_response_required(True, spider.parse19)
511+
yield from check_response_required(False, spider.parse20)
512+
yield from check_response_required(True, spider.parse21)
513+
yield from check_response_required(True, spider.parse22)

0 commit comments

Comments
 (0)