Skip to content

Commit 31329d7

Browse files
Drop support for Python 3.8 (#160)
Fixes #155 --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent c55d006 commit 31329d7

File tree

8 files changed

+49
-54
lines changed

8 files changed

+49
-54
lines changed

.github/workflows/tests.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,12 @@ jobs:
2020
strategy:
2121
matrix:
2222
os: [ubuntu-latest]
23-
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
23+
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
2424
include:
2525
- os: windows-latest
26-
python-version: "3.11"
26+
python-version: "3.13"
2727
- os: macos-latest
28-
python-version: "3.11"
28+
python-version: "3.13"
2929
runs-on: ${{ matrix.os }}
3030
steps:
3131
- uses: actions/checkout@v4

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22

33
## Unreleased
44

5+
### Features
6+
- [GH-155](https://github.com/hamdanal/rich-argparse/issues/155),
7+
[PR-160](https://github.com/hamdanal/rich-argparse/pull/160)
8+
Python 3.8 is no longer supported (EOL since 7/10/2024)
9+
510
## 1.7.0 - 2025-02-08
611

712
### Features

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ typo fixes where an issue may not be needed).
1111

1212
## Getting started
1313

14-
*python* version 3.8 or higher is required for development.
14+
*python* version 3.9 or higher is required for development.
1515

1616
1. Fork the repository on GitHub.
1717

pyproject.toml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ classifiers = [
1717
"Intended Audience :: Developers",
1818
"License :: OSI Approved :: MIT License",
1919
"Operating System :: OS Independent",
20-
"Programming Language :: Python :: 3.8",
2120
"Programming Language :: Python :: 3.9",
2221
"Programming Language :: Python :: 3.10",
2322
"Programming Language :: Python :: 3.11",
@@ -30,7 +29,7 @@ keywords = ["argparse", "rich", "help-formatter", "optparse"]
3029
dependencies = [
3130
"rich >= 11.0.0",
3231
]
33-
requires-python = ">=3.8"
32+
requires-python = ">=3.9"
3433

3534
[project.urls]
3635
Homepage = "https://github.com/hamdanal/rich-argparse"
@@ -65,7 +64,7 @@ isort.extra-standard-library = ["typing_extensions"]
6564
flake8-tidy-imports.ban-relative-imports = "all"
6665

6766
[tool.mypy]
68-
python_version = "3.8"
67+
python_version = "3.9"
6968
strict = true
7069
local_partial_types = true
7170

rich_argparse/_argparse.py

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66

77
import argparse
88
import re
9-
import sys
109

1110
import rich_argparse._lazy_rich as r
1211
from rich_argparse._common import (
@@ -291,16 +290,13 @@ def find_span(_string: str) -> tuple[int, int]:
291290
return _start, _end
292291

293292
for action in options: # start with the options
294-
if sys.version_info >= (3, 9): # pragma: >=3.9 cover
295-
usage = action.format_usage()
296-
if isinstance(action, argparse.BooleanOptionalAction):
297-
for option_string in action.option_strings:
298-
start, end = find_span(option_string)
299-
yield r.Span(start, end, "argparse.args")
300-
pos = end + 1
301-
continue
302-
else: # pragma: <3.9 cover
303-
usage = action.option_strings[0]
293+
usage = action.format_usage()
294+
if isinstance(action, argparse.BooleanOptionalAction):
295+
for option_string in action.option_strings:
296+
start, end = find_span(option_string)
297+
yield r.Span(start, end, "argparse.args")
298+
pos = end + 1
299+
continue
304300
start, end = find_span(usage)
305301
yield r.Span(start, end, "argparse.args")
306302
pos = end + 1
@@ -337,7 +333,7 @@ def _rich_metavar_parts(
337333
("]", False),
338334
)
339335
elif action.nargs == argparse.ZERO_OR_MORE:
340-
if sys.version_info < (3, 9) or len(get_metavar(1)) == 2: # pragma: <3.9 cover
336+
if len(get_metavar(1)) == 2:
341337
metavar = get_metavar(2)
342338
# '[%s [%s ...]]' % metavar
343339
yield from (
@@ -349,7 +345,7 @@ def _rich_metavar_parts(
349345
("...", True),
350346
("]]", False),
351347
)
352-
else: # pragma: >=3.9 cover
348+
else:
353349
# '[%s ...]' % metavar
354350
yield from (
355351
("[", False),
@@ -459,7 +455,7 @@ def _rich_expand_help(self, action: Action) -> r.Text:
459455
for m in re.finditer(rf"\[([^\]]*{printf_pat}[^\]]*)\]", help_string, re.X)
460456
if m.group("mapping") == "default"
461457
),
462-
"default: %(default)s", # pragma: >=3.9 cover # fails on Python 3.8!
458+
"default: %(default)s",
463459
)
464460
msg = (
465461
f"Failed to process default value in help string of argument {action_id!r}."

scripts/run-tests

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@
22

33
set -euxo pipefail
44

5-
for python in 3.{8..14}; do
5+
for python in 3.{9..14}; do
66
uvx --python=${python} --with=. --with-requirements=tests/requirements.txt pytest --cov
77
done

tests/test_argparse.py

Lines changed: 21 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -362,18 +362,12 @@ def test_actions_spans_in_usage():
362362
mut_ex.add_argument("--opt", nargs="?")
363363
mut_ex.add_argument("--opts", nargs="+")
364364

365-
# https://github.com/python/cpython/issues/82619
366-
if sys.version_info < (3, 9): # pragma: <3.9 cover
367-
zom_metavar = "[\x1b[36mzom\x1b[0m [\x1b[36mzom\x1b[0m \x1b[36m...\x1b[0m]]"
368-
else: # pragma: >=3.9 cover
369-
zom_metavar = "[\x1b[36mzom\x1b[0m \x1b[36m...\x1b[0m]"
370-
371365
usage_text = (
372-
f"\x1b[38;5;208mUsage:\x1b[0m \x1b[38;5;244mPROG\x1b[0m [\x1b[36m-h\x1b[0m] "
373-
f"[\x1b[36m--opt\x1b[0m [\x1b[38;5;36mOPT\x1b[0m] | "
374-
f"\x1b[36m--opts\x1b[0m \x1b[38;5;36mOPTS\x1b[0m [\x1b[38;5;36mOPTS\x1b[0m \x1b[38;5;36m...\x1b[0m]]\n "
375-
f"\x1b[36mrequired\x1b[0m \x1b[36mint\x1b[0m \x1b[36mint\x1b[0m [\x1b[36moptional\x1b[0m] "
376-
f"{zom_metavar} \x1b[36moom\x1b[0m [\x1b[36moom\x1b[0m \x1b[36m...\x1b[0m] \x1b[36m...\x1b[0m \x1b[36mparser\x1b[0m \x1b[36m...\x1b[0m"
366+
"\x1b[38;5;208mUsage:\x1b[0m \x1b[38;5;244mPROG\x1b[0m [\x1b[36m-h\x1b[0m] "
367+
"[\x1b[36m--opt\x1b[0m [\x1b[38;5;36mOPT\x1b[0m] | "
368+
"\x1b[36m--opts\x1b[0m \x1b[38;5;36mOPTS\x1b[0m [\x1b[38;5;36mOPTS\x1b[0m \x1b[38;5;36m...\x1b[0m]]\n "
369+
"\x1b[36mrequired\x1b[0m \x1b[36mint\x1b[0m \x1b[36mint\x1b[0m [\x1b[36moptional\x1b[0m] "
370+
"[\x1b[36mzom\x1b[0m \x1b[36m...\x1b[0m] \x1b[36moom\x1b[0m [\x1b[36moom\x1b[0m \x1b[36m...\x1b[0m] \x1b[36m...\x1b[0m \x1b[36mparser\x1b[0m \x1b[36m...\x1b[0m"
377371
)
378372
expected_help_output = f"""\
379373
{usage_text}
@@ -396,9 +390,8 @@ def test_actions_spans_in_usage():
396390
assert parser.format_help() == clean_argparse(expected_help_output)
397391

398392

399-
@pytest.mark.skipif(sys.version_info < (3, 9), reason="not available in 3.8")
400393
@pytest.mark.usefixtures("force_color")
401-
def test_boolean_optional_action_spans(): # pragma: >=3.9 cover
394+
def test_boolean_optional_action_spans():
402395
parser = ArgumentParser("PROG", formatter_class=RichHelpFormatter)
403396
parser.add_argument("--bool", action=argparse.BooleanOptionalAction)
404397
expected_help_output = """\
@@ -732,8 +725,9 @@ def test_rich_lazy_import():
732725
if mod_name != "rich" and not mod_name.startswith("rich.")
733726
}
734727
lazy_rich = {k: v for k, v in r.__dict__.items() if k not in r.__all__}
735-
with patch.dict(sys.modules, sys_modules_no_rich, clear=True), patch.dict(
736-
r.__dict__, lazy_rich, clear=True
728+
with (
729+
patch.dict(sys.modules, sys_modules_no_rich, clear=True),
730+
patch.dict(r.__dict__, lazy_rich, clear=True),
737731
):
738732
parser = ArgumentParser(formatter_class=RichHelpFormatter)
739733
parser.add_argument("--foo", help="foo help")
@@ -809,8 +803,9 @@ def test_legacy_windows(): # pragma: win32 cover
809803
# Legacy windows console on new windows => colors: YES, initialization: YES
810804
init_win_colors = Mock(return_value=True)
811805
parser = ArgumentParser("PROG", formatter_class=RichHelpFormatter)
812-
with patch("rich.console.detect_legacy_windows", return_value=True), patch(
813-
"rich_argparse._common._initialize_win_colors", init_win_colors
806+
with (
807+
patch("rich.console.detect_legacy_windows", return_value=True),
808+
patch("rich_argparse._common._initialize_win_colors", init_win_colors),
814809
):
815810
help = parser.format_help()
816811
assert help == clean_argparse(expected_colored_output)
@@ -819,8 +814,9 @@ def test_legacy_windows(): # pragma: win32 cover
819814
# Legacy windows console on old windows => colors: NO, initialization: YES
820815
init_win_colors = Mock(return_value=False)
821816
parser = ArgumentParser("PROG", formatter_class=RichHelpFormatter)
822-
with patch("rich.console.detect_legacy_windows", return_value=True), patch(
823-
"rich_argparse._common._initialize_win_colors", init_win_colors
817+
with (
818+
patch("rich.console.detect_legacy_windows", return_value=True),
819+
patch("rich_argparse._common._initialize_win_colors", init_win_colors),
824820
):
825821
help = parser.format_help()
826822
assert help == clean_argparse(expected_output)
@@ -834,8 +830,9 @@ def fmt_no_color(prog):
834830

835831
init_win_colors = Mock(return_value=True)
836832
no_colors_parser = ArgumentParser("PROG", formatter_class=fmt_no_color)
837-
with patch("rich.console.detect_legacy_windows", return_value=True), patch(
838-
"rich_argparse._common._initialize_win_colors", init_win_colors
833+
with (
834+
patch("rich.console.detect_legacy_windows", return_value=True),
835+
patch("rich_argparse._common._initialize_win_colors", init_win_colors),
839836
):
840837
help = no_colors_parser.format_help()
841838
assert help == clean_argparse(expected_output)
@@ -1067,10 +1064,6 @@ def test_metavar_spans():
10671064
meg.add_argument("--op7", metavar=("MET1", "MET2", "MET3"), nargs=3)
10681065
help_text = parser.format_help()
10691066

1070-
op3_metavar = "[\x1b[38;5;36mOP3\x1b[0m \x1b[38;5;36m...\x1b[0m]"
1071-
if sys.version_info < (3, 9): # pragma: <3.9 cover
1072-
op3_metavar = f"[\x1b[38;5;36mOP3\x1b[0m {op3_metavar}]"
1073-
10741067
if sys.version_info >= (3, 13): # pragma: >=3.13 cover
10751068
usage_tail = """ |
10761069
\x1b[36m--op2\x1b[0m [\x1b[38;5;36mMET1\x1b[0m [\x1b[38;5;36mMET2\x1b[0m \x1b[38;5;36m...\x1b[0m]] |
@@ -1081,11 +1074,11 @@ def test_metavar_spans():
10811074
\x1b[36m--op7\x1b[0m \x1b[38;5;36mMET1\x1b[0m \x1b[38;5;36mMET2\x1b[0m \x1b[38;5;36mMET3\x1b[0m]
10821075
"""
10831076
else: # pragma: <3.13 cover
1084-
usage_tail = f"""
1077+
usage_tail = """
10851078
| \x1b[36m--op2\x1b[0m
10861079
[\x1b[38;5;36mMET1\x1b[0m [\x1b[38;5;36mMET2\x1b[0m \x1b[38;5;36m...\x1b[0m]]
10871080
| \x1b[36m--op3\x1b[0m
1088-
{op3_metavar}
1081+
[\x1b[38;5;36mOP3\x1b[0m \x1b[38;5;36m...\x1b[0m]
10891082
| \x1b[36m--op4\x1b[0m
10901083
\x1b[38;5;36mMET1\x1b[0m
10911084
[\x1b[38;5;36mMET2\x1b[0m \x1b[38;5;36m...\x1b[0m]
@@ -1109,7 +1102,7 @@ def test_metavar_spans():
11091102
\x1b[39mmessage and exit\x1b[0m
11101103
\x1b[36m--op1\x1b[0m [\x1b[38;5;36mMET\x1b[0m]
11111104
\x1b[36m--op2\x1b[0m [\x1b[38;5;36mMET1\x1b[0m [\x1b[38;5;36mMET2\x1b[0m \x1b[38;5;36m...\x1b[0m]]
1112-
\x1b[36m--op3\x1b[0m {op3_metavar}
1105+
\x1b[36m--op3\x1b[0m [\x1b[38;5;36mOP3\x1b[0m \x1b[38;5;36m...\x1b[0m]
11131106
\x1b[36m--op4\x1b[0m \x1b[38;5;36mMET1\x1b[0m [\x1b[38;5;36mMET2\x1b[0m \x1b[38;5;36m...\x1b[0m]
11141107
\x1b[36m--op5\x1b[0m \x1b[38;5;36mOP5\x1b[0m [\x1b[38;5;36mOP5\x1b[0m \x1b[38;5;36m...\x1b[0m]
11151108
\x1b[36m--op6\x1b[0m \x1b[38;5;36mOP6\x1b[0m \x1b[38;5;36mOP6\x1b[0m \x1b[38;5;36mOP6\x1b[0m

tests/test_optparse.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -301,8 +301,9 @@ def test_rich_lazy_import():
301301
if mod_name != "rich" and not mod_name.startswith("rich.")
302302
}
303303
lazy_rich = {k: v for k, v in r.__dict__.items() if k not in r.__all__}
304-
with patch.dict(sys.modules, sys_modules_no_rich, clear=True), patch.dict(
305-
r.__dict__, lazy_rich, clear=True
304+
with (
305+
patch.dict(sys.modules, sys_modules_no_rich, clear=True),
306+
patch.dict(r.__dict__, lazy_rich, clear=True),
306307
):
307308
parser = OptionParser(formatter=IndentedRichHelpFormatter())
308309
parser.add_option("--foo", help="foo help")
@@ -353,8 +354,9 @@ def test_legacy_windows(legacy_console, old_windows, colors): # pragma: win32 c
353354

354355
init_win_colors = Mock(return_value=not old_windows)
355356
parser = OptionParser(prog="PROG", formatter=IndentedRichHelpFormatter())
356-
with patch("rich.console.detect_legacy_windows", return_value=legacy_console), patch(
357-
"rich_argparse._common._initialize_win_colors", init_win_colors
357+
with (
358+
patch("rich.console.detect_legacy_windows", return_value=legacy_console),
359+
patch("rich_argparse._common._initialize_win_colors", init_win_colors),
358360
):
359361
assert parser.format_help() == dedent(expected_output)
360362
if legacy_console:

0 commit comments

Comments
 (0)