Skip to content

Commit 5794da4

Browse files
[3.12] pythonGH-121970: Use Ruff to check and format the docs tools (pythonGH-122018)
(cherry picked from commit 40855f3) Co-authored-by: Adam Turner <[email protected]> Co-authored-by: Alex Waygood <[email protected]>
1 parent 2cd816e commit 5794da4

File tree

6 files changed

+156
-51
lines changed

6 files changed

+156
-51
lines changed

.pre-commit-config.yaml

+10-2
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,21 @@ repos:
33
rev: v0.3.4
44
hooks:
55
- id: ruff
6-
name: Run Ruff on Lib/test/
6+
name: Run Ruff (lint) on Doc/
7+
args: [--exit-non-zero-on-fix]
8+
files: ^Doc/
9+
- id: ruff
10+
name: Run Ruff (lint) on Lib/test/
711
args: [--exit-non-zero-on-fix]
812
files: ^Lib/test/
913
- id: ruff
10-
name: Run Ruff on Argument Clinic
14+
name: Run Ruff (lint) on Argument Clinic
1115
args: [--exit-non-zero-on-fix, --config=Tools/clinic/.ruff.toml]
1216
files: ^Tools/clinic/|Lib/test/test_clinic.py
17+
- id: ruff-format
18+
name: Run Ruff (format) on Doc/
19+
args: [--check]
20+
files: ^Doc/
1321

1422
- repo: https://github.com/pre-commit/pre-commit-hooks
1523
rev: v4.5.0

Doc/.ruff.toml

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
target-version = "py312" # Align with the version in oldest_supported_sphinx
2+
fix = true
3+
output-format = "full"
4+
line-length = 79
5+
extend-exclude = [
6+
"includes/*",
7+
# Temporary exclusions:
8+
"tools/extensions/c_annotations.py",
9+
"tools/extensions/escape4chm.py",
10+
"tools/extensions/patchlevel.py",
11+
"tools/extensions/pyspecific.py",
12+
]
13+
14+
[lint]
15+
preview = true
16+
select = [
17+
"C4", # flake8-comprehensions
18+
"B", # flake8-bugbear
19+
"E", # pycodestyle
20+
"F", # pyflakes
21+
"FA", # flake8-future-annotations
22+
"FLY", # flynt
23+
"FURB", # refurb
24+
"G", # flake8-logging-format
25+
"I", # isort
26+
"LOG", # flake8-logging
27+
"N", # pep8-naming
28+
"PERF", # perflint
29+
"PGH", # pygrep-hooks
30+
"PT", # flake8-pytest-style
31+
"TCH", # flake8-type-checking
32+
"UP", # pyupgrade
33+
"W", # pycodestyle
34+
]
35+
ignore = [
36+
"E501", # Ignore line length errors (we use auto-formatting)
37+
]
38+
39+
[format]
40+
preview = true
41+
quote-style = "preserve"
42+
docstring-code-format = true
43+
exclude = [
44+
"tools/extensions/lexers/*",
45+
]

Doc/conf.py

+82-34
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import os
1010
import sys
1111
import time
12+
1213
sys.path.append(os.path.abspath('tools/extensions'))
1314
sys.path.append(os.path.abspath('includes'))
1415

@@ -30,7 +31,7 @@
3031

3132
# Skip if downstream redistributors haven't installed it
3233
try:
33-
import sphinxext.opengraph
34+
import sphinxext.opengraph # noqa: F401
3435
except ImportError:
3536
pass
3637
else:
@@ -57,7 +58,8 @@
5758

5859
# We look for the Include/patchlevel.h file in the current Python source tree
5960
# and replace the values accordingly.
60-
import patchlevel
61+
import patchlevel # noqa: E402
62+
6163
version, release = patchlevel.get_version_info()
6264

