diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index fde9d9149bf62b..b10be5b6bd9904 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -3,13 +3,21 @@ repos:
rev: v0.3.4
hooks:
- id: ruff
- name: Run Ruff on Lib/test/
+ name: Run Ruff (lint) on Doc/
+ args: [--exit-non-zero-on-fix]
+ files: ^Doc/
+ - id: ruff
+ name: Run Ruff (lint) on Lib/test/
args: [--exit-non-zero-on-fix]
files: ^Lib/test/
- id: ruff
- name: Run Ruff on Argument Clinic
+ name: Run Ruff (lint) on Argument Clinic
args: [--exit-non-zero-on-fix, --config=Tools/clinic/.ruff.toml]
files: ^Tools/clinic/|Lib/test/test_clinic.py
+ - id: ruff-format
+ name: Run Ruff (format) on Doc/
+ args: [--check]
+ files: ^Doc/
- repo: https://github.com/psf/black-pre-commit-mirror
rev: 24.4.2
diff --git a/Doc/.ruff.toml b/Doc/.ruff.toml
new file mode 100644
index 00000000000000..b617208f78ef6f
--- /dev/null
+++ b/Doc/.ruff.toml
@@ -0,0 +1,45 @@
+target-version = "py312" # Align with the version in oldest_supported_sphinx
+fix = true
+output-format = "full"
+line-length = 79
+extend-exclude = [
+ "includes/*",
+ # Temporary exclusions:
+ "tools/extensions/c_annotations.py",
+ "tools/extensions/escape4chm.py",
+ "tools/extensions/patchlevel.py",
+ "tools/extensions/pyspecific.py",
+]
+
+[lint]
+preview = true
+select = [
+ "C4", # flake8-comprehensions
+ "B", # flake8-bugbear
+ "E", # pycodestyle
+ "F", # pyflakes
+ "FA", # flake8-future-annotations
+ "FLY", # flynt
+ "FURB", # refurb
+ "G", # flake8-logging-format
+ "I", # isort
+ "LOG", # flake8-logging
+ "N", # pep8-naming
+ "PERF", # perflint
+ "PGH", # pygrep-hooks
+ "PT", # flake8-pytest-style
+ "TCH", # flake8-type-checking
+ "UP", # pyupgrade
+ "W", # pycodestyle
+]
+ignore = [
+ "E501", # Ignore line length errors (we use auto-formatting)
+]
+
+[format]
+preview = true
+quote-style = "preserve"
+docstring-code-format = true
+exclude = [
+ "tools/extensions/lexers/*",
+]
diff --git a/Doc/conf.py b/Doc/conf.py
index 5addee0378984a..1e514b57843161 100644
--- a/Doc/conf.py
+++ b/Doc/conf.py
@@ -9,6 +9,7 @@
import os
import sys
import time
+
sys.path.append(os.path.abspath('tools/extensions'))
sys.path.append(os.path.abspath('includes'))
@@ -30,13 +31,13 @@
# Skip if downstream redistributors haven't installed them
try:
- import notfound.extension
+ import notfound.extension # noqa: F401
except ImportError:
pass
else:
extensions.append('notfound.extension')
try:
- import sphinxext.opengraph
+ import sphinxext.opengraph # noqa: F401
except ImportError:
pass
else:
@@ -63,7 +64,8 @@
# We look for the Include/patchlevel.h file in the current Python source tree
# and replace the values accordingly.
-import patchlevel
+import patchlevel # noqa: E402
+
version, release = patchlevel.get_version_info()
rst_epilog = f"""
@@ -298,7 +300,8 @@
# Disable Docutils smartquotes for several translations
smartquotes_excludes = {
- 'languages': ['ja', 'fr', 'zh_TW', 'zh_CN'], 'builders': ['man', 'text'],
+ 'languages': ['ja', 'fr', 'zh_TW', 'zh_CN'],
+ 'builders': ['man', 'text'],
}
# Avoid a warning with Sphinx >= 4.0
@@ -319,11 +322,13 @@
'collapsiblesidebar': True,
'issues_url': '/bugs.html',
'license_url': '/license.html',
- 'root_include_title': False # We use the version switcher instead.
+ 'root_include_title': False, # We use the version switcher instead.
}
if os.getenv("READTHEDOCS"):
- html_theme_options["hosted_on"] = 'Read the Docs'
+ html_theme_options["hosted_on"] = (
+ 'Read the Docs'
+ )
# Override stylesheet fingerprinting for Windows CHM htmlhelp to fix GH-91207
# https://github.com/python/cpython/issues/91207
@@ -337,17 +342,21 @@
# Deployment preview information
# (See .readthedocs.yml and https://docs.readthedocs.io/en/stable/reference/environment-variables.html)
-repository_url = os.getenv("READTHEDOCS_GIT_CLONE_URL")
+is_deployment_preview = os.getenv("READTHEDOCS_VERSION_TYPE") == "external"
+repository_url = os.getenv("READTHEDOCS_GIT_CLONE_URL", "")
+repository_url = repository_url.removesuffix(".git")
html_context = {
- "is_deployment_preview": os.getenv("READTHEDOCS_VERSION_TYPE") == "external",
- "repository_url": repository_url.removesuffix(".git") if repository_url else None,
+ "is_deployment_preview": is_deployment_preview,
+ "repository_url": repository_url or None,
"pr_id": os.getenv("READTHEDOCS_VERSION"),
"enable_analytics": os.getenv("PYTHON_DOCS_ENABLE_ANALYTICS"),
}
# This 'Last updated on:' timestamp is inserted at the bottom of every page.
html_time = int(os.environ.get('SOURCE_DATE_EPOCH', time.time()))
-html_last_updated_fmt = time.strftime('%b %d, %Y (%H:%M UTC)', time.gmtime(html_time))
+html_last_updated_fmt = time.strftime(
+ '%b %d, %Y (%H:%M UTC)', time.gmtime(html_time)
+)
# Path to find HTML templates.
templates_path = ['tools/templates']
@@ -407,30 +416,70 @@
# (source start file, target name, title, author, document class [howto/manual]).
_stdauthor = 'Guido van Rossum and the Python development team'
latex_documents = [
- ('c-api/index', 'c-api.tex',
- 'The Python/C API', _stdauthor, 'manual'),
- ('extending/index', 'extending.tex',
- 'Extending and Embedding Python', _stdauthor, 'manual'),
- ('installing/index', 'installing.tex',
- 'Installing Python Modules', _stdauthor, 'manual'),
- ('library/index', 'library.tex',
- 'The Python Library Reference', _stdauthor, 'manual'),
- ('reference/index', 'reference.tex',
- 'The Python Language Reference', _stdauthor, 'manual'),
- ('tutorial/index', 'tutorial.tex',
- 'Python Tutorial', _stdauthor, 'manual'),
- ('using/index', 'using.tex',
- 'Python Setup and Usage', _stdauthor, 'manual'),
- ('faq/index', 'faq.tex',
- 'Python Frequently Asked Questions', _stdauthor, 'manual'),
- ('whatsnew/' + version, 'whatsnew.tex',
- 'What\'s New in Python', 'A. M. Kuchling', 'howto'),
+ ('c-api/index', 'c-api.tex', 'The Python/C API', _stdauthor, 'manual'),
+ (
+ 'extending/index',
+ 'extending.tex',
+ 'Extending and Embedding Python',
+ _stdauthor,
+ 'manual',
+ ),
+ (
+ 'installing/index',
+ 'installing.tex',
+ 'Installing Python Modules',
+ _stdauthor,
+ 'manual',
+ ),
+ (
+ 'library/index',
+ 'library.tex',
+ 'The Python Library Reference',
+ _stdauthor,
+ 'manual',
+ ),
+ (
+ 'reference/index',
+ 'reference.tex',
+ 'The Python Language Reference',
+ _stdauthor,
+ 'manual',
+ ),
+ (
+ 'tutorial/index',
+ 'tutorial.tex',
+ 'Python Tutorial',
+ _stdauthor,
+ 'manual',
+ ),
+ (
+ 'using/index',
+ 'using.tex',
+ 'Python Setup and Usage',
+ _stdauthor,
+ 'manual',
+ ),
+ (
+ 'faq/index',
+ 'faq.tex',
+ 'Python Frequently Asked Questions',
+ _stdauthor,
+ 'manual',
+ ),
+ (
+ 'whatsnew/' + version,
+ 'whatsnew.tex',
+ 'What\'s New in Python',
+ 'A. M. Kuchling',
+ 'howto',
+ ),
]
# Collect all HOWTOs individually
-latex_documents.extend(('howto/' + fn[:-4], 'howto-' + fn[:-4] + '.tex',
- '', _stdauthor, 'howto')
- for fn in os.listdir('howto')
- if fn.endswith('.rst') and fn != 'index.rst')
+latex_documents.extend(
+ ('howto/' + fn[:-4], 'howto-' + fn[:-4] + '.tex', '', _stdauthor, 'howto')
+ for fn in os.listdir('howto')
+ if fn.endswith('.rst') and fn != 'index.rst'
+)
# Documents to append as an appendix to all manuals.
latex_appendices = ['glossary', 'about', 'license', 'copyright']
@@ -458,8 +507,7 @@
'test($|_)',
]
-coverage_ignore_classes = [
-]
+coverage_ignore_classes = []
# Glob patterns for C source files for C API coverage, relative to this directory.
coverage_c_path = [
@@ -476,7 +524,7 @@
# The coverage checker will ignore all C items whose names match these regexes
# (using re.match) -- the keys must be the same as in coverage_c_regexes.
coverage_ignore_c_items = {
-# 'cfunction': [...]
+ # 'cfunction': [...]
}
diff --git a/Doc/tools/check-warnings.py b/Doc/tools/check-warnings.py
index 67623b83d3a67d..c686eecf8d9271 100644
--- a/Doc/tools/check-warnings.py
+++ b/Doc/tools/check-warnings.py
@@ -2,6 +2,7 @@
"""
Check the output of running Sphinx in nit-picky mode (missing references).
"""
+
from __future__ import annotations
import argparse
@@ -206,7 +207,9 @@ def annotate_diff(
def fail_if_regression(
- warnings: list[str], files_with_expected_nits: set[str], files_with_nits: set[str]
+ warnings: list[str],
+ files_with_expected_nits: set[str],
+ files_with_nits: set[str],
) -> int:
"""
Ensure some files always pass Sphinx nit-picky mode (no missing references).
@@ -252,17 +255,11 @@ def fail_if_new_news_nit(warnings: list[str], threshold: int) -> int:
"""
Ensure no warnings are found in the NEWS file before a given line number.
"""
- news_nits = (
- warning
- for warning in warnings
- if "/build/NEWS:" in warning
- )
+ news_nits = (warning for warning in warnings if "/build/NEWS:" in warning)
# Nits found before the threshold line
new_news_nits = [
- nit
- for nit in news_nits
- if int(nit.split(":")[1]) <= threshold
+ nit for nit in news_nits if int(nit.split(":")[1]) <= threshold
]
if new_news_nits:
@@ -311,7 +308,8 @@ def main(argv: list[str] | None = None) -> int:
exit_code = 0
wrong_directory_msg = "Must run this script from the repo root"
- assert Path("Doc").exists() and Path("Doc").is_dir(), wrong_directory_msg
+ if not Path("Doc").exists() or not Path("Doc").is_dir():
+ raise RuntimeError(wrong_directory_msg)
with Path("Doc/sphinx-warnings.txt").open(encoding="UTF-8") as f:
warnings = f.read().splitlines()
@@ -339,7 +337,9 @@ def main(argv: list[str] | None = None) -> int:
)
if args.fail_if_improved:
- exit_code += fail_if_improved(files_with_expected_nits, files_with_nits)
+ exit_code += fail_if_improved(
+ files_with_expected_nits, files_with_nits
+ )
if args.fail_if_new_news_nit:
exit_code += fail_if_new_news_nit(warnings, args.fail_if_new_news_nit)
diff --git a/Doc/tools/extensions/glossary_search.py b/Doc/tools/extensions/glossary_search.py
index 2448529125cb1f..502b6cd95bcb94 100644
--- a/Doc/tools/extensions/glossary_search.py
+++ b/Doc/tools/extensions/glossary_search.py
@@ -17,7 +17,11 @@
logger = logging.getLogger(__name__)
-def process_glossary_nodes(app: Sphinx, doctree: nodes.document, _docname: str) -> None:
+def process_glossary_nodes(
+ app: Sphinx,
+ doctree: nodes.document,
+ _docname: str,
+) -> None:
if app.builder.format != 'html' or app.builder.embedded:
return
@@ -34,7 +38,7 @@ def process_glossary_nodes(app: Sphinx, doctree: nodes.document, _docname: str)
rendered = app.builder.render_partial(definition)
terms[term.lower()] = {
'title': term,
- 'body': rendered['html_body']
+ 'body': rendered['html_body'],
}
@@ -42,7 +46,7 @@ def write_glossary_json(app: Sphinx, _exc: Exception) -> None:
if not getattr(app.env, 'glossary_terms', None):
return
- logger.info(f'Writing glossary.json', color='green')
+ logger.info('Writing glossary.json', color='green')
dest = Path(app.outdir, '_static', 'glossary.json')
dest.parent.mkdir(exist_ok=True)
dest.write_text(json.dumps(app.env.glossary_terms), encoding='utf-8')
diff --git a/Doc/tools/extensions/lexers/asdl_lexer.py b/Doc/tools/extensions/lexers/asdl_lexer.py
index 2cea058566ad85..3a74174a1f7dfb 100644
--- a/Doc/tools/extensions/lexers/asdl_lexer.py
+++ b/Doc/tools/extensions/lexers/asdl_lexer.py
@@ -28,7 +28,7 @@ class ASDLLexer(RegexLexer):
# Keep in line with ``builtin_types`` from Parser/asdl.py.
# ASDL's 4 builtin types are
# constant, identifier, int, string
- ('constant|identifier|int|string', Name.Builtin),
+ ("constant|identifier|int|string", Name.Builtin),
(r"attributes", Name.Builtin),
(
_name + _text_ws + "(=)",