Skip to content

Commit 62c68c5

Browse files
Require python>=3.10 and support 3.13, 3.14 (#177)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent a12d087 commit 62c68c5

File tree

7 files changed

+96
-63
lines changed

7 files changed

+96
-63
lines changed

.github/workflows/release.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@ jobs:
1717
TOX_PARALLEL_NO_SPINNER: 1
1818

1919
steps:
20-
- name: Switch to using Python 3.8 by default
20+
- name: Switch to using Python 3.10 by default
2121
uses: actions/setup-python@v5
2222
with:
23-
python-version: 3.8
23+
python-version: "3.10"
2424
- name: Install tox
2525
run: >-
2626
python3 -m

.github/workflows/tox.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ jobs:
2020
id: generate_matrix
2121
uses: coactions/matrix@v4
2222
with:
23+
min_python: "3.10"
24+
max_python: "3.14"
25+
default_python: "3.10"
2326
other_names: |
2427
lint
2528
packaging

.pre-commit-config.yaml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,27 @@
11
---
22
repos:
33
- repo: https://github.com/pre-commit/pre-commit-hooks
4-
rev: v4.6.0 # Use the ref you want to point at
4+
rev: v5.0.0 # Use the ref you want to point at
55
hooks:
66
- id: trailing-whitespace
77
- id: check-yaml
88
- id: end-of-file-fixer
99
- id: trailing-whitespace
1010
- id: check-executables-have-shebangs
1111
- repo: https://github.com/asottile/pyupgrade
12-
rev: v3.17.0
12+
rev: v3.20.0
1313
hooks:
1414
- id: pyupgrade
1515
- repo: https://github.com/psf/black
16-
rev: 24.8.0
16+
rev: 25.1.0
1717
hooks:
1818
- id: black
1919
- repo: https://github.com/pycqa/flake8
20-
rev: 7.1.1
20+
rev: 7.2.0
2121
hooks:
2222
- id: flake8
2323
- repo: https://github.com/pre-commit/mirrors-mypy
24-
rev: v1.11.2
24+
rev: v1.16.0
2525
hooks:
2626
- id: mypy
2727
# empty args needed in order to match mypy cli behavior
@@ -33,7 +33,7 @@ repos:
3333
- types-setuptools
3434
- types-docutils
3535
- repo: https://github.com/PyCQA/pylint
36-
rev: v3.2.6
36+
rev: v3.3.7
3737
hooks:
3838
- id: pylint
3939
additional_dependencies:

pyproject.toml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,11 @@ classifiers = [
2121
"Operating System :: POSIX :: Linux",
2222
"Programming Language :: Python",
2323
"Programming Language :: Python :: 3",
24-
"Programming Language :: Python :: 3.8",
25-
"Programming Language :: Python :: 3.9",
2624
"Programming Language :: Python :: 3.10",
2725
"Programming Language :: Python :: 3.11",
2826
"Programming Language :: Python :: 3.12",
27+
"Programming Language :: Python :: 3.13",
28+
"Programming Language :: Python :: 3.14",
2929
"Topic :: System :: Systems Administration",
3030
"Topic :: Utilities",
3131
]
@@ -34,7 +34,7 @@ keywords = [
3434
"rst",
3535
"linter",
3636
]
37-
requires-python = ">=3.8"
37+
requires-python = ">=3.10"
3838
dependencies = [
3939
# Ceiled due to DeprecationWarning: The frontend.OptionParser class will be
4040
# replaced by a subclass of argparse.ArgumentParser in Docutils 0.21 or later.
@@ -78,7 +78,7 @@ module = [
7878
ignore_missing_imports = true
7979

8080
[tool.pylint.MAIN]
81-
py-version = "3.8.0"
81+
py-version = "3.10.0"
8282

8383
[tool.pylint."MESSAGES CONTROL"]
8484

src/doc8/main.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,7 @@ def __init__(self):
343343
def total_errors(self):
344344
return len(self.errors)
345345

346+
# pylint: disable=too-many-positional-arguments
346347
def error(self, check_name, filename, line_num, code, message):
347348
self.errors.append((check_name, filename, line_num, code, message))
348349

@@ -434,8 +435,7 @@ def main():
434435
"--config",
435436
metavar="path",
436437
action="append",
437-
help="user config file location"
438-
" (default: %s)." % ", ".join(CONFIG_FILENAMES),
438+
help="user config file location (default: %s)." % ", ".join(CONFIG_FILENAMES),
439439
default=defaults["config"],
440440
)
441441
parser.add_argument(
@@ -495,8 +495,7 @@ def main():
495495
action="store",
496496
metavar="int",
497497
type=int,
498-
help="maximum allowed line"
499-
" length (default: %s)." % defaults["max_line_length"],
498+
help="maximum allowed line length (default: %s)." % defaults["max_line_length"],
500499
default=defaults["max_line_length"],
501500
)
502501
parser.add_argument(

src/doc8/tests/test_main.py

Lines changed: 75 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -149,8 +149,10 @@ class TestCommandLine(unittest.TestCase):
149149
"""
150150

151151
def test_main__no_quiet_no_verbose__output_is_not_quiet(self):
152-
with TmpFs() as tmpfs, Capture() as (out, err), patch(
153-
"argparse._sys.argv", ["doc8", tmpfs.path]
152+
with (
153+
TmpFs() as tmpfs,
154+
Capture() as (out, err),
155+
patch("argparse._sys.argv", ["doc8", tmpfs.path]),
154156
):
155157
tmpfs.mock()
156158
state = main()
@@ -159,8 +161,10 @@ def test_main__no_quiet_no_verbose__output_is_not_quiet(self):
159161
self.assertEqual(state, 1)
160162

161163
def test_main__quiet_no_verbose__output_is_quiet(self):
162-
with TmpFs() as tmpfs, Capture() as (out, err), patch(
163-
"argparse._sys.argv", ["doc8", "--quiet", tmpfs.path]
164+
with (
165+
TmpFs() as tmpfs,
166+
Capture() as (out, err),
167+
patch("argparse._sys.argv", ["doc8", "--quiet", tmpfs.path]),
164168
):
165169
tmpfs.mock()
166170
state = main()
@@ -169,8 +173,10 @@ def test_main__quiet_no_verbose__output_is_quiet(self):
169173
self.assertEqual(state, 1)
170174

171175
def test_main__no_quiet_verbose__output_is_verbose(self):
172-
with TmpFs() as tmpfs, Capture() as (out, err), patch(
173-
"argparse._sys.argv", ["doc8", "--verbose", tmpfs.path]
176+
with (
177+
TmpFs() as tmpfs,
178+
Capture() as (out, err),
179+
patch("argparse._sys.argv", ["doc8", "--verbose", tmpfs.path]),
174180
):
175181
tmpfs.mock()
176182
state = main()
@@ -282,8 +288,9 @@ def test_args__no_args__defaults(self):
282288

283289
def test_args__paths__overrides_default(self):
284290
mock_scan = MagicMock(return_value=([], 0))
285-
with patch("doc8.main.scan", mock_scan), patch(
286-
"argparse._sys.argv", ["doc8", "path1", "path2"]
291+
with (
292+
patch("doc8.main.scan", mock_scan),
293+
patch("argparse._sys.argv", ["doc8", "path1", "path2"]),
287294
):
288295
state = main()
289296
self.assertEqual(state, 0)
@@ -292,48 +299,58 @@ def test_args__paths__overrides_default(self):
292299
def test_args__config__overrides_default(self):
293300
mock_scan = MagicMock(return_value=([], 0))
294301
mock_config = MagicMock(return_value={})
295-
with patch("doc8.main.scan", mock_scan), patch(
296-
"doc8.main.extract_config", mock_config
297-
), patch(
298-
"argparse._sys.argv", ["doc8", "--config", "path1", "--config", "path2"]
302+
with (
303+
patch("doc8.main.scan", mock_scan),
304+
patch("doc8.main.extract_config", mock_config),
305+
patch(
306+
"argparse._sys.argv", ["doc8", "--config", "path1", "--config", "path2"]
307+
),
299308
):
300309
state = main()
301310
self.assertEqual(state, 0)
302311
mock_scan.assert_called_once_with(self.get_args(config=["path1", "path2"]))
303312

304313
def test_args__allow_long_titles__overrides_default(self):
305314
mock_scan = MagicMock(return_value=([], 0))
306-
with patch("doc8.main.scan", mock_scan), patch(
307-
"argparse._sys.argv", ["doc8", "--allow-long-titles"]
315+
with (
316+
patch("doc8.main.scan", mock_scan),
317+
patch("argparse._sys.argv", ["doc8", "--allow-long-titles"]),
308318
):
309319
state = main()
310320
self.assertEqual(state, 0)
311321
mock_scan.assert_called_once_with(self.get_args(allow_long_titles=True))
312322

313323
def test_args__ignore__overrides_default(self):
314324
mock_scan = MagicMock(return_value=([], 0))
315-
with patch("doc8.main.scan", mock_scan), patch(
316-
"argparse._sys.argv",
317-
["doc8", "--ignore", "D002", "--ignore", "D002", "--ignore", "D005"],
325+
with (
326+
patch("doc8.main.scan", mock_scan),
327+
patch(
328+
"argparse._sys.argv",
329+
["doc8", "--ignore", "D002", "--ignore", "D002", "--ignore", "D005"],
330+
),
318331
):
319332
state = main()
320333
self.assertEqual(state, 0)
321334
mock_scan.assert_called_once_with(self.get_args(ignore={"D002", "D005"}))
322335

323336
def test_args__sphinx__overrides_default(self):
324337
mock_scan = MagicMock(return_value=([], 0))
325-
with patch("doc8.main.scan", mock_scan), patch(
326-
"argparse._sys.argv", ["doc8", "--no-sphinx"]
338+
with (
339+
patch("doc8.main.scan", mock_scan),
340+
patch("argparse._sys.argv", ["doc8", "--no-sphinx"]),
327341
):
328342
state = main()
329343
self.assertEqual(state, 0)
330344
mock_scan.assert_called_once_with(self.get_args(sphinx=False))
331345

332346
def test_args__ignore_path__overrides_default(self):
333347
mock_scan = MagicMock(return_value=([], 0))
334-
with patch("doc8.main.scan", mock_scan), patch(
335-
"argparse._sys.argv",
336-
["doc8", "--ignore-path", "path1", "--ignore-path", "path2"],
348+
with (
349+
patch("doc8.main.scan", mock_scan),
350+
patch(
351+
"argparse._sys.argv",
352+
["doc8", "--ignore-path", "path1", "--ignore-path", "path2"],
353+
),
337354
):
338355
state = main()
339356
self.assertEqual(state, 0)
@@ -343,15 +360,18 @@ def test_args__ignore_path__overrides_default(self):
343360

344361
def test_args__ignore_path_errors__overrides_default(self):
345362
mock_scan = MagicMock(return_value=([], 0))
346-
with patch("doc8.main.scan", mock_scan), patch(
347-
"argparse._sys.argv",
348-
[
349-
"doc8",
350-
"--ignore-path-errors",
351-
"path1;D002",
352-
"--ignore-path-errors",
353-
"path2;D005",
354-
],
363+
with (
364+
patch("doc8.main.scan", mock_scan),
365+
patch(
366+
"argparse._sys.argv",
367+
[
368+
"doc8",
369+
"--ignore-path-errors",
370+
"path1;D002",
371+
"--ignore-path-errors",
372+
"path2;D005",
373+
],
374+
),
355375
):
356376
state = main()
357377
self.assertEqual(state, 0)
@@ -361,26 +381,29 @@ def test_args__ignore_path_errors__overrides_default(self):
361381

362382
def test_args__default_extension__overrides_default(self):
363383
mock_scan = MagicMock(return_value=([], 0))
364-
with patch("doc8.main.scan", mock_scan), patch(
365-
"argparse._sys.argv", ["doc8", "--default-extension", "rst"]
384+
with (
385+
patch("doc8.main.scan", mock_scan),
386+
patch("argparse._sys.argv", ["doc8", "--default-extension", "rst"]),
366387
):
367388
state = main()
368389
self.assertEqual(state, 0)
369390
mock_scan.assert_called_once_with(self.get_args(default_extension="rst"))
370391

371392
def test_args__file_encoding__overrides_default(self):
372393
mock_scan = MagicMock(return_value=([], 0))
373-
with patch("doc8.main.scan", mock_scan), patch(
374-
"argparse._sys.argv", ["doc8", "--file-encoding", "utf8"]
394+
with (
395+
patch("doc8.main.scan", mock_scan),
396+
patch("argparse._sys.argv", ["doc8", "--file-encoding", "utf8"]),
375397
):
376398
state = main()
377399
self.assertEqual(state, 0)
378400
mock_scan.assert_called_once_with(self.get_args(file_encoding="utf8"))
379401

380402
def test_args__max_line_length__overrides_default(self):
381403
mock_scan = MagicMock(return_value=([], 0))
382-
with patch("doc8.main.scan", mock_scan), patch(
383-
"argparse._sys.argv", ["doc8", "--max-line-length", "88"]
404+
with (
405+
patch("doc8.main.scan", mock_scan),
406+
patch("argparse._sys.argv", ["doc8", "--max-line-length", "88"]),
384407
):
385408
state = main()
386409
self.assertEqual(state, 0)
@@ -389,8 +412,12 @@ def test_args__max_line_length__overrides_default(self):
389412
def test_args__extension__overrides_default(self):
390413
# ": [".rst", ".txt"],
391414
mock_scan = MagicMock(return_value=([], 0))
392-
with patch("doc8.main.scan", mock_scan), patch(
393-
"argparse._sys.argv", ["doc8", "--extension", "ext1", "--extension", "ext2"]
415+
with (
416+
patch("doc8.main.scan", mock_scan),
417+
patch(
418+
"argparse._sys.argv",
419+
["doc8", "--extension", "ext1", "--extension", "ext2"],
420+
),
394421
):
395422
state = main()
396423
self.assertEqual(state, 0)
@@ -400,26 +427,29 @@ def test_args__extension__overrides_default(self):
400427

401428
def test_args__quiet__overrides_default(self):
402429
mock_scan = MagicMock(return_value=([], 0))
403-
with patch("doc8.main.scan", mock_scan), patch(
404-
"argparse._sys.argv", ["doc8", "--quiet"]
430+
with (
431+
patch("doc8.main.scan", mock_scan),
432+
patch("argparse._sys.argv", ["doc8", "--quiet"]),
405433
):
406434
state = main()
407435
self.assertEqual(state, 0)
408436
mock_scan.assert_called_once_with(self.get_args(quiet=True))
409437

410438
def test_args__verbose__overrides_default(self):
411439
mock_scan = MagicMock(return_value=([], 0))
412-
with patch("doc8.main.scan", mock_scan), patch(
413-
"argparse._sys.argv", ["doc8", "--verbose"]
440+
with (
441+
patch("doc8.main.scan", mock_scan),
442+
patch("argparse._sys.argv", ["doc8", "--verbose"]),
414443
):
415444
state = main()
416445
self.assertEqual(state, 0)
417446
mock_scan.assert_called_once_with(self.get_args(verbose=True))
418447

419448
def test_args__version__overrides_default(self):
420449
mock_scan = MagicMock(return_value=([], 0))
421-
with patch("doc8.main.scan", mock_scan), patch(
422-
"argparse._sys.argv", ["doc8", "--version"]
450+
with (
451+
patch("doc8.main.scan", mock_scan),
452+
patch("argparse._sys.argv", ["doc8", "--version"]),
423453
):
424454
state = main()
425455
self.assertEqual(state, 0)

src/doc8/version.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,14 @@
1313
# under the License.
1414

1515
"""doc8 version information."""
16+
1617
try:
1718
from ._version import version as __version__
1819
except ImportError: # pragma: no branch
1920
try:
20-
import pkg_resources
21+
from importlib.metadata import version
2122

22-
__version__ = pkg_resources.get_distribution("doc8").version
23+
__version__ = version("doc8")
2324
except Exception: # pylint: disable=broad-except
2425
# this is the fallback SemVer version picked by setuptools_scm when tag
2526
# information is not available.

0 commit comments

Comments
 (0)