diff --git a/pyproject.toml b/pyproject.toml
index c0297e192..7dc1f6ec3 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -122,6 +122,9 @@ profile = "black"
[tool.pylint.MASTER]
persistent = "no"
jobs = 1 # >1 hides results
+extension-pkg-allow-list=[
+ "lxml",
+]
[tool.pylint."MESSAGES CONTROL"]
disable = [
@@ -129,11 +132,7 @@ disable = [
"arguments-differ",
"arguments-renamed",
"attribute-defined-outside-init",
- "bad-classmethod-argument",
- "bare-except",
- "broad-except",
- "broad-exception-raised",
- "c-extension-no-member",
+ "broad-exception-caught",
"consider-using-with",
"cyclic-import",
"dangerous-default-value",
@@ -141,9 +140,6 @@ disable = [
"duplicate-code", # https://github.com/PyCQA/pylint/issues/214
"eval-used",
"fixme",
- "function-redefined",
- "global-statement",
- "implicit-str-concat",
"import-error",
"import-outside-toplevel",
"inherit-non-class",
@@ -155,7 +151,6 @@ disable = [
"logging-format-interpolation",
"logging-fstring-interpolation",
"logging-not-lazy",
- "lost-exception",
"missing-docstring",
"no-member",
"no-method-argument",
@@ -169,13 +164,11 @@ disable = [
"raise-missing-from",
"redefined-builtin",
"redefined-outer-name",
- "reimported",
"signature-differs",
"too-few-public-methods",
"too-many-ancestors",
"too-many-arguments",
"too-many-branches",
- "too-many-format-args",
"too-many-function-args",
"too-many-instance-attributes",
"too-many-lines",
@@ -184,14 +177,11 @@ disable = [
"too-many-return-statements",
"unbalanced-tuple-unpacking",
"unnecessary-dunder-call",
- "unnecessary-pass",
- "unreachable",
"unused-argument",
"unused-import",
"unused-variable",
"used-before-assignment",
"useless-return",
- "wildcard-import",
"wrong-import-position",
]
@@ -246,6 +236,8 @@ extend-select = [
"PGH",
# flake8-pie
"PIE",
+ # pylint
+ "PL",
# flake8-pyi
"PYI",
# flake8-quotes
@@ -320,6 +312,18 @@ ignore = [
"D403",
# `try`-`except` within a loop incurs performance overhead
"PERF203",
+ # Too many return statements
+ "PLR0911",
+ # Too many branches
+ "PLR0912",
+ # Too many arguments in function definition
+ "PLR0913",
+ # Too many statements
+ "PLR0915",
+ # Magic value used in comparison
+ "PLR2004",
+ # `for` loop variable overwritten by assignment target
+ "PLW2901",
# Use of `assert` detected; needed for mypy
"S101",
# FTP-related functions are being called; https://github.com/scrapy/scrapy/issues/4180
diff --git a/scrapy/__init__.py b/scrapy/__init__.py
index 921296502..c19710a6a 100644
--- a/scrapy/__init__.py
+++ b/scrapy/__init__.py
@@ -31,7 +31,7 @@ version_info = tuple(int(v) if v.isdigit() else v for v in __version__.split("."
def __getattr__(name: str):
if name == "twisted_version":
- import warnings
+ import warnings # pylint: disable=reimported
from twisted import version as _txv
diff --git a/scrapy/cmdline.py b/scrapy/cmdline.py
index b6f19a37f..9a24871de 100644
--- a/scrapy/cmdline.py
+++ b/scrapy/cmdline.py
@@ -74,7 +74,7 @@ def _get_commands_from_entry_points(
if inspect.isclass(obj):
cmds[entry_point.name] = obj()
else:
- raise Exception(f"Invalid entry point {entry_point.name}")
+ raise ValueError(f"Invalid entry point {entry_point.name}")
return cmds
diff --git a/scrapy/exporters.py b/scrapy/exporters.py
index b6997ef67..cdb7ac159 100644
--- a/scrapy/exporters.py
+++ b/scrapy/exporters.py
@@ -92,11 +92,10 @@ class BaseItemExporter:
field_iter = (
(x, y) for x, y in self.fields_to_export.items() if x in item
)
+ elif include_empty:
+ field_iter = self.fields_to_export
else:
- if include_empty:
- field_iter = self.fields_to_export
- else:
- field_iter = (x for x in self.fields_to_export if x in item)
+ field_iter = (x for x in self.fields_to_export if x in item)
for field_name in field_iter:
if isinstance(field_name, str):
diff --git a/scrapy/utils/defer.py b/scrapy/utils/defer.py
index 9f1b816c8..000ab5c65 100644
--- a/scrapy/utils/defer.py
+++ b/scrapy/utils/defer.py
@@ -398,7 +398,7 @@ def maybeDeferred_coro(
"""Copy of defer.maybeDeferred that also converts coroutines to Deferreds."""
try:
result = f(*args, **kw)
- except: # noqa: E722,B001
+ except: # noqa: E722 # pylint: disable=bare-except
return defer.fail(failure.Failure(captureVars=Deferred.debug))
if isinstance(result, Deferred):
diff --git a/scrapy/utils/deprecate.py b/scrapy/utils/deprecate.py
index 32430cd6c..0a0acc742 100644
--- a/scrapy/utils/deprecate.py
+++ b/scrapy/utils/deprecate.py
@@ -60,7 +60,7 @@ def create_deprecated_class(
deprecated_class: type | None = None
warned_on_subclass: bool = False
- def __new__(
+ def __new__( # pylint: disable=bad-classmethod-argument
metacls, name: str, bases: tuple[type, ...], clsdict_: dict[str, Any]
) -> type:
cls = super().__new__(metacls, name, bases, clsdict_)
diff --git a/scrapy/utils/log.py b/scrapy/utils/log.py
index a40b835cd..6165d1f72 100644
--- a/scrapy/utils/log.py
+++ b/scrapy/utils/log.py
@@ -130,7 +130,7 @@ _scrapy_root_handler: logging.Handler | None = None
def install_scrapy_root_handler(settings: Settings) -> None:
- global _scrapy_root_handler
+ global _scrapy_root_handler # noqa: PLW0603 # pylint: disable=global-statement
if (
_scrapy_root_handler is not None
diff --git a/scrapy/utils/reactor.py b/scrapy/utils/reactor.py
index 2d781cc27..66a06a9f0 100644
--- a/scrapy/utils/reactor.py
+++ b/scrapy/utils/reactor.py
@@ -149,12 +149,11 @@ def verify_installed_reactor(reactor_path: str) -> None:
reactor_class = load_object(reactor_path)
if not reactor.__class__ == reactor_class:
- msg = (
+ raise RuntimeError(
"The installed reactor "
f"({reactor.__module__}.{reactor.__class__.__name__}) does not "
f"match the requested one ({reactor_path})"
)
- raise Exception(msg)
def verify_installed_asyncio_event_loop(loop_path: str) -> None:
@@ -168,7 +167,7 @@ def verify_installed_asyncio_event_loop(loop_path: str) -> None:
f".{reactor._asyncioEventloop.__class__.__qualname__}"
)
specified = f"{loop_class.__module__}.{loop_class.__qualname__}"
- raise Exception(
+ raise RuntimeError(
"Scrapy found an asyncio Twisted reactor already "
f"installed, and its event loop class ({installed}) does "
"not match the one specified in the ASYNCIO_EVENT_LOOP "
diff --git a/scrapy/utils/spider.py b/scrapy/utils/spider.py
index e58eb8134..5277a292c 100644
--- a/scrapy/utils/spider.py
+++ b/scrapy/utils/spider.py
@@ -52,10 +52,6 @@ def iter_spider_classes(module: ModuleType) -> Iterable[type[Spider]]:
"""Return an iterator over all spider classes defined in the given module
that can be instantiated (i.e. which have name)
"""
- # this needs to be imported here until get rid of the spider manager
- # singleton in scrapy.spider.spiders
- from scrapy.spiders import Spider
-
for obj in vars(module).values():
if (
inspect.isclass(obj)
diff --git a/scrapy/utils/url.py b/scrapy/utils/url.py
index 2539f30c7..1cbfbfd99 100644
--- a/scrapy/utils/url.py
+++ b/scrapy/utils/url.py
@@ -14,7 +14,7 @@ from urllib.parse import ParseResult, urldefrag, urlparse, urlunparse
# scrapy.utils.url was moved to w3lib.url and import * ensures this
# move doesn't break old code
-from w3lib.url import * # pylint: disable=unused-wildcard-import
+from w3lib.url import * # pylint: disable=unused-wildcard-import,wildcard-import
from w3lib.url import _safe_chars, _unquotepath # noqa: F401
from scrapy.utils.python import to_unicode
@@ -50,7 +50,9 @@ def url_has_any_extension(url: UrlT, extensions: Iterable[str]) -> bool:
return any(lowercase_path.endswith(ext) for ext in extensions)
-def parse_url(url: UrlT, encoding: str | None = None) -> ParseResult:
+def parse_url( # pylint: disable=function-redefined
+ url: UrlT, encoding: str | None = None
+) -> ParseResult:
"""Return urlparsed url from the given argument (which could be an already
parsed url)
"""
diff --git a/tests/test_cmdline_crawl_with_pipeline/test_spider/pipelines.py b/tests/test_cmdline_crawl_with_pipeline/test_spider/pipelines.py
index af15cac68..3e29c70ed 100644
--- a/tests/test_cmdline_crawl_with_pipeline/test_spider/pipelines.py
+++ b/tests/test_cmdline_crawl_with_pipeline/test_spider/pipelines.py
@@ -8,7 +8,7 @@ class TestSpiderPipeline:
class TestSpiderExceptionPipeline:
def open_spider(self, spider):
- raise Exception("exception")
+ raise RuntimeError("exception")
def process_item(self, item, spider):
return item
diff --git a/tests/test_downloader_handlers.py b/tests/test_downloader_handlers.py
index 3fcba4ef2..8ecba41bf 100644
--- a/tests/test_downloader_handlers.py
+++ b/tests/test_downloader_handlers.py
@@ -349,11 +349,6 @@ class HttpTestCase(unittest.TestCase):
request = Request(self.getURL("host"), headers={"Host": host})
return self.download_request(request, Spider("foo")).addCallback(_test)
- d = self.download_request(request, Spider("foo"))
- d.addCallback(lambda r: r.body)
- d.addCallback(self.assertEqual, b"localhost")
- return d
-
def test_content_length_zero_bodyless_post_request_headers(self):
"""Tests if "Content-Length: 0" is sent for bodyless POST requests.
diff --git a/tests/test_downloadermiddleware.py b/tests/test_downloadermiddleware.py
index e650b4936..c581e7596 100644
--- a/tests/test_downloadermiddleware.py
+++ b/tests/test_downloadermiddleware.py
@@ -178,7 +178,7 @@ class ProcessExceptionInvalidOutput(ManagerTestCase):
class InvalidProcessExceptionMiddleware:
def process_request(self, request, spider):
- raise Exception
+ raise RuntimeError
def process_exception(self, request, exception, spider):
return 1
diff --git a/tests/test_extension_periodic_log.py b/tests/test_extension_periodic_log.py
index b7312bbcd..15129e31f 100644
--- a/tests/test_extension_periodic_log.py
+++ b/tests/test_extension_periodic_log.py
@@ -192,4 +192,3 @@ class TestPeriodicLog(unittest.TestCase):
{"PERIODIC_LOG_STATS": {"include": ["downloader/"], "exclude": ["bytes"]}},
lambda k, v: "downloader/" in k and "bytes" not in k,
)
- #
diff --git a/tests/test_feedexport.py b/tests/test_feedexport.py
index 2debbe0d7..b087aaab1 100644
--- a/tests/test_feedexport.py
+++ b/tests/test_feedexport.py
@@ -727,7 +727,7 @@ class ExceptionJsonItemExporter(JsonItemExporter):
"""JsonItemExporter that throws an exception every time export_item is called."""
def export_item(self, _):
- raise Exception("foo")
+ raise RuntimeError("foo")
class FeedExportTest(FeedExportTestBase):
diff --git a/tests/test_loader_deprecated.py b/tests/test_loader_deprecated.py
index f9b841a61..4bf22f6a0 100644
--- a/tests/test_loader_deprecated.py
+++ b/tests/test_loader_deprecated.py
@@ -330,10 +330,10 @@ class BasicItemLoaderTest(unittest.TestCase):
il.add_value("name", ["mar", "ta"])
self.assertEqual(il.get_output_value("name"), "Mar Ta")
- class TakeFirstItemLoader(TestItemLoader):
+ class TakeFirstItemLoader2(TestItemLoader):
name_out = Join("
")
- il = TakeFirstItemLoader()
+ il = TakeFirstItemLoader2()
il.add_value("name", ["mar", "ta"])
self.assertEqual(il.get_output_value("name"), "Mar
Ta")
diff --git a/tests/test_spidermiddleware.py b/tests/test_spidermiddleware.py
index f2a57bd88..ba64ba721 100644
--- a/tests/test_spidermiddleware.py
+++ b/tests/test_spidermiddleware.py
@@ -78,7 +78,7 @@ class ProcessSpiderExceptionInvalidOutput(SpiderMiddlewareTestCase):
class RaiseExceptionProcessSpiderOutputMiddleware:
def process_spider_output(self, response, result, spider):
- raise Exception
+ raise RuntimeError
self.mwman._add_middleware(InvalidProcessSpiderOutputExceptionMiddleware())
self.mwman._add_middleware(RaiseExceptionProcessSpiderOutputMiddleware())
diff --git a/tests/test_spidermiddleware_output_chain.py b/tests/test_spidermiddleware_output_chain.py
index 4c19d167f..e51957497 100644
--- a/tests/test_spidermiddleware_output_chain.py
+++ b/tests/test_spidermiddleware_output_chain.py
@@ -247,7 +247,6 @@ class NotGeneratorFailMiddleware:
r["processed"].append(f"{self.__class__.__name__}.process_spider_output")
out.append(r)
raise ReferenceError
- return out
def process_spider_exception(self, response, exception, spider):
method = f"{self.__class__.__name__}.process_spider_exception"
diff --git a/tests/test_utils_asyncio.py b/tests/test_utils_asyncio.py
index 1c93829e9..e00f69573 100644
--- a/tests/test_utils_asyncio.py
+++ b/tests/test_utils_asyncio.py
@@ -26,7 +26,7 @@ class AsyncioTest(TestCase):
with warnings.catch_warnings(record=True) as w:
install_reactor("twisted.internet.asyncioreactor.AsyncioSelectorReactor")
self.assertEqual(len(w), 0)
- from twisted.internet import reactor
+ from twisted.internet import reactor # pylint: disable=reimported
assert original_reactor == reactor
diff --git a/tests/test_utils_defer.py b/tests/test_utils_defer.py
index 3f153bdc0..e4ab97e5d 100644
--- a/tests/test_utils_defer.py
+++ b/tests/test_utils_defer.py
@@ -153,7 +153,7 @@ class AsyncDefTestsuiteTest(unittest.TestCase):
@mark.xfail(reason="Checks that the test is actually executed", strict=True)
@deferred_f_from_coro_f
async def test_deferred_f_from_coro_f_xfail(self):
- raise Exception("This is expected to be raised")
+ raise RuntimeError("This is expected to be raised")
class AsyncCooperatorTest(unittest.TestCase):
diff --git a/tests/test_utils_misc/test_return_with_argument_inside_generator.py b/tests/test_utils_misc/test_return_with_argument_inside_generator.py
index c7774751e..480729d11 100644
--- a/tests/test_utils_misc/test_return_with_argument_inside_generator.py
+++ b/tests/test_utils_misc/test_return_with_argument_inside_generator.py
@@ -32,7 +32,6 @@ def top_level_return_none():
https://example.org
"""
yield url
- return
def generator_that_returns_stuff():
@@ -103,11 +102,9 @@ https://example.org
def test_generators_return_none(self):
def f2():
yield 1
- return None
def g2():
yield 1
- return
def h2():
yield 1
@@ -132,7 +129,6 @@ https://example.org
https://example.org
"""
yield url
- return
def l2():
return
@@ -181,12 +177,10 @@ https://example.org
@decorator
def f3():
yield 1
- return None
@decorator
def g3():
yield 1
- return
@decorator
def h3():
@@ -215,7 +209,6 @@ https://example.org
https://example.org
"""
yield url
- return
@decorator
def l3():
diff --git a/tests/test_utils_spider.py b/tests/test_utils_spider.py
index dd1d26448..ae59d0137 100644
--- a/tests/test_utils_spider.py
+++ b/tests/test_utils_spider.py
@@ -26,7 +26,7 @@ class UtilsSpidersTestCase(unittest.TestCase):
self.assertEqual(list(iterate_spider_output([r, i, o])), [r, i, o])
def test_iter_spider_classes(self):
- import tests.test_utils_spider # pylint: disable=import-self
+ import tests.test_utils_spider # noqa: PLW0406 # pylint: disable=import-self
it = iter_spider_classes(tests.test_utils_spider)
self.assertEqual(set(it), {MySpider1, MySpider2})
diff --git a/tests/test_utils_url.py b/tests/test_utils_url.py
index 59a95b0e2..a15ad749d 100644
--- a/tests/test_utils_url.py
+++ b/tests/test_utils_url.py
@@ -327,8 +327,6 @@ def create_guess_scheme_t(args):
def create_skipped_scheme_t(args):
def do_expected(self):
raise unittest.SkipTest(args[2])
- url = guess_scheme(args[0])
- assert url.startswith(args[1])
return do_expected