Skip to content

Commit b49ad58

Browse files
committed
Adopt Ruff and use stricter MyPy settings
1 parent dc9a501 commit b49ad58

File tree

12 files changed

+162
-75
lines changed

12 files changed

+162
-75
lines changed

.flake8

Lines changed: 0 additions & 4 deletions
This file was deleted.

.github/workflows/test.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,9 @@ jobs:
7171
runs-on: ubuntu-latest
7272
strategy:
7373
matrix:
74-
env: [flake8, mypy]
74+
env:
75+
- ruff
76+
- mypy
7577

7678
steps:
7779
- uses: actions/checkout@v3

.ruff.toml

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
target-version = "py39" # Pin Ruff to Python 3.9
2+
output-format = "full"
3+
line-length = 95
4+
5+
[lint]
6+
preview = true
7+
select = [
8+
# "ANN", # flake8-annotations
9+
"C4", # flake8-comprehensions
10+
"COM", # flake8-commas
11+
"B", # flake8-bugbear
12+
"DTZ", # flake8-datetimez
13+
"E", # pycodestyle
14+
"EM", # flake8-errmsg
15+
"EXE", # flake8-executable
16+
"F", # pyflakes
17+
"FA", # flake8-future-annotations
18+
"FLY", # flynt
19+
"FURB", # refurb
20+
"G", # flake8-logging-format
21+
"I", # isort
22+
"ICN", # flake8-import-conventions
23+
"INT", # flake8-gettext
24+
"LOG", # flake8-logging
25+
"PERF", # perflint
26+
"PGH", # pygrep-hooks
27+
"PIE", # flake8-pie
28+
"PT", # flake8-pytest-style
29+
"SIM", # flake8-simplify
30+
"SLOT", # flake8-slots
31+
"TCH", # flake8-type-checking
32+
"UP", # pyupgrade
33+
"W", # pycodestyle
34+
"YTT", # flake8-2020
35+
]
36+
ignore = [
37+
"E116",
38+
"E241",
39+
"E251",
40+
]
41+
42+
[lint.per-file-ignores]
43+
"tests/*" = [
44+
"ANN", # tests don't need annotations
45+
]
46+
47+
[lint.isort]
48+
forced-separate = [
49+
"tests",
50+
]
51+
required-imports = [
52+
"from __future__ import annotations",
53+
]

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ clean-mypyfiles:
4747

4848
.PHONY: style-check
4949
style-check:
50-
@flake8
50+
@ruff check
5151

5252
.PHONY: type-check
5353
type-check:

