From 583df9f7d063ac0f48eafccdd463f3195a084d9b Mon Sep 17 00:00:00 2001 From: Andrey Rakhmatullin Date: Sat, 22 Jul 2023 17:44:37 +0400 Subject: [PATCH 1/5] Simplify skipping uvloop tests. --- conftest.py | 16 ++++++++++++++ pytest.ini | 1 + tests/test_commands.py | 14 +----------- tests/test_crawler.py | 50 ++++-------------------------------------- 4 files changed, 22 insertions(+), 59 deletions(-) diff --git a/conftest.py b/conftest.py index e1d4b1213..fa5193470 100644 --- a/conftest.py +++ b/conftest.py @@ -1,6 +1,10 @@ +import platform +import sys from pathlib import Path import pytest +from twisted import version as twisted_version +from twisted.python.versions import Version from twisted.web.http import H2_ENABLED from scrapy.utils.reactor import install_reactor @@ -73,6 +77,18 @@ def only_not_asyncio(request, reactor_pytest): pytest.skip("This test is only run without --reactor=asyncio") +@pytest.fixture(autouse=True) +def requires_uvloop(request): + if not request.node.get_closest_marker("requires_uvloop"): + return + if sys.implementation.name == "pypy": + pytest.skip("uvloop does not support pypy properly") + if platform.system() == "Windows": + pytest.skip("uvloop does not support Windows") + if twisted_version == Version("twisted", 21, 2, 0): + pytest.skip("https://twistedmatrix.com/trac/ticket/10106") + + def pytest_configure(config): if config.getoption("--reactor") == "asyncio": install_reactor("twisted.internet.asyncioreactor.AsyncioSelectorReactor") diff --git a/pytest.ini b/pytest.ini index 866f0c950..16983be5e 100644 --- a/pytest.ini +++ b/pytest.ini @@ -20,6 +20,7 @@ addopts = markers = only_asyncio: marks tests as only enabled when --reactor=asyncio is passed only_not_asyncio: marks tests as only enabled when --reactor=asyncio is not passed + requires_uvloop: marks tests as only enabled when uvloop is known to be working filterwarnings = ignore:scrapy.downloadermiddlewares.decompression is deprecated ignore:Module scrapy.utils.reqser is deprecated diff --git a/tests/test_commands.py b/tests/test_commands.py index 014f50e92..03d768d1a 100644 --- a/tests/test_commands.py +++ b/tests/test_commands.py @@ -18,8 +18,6 @@ from typing import Dict, Generator, Optional, Union from unittest import skipIf from pytest import mark -from twisted import version as twisted_version -from twisted.python.versions import Version from twisted.trial import unittest import scrapy @@ -802,17 +800,7 @@ class MySpider(scrapy.Spider): "Using reactor: twisted.internet.asyncioreactor.AsyncioSelectorReactor", log ) - @mark.skipif( - sys.implementation.name == "pypy", - reason="uvloop does not support pypy properly", - ) - @mark.skipif( - platform.system() == "Windows", reason="uvloop does not support Windows" - ) - @mark.skipif( - twisted_version == Version("twisted", 21, 2, 0), - reason="https://twistedmatrix.com/trac/ticket/10106", - ) + @mark.requires_uvloop def test_custom_asyncio_loop_enabled_true(self): log = self.get_log( self.debug_log_spider, diff --git a/tests/test_crawler.py b/tests/test_crawler.py index d54a2cb7e..68e58144b 100644 --- a/tests/test_crawler.py +++ b/tests/test_crawler.py @@ -8,9 +8,7 @@ from pathlib import Path from packaging.version import parse as parse_version from pytest import mark, raises -from twisted import version as twisted_version from twisted.internet import defer -from twisted.python.versions import Version from twisted.trial import unittest from w3lib import __version__ as w3lib_version @@ -466,17 +464,7 @@ class CrawlerProcessSubprocess(ScriptRunnerMixin, unittest.TestCase): log, ) - @mark.skipif( - sys.implementation.name == "pypy", - reason="uvloop does not support pypy properly", - ) - @mark.skipif( - platform.system() == "Windows", reason="uvloop does not support Windows" - ) - @mark.skipif( - twisted_version == Version("twisted", 21, 2, 0), - reason="https://twistedmatrix.com/trac/ticket/10106", - ) + @mark.requires_uvloop def test_custom_loop_asyncio(self): log = self.run_script("asyncio_custom_loop.py") self.assertIn("Spider closed (finished)", log) @@ -485,17 +473,7 @@ class CrawlerProcessSubprocess(ScriptRunnerMixin, unittest.TestCase): ) self.assertIn("Using asyncio event loop: uvloop.Loop", log) - @mark.skipif( - sys.implementation.name == "pypy", - reason="uvloop does not support pypy properly", - ) - @mark.skipif( - platform.system() == "Windows", reason="uvloop does not support Windows" - ) - @mark.skipif( - twisted_version == Version("twisted", 21, 2, 0), - reason="https://twistedmatrix.com/trac/ticket/10106", - ) + @mark.requires_uvloop def test_custom_loop_asyncio_deferred_signal(self): log = self.run_script("asyncio_deferred_signal.py", "uvloop.Loop") self.assertIn("Spider closed (finished)", log) @@ -505,17 +483,7 @@ class CrawlerProcessSubprocess(ScriptRunnerMixin, unittest.TestCase): self.assertIn("Using asyncio event loop: uvloop.Loop", log) self.assertIn("async pipeline opened!", log) - @mark.skipif( - sys.implementation.name == "pypy", - reason="uvloop does not support pypy properly", - ) - @mark.skipif( - platform.system() == "Windows", reason="uvloop does not support Windows" - ) - @mark.skipif( - twisted_version == Version("twisted", 21, 2, 0), - reason="https://twistedmatrix.com/trac/ticket/10106", - ) + @mark.requires_uvloop def test_asyncio_enabled_reactor_same_loop(self): log = self.run_script("asyncio_enabled_reactor_same_loop.py") self.assertIn("Spider closed (finished)", log) @@ -524,17 +492,7 @@ class CrawlerProcessSubprocess(ScriptRunnerMixin, unittest.TestCase): ) self.assertIn("Using asyncio event loop: uvloop.Loop", log) - @mark.skipif( - sys.implementation.name == "pypy", - reason="uvloop does not support pypy properly", - ) - @mark.skipif( - platform.system() == "Windows", reason="uvloop does not support Windows" - ) - @mark.skipif( - twisted_version == Version("twisted", 21, 2, 0), - reason="https://twistedmatrix.com/trac/ticket/10106", - ) + @mark.requires_uvloop def test_asyncio_enabled_reactor_different_loop(self): log = self.run_script("asyncio_enabled_reactor_different_loop.py") self.assertNotIn("Spider closed (finished)", log) From e058a05763b45086edaf8b52f067648c0c50ae21 Mon Sep 17 00:00:00 2001 From: Andrey Rakhmatullin Date: Sat, 22 Jul 2023 17:51:13 +0400 Subject: [PATCH 2/5] Skip tests that don't work on Python 3.12. --- conftest.py | 2 ++ tests/requirements.txt | 10 ++++++---- tests/test_feedexport.py | 3 +++ tests/test_pipeline_files.py | 5 +++++ 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/conftest.py b/conftest.py index fa5193470..635935748 100644 --- a/conftest.py +++ b/conftest.py @@ -87,6 +87,8 @@ def requires_uvloop(request): pytest.skip("uvloop does not support Windows") if twisted_version == Version("twisted", 21, 2, 0): pytest.skip("https://twistedmatrix.com/trac/ticket/10106") + if sys.version_info >= (3, 12): + pytest.skip("uvloop doesn't support Python 3.12 yet") def pytest_configure(config): diff --git a/tests/requirements.txt b/tests/requirements.txt index 618949795..37186f3a7 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -1,15 +1,17 @@ # Tests requirements attrs -pyftpdlib +# https://github.com/giampaolo/pyftpdlib/issues/560 +pyftpdlib; python_version < "3.12" pytest pytest-cov==4.0.0 pytest-xdist sybil >= 1.3.0 # https://github.com/cjw296/sybil/issues/20#issuecomment-605433422 testfixtures -uvloop; platform_system != "Windows" +# uvloop currently doesn't build on 3.12 +uvloop; platform_system != "Windows" and python_version < "3.12" -# optional for shell wrapper tests -bpython +# bpython requires greenlet which currently doesn't build on 3.12 +bpython; python_version < "3.12" # optional for shell wrapper tests brotli # optional for HTTP compress downloader middleware tests zstandard; implementation_name != 'pypy' # optional for HTTP compress downloader middleware tests ipython diff --git a/tests/test_feedexport.py b/tests/test_feedexport.py index 8df86dbd7..eace59d37 100644 --- a/tests/test_feedexport.py +++ b/tests/test_feedexport.py @@ -125,6 +125,9 @@ class FileFeedStorageTest(unittest.TestCase): path.unlink() +@pytest.mark.skipif( + sys.version_info >= (3, 12), reason="pyftpdlib doesn't support Python 3.12 yet" +) class FTPFeedStorageTest(unittest.TestCase): def get_test_spider(self, settings=None): class TestSpider(scrapy.Spider): diff --git a/tests/test_pipeline_files.py b/tests/test_pipeline_files.py index 87f3a0295..fe7b26740 100644 --- a/tests/test_pipeline_files.py +++ b/tests/test_pipeline_files.py @@ -1,6 +1,7 @@ import dataclasses import os import random +import sys import time from datetime import datetime from io import BytesIO @@ -11,6 +12,7 @@ from unittest import mock from urllib.parse import urlparse import attr +import pytest from itemadapter import ItemAdapter from twisted.internet import defer from twisted.trial import unittest @@ -641,6 +643,9 @@ class TestGCSFilesStore(unittest.TestCase): store.bucket.get_blob.assert_called_with(expected_blob_path) +@pytest.mark.skipif( + sys.version_info >= (3, 12), reason="pyftpdlib doesn't support Python 3.12 yet" +) class TestFTPFileStore(unittest.TestCase): @defer.inlineCallbacks def test_persist(self): From a346732275b425e4fbebc3bdf133df961528df87 Mon Sep 17 00:00:00 2001 From: Andrey Rakhmatullin Date: Sat, 22 Jul 2023 17:54:55 +0400 Subject: [PATCH 3/5] Skip more non-test files during discovery. --- conftest.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/conftest.py b/conftest.py index 635935748..2bfa46f5a 100644 --- a/conftest.py +++ b/conftest.py @@ -18,6 +18,10 @@ def _py_files(folder): collect_ignore = [ # not a test, but looks like a test "scrapy/utils/testsite.py", + "tests/ftpserver.py", + "tests/mockserver.py", + "tests/pipelines.py", + "tests/spiders.py", # contains scripts to be run by tests/test_crawler.py::CrawlerProcessSubprocess *_py_files("tests/CrawlerProcess"), # contains scripts to be run by tests/test_crawler.py::CrawlerRunnerSubprocess From 21b6dc5f9fbd2607d6f4df20256dcdd9d5b9dae4 Mon Sep 17 00:00:00 2001 From: Andrey Rakhmatullin Date: Sat, 22 Jul 2023 17:55:32 +0400 Subject: [PATCH 4/5] Add 3.12 CI jobs. --- .github/workflows/tests-ubuntu.yml | 12 +++++++++++- setup.py | 1 + 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests-ubuntu.yml b/.github/workflows/tests-ubuntu.yml index 39e3b0af7..54b3fbaa2 100644 --- a/.github/workflows/tests-ubuntu.yml +++ b/.github/workflows/tests-ubuntu.yml @@ -48,6 +48,16 @@ jobs: env: TOXENV: botocore + - python-version: "3.12.0-beta.4" + env: + TOXENV: py + - python-version: "3.12.0-beta.4" + env: + TOXENV: asyncio + - python-version: "3.12.0-beta.4" + env: + TOXENV: extra-deps + steps: - uses: actions/checkout@v3 @@ -57,7 +67,7 @@ jobs: python-version: ${{ matrix.python-version }} - name: Install system libraries - if: matrix.python-version == 'pypy3.9' || contains(matrix.env.TOXENV, 'pinned') + if: matrix.python-version == 'pypy3.9' || contains(matrix.env.TOXENV, 'pinned') || contains(matrix.python-version, '3.12.0') run: | sudo apt-get update sudo apt-get install libxml2-dev libxslt-dev diff --git a/setup.py b/setup.py index 1f214571b..405633f55 100644 --- a/setup.py +++ b/setup.py @@ -62,6 +62,7 @@ setup( "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Internet :: WWW/HTTP", From 53f8570786fbc7c90bc9990a22279d90a52025b3 Mon Sep 17 00:00:00 2001 From: Andrey Rakhmatullin Date: Sat, 22 Jul 2023 18:46:44 +0400 Subject: [PATCH 5/5] Add support for the new entry_points() interface. --- scrapy/cmdline.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/scrapy/cmdline.py b/scrapy/cmdline.py index efc9b36ea..6580ba9ce 100644 --- a/scrapy/cmdline.py +++ b/scrapy/cmdline.py @@ -48,7 +48,11 @@ def _get_commands_from_module(module, inproject): def _get_commands_from_entry_points(inproject, group="scrapy.commands"): cmds = {} - for entry_point in entry_points().get(group, {}): + if sys.version_info >= (3, 10): + eps = entry_points(group=group) + else: + eps = entry_points().get(group, ()) + for entry_point in eps: obj = entry_point.load() if inspect.isclass(obj): cmds[entry_point.name] = obj()