6365
rst_epilog = f"""
@@ -282,7 +284,8 @@
282284

283285
# Disable Docutils smartquotes for several translations
284286
smartquotes_excludes = {
285-
'languages': ['ja', 'fr', 'zh_TW', 'zh_CN'], 'builders': ['man', 'text'],
287+
'languages': ['ja', 'fr', 'zh_TW', 'zh_CN'],
288+
'builders': ['man', 'text'],
286289
}
287290

288291
# Avoid a warning with Sphinx >= 4.0
@@ -303,11 +306,13 @@
303306
'collapsiblesidebar': True,
304307
'issues_url': '/bugs.html',
305308
'license_url': '/license.html',
306-
'root_include_title': False # We use the version switcher instead.
309+
'root_include_title': False, # We use the version switcher instead.
307310
}
308311

309312
if os.getenv("READTHEDOCS"):
310-
html_theme_options["hosted_on"] = '<a href="https://about.readthedocs.com/">Read the Docs</a>'
313+
html_theme_options["hosted_on"] = (
314+
'<a href="https://about.readthedocs.com/">Read the Docs</a>'
315+
)
311316

312317
# Override stylesheet fingerprinting for Windows CHM htmlhelp to fix GH-91207
313318
# https://github.com/python/cpython/issues/91207
@@ -321,17 +326,21 @@
321326

322327
# Deployment preview information
323328
# (See .readthedocs.yml and https://docs.readthedocs.io/en/stable/reference/environment-variables.html)
324-
repository_url = os.getenv("READTHEDOCS_GIT_CLONE_URL")
329+
is_deployment_preview = os.getenv("READTHEDOCS_VERSION_TYPE") == "external"
330+
repository_url = os.getenv("READTHEDOCS_GIT_CLONE_URL", "")
331+
repository_url = repository_url.removesuffix(".git")
325332
html_context = {
326-
"is_deployment_preview": os.getenv("READTHEDOCS_VERSION_TYPE") == "external",
327-
"repository_url": repository_url.removesuffix(".git") if repository_url else None,
333+
"is_deployment_preview": is_deployment_preview,
334+
"repository_url": repository_url or None,
328335
"pr_id": os.getenv("READTHEDOCS_VERSION"),
329336
"enable_analytics": os.getenv("PYTHON_DOCS_ENABLE_ANALYTICS"),
330337
}
331338

332339
# This 'Last updated on:' timestamp is inserted at the bottom of every page.
333340
html_time = int(os.environ.get('SOURCE_DATE_EPOCH', time.time()))
334-
html_last_updated_fmt = time.strftime('%b %d, %Y (%H:%M UTC)', time.gmtime(html_time))
341+
html_last_updated_fmt = time.strftime(
342+
'%b %d, %Y (%H:%M UTC)', time.gmtime(html_time)
343+
)
335344

336345
# Path to find HTML templates.
337346
templates_path = ['tools/templates']
@@ -391,30 +400,70 @@
391400
# (source start file, target name, title, author, document class [howto/manual]).
392401
_stdauthor = 'Guido van Rossum and the Python development team'
393402
latex_documents = [
394-
('c-api/index', 'c-api.tex',
395-
'The Python/C API', _stdauthor, 'manual'),
396-
('extending/index', 'extending.tex',
397-
'Extending and Embedding Python', _stdauthor, 'manual'),
398-
('installing/index', 'installing.tex',
399-
'Installing Python Modules', _stdauthor, 'manual'),
400-
('library/index', 'library.tex',
401-
'The Python Library Reference', _stdauthor, 'manual'),
402-
('reference/index', 'reference.tex',
403-
'The Python Language Reference', _stdauthor, 'manual'),
404-
('tutorial/index', 'tutorial.tex',
405-
'Python Tutorial', _stdauthor, 'manual'),
406-
('using/index', 'using.tex',
407-
'Python Setup and Usage', _stdauthor, 'manual'),
408-
('faq/index', 'faq.tex',
409-
'Python Frequently Asked Questions', _stdauthor, 'manual'),
410-
('whatsnew/' + version, 'whatsnew.tex',
411-
'What\'s New in Python', 'A. M. Kuchling', 'howto'),
403+
('c-api/index', 'c-api.tex', 'The Python/C API', _stdauthor, 'manual'),
404+
(
405+
'extending/index',
406+
'extending.tex',
407+
'Extending and Embedding Python',
408+
_stdauthor,
409+
'manual',
410+
),
411+
(
412+
'installing/index',
413+
'installing.tex',
414+
'Installing Python Modules',
415+
_stdauthor,
416+
'manual',
417+
),
418+
(
419+
'library/index',
420+
'library.tex',
421+
'The Python Library Reference',
422+
_stdauthor,
423+
'manual',
424+
),
425+
(
426+
'reference/index',
427+
'reference.tex',
428+
'The Python Language Reference',
429+
_stdauthor,
430+
'manual',
431+
),
432+
(
433+
'tutorial/index',
434+
'tutorial.tex',
435+
'Python Tutorial',
436+
_stdauthor,
437+
'manual',
438+
),
439+
(
440+
'using/index',
441+
'using.tex',
442+
'Python Setup and Usage',
443+
_stdauthor,
444+
'manual',
445+
),
446+
(
447+
'faq/index',
448+
'faq.tex',
449+
'Python Frequently Asked Questions',
450+
_stdauthor,
451+
'manual',
452+
),
453+
(
454+
'whatsnew/' + version,
455+
'whatsnew.tex',
456+
'What\'s New in Python',
457+
'A. M. Kuchling',
458+
'howto',
459+
),
412460
]
413461
# Collect all HOWTOs individually
414-
latex_documents.extend(('howto/' + fn[:-4], 'howto-' + fn[:-4] + '.tex',
415-
'', _stdauthor, 'howto')
416-
for fn in os.listdir('howto')
417-
if fn.endswith('.rst') and fn != 'index.rst')
462+
latex_documents.extend(
463+
('howto/' + fn[:-4], 'howto-' + fn[:-4] + '.tex', '', _stdauthor, 'howto')
464+
for fn in os.listdir('howto')
465+
if fn.endswith('.rst') and fn != 'index.rst'
466+
)
418467

419468
# Documents to append as an appendix to all manuals.
420469
latex_appendices = ['glossary', 'about', 'license', 'copyright']
@@ -443,8 +492,7 @@
443492
'test($|_)',
444493
]
445494

446-
coverage_ignore_classes = [
447-
]
495+
coverage_ignore_classes = []
448496

449497
# Glob patterns for C source files for C API coverage, relative to this directory.
450498
coverage_c_path = [
@@ -461,7 +509,7 @@
461509
# The coverage checker will ignore all C items whose names match these regexes
462510
# (using re.match) -- the keys must be the same as in coverage_c_regexes.
463511
coverage_ignore_c_items = {
464-
# 'cfunction': [...]
512+
# 'cfunction': [...]
465513
}
466514

467515

Doc/tools/check-warnings.py

+11-11
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"""
33
Check the output of running Sphinx in nit-picky mode (missing references).
44
"""
5+
56
from __future__ import annotations
67

78
import argparse
@@ -206,7 +207,9 @@ def annotate_diff(
206207

207208

208209
def fail_if_regression(
209-
warnings: list[str], files_with_expected_nits: set[str], files_with_nits: set[str]
210+
warnings: list[str],
211+
files_with_expected_nits: set[str],
212+
files_with_nits: set[str],
210213
) -> int:
211214
"""
212215
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:
252255
"""
253256
Ensure no warnings are found in the NEWS file before a given line number.
254257
"""
255-
news_nits = (
256-
warning
257-
for warning in warnings
258-
if "/build/NEWS:" in warning
259-
)
258+
news_nits = (warning for warning in warnings if "/build/NEWS:" in warning)
260259

