Add llms.txt and llms-full.txt generation (#7380)

This commit is contained in:
Adrian 2026-04-06 10:24:21 +02:00 committed by GitHub
parent 58d85282cf
commit f8d103a65a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 301 additions and 88 deletions

View File

@ -26,9 +26,13 @@ jobs:
- python-version: "3.10" - python-version: "3.10"
env: env:
TOXENV: typing-tests TOXENV: typing-tests
- python-version: "3.13" # Keep in sync with .readthedocs.yml # Keep in sync with pyproject.toml tool.sphinx-scrapy.python-version.
- python-version: "3.13"
env: env:
TOXENV: docs TOXENV: docs
- python-version: "3.13"
env:
TOXENV: docs-tests
- python-version: "3.13" - python-version: "3.13"
env: env:
TOXENV: twinecheck TOXENV: twinecheck

2
.gitignore vendored
View File

@ -3,7 +3,7 @@
*.pyc *.pyc
_trial_temp* _trial_temp*
dropin.cache dropin.cache
docs/build docs/_build
*egg-info *egg-info
.tox/ .tox/
venv/ venv/

View File

@ -26,3 +26,7 @@ repos:
rev: v1.0.2 rev: v1.0.2
hooks: hooks:
- id: sphinx-lint - id: sphinx-lint
- repo: https://github.com/scrapy/sphinx-scrapy
rev: 0.7.0
hooks:
- id: sphinx-scrapy

View File

@ -1,17 +1,9 @@
version: 2 version: 2
formats: all
sphinx:
configuration: docs/conf.py
fail_on_warning: true
build: build:
os: ubuntu-24.04 os: ubuntu-24.04
tools: tools:
# For available versions, see: python: "3.13"
# https://docs.readthedocs.io/en/stable/config-file/v2.html#build-tools-python commands:
python: "3.13" # Keep in sync with .github/workflows/checks.yml - pip install tox
- tox -e docs
python: - cp -a docs/_build/all/. $READTHEDOCS_OUTPUT/html/
install:
- requirements: docs/requirements.txt
- path: .

View File

@ -65,4 +65,4 @@ To compile the documentation to HTML run the following command::
tox -e docs tox -e docs
Documentation will be generated (in HTML format) inside the ``.tox/docs/tmp/html`` dir. Documentation will be generated inside the ``docs/_build/all`` dir.

View File

@ -77,6 +77,25 @@ def make_setting_element(
return item return item
def make_setting_markdown_item(
setting_data: SettingData, app: Sphinx, fromdocname: str
) -> str:
uri = app.builder.get_relative_uri(fromdocname, setting_data["docname"])
if uri.startswith("#"):
target = f"#{setting_data['refid']}"
else:
target = f"{uri}#{setting_data['refid']}"
return f"* [{setting_data['setting_name']}]({target})"
def _iter_sorted_settings(env: Any, fromdocname: str) -> list[SettingData]:
return [
d
for d in sorted(env.scrapy_all_settings, key=itemgetter("setting_name")) # type: ignore[attr-defined]
if fromdocname != d["docname"]
]
def replace_settingslist_nodes( def replace_settingslist_nodes(
app: Sphinx, doctree: document, fromdocname: str app: Sphinx, doctree: document, fromdocname: str
) -> None: ) -> None:
@ -87,13 +106,29 @@ def replace_settingslist_nodes(
settings_list.extend( settings_list.extend(
[ [
make_setting_element(d, app, fromdocname) make_setting_element(d, app, fromdocname)
for d in sorted(env.scrapy_all_settings, key=itemgetter("setting_name")) # type: ignore[attr-defined] for d in _iter_sorted_settings(env, fromdocname)
if fromdocname != d["docname"]
] ]
) )
node.replace_self(settings_list) node.replace_self(settings_list)
def visit_settingslist_node_markdown(translator: Any, _node: Node) -> None:
builder = translator.builder
env = builder.env
fromdocname = getattr(builder, "current_doc_name", env.docname)
lines = [
make_setting_markdown_item(setting_data, builder.app, fromdocname)
for setting_data in _iter_sorted_settings(env, fromdocname)
]
if lines:
translator.add("\n".join(lines), prefix_eol=2, suffix_eol=2)
raise nodes.SkipNode
def depart_settingslist_node_markdown(_translator: Any, _node: Node) -> None:
return None
def source_role( def source_role(
name, rawtext, text: str, lineno, inliner, options=None, content=None name, rawtext, text: str, lineno, inliner, options=None, content=None
) -> tuple[list[Any], list[Any]]: ) -> tuple[list[Any], list[Any]]:
@ -127,32 +162,19 @@ def rev_role(
def setup(app: Sphinx) -> dict[str, Any]: def setup(app: Sphinx) -> dict[str, Any]:
app.add_crossref_type(
directivename="setting",
rolename="setting",
indextemplate="pair: %s; setting",
)
app.add_crossref_type(
directivename="signal",
rolename="signal",
indextemplate="pair: %s; signal",
)
app.add_crossref_type(
directivename="command",
rolename="command",
indextemplate="pair: %s; command",
)
app.add_crossref_type(
directivename="reqmeta",
rolename="reqmeta",
indextemplate="pair: %s; reqmeta",
)
app.add_role("source", source_role) app.add_role("source", source_role)
app.add_role("commit", commit_role) app.add_role("commit", commit_role)
app.add_role("issue", issue_role) app.add_role("issue", issue_role)
app.add_role("rev", rev_role) app.add_role("rev", rev_role)
app.add_node(SettingslistNode) app.add_node(
SettingslistNode,
markdown=(visit_settingslist_node_markdown, depart_settingslist_node_markdown),
singlemarkdown=(
visit_settingslist_node_markdown,
depart_settingslist_node_markdown,
),
)
app.add_directive("settingslist", SettingsListDirective) app.add_directive("settingslist", SettingsListDirective)
app.connect("doctree-read", collect_scrapy_settings_refs) app.connect("doctree-read", collect_scrapy_settings_refs)

View File

@ -28,11 +28,9 @@ author = "Scrapy developers"
extensions = [ extensions = [
"notfound.extension", "notfound.extension",
"scrapydocs", "scrapydocs",
"sphinx.ext.autodoc", "sphinx_scrapy",
"scrapyfixautodoc", # Must be after "sphinx.ext.autodoc" "scrapyfixautodoc", # Must be after "sphinx.ext.autodoc"
"sphinx.ext.coverage", "sphinx.ext.coverage",
"sphinx.ext.intersphinx",
"sphinx.ext.viewcode",
"sphinx_rtd_dark_mode", "sphinx_rtd_dark_mode",
] ]
@ -147,22 +145,24 @@ coverage_ignore_pyobjects = [
# -- Options for the InterSphinx extension ----------------------------------- # -- Options for the InterSphinx extension -----------------------------------
# https://www.sphinx-doc.org/en/master/usage/extensions/intersphinx.html#configuration # https://www.sphinx-doc.org/en/master/usage/extensions/intersphinx.html#configuration
intersphinx_mapping = {
"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),
"itemloaders": ("https://itemloaders.readthedocs.io/en/latest/", None),
"parsel": ("https://parsel.readthedocs.io/en/latest/", None),
"pytest": ("https://docs.pytest.org/en/latest", None),
"python": ("https://docs.python.org/3", None),
"sphinx": ("https://www.sphinx-doc.org/en/master", None),
"tox": ("https://tox.wiki/en/latest/", None),
"twisted": ("https://docs.twisted.org/en/stable/", None),
"twistedapi": ("https://docs.twisted.org/en/stable/api/", None),
"w3lib": ("https://w3lib.readthedocs.io/en/latest", None),
}
intersphinx_disabled_reftypes: Sequence[str] = [] intersphinx_disabled_reftypes: Sequence[str] = []
# sphinx-scrapy ---------------------------------------------------------------
scrapy_intersphinx_enable = [
"attrs",
"coverage",
"cryptography",
"cssselect",
"itemloaders",
"parsel",
"pytest",
"sphinx",
"tox",
"twisted",
"twistedapi",
"w3lib",
]
# -- Other options ------------------------------------------------------------ # -- Other options ------------------------------------------------------------
default_dark_mode = False default_dark_mode = False

8
docs/requirements.in Normal file
View File

@ -0,0 +1,8 @@
h2
pydantic
scrapy-spider-metadata
sphinx
sphinx-notfound-page
sphinx-rtd-theme
sphinx-rtd-dark-mode
sphinx-scrapy @ git+https://github.com/scrapy/sphinx-scrapy.git@0.7.0

View File

@ -1,7 +1,192 @@
# This file was autogenerated by uv via the following command:
# uv pip compile requirements.in -o requirements.txt
alabaster==1.0.0
# via sphinx
annotated-types==0.7.0
# via pydantic
attrs==26.1.0
# via
# service-identity
# twisted
automat==25.4.16
# via twisted
babel==2.18.0
# via sphinx
certifi==2026.2.25
# via requests
cffi==2.0.0
# via cryptography
charset-normalizer==3.4.6
# via requests
constantly==23.10.4
# via twisted
cryptography==46.0.6
# via
# pyopenssl
# scrapy
# service-identity
cssselect==1.4.0
# via
# parsel
# scrapy
defusedxml==0.7.1
# via scrapy
docutils==0.22.4
# via
# sphinx
# sphinx-markdown-builder
# sphinx-rtd-theme
filelock==3.25.2
# via tldextract
h2==4.3.0 h2==4.3.0
pydantic==2.12.3 # via -r requirements.in
hpack==4.1.0
# via h2
hyperframe==6.1.0
# via h2
hyperlink==21.0.0
# via twisted
idna==3.11
# via
# hyperlink
# requests
# tldextract
imagesize==2.0.0
# via sphinx
incremental==24.11.0
# via twisted
itemadapter==0.13.1
# via
# itemloaders
# scrapy
itemloaders==1.4.0
# via scrapy
jinja2==3.1.6
# via sphinx
jmespath==1.1.0
# via
# itemloaders
# parsel
lxml==6.0.2
# via
# parsel
# scrapy
markupsafe==3.0.3
# via jinja2
packaging==26.0
# via
# incremental
# parsel
# scrapy
# scrapy-spider-metadata
# sphinx
# sphinx-scrapy
parsel==1.11.0
# via
# itemloaders
# scrapy
protego==0.6.0
# via scrapy
pyasn1==0.6.3
# via
# pyasn1-modules
# service-identity
pyasn1-modules==0.4.2
# via service-identity
pycparser==3.0
# via cffi
pydantic==2.12.5
# via
# -r requirements.in
# scrapy-spider-metadata
pydantic-core==2.41.5
# via pydantic
pydispatcher==2.0.7
# via scrapy
pygments==2.19.2
# via sphinx
pyopenssl==26.0.0
# via scrapy
queuelib==1.9.0
# via scrapy
requests==2.33.0
# via
# requests-file
# sphinx
# tldextract
requests-file==3.0.1
# via tldextract
roman-numerals==4.1.0
# via sphinx
scrapy==2.14.2
# via scrapy-spider-metadata
scrapy-spider-metadata==0.2.0 scrapy-spider-metadata==0.2.0
sphinx==8.1.3 # via -r requirements.in
sphinx-notfound-page==1.0.4 service-identity==24.2.0
sphinx-rtd-theme==3.0.2 # via scrapy
snowballstemmer==3.0.1
# via sphinx
sphinx==9.1.0
# via
# -r requirements.in
# sphinx-copybutton
# sphinx-llms-txt
# sphinx-markdown-builder
# sphinx-notfound-page
# sphinx-rtd-theme
# sphinx-scrapy
# sphinxcontrib-jquery
sphinx-copybutton==0.5.2
# via sphinx-scrapy
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
# via sphinx-scrapy
sphinx-notfound-page==1.1.0
# via -r requirements.in
sphinx-rtd-dark-mode==1.3.0 sphinx-rtd-dark-mode==1.3.0
# via -r requirements.in
sphinx-rtd-theme==3.1.0
# via
# -r requirements.in
# sphinx-rtd-dark-mode
sphinx-scrapy @ git+https://github.com/scrapy/sphinx-scrapy.git@46c52fc3a6d5ee632a8ef6bccf4daa1ff53e96fd
# via -r requirements.in
sphinxcontrib-applehelp==2.0.0
# via sphinx
sphinxcontrib-devhelp==2.0.0
# via sphinx
sphinxcontrib-htmlhelp==2.1.0
# via sphinx
sphinxcontrib-jquery==4.1
# via sphinx-rtd-theme
sphinxcontrib-jsmath==1.0.1
# via sphinx
sphinxcontrib-qthelp==2.0.0
# via sphinx
sphinxcontrib-serializinghtml==2.0.0
# via sphinx
tabulate==0.10.0
# via sphinx-markdown-builder
tldextract==5.3.1
# via scrapy
twisted==25.5.0
# via scrapy
typing-extensions==4.15.0
# via
# pydantic
# pydantic-core
# twisted
# typing-inspection
typing-inspection==0.4.2
# via pydantic
urllib3==2.6.3
# via requests
w3lib==2.4.1
# via
# parsel
# scrapy
zope-interface==8.2
# via
# scrapy
# twisted

View File

@ -198,7 +198,7 @@ scrapy.Spider
The ``parse`` method is in charge of processing the response and returning The ``parse`` method is in charge of processing the response and returning
scraped data and/or more URLs to follow. Other Requests callbacks have scraped data and/or more URLs to follow. Other Requests callbacks have
the same requirements as the :class:`Spider` class. the same requirements as the :class:`~scrapy.Spider` class.
This method, as well as any other Request callback, must return a This method, as well as any other Request callback, must return a
:class:`~scrapy.Request` object, an :ref:`item object <topics-items>`, an :class:`~scrapy.Request` object, an :ref:`item object <topics-items>`, an

View File

@ -464,3 +464,6 @@ split-on-trailing-comma = false
[tool.ruff.lint.pydocstyle] [tool.ruff.lint.pydocstyle]
convention = "pep257" convention = "pep257"
[tool.sphinx-scrapy]
python-version = "3.13" # Keep in sync with .github/workflows/checks.yml.

View File

@ -25,11 +25,11 @@ if TYPE_CHECKING:
# typing.Self requires Python 3.11 # typing.Self requires Python 3.11
from typing_extensions import Self from typing_extensions import Self
import scrapy
from scrapy.crawler import Crawler from scrapy.crawler import Crawler
from scrapy.http import Response from scrapy.http import Response
from scrapy.http.request import Request from scrapy.http.request import Request
from scrapy.settings import BaseSettings from scrapy.settings import BaseSettings
from scrapy.spiders import Spider
retry_logger = getLogger(__name__) retry_logger = getLogger(__name__)
@ -38,7 +38,7 @@ retry_logger = getLogger(__name__)
def get_retry_request( def get_retry_request(
request: Request, request: Request,
*, *,
spider: Spider, spider: scrapy.Spider,
reason: str | Exception | type[Exception] = "unspecified", reason: str | Exception | type[Exception] = "unspecified",
max_retry_times: int | None = None, max_retry_times: int | None = None,
priority_adjust: int | None = None, priority_adjust: int | None = None,
@ -145,7 +145,10 @@ class RetryMiddleware:
@_warn_spider_arg @_warn_spider_arg
def process_response( def process_response(
self, request: Request, response: Response, spider: Spider | None = None self,
request: Request,
response: Response,
spider: scrapy.Spider | None = None,
) -> Request | Response: ) -> Request | Response:
if request.meta.get("dont_retry", False): if request.meta.get("dont_retry", False):
return response return response
@ -156,7 +159,10 @@ class RetryMiddleware:
@_warn_spider_arg @_warn_spider_arg
def process_exception( def process_exception(
self, request: Request, exception: Exception, spider: Spider | None = None self,
request: Request,
exception: Exception,
spider: scrapy.Spider | None = None,
) -> Request | Response | None: ) -> Request | Response | None:
if isinstance(exception, self.exceptions_to_retry) and not request.meta.get( if isinstance(exception, self.exceptions_to_retry) and not request.meta.get(
"dont_retry", False "dont_retry", False

33
tox.ini
View File

@ -4,7 +4,9 @@
# and then run "tox" from this directory. # and then run "tox" from this directory.
[tox] [tox]
envlist = pre-commit,pylint,typing,py requires =
sphinx-scrapy @ git+https://github.com/scrapy/sphinx-scrapy.git@0.7.0
envlist = pre-commit,pylint,typing,py,docs
minversion = 1.7.0 minversion = 1.7.0
[test-requirements] [test-requirements]
@ -238,38 +240,25 @@ commands =
setenv = setenv =
{[pinned]setenv} {[pinned]setenv}
[docs] [testenv:docs-tests]
changedir = docs changedir = docs
deps =
-rdocs/requirements.txt
setenv =
READTHEDOCS_PROJECT=scrapy
READTHEDOCS_VERSION=master
[testenv:docs]
basepython = python3
changedir = {[docs]changedir}
deps = deps =
{[test-requirements]deps} {[test-requirements]deps}
{[docs]deps} -rdocs/requirements.txt
setenv = {[docs]setenv}
commands = commands =
sphinx-build -W -b html . {envtmpdir}/html
pytest pytest
[testenv:docs-coverage] [testenv:docs-coverage]
basepython = python3 changedir = docs
changedir = {[docs]changedir} deps =
deps = {[docs]deps} -rdocs/requirements.txt
setenv = {[docs]setenv}
commands = commands =
sphinx-build -b coverage . {envtmpdir}/coverage sphinx-build -b coverage . {envtmpdir}/coverage
[testenv:docs-links] [testenv:docs-links]
basepython = python3 changedir = docs
changedir = {[docs]changedir} deps =
deps = {[docs]deps} -rdocs/requirements.txt
setenv = {[docs]setenv}
commands = commands =
sphinx-build -W -b linkcheck . {envtmpdir}/linkcheck sphinx-build -W -b linkcheck . {envtmpdir}/linkcheck