diff --git a/README.md b/README.md index d4354ca..cf58f68 100644 --- a/README.md +++ b/README.md @@ -10,5 +10,6 @@ A Flake8 plugin to catch common issues on Scrapy spiders. | SCP02 | There are URLs in `allowed_domains` | | SCP03 | Usage of `urljoin(response.url, '/foo')` instead of `response.urljoin('/foo')` | | SCP04 | Usage of `Selector(response)` in callback | +| SCP05 | Usage of a lambda function as the request callback | This is a work in progress, so new issues will be added to this list. diff --git a/finders/unsupported.py b/finders/unsupported.py new file mode 100644 index 0000000..ea94e45 --- /dev/null +++ b/finders/unsupported.py @@ -0,0 +1,40 @@ +import ast + +from finders import IssueFinder + + +class LambdaCallbackIssueFinder(IssueFinder): + msg_code = 'SCP05' + msg_info = 'callback should not be a lambda' + + def is_scrapy_request(self, node): + return ( + isinstance(node.func, ast.Attribute) and + node.func.value.id == 'scrapy' and + node.func.attr == 'Request' + ) + + def is_raw_request(self, node): + return ( + isinstance(node.func, ast.Name) and + node.func.id == 'Request' + ) + + def issue_applies(self, node): + return ( + self.is_raw_request(node) or + self.is_scrapy_request(node) + ) + + def find_issues(self, node): + if not self.issue_applies(node): + return + + if len(node.args) >= 2: + callback = node.args[1] + if isinstance(callback, ast.Lambda): + return [(callback.lineno, callback.col_offset, self.message)] + + for kw in node.keywords: + if kw.arg == 'callback' and isinstance(kw.value, ast.Lambda): + return [(node.lineno, node.col_offset, self.message)] diff --git a/flake8_scrapy.py b/flake8_scrapy.py index c1198f4..29d79dc 100644 --- a/flake8_scrapy.py +++ b/flake8_scrapy.py @@ -4,7 +4,7 @@ UnreachableDomainIssueFinder, UrlInAllowedDomainsIssueFinder, ) from finders.oldstyle import OldSelectorIssueFinder, UrlJoinIssueFinder - +from finders.unsupported import LambdaCallbackIssueFinder __version__ = '0.0.1' @@ -22,6 +22,7 @@ def __init__(self, *args, **kwargs): ], 'Call': [ UrlJoinIssueFinder(), + LambdaCallbackIssueFinder(), ] } diff --git a/tests/test_unsupported.py b/tests/test_unsupported.py new file mode 100644 index 0000000..a52e359 --- /dev/null +++ b/tests/test_unsupported.py @@ -0,0 +1,16 @@ +import pytest + +from . import run_checker + + +@pytest.mark.parametrize('code,expected', [ + ('Request(x, callback=lambda x: x)', 1), + ('scrapy.Request(x, callback=lambda x: x)', 1), + ('scrapy.Request(x, lambda x: x)', 1), + ('Request(x, callback=self.parse)', 0), + ('scrapy.Request(x, callback=self.parse)', 0), + ('scrapy.Request(x, self.parse)', 0), +]) +def test_find_lambda_callback_issue(code, expected): + issues = run_checker(code) + assert len(issues) == expected