Skip to content

Commit

Permalink
Merge pull request #244 from sandialabs/15-drop-support-for-python-3.8
Browse files Browse the repository at this point in the history
chore!: Drop support for Python 3.8
  • Loading branch information
jmgate authored Dec 3, 2024
2 parents 65aa685 + 5c448ec commit eb22039
Show file tree
Hide file tree
Showing 6 changed files with 40 additions and 70 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/continuous-integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
version: ["3.9", "3.10", "3.11", "3.12"]
steps:

- name: Harden Runner
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
[![pre-commit.ci Status](https://results.pre-commit.ci/badge/github/sandialabs/reverse_argparse/master.svg)](https://results.pre-commit.ci/latest/github/sandialabs/reverse_argparse/master)
[![PyPI - Version](https://img.shields.io/pypi/v/reverse-argparse?label=PyPI)](https://pypi.org/project/reverse-argparse/)
![PyPI - Downloads](https://img.shields.io/pypi/dm/reverse-argparse?label=PyPI%20downloads)
![Python Version](https://img.shields.io/badge/Python-3.8|3.9|3.10|3.11|3.12-blue.svg)
![Python Version](https://img.shields.io/badge/Python-3.9|3.10|3.11|3.12-blue.svg)
[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)

# reverse_argparse
Expand Down
2 changes: 1 addition & 1 deletion doc/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ reverse_argparse
.. |PyPI Version| image:: https://img.shields.io/pypi/v/reverse-argparse?label=PyPI
:target: https://pypi.org/project/reverse-argparse/
.. |PyPI Downloads| image:: https://img.shields.io/pypi/dm/reverse-argparse?label=PyPI%20downloads
.. |Python Version| image:: https://img.shields.io/badge/Python-3.8|3.9|3.10|3.11|3.12-blue.svg
.. |Python Version| image:: https://img.shields.io/badge/Python-3.9|3.10|3.11|3.12-blue.svg
.. |Ruff| image:: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json
:target: https://github.com/astral-sh/ruff

Expand Down
1 change: 0 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ classifiers = [
"Operating System :: OS Independent",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
Expand Down
23 changes: 9 additions & 14 deletions reverse_argparse/reverse_argparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,10 @@
# SPDX-License-Identifier: BSD-3-Clause

import re
import sys
from argparse import SUPPRESS, Action, ArgumentParser, Namespace
from typing import List, Sequence
from typing import Sequence


BOOLEAN_OPTIONAL_ACTION_MINOR_VERSION = 9
SHORT_OPTION_LENGTH = 2


Expand All @@ -38,20 +36,20 @@ class ReverseArgumentParser:
such that they're able to reproduce a prior run of a script exactly.
Attributes:
_args (List[str]): The list of arguments corresponding to each
_args (list[str]): The list of arguments corresponding to each
:class:`argparse.Action` in the given parser, which is built
up as the arguments are unparsed.
_indent (int): The number of spaces with which to indent
subsequent lines when pretty-printing the effective command
line invocation.
_namespace (Namespace): The parsed arguments.
_parsers (List[argparse.ArgumentParser]): The parser that was
_parsers (list[argparse.ArgumentParser]): The parser that was
used to generate the parsed arguments. This is a ``list``
(conceptually a stack) to allow for sub-parsers, so the
outer-most parser is the first item in the list, and
sub-parsers are pushed onto and popped off of the stack as
they are processed.
_unparsed (List[bool]): A list in which the elements indicate
_unparsed (list[bool]): A list in which the elements indicate
whether the corresponding parser in :attr:`parsers` has been
unparsed.
"""
Expand Down Expand Up @@ -136,10 +134,7 @@ def _unparse_action(self, action: Action) -> None: # noqa: C901, PLR0912
self._unparse_sub_parsers_action(action)
elif action_type == "_VersionAction": # pragma: no cover
return
elif (
action_type == "BooleanOptionalAction"
and sys.version_info.minor >= BOOLEAN_OPTIONAL_ACTION_MINOR_VERSION
):
elif action_type == "BooleanOptionalAction":
self._unparse_boolean_optional_action(action)
else: # pragma: no cover
message = (
Expand Down Expand Up @@ -202,7 +197,7 @@ def get_pretty_command_line_invocation(self) -> str:

def _get_long_option_strings(
self, option_strings: Sequence[str]
) -> List[str]:
) -> list[str]:
"""
Get the long options from a list of options strings.
Expand All @@ -224,7 +219,7 @@ def _get_long_option_strings(

def _get_short_option_strings(
self, option_strings: Sequence[str]
) -> List[str]:
) -> list[str]:
"""
Get the short options from a list of options strings.
Expand Down Expand Up @@ -278,7 +273,7 @@ def _get_option_string(
return short_options[0]
return ""

def _append_list_of_list_of_args(self, args: List[List[str]]) -> None:
def _append_list_of_list_of_args(self, args: list[list[str]]) -> None:
"""
Append to the list of unparsed arguments.
Expand All @@ -293,7 +288,7 @@ def _append_list_of_list_of_args(self, args: List[List[str]]) -> None:
for line in args:
self._args.append(self._indent_str + " ".join(line))

def _append_list_of_args(self, args: List[str]) -> None:
def _append_list_of_args(self, args: list[str]) -> None:
"""
Append to the list of unparsed arguments.
Expand Down
80 changes: 28 additions & 52 deletions test/test_reverse_argparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,13 @@
# SPDX-License-Identifier: BSD-3-Clause

import shlex
import sys
from argparse import SUPPRESS, ArgumentParser, Namespace
from argparse import SUPPRESS, ArgumentParser, BooleanOptionalAction, Namespace

import pytest

from reverse_argparse import ReverseArgumentParser


BOOLEAN_OPTIONAL_ACTION_MINOR_VERSION = 9


if sys.version_info.minor >= BOOLEAN_OPTIONAL_ACTION_MINOR_VERSION:
from argparse import BooleanOptionalAction


@pytest.fixture
def parser() -> ArgumentParser:
"""
Expand Down Expand Up @@ -51,10 +43,7 @@ def parser() -> ArgumentParser:
)
p.add_argument("--verbose", "-v", action="count", default=2)
p.add_argument("--ext", action="extend", nargs="*")
if sys.version_info.minor >= BOOLEAN_OPTIONAL_ACTION_MINOR_VERSION:
p.add_argument(
"--bool-opt", action=BooleanOptionalAction, default=False
)
p.add_argument("--bool-opt", action=BooleanOptionalAction, default=False)
return p


Expand Down Expand Up @@ -144,20 +133,12 @@ def test_get_effective_command_line_invocation(parser, args) -> None:
namespace = parser.parse_args(shlex.split(args))
unparser = ReverseArgumentParser(parser, namespace)
expected = (
(
"--opt1 opt1-val --opt2 opt2-val1 opt2-val2 --store-true "
"--store-false --needs-quotes 'hello world' --default 42 --app1 "
"app1-val1 --app1 app1-val2 --app2 app2-val1 --app2 app2-val2 "
"--app-nargs app-nargs1-val1 app-nargs1-val2 --app-nargs "
"app-nargs2-val --const --app-const1 --app-const2 -vv --ext "
"ext-val1 ext-val2 ext-val3 "
)
+ (
"--no-bool-opt "
if sys.version_info.minor >= BOOLEAN_OPTIONAL_ACTION_MINOR_VERSION
else ""
)
+ "pos1-val1 pos1-val2 pos2-val"
"--opt1 opt1-val --opt2 opt2-val1 opt2-val2 --store-true "
"--store-false --needs-quotes 'hello world' --default 42 --app1 "
"app1-val1 --app1 app1-val2 --app2 app2-val1 --app2 app2-val2 "
"--app-nargs app-nargs1-val1 app-nargs1-val2 --app-nargs "
"app-nargs2-val --const --app-const1 --app-const2 -vv --ext ext-val1 "
"ext-val2 ext-val3 --no-bool-opt pos1-val1 pos1-val2 pos2-val"
)
result = strip_first_entry(
unparser.get_effective_command_line_invocation()
Expand Down Expand Up @@ -186,10 +167,9 @@ def test_get_pretty_command_line_invocation(parser, args) -> None:
--app-const1 \\
--app-const2 \\
-vv \\
--ext ext-val1 ext-val2 ext-val3 \\"""
if sys.version_info.minor >= BOOLEAN_OPTIONAL_ACTION_MINOR_VERSION:
expected += "\n --no-bool-opt \\"
expected += """\n pos1-val1 pos1-val2 \\
--ext ext-val1 ext-val2 ext-val3 \\
--no-bool-opt \\
pos1-val1 pos1-val2 \\
pos2-val"""
result = strip_first_line(unparser.get_pretty_command_line_invocation())
assert result == expected
Expand Down Expand Up @@ -274,16 +254,15 @@ def test__unparse_args_boolean_optional_action() -> None:
With a ``BooleanOptionalAction``, which became available in Python
3.9.
"""
if sys.version_info.minor >= BOOLEAN_OPTIONAL_ACTION_MINOR_VERSION:
parser = ArgumentParser()
parser.add_argument("--foo", action=BooleanOptionalAction)
try:
namespace = parser.parse_args(shlex.split("--foo"))
except SystemExit:
namespace = Namespace()
unparser = ReverseArgumentParser(parser, namespace)
unparser._unparse_args()
assert unparser._args[1:] == [" --foo"]
parser = ArgumentParser()
parser.add_argument("--foo", action=BooleanOptionalAction)
try:
namespace = parser.parse_args(shlex.split("--foo"))
except SystemExit:
namespace = Namespace()
unparser = ReverseArgumentParser(parser, namespace)
unparser._unparse_args()
assert unparser._args[1:] == [" --foo"]


def test__unparse_args_already_unparsed() -> None:
Expand Down Expand Up @@ -635,14 +614,11 @@ def test__unparse_extend_action() -> None:
)
def test__unparse_boolean_optional_action(default, args, expected) -> None:
"""Ensure ``BooleanOptionalAction`` actions are handled appropriately."""
if sys.version_info.minor >= BOOLEAN_OPTIONAL_ACTION_MINOR_VERSION:
parser = ArgumentParser()
action = parser.add_argument(
"--bool-opt", action=BooleanOptionalAction, default=default
)
namespace = parser.parse_args(shlex.split(args))
unparser = ReverseArgumentParser(parser, namespace)
unparser._unparse_boolean_optional_action(action)
assert unparser._args[1:] == (
[expected] if expected is not None else []
)
parser = ArgumentParser()
action = parser.add_argument(
"--bool-opt", action=BooleanOptionalAction, default=default
)
namespace = parser.parse_args(shlex.split(args))
unparser = ReverseArgumentParser(parser, namespace)
unparser._unparse_boolean_optional_action(action)
assert unparser._args[1:] == ([expected] if expected is not None else [])

0 comments on commit eb22039

Please sign in to comment.