261260
# Nits found before the threshold line
262261
new_news_nits = [
263-
nit
264-
for nit in news_nits
265-
if int(nit.split(":")[1]) <= threshold
262+
nit for nit in news_nits if int(nit.split(":")[1]) <= threshold
266263
]
267264

268265
if new_news_nits:
@@ -311,7 +308,8 @@ def main(argv: list[str] | None = None) -> int:
311308
exit_code = 0
312309

313310
wrong_directory_msg = "Must run this script from the repo root"
314-
assert Path("Doc").exists() and Path("Doc").is_dir(), wrong_directory_msg
311+
if not Path("Doc").exists() or not Path("Doc").is_dir():
312+
raise RuntimeError(wrong_directory_msg)
315313

316314
with Path("Doc/sphinx-warnings.txt").open(encoding="UTF-8") as f:
317315
warnings = f.read().splitlines()
@@ -339,7 +337,9 @@ def main(argv: list[str] | None = None) -> int:
339337
)
340338

341339
if args.fail_if_improved:
342-
exit_code += fail_if_improved(files_with_expected_nits, files_with_nits)
340+
exit_code += fail_if_improved(
341+
files_with_expected_nits, files_with_nits
342+
)
343343

344344
if args.fail_if_new_news_nit:
345345
exit_code += fail_if_new_news_nit(warnings, args.fail_if_new_news_nit)

Doc/tools/extensions/glossary_search.py

+7-3
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,11 @@
1717
logger = logging.getLogger(__name__)
1818

1919

20-
def process_glossary_nodes(app: Sphinx, doctree: nodes.document, _docname: str) -> None:
20+
def process_glossary_nodes(
21+
app: Sphinx,
22+
doctree: nodes.document,
23+
_docname: str,
24+
) -> None:
2125
if app.builder.format != 'html' or app.builder.embedded:
2226
return
2327

@@ -34,15 +38,15 @@ def process_glossary_nodes(app: Sphinx, doctree: nodes.document, _docname: str)
3438
rendered = app.builder.render_partial(definition)
3539
terms[term.lower()] = {
3640
'title': term,
37-
'body': rendered['html_body']
41+
'body': rendered['html_body'],
3842
}
3943

4044

4145
def write_glossary_json(app: Sphinx, _exc: Exception) -> None:
4246
if not getattr(app.env, 'glossary_terms', None):
4347
return
4448

45-
logger.info(f'Writing glossary.json', color='green')
49+
logger.info('Writing glossary.json', color='green')
4650
dest = Path(app.outdir, '_static', 'glossary.json')
4751
dest.parent.mkdir(exist_ok=True)
4852
dest.write_text(json.dumps(app.env.glossary_terms), encoding='utf-8')

Doc/tools/extensions/lexers/asdl_lexer.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ class ASDLLexer(RegexLexer):
2828
# Keep in line with ``builtin_types`` from Parser/asdl.py.
2929
# ASDL's 4 builtin types are
3030
# constant, identifier, int, string
31-
('constant|identifier|int|string', Name.Builtin),
31+
("constant|identifier|int|string", Name.Builtin),
3232
(r"attributes", Name.Builtin),
3333
(
3434
_name + _text_ws + "(=)",

0 commit comments

Comments
 (0)