Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package python-sphinx-scrapy for
openSUSE:Factory checked in at 2026-07-06 12:32:59
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-sphinx-scrapy (Old)
and /work/SRC/openSUSE:Factory/.python-sphinx-scrapy.new.1982 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-sphinx-scrapy"
Mon Jul 6 12:32:59 2026 rev:2 rq:1363879 version:0.8.6
Changes:
--------
---
/work/SRC/openSUSE:Factory/python-sphinx-scrapy/python-sphinx-scrapy.changes
2026-04-23 17:13:13.029921198 +0200
+++
/work/SRC/openSUSE:Factory/.python-sphinx-scrapy.new.1982/python-sphinx-scrapy.changes
2026-07-06 12:35:37.062260245 +0200
@@ -1,0 +2,18 @@
+Sun Jul 5 15:39:11 UTC 2026 - Dirk Müller <[email protected]>
+
+- update to 0.8.6:
+ * Add index templates for extension-defined cross-reference
+ types so related objects appear in generated Sphinx indexes.
+ * Normalize project IDs loaded from :file:`pyproject.toml` and
+ inferred from Sphinx metadata.
+ * Fix links in :file:`llms.txt`.
+ * Rewrite generated Markdown links for supported Intersphinx
+ targets from .html to .md.
+ * Extended easy Intersphinx configuration to aiohttp curl-cffi
+ niquests
+ * Document installation from Git for sphinx-scrapy and sphinx-
+ scrapy[tox].
+ * Upgrade sphinx-markdown-builder.
+
+
+-------------------------------------------------------------------
Old:
----
sphinx_scrapy-0.8.2.tar.gz
New:
----
sphinx_scrapy-0.8.6.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-sphinx-scrapy.spec ++++++
--- /var/tmp/diff_new_pack.5YFjAE/_old 2026-07-06 12:35:38.462308892 +0200
+++ /var/tmp/diff_new_pack.5YFjAE/_new 2026-07-06 12:35:38.466309031 +0200
@@ -19,7 +19,7 @@
# Enable libalternatives by default
%bcond_without libalternatives
Name: python-sphinx-scrapy
-Version: 0.8.2
+Version: 0.8.6
Release: 0
Summary: Sphinx extension for documentation in the Scrapy ecosystem
License: BSD-3-Clause
++++++ sphinx_scrapy-0.8.2.tar.gz -> sphinx_scrapy-0.8.6.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/sphinx-scrapy-0.8.2/CHANGES.rst
new/sphinx-scrapy-0.8.6/CHANGES.rst
--- old/sphinx-scrapy-0.8.2/CHANGES.rst 2026-04-10 14:54:33.000000000 +0200
+++ new/sphinx-scrapy-0.8.6/CHANGES.rst 2026-05-12 09:03:58.000000000 +0200
@@ -2,6 +2,39 @@
Release notes
=============
+0.8.6 (2026-05-12)
+==================
+
+- Add index templates for extension-defined cross-reference types so related
+ objects appear in generated Sphinx indexes.
+
+0.8.5 (2026-04-29)
+==================
+
+- Normalize project IDs loaded from :file:`pyproject.toml` and inferred from
+ Sphinx metadata.
+
+0.8.4 (2026-04-28)
+==================
+
+- Fix links in :file:`llms.txt`.
+
+0.8.3 (2026-04-22)
+==================
+
+- Rewrite generated Markdown links for supported Intersphinx targets from
+ ``.html`` to ``.md``.
+
+- | Extended easy Intersphinx configuration to
+ | `aiohttp <https://docs.aiohttp.org/en/stable/>`_
+ | `curl-cffi <https://curl-cffi.readthedocs.io/en/latest/>`_
+ | `niquests <https://niquests.readthedocs.io/en/latest/>`_
+
+- Document installation from Git for ``sphinx-scrapy`` and
+ ``sphinx-scrapy[tox]``.
+
+- Upgrade sphinx-markdown-builder.
+
0.8.2 (2026-04-10)
==================
@@ -14,7 +47,7 @@
==================
- Addressed warnings affecting sitemap generation due to missing ``/`` at the
- end of InterSphinx URLs.
+ end of Intersphinx URLs.
0.8.0 (2026-04-09)
==================
@@ -53,7 +86,7 @@
- Added a "Copy as Markdown" button on HTML pages that copies each page's
corresponding ``.md`` counterpart.
-- | Extended easy intersphinx configuration to
+- | Extended easy Intersphinx configuration to
| `coverage <https://coverage.readthedocs.io/en/latest>`_
| `cryptography <https://cryptography.io/en/latest/>`_
| `multidict <https://multidict.aio-libs.org/en/latest/>`_
@@ -69,7 +102,7 @@
0.6.0 (2025-09-16)
==================
-| Extended easy intersphinx configuration to:
+| Extended easy Intersphinx configuration to:
| `pydantic <https://docs.pydantic.dev/latest/>`_
@@ -82,7 +115,7 @@
0.4.0 (2025-09-16)
==================
-| Extended easy intersphinx configuration to:
+| Extended easy Intersphinx configuration to:
| `packaging <https://packaging.pypa.io/en/stable/>`_
| `pytest <https://docs.pytest.org/en/stable/>`_
@@ -90,7 +123,7 @@
0.3.0 (2025-09-02)
==================
-| Extended easy intersphinx configuration to:
+| Extended easy Intersphinx configuration to:
| `jinja <https://jinja.palletsprojects.com/en/latest/>`_
| `lxml <https://lxml.de/apidoc/>`_
@@ -98,7 +131,7 @@
0.2.0 (2025-06-16)
==================
-| Extended easy intersphinx configuration to:
+| Extended easy Intersphinx configuration to:
| `dateparser <https://dateparser.readthedocs.io/en/latest/>`_
| `form2request <https://form2request.readthedocs.io/en/latest/>`_
| `formasaurus <https://formasaurus.readthedocs.io/en/latest/>`_
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/sphinx-scrapy-0.8.2/README.rst
new/sphinx-scrapy-0.8.6/README.rst
--- old/sphinx-scrapy-0.8.2/README.rst 2026-04-10 14:54:33.000000000 +0200
+++ new/sphinx-scrapy-0.8.6/README.rst 2026-05-12 09:03:58.000000000 +0200
@@ -56,9 +56,12 @@
#. Add to ``docs/requirements.txt``:
- .. code-block::
+ Install from Git, because this project currently depends on Git-based
+ packages and cannot be published to PyPI:
+
+ .. code-block:: text
- sphinx-scrapy==0.7.2
+ sphinx-scrapy @ git+https://github.com/scrapy/[email protected]
#. Add to ``docs/conf.py``:
@@ -108,7 +111,7 @@
repos:
- repo: https://github.com/scrapy/sphinx-scrapy
- rev: 0.8.2
+ rev: 0.8.6
hooks:
- id: sphinx-scrapy
@@ -118,7 +121,7 @@
[tox]
requires =
- sphinx-scrapy[tox]==0.8.2
+ sphinx-scrapy[tox] @
git+https://github.com/scrapy/[email protected]
envlist = …,docs
.. note:: ``docs`` in ``envlist`` is required.
@@ -156,10 +159,12 @@
``scrapy_intersphinx_enable`` supports the following packages:
+| `aiohttp <https://docs.aiohttp.org/en/stable/>`_
| `attrs <https://www.attrs.org/en/stable/>`_
| `coverage <https://coverage.readthedocs.io/en/latest>`_
| `cryptography <https://cryptography.io/en/latest/>`_
| `cssselect <https://cssselect.readthedocs.io/en/latest>`_
+| `curl-cffi <https://curl-cffi.readthedocs.io/en/latest/>`_
| `dateparser <https://dateparser.readthedocs.io/en/latest/>`_
| `form2request <https://form2request.readthedocs.io/en/latest/>`_
| `formasaurus <https://formasaurus.readthedocs.io/en/latest/>`_
@@ -167,6 +172,7 @@
| `jinja <https://jinja.palletsprojects.com/en/latest/>`_
| `lxml <https://lxml.de/apidoc/>`_
| `multidict <https://multidict.aio-libs.org/en/latest/>`_
+| `niquests <https://niquests.readthedocs.io/en/latest/>`_
| `packaging <https://packaging.pypa.io/en/stable/>`_
| `parsel <https://parsel.readthedocs.io/en/latest/>`_
| `pydantic <https://docs.pydantic.dev/latest/>`_
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/sphinx-scrapy-0.8.2/pyproject.toml
new/sphinx-scrapy-0.8.6/pyproject.toml
--- old/sphinx-scrapy-0.8.2/pyproject.toml 2026-04-10 14:54:33.000000000
+0200
+++ new/sphinx-scrapy-0.8.6/pyproject.toml 2026-05-12 09:03:58.000000000
+0200
@@ -4,14 +4,14 @@
[project]
name = "sphinx-scrapy"
-version = "0.8.2"
+version = "0.8.6"
description = "Sphinx extension for documentation in the Scrapy ecosystem"
dependencies = [
"packaging",
"sphinx",
"sphinx-copybutton",
"sphinx-llms-txt @
git+https://github.com/zytedata/sphinx-llms-txt.git@5e8866cb0cc249aa2017ad9050b3b83a7ca16f69",
- "sphinx-markdown-builder @
git+https://github.com/zytedata/sphinx-markdown-builder.git@d12c2d188009c5230f18cd6a904c03d1eca066ed",
+ "sphinx-markdown-builder @
git+https://github.com/zytedata/sphinx-markdown-builder.git@cfe4c0bfd7b4542f7e6b65a58cdf9ec765829940",
"sphinx-sitemap",
"tomli; python_version < '3.11'",
]
@@ -59,7 +59,7 @@
strict = true
[tool.bumpversion]
-current_version = "0.8.2"
+current_version = "0.8.6"
commit = true
tag = true
tag_name = "{new_version}"
@@ -74,13 +74,16 @@
filename = "README.rst"
search = "rev: {current_version}"
replace = "rev: {new_version}"
-regex = false
[[tool.bumpversion.files]]
filename = "README.rst"
-search = "sphinx-scrapy[tox]=={current_version}"
-replace = "sphinx-scrapy[tox]=={new_version}"
-regex = false
+search = "sphinx-scrapy @
git+https://github.com/scrapy/sphinx-scrapy.git@{current_version}"
+replace = "sphinx-scrapy @
git+https://github.com/scrapy/sphinx-scrapy.git@{new_version}"
+
+[[tool.bumpversion.files]]
+filename = "README.rst"
+search = "sphinx-scrapy[tox] @
git+https://github.com/scrapy/sphinx-scrapy.git@{current_version}"
+replace = "sphinx-scrapy[tox] @
git+https://github.com/scrapy/sphinx-scrapy.git@{new_version}"
[tool.ruff.lint]
extend-select = [
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/sphinx-scrapy-0.8.2/sphinx_scrapy/__init__.py
new/sphinx-scrapy-0.8.6/sphinx_scrapy/__init__.py
--- old/sphinx-scrapy-0.8.2/sphinx_scrapy/__init__.py 2026-04-10
14:54:33.000000000 +0200
+++ new/sphinx-scrapy-0.8.6/sphinx_scrapy/__init__.py 2026-05-12
09:03:58.000000000 +0200
@@ -4,7 +4,7 @@
from logging import getLogger
from typing import TYPE_CHECKING
-from .config import load_project_config
+from .config import load_project_config, normalize_project_id
if TYPE_CHECKING:
from collections.abc import Generator
@@ -15,10 +15,12 @@
logger = getLogger(__name__)
INTERSPHINX_MAPPING = {
+ "aiohttp": ("https://docs.aiohttp.org/en/stable/", None),
"attrs": ("https://www.attrs.org/en/stable/", None),
"coverage": ("https://coverage.readthedocs.io/en/latest/", None),
"cryptography": ("https://cryptography.io/en/latest/", None),
"cssselect": ("https://cssselect.readthedocs.io/en/latest/", None),
+ "curl-cffi": ("https://curl-cffi.readthedocs.io/en/latest/", None),
"dateparser": ("https://dateparser.readthedocs.io/en/latest/", None),
"form2request": ("https://form2request.readthedocs.io/en/latest/", None),
"formasaurus": ("https://formasaurus.readthedocs.io/en/latest/", None),
@@ -26,6 +28,7 @@
"jinja": ("https://jinja.palletsprojects.com/en/latest/", None),
"lxml": ("https://lxml.de/apidoc/", None),
"multidict": ("https://multidict.aio-libs.org/en/latest/", None),
+ "niquests": ("https://niquests.readthedocs.io/en/latest/", None),
"packaging": ("https://packaging.pypa.io/en/stable/", None),
"parsel": ("https://parsel.readthedocs.io/en/latest/", None),
"pydantic": ("https://pydantic.dev/docs/validation/latest/", None),
@@ -252,6 +255,7 @@
app.add_crossref_type(
directivename=crossref_type,
rolename=crossref_type,
+ indextemplate=f"pair: %s; {crossref_type}",
)
@@ -302,9 +306,9 @@
package: str | None = None
project_config = load_project_config()
if project_config.project_id:
- package = project_config.project_id
+ package = normalize_project_id(project_config.project_id)
elif hasattr(config, "project"):
- package = re.sub(r"[\s_]+", "-", str(config.project)).lower()
+ package = normalize_project_id(re.sub(r"\s+", "-",
str(config.project)))
if not package:
return
if package in INTERSPHINX_MAPPING:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/sphinx-scrapy-0.8.2/sphinx_scrapy/cli.py
new/sphinx-scrapy-0.8.6/sphinx_scrapy/cli.py
--- old/sphinx-scrapy-0.8.2/sphinx_scrapy/cli.py 2026-04-10
14:54:33.000000000 +0200
+++ new/sphinx-scrapy-0.8.6/sphinx_scrapy/cli.py 2026-05-12
09:03:58.000000000 +0200
@@ -1,20 +1,258 @@
from __future__ import annotations
import argparse
+from collections.abc import Callable
import concurrent.futures
+import re
import shutil
import sys
+from http import HTTPStatus
from typing import TYPE_CHECKING
+from urllib.error import HTTPError, URLError
+from urllib.parse import urlsplit, urlunsplit
+from urllib.request import Request, urlopen
from packaging.version import InvalidVersion, Version
from sphinx.cmd.build import main as sphinx_build_main
-from .config import LATEST_RTD_PYTHON_VERSION, load_project_config
+from . import INTERSPHINX_MAPPING
+from .config import LATEST_RTD_PYTHON_VERSION, load_project_config,
normalize_project_id
if TYPE_CHECKING:
from pathlib import Path
+URL_PATTERN = re.compile(r"https?://[^\s<>()\[\]{}\"']+")
+MARKDOWN_LINK_PATTERN = re.compile(
+ r"(?P<prefix>!?\[[^\]]*\]\()"
+ r"(?P<target>[^)\s]+)"
+ r"(?P<title>\s+\"[^\"]*\")?"
+ r"(?P<suffix>\))"
+)
+
+
+def _intersphinx_base_urls() -> tuple[str, ...]:
+ bases = {url for url, _inventory in INTERSPHINX_MAPPING.values()}
+ return tuple(sorted(bases, key=len, reverse=True))
+
+
+def _project_docs_base_url(project_id: str | None) -> str | None:
+ if not project_id:
+ return None
+ normalized_project_id = normalize_project_id(project_id)
+ if not normalized_project_id:
+ return None
+ if normalized_project_id in INTERSPHINX_MAPPING:
+ return INTERSPHINX_MAPPING[normalized_project_id][0]
+ return f"https://{normalized_project_id}.readthedocs.io/en/latest/"
+
+
+def _split_wrapped_markdown_target(target: str) -> tuple[str, str, str]:
+ if len(target) >= 2 and target.startswith("<") and target.endswith(">"):
+ return "<", target[1:-1], ">"
+ return "", target, ""
+
+
+def _rewrite_markdown_links(
+ content: str,
+ rewrite_target: Callable[[str], str],
+) -> str:
+ in_fence = False
+ fence_char = ""
+ rewritten_lines: list[str] = []
+
+ def replacement(match: re.Match[str]) -> str:
+ target = match.group("target")
+ rewritten_target = rewrite_target(target)
+ if rewritten_target == target:
+ return match.group(0)
+
+ title = match.group("title") or ""
+ return (
+ f"{match.group('prefix')}"
+ f"{rewritten_target}"
+ f"{title}"
+ f"{match.group('suffix')}"
+ )
+
+ for line in content.splitlines(keepends=True):
+ stripped = line.lstrip()
+ if stripped.startswith("```") or stripped.startswith("~~~"):
+ marker = stripped[0]
+ if not in_fence:
+ in_fence = True
+ fence_char = marker
+ elif marker == fence_char:
+ in_fence = False
+ fence_char = ""
+ rewritten_lines.append(line)
+ continue
+
+ if in_fence:
+ rewritten_lines.append(line)
+ continue
+
+ rewritten_lines.append(MARKDOWN_LINK_PATTERN.sub(replacement, line))
+
+ return "".join(rewritten_lines)
+
+
+def _rewrite_llms_link_target(
+ target: str,
+ docs_host: str,
+ docs_path_prefix: str,
+) -> str:
+ wrapper_prefix, wrapped_target, wrapper_suffix = (
+ _split_wrapped_markdown_target(target)
+ )
+ parts = urlsplit(wrapped_target)
+
+ if parts.scheme or parts.netloc:
+ if parts.scheme not in {"http", "https"}:
+ return target
+ if parts.netloc.lower() != docs_host:
+ return target
+
+ if not parts.path.endswith(".md"):
+ return target
+
+ clean_path = parts.path.lstrip("/")
+ while clean_path.startswith("./"):
+ clean_path = clean_path[2:]
+ if clean_path.startswith("../"):
+ return target
+
+ if docs_path_prefix and not clean_path.startswith(docs_path_prefix):
+ clean_path = f"{docs_path_prefix}{clean_path}"
+
+ rewritten_target = urlunsplit(("", "", clean_path, parts.query,
parts.fragment))
+ return f"{wrapper_prefix}{rewritten_target}{wrapper_suffix}"
+
+
+def _rewrite_llms_links_to_docs_path(
+ output_dir: Path,
+ docs_base_url: str | None,
+) -> None:
+ llms_path = output_dir / "llms.txt"
+ if docs_base_url is None or not llms_path.is_file():
+ return
+
+ docs_parts = urlsplit(docs_base_url)
+ docs_host = docs_parts.netloc.lower()
+ docs_path = docs_parts.path.strip("/")
+ docs_path_prefix = f"{docs_path}/" if docs_path else ""
+
+ content = llms_path.read_text(encoding="utf-8")
+ rewritten = _rewrite_markdown_links(
+ content,
+ lambda target: _rewrite_llms_link_target(target, docs_host,
docs_path_prefix),
+ )
+ if rewritten != content:
+ llms_path.write_text(rewritten, encoding="utf-8")
+
+
+def _iter_markdown_outputs(output_dir: Path) -> list[Path]:
+ files = sorted(output_dir.rglob("*.md"))
+ for filename in ("llms.txt", "llms-full.txt"):
+ path = output_dir / filename
+ if path.is_file():
+ files.append(path)
+ return files
+
+
+def _matching_base_url(url: str, base_urls: tuple[str, ...]) -> str | None:
+ for base_url in base_urls:
+ if url.startswith(base_url):
+ return base_url
+ return None
+
+
+def _supports_markdown(base_url: str) -> bool:
+ probe_url = f"{base_url.rstrip('/')}/index.md"
+ headers = {"User-Agent": "sphinx-scrapy"}
+ for method in ("HEAD", "GET"):
+ request = Request(probe_url, headers=headers, method=method) # noqa:
S310
+ try:
+ with urlopen(request, timeout=5) as response: # noqa: S310
+ return HTTPStatus.OK <= response.status <
HTTPStatus.BAD_REQUEST
+ except HTTPError as error:
+ if method == "HEAD" and error.code in {
+ HTTPStatus.METHOD_NOT_ALLOWED,
+ HTTPStatus.NOT_IMPLEMENTED,
+ }:
+ continue
+ return False
+ except URLError:
+ return False
+ return False
+
+
+def _rewrite_url_to_markdown(url: str, enabled_base_urls: set[str], base_urls:
tuple[str, ...]) -> str:
+ base_url = _matching_base_url(url, base_urls)
+ if not base_url or base_url not in enabled_base_urls:
+ return url
+
+ parts = urlsplit(url)
+ path = parts.path
+ if not path.endswith(".html"):
+ return url
+ new_path = f"{path[:-5]}.md"
+
+ return urlunsplit((parts.scheme, parts.netloc, new_path, parts.query,
parts.fragment))
+
+
+def _rewrite_intersphinx_links_to_markdown(output_dir: Path) -> None:
+ target_files = _iter_markdown_outputs(output_dir)
+ if not target_files:
+ return
+
+ base_urls = _intersphinx_base_urls()
+ file_contents: dict[Path, str] = {}
+ candidate_base_urls: set[str] = set()
+
+ for file in target_files:
+ content = file.read_text(encoding="utf-8")
+ file_contents[file] = content
+ for match in URL_PATTERN.finditer(content):
+ url = match.group(0)
+ if ".html" not in url:
+ continue
+ base_url = _matching_base_url(url, base_urls)
+ if base_url is not None:
+ candidate_base_urls.add(base_url)
+
+ if not candidate_base_urls:
+ return
+
+ with concurrent.futures.ThreadPoolExecutor(
+ max_workers=min(8, len(candidate_base_urls))
+ ) as executor:
+ availability = {
+ base_url: executor.submit(_supports_markdown, base_url)
+ for base_url in candidate_base_urls
+ }
+ enabled_base_urls = {
+ base_url
+ for base_url, future in availability.items()
+ if future.result()
+ }
+
+ if not enabled_base_urls:
+ return
+
+ for file, content in file_contents.items():
+ rewritten = URL_PATTERN.sub(
+ lambda match: _rewrite_url_to_markdown(
+ match.group(0),
+ enabled_base_urls,
+ base_urls,
+ ),
+ content,
+ )
+ if rewritten != content:
+ file.write_text(rewritten, encoding="utf-8")
+
+
def _builder_settings(builder: str) -> list[str]:
if builder == "html":
return ["-D", "llms_txt_uri_template={docname}.md"]
@@ -42,6 +280,7 @@
def build_docs() -> int:
config = load_project_config()
+ docs_base_url = _project_docs_base_url(config.project_id)
docs_dir = config.root / "docs"
if not docs_dir.is_dir():
print("docs directory not found", file=sys.stderr)
@@ -71,6 +310,8 @@
shutil.copytree(sphinx_build_dir / "html", all_dir, dirs_exist_ok=True)
shutil.copytree(sphinx_build_dir / "markdown", all_dir, dirs_exist_ok=True)
shutil.copy2(sphinx_build_dir / "singlemarkdown" / "index.md", all_dir /
"llms-full.txt")
+ _rewrite_intersphinx_links_to_markdown(all_dir)
+ _rewrite_llms_links_to_docs_path(all_dir, docs_base_url)
print("\nDocumentation generated in docs/_build/all.")
return 0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/sphinx-scrapy-0.8.2/sphinx_scrapy/config.py
new/sphinx-scrapy-0.8.6/sphinx_scrapy/config.py
--- old/sphinx-scrapy-0.8.2/sphinx_scrapy/config.py 2026-04-10
14:54:33.000000000 +0200
+++ new/sphinx-scrapy-0.8.6/sphinx_scrapy/config.py 2026-05-12
09:03:58.000000000 +0200
@@ -3,6 +3,8 @@
from dataclasses import dataclass, field
from pathlib import Path
+from packaging.utils import canonicalize_name
+
try:
import tomllib
except ModuleNotFoundError:
@@ -34,6 +36,10 @@
return {str(key) for key in optional_dependencies}
+def normalize_project_id(project_id: str) -> str:
+ return canonicalize_name(project_id.strip())
+
+
def load_project_config(root: Path | None = None) -> ProjectConfig:
"""Load project configuration from a pyproject.toml if available.
@@ -54,10 +60,15 @@
scrapy_data = tool_data.get("sphinx-scrapy", {})
python_version = scrapy_data.get("python-version",
LATEST_RTD_PYTHON_VERSION)
extras = get_extras(pyproject_data)
- project_id = pyproject_data.get("project", {}).get("name")
+ raw_project_id = pyproject_data.get("project", {}).get("name")
+ project_id = None
+ if raw_project_id is not None:
+ normalized_project_id = normalize_project_id(str(raw_project_id))
+ if normalized_project_id:
+ project_id = normalized_project_id
return ProjectConfig(
root=project_root,
python_version=str(python_version) if python_version is not None else
None,
extras=extras,
- project_id=str(project_id) if project_id is not None else None,
+ project_id=project_id,
)