Skip to content

Commit

Permalink
feat: use local version of BooleanOptionalAction to support Python …
Browse files Browse the repository at this point in the history
…3.7 and 3.8
  • Loading branch information
jayanthkoushik committed Dec 6, 2021
1 parent 9648ef8 commit 61c5a89
Show file tree
Hide file tree
Showing 5 changed files with 30 additions and 27 deletions.
28 changes: 25 additions & 3 deletions corgy/_corgy.py
Expand Up @@ -32,6 +32,29 @@
_T = TypeVar("_T", bound="Corgy")


class BooleanOptionalAction(argparse.Action):
# :meta private:
# Backport of `argparse.BooleanOptionalAction` from Python 3.9.
# Taken almost verbatim from `CPython/Lib/argparse.py`.
def __init__(self, option_strings, dest, *args, **kwargs):

_option_strings = []
for option_string in option_strings:
_option_strings.append(option_string)

if option_string.startswith("--"):
option_string = "--no-" + option_string[2:]
_option_strings.append(option_string)

super().__init__(
option_strings=_option_strings, dest=dest, nargs=0, *args, **kwargs
)

def __call__(self, parser, namespace, values, option_string=None):
if option_string in self.option_strings:
setattr(namespace, self.dest, not option_string.startswith("--no-"))


class _CorgyMeta(type):
"""Metaclass for `Corgy`.
Expand Down Expand Up @@ -312,8 +335,7 @@ class A:
x: A
**Bool**
`bool` types (when not in a sequence) are converted to
`argparse.BooleanOptionalAction` on Python 3.9 and above::
`bool` types (when not in a sequence) are converted to a pair of options::
class A(Corgy):
arg: bool
Expand Down Expand Up @@ -487,7 +509,7 @@ def add_args_to_parser(
# `--<var-name>`/`--no-<var-name>` arguments.
var_action: Optional[Type[argparse.Action]]
if var_base_type is bool and var_nargs is None:
var_action = getattr(argparse, "BooleanOptionalAction", None)
var_action = BooleanOptionalAction
else:
var_action = None

Expand Down
13 changes: 0 additions & 13 deletions corgy/_helpfmt.py
Expand Up @@ -11,9 +11,6 @@
from typing import Optional, Sequence, Tuple, Union
from unittest.mock import patch

if sys.version_info >= (3, 9):
from argparse import BooleanOptionalAction

__all__ = ("CorgyHelpFormatter",)

# These placeholders are used to replace special characters and words, so they can be
Expand Down Expand Up @@ -213,10 +210,6 @@ def using_colors(self) -> bool:
re.DOTALL,
)

# Regex to match the default value added to help by `BooleanOptionalAction`.
if sys.version_info >= (3, 9):
_pattern_bool_opt_default = re.compile(r" \(default: .*\)")

@staticmethod
@lru_cache(maxsize=None)
def _pattern_placeholder_text(placeholder: str) -> re.Pattern:
Expand Down Expand Up @@ -420,12 +413,6 @@ def _format_action(self, action: Action) -> str:
f"default: {self._stringify(action.default, action.type)}"
)

if sys.version_info >= (3, 9):
if isinstance(action, BooleanOptionalAction) and action.help:
# BooleanOptionalAction adds the default value to the help text.
# Remove it, since we already have it.
action.help = self._pattern_bool_opt_default.sub("", action.help)

# Add qualifier to choice list e.g. `({a/b/c} required)`.
if choice_list_fmt or arg_qualifier:
if self.using_colors:
Expand Down
3 changes: 1 addition & 2 deletions docs/corgy.md
Expand Up @@ -205,8 +205,7 @@ x: A
```

**Bool**
`bool` types (when not in a sequence) are converted to
`argparse.BooleanOptionalAction` on Python 3.9 and above:
`bool` types (when not in a sequence) are converted to a pair of options:

```python
class A(Corgy):
Expand Down
7 changes: 3 additions & 4 deletions tests/test_corgy.py
Expand Up @@ -20,6 +20,7 @@

import corgy
from corgy import Corgy, CorgyHelpFormatter, corgyparser
from corgy._corgy import BooleanOptionalAction


class TestCorgyMeta(unittest.TestCase):
Expand Down Expand Up @@ -370,24 +371,22 @@ class C(Corgy):
C.add_args_to_parser(self.parser)
self.parser.add_argument.assert_called_once_with("--x", type=T, default=t)

@skipIf(sys.version_info < (3, 9), "`BooleanOptionalAction` unavailable")
def test_add_args_converts_bool_to_action(self):
class C(Corgy):
x: bool

C.add_args_to_parser(self.parser)
self.parser.add_argument.assert_called_once_with(
"--x", type=bool, action=argparse.BooleanOptionalAction, required=True
"--x", type=bool, action=BooleanOptionalAction, required=True
)

@skipIf(sys.version_info < (3, 9), "`BooleanOptionalAction` unavailable")
def test_add_args_handles_default_for_bool_type(self):
class C(Corgy):
x: bool = False

C.add_args_to_parser(self.parser)
self.parser.add_argument.assert_called_once_with(
"--x", type=bool, action=argparse.BooleanOptionalAction, default=False
"--x", type=bool, action=BooleanOptionalAction, default=False
)

def test_add_args_does_not_convert_bool_sequence_to_action(self):
Expand Down
6 changes: 1 addition & 5 deletions tests/test_helpfmt.py
@@ -1,14 +1,11 @@
import sys
from argparse import ArgumentParser, SUPPRESS
from unittest import skipIf, TestCase
from unittest.mock import Mock, patch

from corgy import CorgyHelpFormatter
from corgy._corgy import BooleanOptionalAction
from corgy._helpfmt import _ColorHelper

if sys.version_info >= (3, 9):
from argparse import BooleanOptionalAction

_COLOR_HELPER = _ColorHelper(skip_tty_check=True)
_CRAYONS = _COLOR_HELPER.crayons

Expand Down Expand Up @@ -366,7 +363,6 @@ def test_corgy_help_formatter_handles_different_prefix_chars(self):
f" {_O('+++x')} {_M('str')} x help ({_K('optional')})",
)

@skipIf(sys.version_info < (3, 9), "`BooleanOptionalAction` not available")
def test_corgy_help_formatter_handles_boolean_optional_action(self):
self.assertEqual(
self._get_arg_help(
Expand Down

0 comments on commit 61c5a89

Please sign in to comment.