diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index de5ad0a39..13709efde 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -27,6 +27,6 @@ repos: hooks: - id: sphinx-lint - repo: https://github.com/scrapy/sphinx-scrapy - rev: 0.7.1 + rev: 0.8.3 hooks: - id: sphinx-scrapy diff --git a/docs/news.rst b/docs/news.rst index d1276b82a..aef567a2a 100644 --- a/docs/news.rst +++ b/docs/news.rst @@ -3,6 +3,31 @@ Release notes ============= +.. _release-2.15.1: + +Scrapy 2.15.1 (2026-04-23) +-------------------------- + +Bug fixes +~~~~~~~~~ + +- Sharing of the SSL context between multiple connections, introduced in + Scrapy 2.15.0, is reverted as it caused problems and wasn't actually + needed. + (:issue:`7445`, :issue:`7450`) + +- Fixed :meth:`scrapy.settings.BaseSettings.getwithbase` failing on keys with + dots that aren't import names. It now works the way it worked before Scrapy + 2.15.0, without trying to match class objects and import path. A separate + method, + :func:`~scrapy.settings.BaseSettings.get_component_priority_dict_with_base`, + was added that does that, and it is now used for :ref:`component priority + dictionaries `. + (:issue:`7426`, :issue:`7449`) + +- Documentation rendering improvements. + (:issue:`7452`, :issue:`7454`) + .. _release-2.15.0: Scrapy 2.15.0 (2026-04-09) diff --git a/docs/requirements.in b/docs/requirements.in index 3b1cbb226..13b9dfb5d 100644 --- a/docs/requirements.in +++ b/docs/requirements.in @@ -5,4 +5,4 @@ sphinx sphinx-notfound-page sphinx-rtd-theme sphinx-rtd-dark-mode -sphinx-scrapy @ git+https://github.com/scrapy/sphinx-scrapy.git@0.7.1 +sphinx-scrapy @ git+https://github.com/scrapy/sphinx-scrapy.git@0.8.3 diff --git a/docs/requirements.txt b/docs/requirements.txt index 6da4a52c9..4d9eb5454 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile requirements.in -o requirements.txt +# uv pip compile -p 3.13 requirements.in -o requirements.txt alabaster==1.0.0 # via sphinx annotated-types==0.7.0 @@ -130,6 +130,7 @@ sphinx==9.1.0 # via # -r requirements.in # sphinx-copybutton + # sphinx-last-updated-by-git # sphinx-llms-txt # sphinx-markdown-builder # sphinx-notfound-page @@ -138,9 +139,11 @@ sphinx==9.1.0 # sphinxcontrib-jquery sphinx-copybutton==0.5.2 # via sphinx-scrapy +sphinx-last-updated-by-git==0.3.8 + # via sphinx-sitemap sphinx-llms-txt @ git+https://github.com/zytedata/sphinx-llms-txt.git@5e8866cb0cc249aa2017ad9050b3b83a7ca16f69 # via sphinx-scrapy -sphinx-markdown-builder @ git+https://github.com/zytedata/sphinx-markdown-builder.git@ac9f8babfe622e4300099ab44b96d9d9228e742e +sphinx-markdown-builder @ git+https://github.com/zytedata/sphinx-markdown-builder.git@cfe4c0bfd7b4542f7e6b65a58cdf9ec765829940 # via sphinx-scrapy sphinx-notfound-page==1.1.0 # via -r requirements.in @@ -150,8 +153,10 @@ sphinx-rtd-theme==3.1.0 # via # -r requirements.in # sphinx-rtd-dark-mode -sphinx-scrapy @ git+https://github.com/scrapy/sphinx-scrapy.git@2b5f6c7de64c8317cb771fdeb2e5020d1c9c9dcf +sphinx-scrapy @ git+https://github.com/scrapy/sphinx-scrapy.git@f20366277f2598d0c8a60e55fe282aff2da40dcf # via -r requirements.in +sphinx-sitemap==2.9.0 + # via sphinx-scrapy sphinxcontrib-applehelp==2.0.0 # via sphinx sphinxcontrib-devhelp==2.0.0 diff --git a/pyproject.toml b/pyproject.toml index abafa3037..42d00d526 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -154,7 +154,7 @@ module = [ ignore_missing_imports = true [tool.bumpversion] -current_version = "2.15.0" +current_version = "2.15.1" commit = true tag = true tag_name = "{new_version}" diff --git a/scrapy/VERSION b/scrapy/VERSION index 68e69e405..3b1fc7950 100644 --- a/scrapy/VERSION +++ b/scrapy/VERSION @@ -1 +1 @@ -2.15.0 +2.15.1 diff --git a/scrapy/utils/response.py b/scrapy/utils/response.py index 8b2417bfb..abb5f6a70 100644 --- a/scrapy/utils/response.py +++ b/scrapy/utils/response.py @@ -99,7 +99,7 @@ def open_in_browser( if isinstance(response, HtmlResponse): if b"' + repl = rf'\g<0>' body = re.sub(rb"]*?>)", to_bytes(repl), body, count=1) ext = ".html" elif isinstance(response, TextResponse): diff --git a/tests/test_utils_response.py b/tests/test_utils_response.py index 0aeb5594f..381a9c3ff 100644 --- a/tests/test_utils_response.py +++ b/tests/test_utils_response.py @@ -111,36 +111,27 @@ def test_response_status_message(): assert response_status_message(573) == "573 Unknown Status" -def test_inject_base_url(): - url = "http://www.example.com" - - def check_base_url(burl): - path = urlparse(burl).path - if not path or not Path(path).exists(): - path = burl.replace("file://", "") - bbody = Path(path).read_bytes() - assert bbody.count(b'') == 1 - return True - - r1 = HtmlResponse( - url, - body=b""" +@pytest.mark.parametrize( + "body", + [ + pytest.param( + b""" Dummy

Hello world.

""", - ) - r2 = HtmlResponse( - url, - body=b""" + id="Simple", + ), + pytest.param( + b""" Dummy Hello world. """, - ) - r3 = HtmlResponse( - url, - body=b""" + id=" with attrs", + ), + pytest.param( + b""" Dummy @@ -148,19 +139,19 @@ def test_inject_base_url():

Hello world.

""", - ) - r4 = HtmlResponse( - url, - body=b""" + id="Misleading tag", + ), + pytest.param( + b""" Dummy

Hello world.

""", - ) - r5 = HtmlResponse( - url, - body=b""" + id="Misleading comment", + ), + pytest.param( + b"""

Hello world.

""", - ) + id="Conditional comment", + ), + ], +) +def test_inject_base_url(body: bytes) -> None: + url = "http://www.example.com" - assert open_in_browser(r1, _openfunc=check_base_url), "Inject base url" - assert open_in_browser(r2, _openfunc=check_base_url), ( - "Inject base url with argumented head" - ) - assert open_in_browser(r3, _openfunc=check_base_url), ( - "Inject unique base url with misleading tag" - ) - assert open_in_browser(r4, _openfunc=check_base_url), ( - "Inject unique base url with misleading comment" - ) - assert open_in_browser(r5, _openfunc=check_base_url), ( - "Inject unique base url with conditional comment" - ) + def check_base_url(burl): + path = urlparse(burl).path + if not path or not Path(path).exists(): + path = burl.replace("file://", "") + bbody = Path(path).read_bytes() + assert bbody.count(b'>') == 1 + assert b"