pyproject.toml

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,9 @@ test = [
4848
"defusedxml>=0.7.1", # for secure XML/HTML parsing
4949
]
5050
lint = [
51-
"flake8",
51+
"ruff==0.5.5",
5252
"mypy",
53-
"docutils-stubs",
53+
"types-docutils",
5454
]
5555
standalone = [
5656
"Sphinx>=5",
@@ -73,4 +73,34 @@ include = [
7373
]
7474

7575
[tool.mypy]
76-
ignore_missing_imports = true
76+
python_version = "3.9"
77+
packages = [
78+
"sphinxcontrib",
79+
"tests",
80+
]
81+
exclude = [
82+
"tests/roots",
83+
]
84+
check_untyped_defs = true
85+
disallow_any_generics = true
86+
disallow_incomplete_defs = true
87+
disallow_subclassing_any = true
88+
disallow_untyped_calls = true
89+
disallow_untyped_decorators = true
90+
disallow_untyped_defs = true
91+
explicit_package_bases = true
92+
extra_checks = true
93+
no_implicit_reexport = true
94+
show_column_numbers = true
95+
show_error_context = true
96+
strict_optional = true
97+
warn_redundant_casts = true
98+
warn_unused_configs = true
99+
warn_unused_ignores = true
100+
enable_error_code = [
101+
"type-arg",
102+
"redundant-self",
103+
"truthy-iterable",
104+
"ignore-without-code",
105+
"unused-awaitable",
106+
]

sphinxcontrib/qthelp/__init__.py

Lines changed: 35 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,11 @@
88
import re
99
from collections.abc import Iterable
1010
from os import path
11-
from typing import Any, cast
11+
from pathlib import Path
12+
from typing import TYPE_CHECKING, Any, cast
1213

1314
from docutils import nodes
14-
from docutils.nodes import Node
1515
from sphinx import addnodes
16-
from sphinx.application import Sphinx
1716
from sphinx.builders.html import StandaloneHTMLBuilder
1817
from sphinx.environment.adapters.indexentries import IndexEntries
1918
from sphinx.locale import get_translation
@@ -22,6 +21,9 @@
2221
from sphinx.util.osutil import canon_path, make_filename
2322
from sphinx.util.template import SphinxRenderer
2423

24+
if TYPE_CHECKING:
25+
from docutils.nodes import Node
26+
from sphinx.application import Sphinx
2527

2628
__version__ = '1.0.8'
2729
__version_info__ = (1, 0, 8)
@@ -78,7 +80,7 @@ def init(self) -> None:
7880
self.link_suffix = '.html'
7981
# self.config.html_style = 'traditional.css'
8082

81-
def get_theme_config(self) -> tuple[str, dict]:
83+
def get_theme_config(self) -> tuple[str, dict[str, str | int | bool]]:
8284
return self.config.qthelp_theme, self.config.qthelp_theme_options
8385

8486
def handle_finish(self) -> None:
@@ -97,55 +99,55 @@ def build_qhp(self, outdir: str | os.PathLike[str], outname: str) -> None:
9799

98100
sections = []
99101
matcher = NodeMatcher(addnodes.compact_paragraph, toctree=True)
100-
for node in tocdoc.traverse(matcher): # type: addnodes.compact_paragraph
102+
for node in tocdoc.findall(matcher):
101103
sections.extend(self.write_toc(node))
102104

103-
for indexname, indexcls, content, collapse in self.domain_indices:
105+
for indexname, indexcls, _content, _collapse in self.domain_indices:
104106
item = section_template % {'title': indexcls.localname,
105107
'ref': indexname + self.out_suffix}
106108
sections.append(' ' * 4 * 4 + item)
107-
sections = '\n'.join(sections) # type: ignore
109+
sections = '\n'.join(sections) # type: ignore[assignment]
108110

109111
# keywords
110112
keywords = []
111113
index = IndexEntries(self.env).create_index(self, group_entries=False)
112-
for (key, group) in index:
113-
for title, (refs, subitems, key_) in group:
114+
for (_group_key, group) in index:
115+
for title, (refs, subitems, _category_key) in group:
114116
keywords.extend(self.build_keywords(title, refs, subitems))
115-
keywords = '\n'.join(keywords) # type: ignore
117+
keywords = '\n'.join(keywords) # type: ignore[assignment]
116118

117119
# it seems that the "namespace" may not contain non-alphanumeric
118120
# characters, and more than one successive dot, or leading/trailing
119121
# dots, are also forbidden
120122
if self.config.qthelp_namespace:
121123
nspace = self.config.qthelp_namespace
122124
else:
123-
nspace = 'org.sphinx.%s.%s' % (outname, self.config.version)
125+
nspace = f'org.sphinx.{outname}.{self.config.version}'
124126

125127
nspace = re.sub(r'[^a-zA-Z0-9.\-]', '', nspace)
126128
nspace = re.sub(r'\.+', '.', nspace).strip('.')
127129
nspace = nspace.lower()
128130

129131
# write the project file
130-
with open(path.join(outdir, outname + '.qhp'), 'w', encoding='utf-8') as f:
131-
body = render_file('project.qhp', outname=outname,
132-
title=self.config.html_title, version=self.config.version,
133-
project=self.config.project, namespace=nspace,
134-
master_doc=self.config.master_doc,
135-
sections=sections, keywords=keywords,
136-
files=self.get_project_files(outdir))
137-
f.write(body)
132+
body = render_file('project.qhp', outname=outname,
133+
title=self.config.html_title, version=self.config.version,
134+
project=self.config.project, namespace=nspace,
135+
master_doc=self.config.master_doc,
136+
sections=sections, keywords=keywords,
137+
files=self.get_project_files(outdir))
138+
filename = Path(outdir, f'{outname}.qhp')
139+
filename.write_text(body, encoding='utf-8')
138140

139141
homepage = 'qthelp://' + posixpath.join(
140142
nspace, 'doc', self.get_target_uri(self.config.master_doc))
141-
startpage = 'qthelp://' + posixpath.join(nspace, 'doc', 'index%s' % self.link_suffix)
143+
startpage = 'qthelp://' + posixpath.join(nspace, 'doc', f'index{self.link_suffix}')
142144

143145
logger.info(__('writing collection project file...'))
144-
with open(path.join(outdir, outname + '.qhcp'), 'w', encoding='utf-8') as f:
145-
body = render_file('project.qhcp', outname=outname,
146-
title=self.config.html_short_title,
147-
homepage=homepage, startpage=startpage)
148-
f.write(body)
146+
body = render_file('project.qhcp', outname=outname,
147+
title=self.config.html_short_title,
148+
homepage=homepage, startpage=startpage)
149+
filename = Path(outdir, f'{outname}.qhcp')
150+
filename.write_text(body, encoding='utf-8')
149151

150152
def isdocnode(self, node: Node) -> bool:
151153
if not isinstance(node, nodes.list_item):
@@ -156,9 +158,7 @@ def isdocnode(self, node: Node) -> bool:
156158
return False
157159
if not isinstance(node[0][0], nodes.reference):
158160
return False
159-
if not isinstance(node[1], nodes.bullet_list):
160-
return False
161-
return True
161+
return isinstance(node[1], nodes.bullet_list)
162162

163163
def write_toc(self, node: Node, indentlevel: int = 4) -> list[str]:
164164
parts: list[str] = []
@@ -167,8 +167,7 @@ def write_toc(self, node: Node, indentlevel: int = 4) -> list[str]:
167167
reference = cast(nodes.reference, compact_paragraph[0])
168168
link = reference['refuri']
169169
title = html.escape(reference.astext()).replace('"', '"')
170-
item = '<section title="%(title)s" ref="%(ref)s">' % \
171-
{'title': title, 'ref': link}
170+
item = f'<section title="{title}" ref="{link}">'
172171
parts.append(' ' * 4 * indentlevel + item)
173172

174173
bullet_list = cast(nodes.bullet_list, node[1])
@@ -185,10 +184,7 @@ def write_toc(self, node: Node, indentlevel: int = 4) -> list[str]:
185184
item = section_template % {'title': title, 'ref': link}
186185
item = ' ' * 4 * indentlevel + item
187186
parts.append(item.encode('ascii', 'xmlcharrefreplace').decode())
188-
elif isinstance(node, nodes.bullet_list):
189-
for subnode in node:
190-
parts.extend(self.write_toc(subnode, indentlevel))
191-
elif isinstance(node, addnodes.compact_paragraph):
187+
elif isinstance(node, (nodes.bullet_list, addnodes.compact_paragraph)):
192188
for subnode in node:
193189
parts.extend(self.write_toc(subnode, indentlevel))
194190

@@ -203,16 +199,16 @@ def keyword_item(self, name: str, ref: Any) -> str:
203199
# descr = groupdict.get('descr')
204200
if shortname.endswith('()'):
205201
shortname = shortname[:-2]
206-
id = html.escape('%s.%s' % (id, shortname), True)
202+
id = html.escape(f'{id}.{shortname}', True)
207203
else:
208204
id = None
209205

210206
nameattr = html.escape(name, quote=True)
211207
refattr = html.escape(ref[1], quote=True)
212208
if id:
213-
item = ' ' * 12 + '<keyword name="%s" id="%s" ref="%s"/>' % (nameattr, id, refattr)
209+
item = ' ' * 12 + f'<keyword name="{nameattr}" id="{id}" ref="{refattr}"/>'
214210
else:
215-
item = ' ' * 12 + '<keyword name="%s" ref="%s"/>' % (nameattr, refattr)
211+
item = ' ' * 12 + f'<keyword name="{nameattr}" ref="{refattr}"/>'
216212
item.encode('ascii', 'xmlcharrefreplace')
217213
return item
218214

@@ -224,7 +220,7 @@ def build_keywords(self, title: str, refs: list[Any], subitems: Any) -> list[str
224220
if len(refs) == 1:
225221
keywords.append(self.keyword_item(title, refs[0]))
226222
elif len(refs) > 1:
227-
for i, ref in enumerate(refs): # XXX
223+
for _i, ref in enumerate(refs): # XXX # NoQA: FURB148
228224
# item = (' '*12 +
229225
# '<keyword name="%s [%d]" ref="%s"/>' % (
230226
# title, i, ref))
@@ -242,7 +238,7 @@ def get_project_files(self, outdir: str | os.PathLike[str]) -> list[str]:
242238
project_files = []
243239
staticdir = path.join(outdir, '_static')
244240
imagesdir = path.join(outdir, self.imagedir)
245-
for root, dirs, files in os.walk(outdir):
241+
for root, _dirs, files in os.walk(outdir):
246242
resourcedir = root.startswith((staticdir, imagesdir))
247243
for fn in sorted(files):
248244
if (resourcedir and not fn.endswith('.js')) or fn.endswith('.html'):

sphinxcontrib/qthelp/py.typed

Whitespace-only changes.

tests/conftest.py

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,14 @@
1+
from __future__ import annotations
2+
13
from pathlib import Path
24

35
import pytest
46

5-
import sphinx
6-
7-
pytest_plugins = 'sphinx.testing.fixtures'
7+
pytest_plugins = (
8+
'sphinx.testing.fixtures',
9+
)
810

911

1012
@pytest.fixture(scope='session')
11-
def rootdir():
12-
if sphinx.version_info[:2] < (7, 2):
13-
from sphinx.testing.path import path
14-
15-
return path(__file__).parent.abspath() / 'roots'
16-
13+
def rootdir() -> Path:
1714
return Path(__file__).resolve().parent / 'roots'

tests/roots/test-basic/conf.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1+
from __future__ import annotations
2+
13
project = 'Python'

tests/roots/test-need-escaped/conf.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
1+
from __future__ import annotations
2+
13
project = 'need <b>"escaped"</b> project'
24
smartquotes = False

0 commit comments

Comments
 (0)