Skip to content

Commit

Permalink
feat: add support for typing.Optional <-> nargs="?"
Browse files Browse the repository at this point in the history
  • Loading branch information
jnoortheen committed Dec 23, 2020
1 parent 6060ac7 commit 12ab8c7
Show file tree
Hide file tree
Showing 4 changed files with 52 additions and 36 deletions.
6 changes: 5 additions & 1 deletion arger/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,11 @@ def __init__(self, *args, **kwargs):
self.is_enum = False
if typ is not _EMPTY:
origin = tp_utils.get_origin(typ)
if self.is_iterable:

if tp_utils.is_optional(typ) and "default" not in kwargs:
kwargs["nargs"] = "?"
origin = tp_utils.unpack_type(typ)
elif self.is_iterable:
origin, kwargs["nargs"] = get_nargs(typ)

if tp_utils.is_enum(origin):
Expand Down
21 changes: 15 additions & 6 deletions arger/typing_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import sys
from enum import Enum
from inspect import isclass
from typing import Any, FrozenSet, List, Set, Tuple, TypeVar
from typing import Any, FrozenSet, List, Set, Tuple, TypeVar, Union

NEW_TYPING = sys.version_info[:3] >= (3, 7, 0) # PEP 560

Expand All @@ -27,7 +27,7 @@ def define_old_types():
Set: set,
FrozenSet: frozenset,
}.items():
if hasattr(tp, '__name__'):
if hasattr(tp, "__name__"):
origins[tp.__name__] = orig # type: ignore
return origins

Expand All @@ -36,7 +36,7 @@ def get_origin(tp):
"""Return the python class for the GenericAlias. Dict->dict, List->list..."""
origin = _get_origin(tp)

if not NEW_TYPING and hasattr(tp, '__name__'):
if not NEW_TYPING and hasattr(tp, "__name__"):
old_type_origins = define_old_types()
if tp.__name__ in old_type_origins:
return old_type_origins[tp.__name__]
Expand All @@ -52,7 +52,7 @@ def match_types(tp, *matches) -> bool:
return any([get_origin(m) is get_origin(tp) for m in matches])


ARGS = '__args__'
ARGS = "__args__"


def get_inner_args(tp):
Expand All @@ -71,7 +71,7 @@ def unpack_type(tp, default=str) -> Any:
"""
args = get_inner_args(tp)
if args:
if str(args[0]) not in {'~T', 'typing.Any'}:
if str(args[0]) not in {"~T", "typing.Any"}:
return args[0]
return default

Expand All @@ -89,6 +89,15 @@ def is_tuple(tp):
return match_types(tp, tuple)


def is_optional(tp):
"""Check that tp = typing.Optional[typ1]"""
if match_types(tp, Union):
args = get_inner_args(tp)
if len(args) == 2:
return type(None) in args
return False


def cast(tp, val) -> Any:
# https://github.com/contains-io/typingplus/blob/master/typingplus.py
# for advanced casting one should use pydantic
Expand All @@ -114,4 +123,4 @@ def cast(tp, val) -> Any:
return origin(val)


T = TypeVar('T')
T = TypeVar("T")
1 change: 1 addition & 0 deletions docs/1-arger.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ a:int, # -> add_argument(dest='a', type=int)
a:Tuple[int, ...], # -> add_argument(dest='a', type=int, nargs='+') : one or more
a:Tuple[int, int], # -> add_argument(dest='a', type=int, nargs='2') : consumes 2 positional
a:List[int], # -> add_argument(dest="a", type=int, nargs="*") : zero or more
a:Optional[int], # -> add_argument(dest="a", type=int, nargs="?") : zero or one positional
a:Enum(
'AnySubClsOfEnum',
'ONE TWO'
Expand Down
60 changes: 31 additions & 29 deletions tests/test_args_opts/test_arguments.py
Original file line number Diff line number Diff line change
@@ -1,51 +1,53 @@
from decimal import Decimal
from enum import Enum
from typing import List, Tuple
from typing import List, Optional, Tuple

import pytest


class Num(Enum):
one = '1. one'
two = '2. two'
one = "1. one"
two = "2. two"


Num2 = Enum("Num2", "one two")


@pytest.mark.parametrize(
'name, tp, input, expected',
"name, tp, input, expected",
[
# simple types
('an_int', int, '20', 20),
('a_float', float, '25', 25.0),
('a_deci', Decimal, '25', 25.0),
('a_cmplx', complex, '4+8j', 4 + 8j),
('a_str', str, 'new-str', 'new-str'),
('enum', Num, 'one', Num.one),
('enum', Num, 'two', Num.two),
('enum', Num2, 'one', Num2.one),
('enum', Num2, 'one', Num2.one),
("an_int", int, "20", 20),
("a_float", float, "25", 25.0),
("a_deci", Decimal, "25", 25.0),
("a_cmplx", complex, "4+8j", 4 + 8j),
("a_str", str, "new-str", "new-str"),
("optional", Optional[str], "", None),
("optional", Optional[str], "a-str", "a-str"),
("enum", Num, "one", Num.one),
("enum", Num, "two", Num.two),
("enum", Num2, "one", Num2.one),
("enum", Num2, "one", Num2.one),
# container types
('a_tuple', tuple, '1 2 3', ('1', '2', '3')),
('a_tuple', Tuple[int, ...], '1 2 3', (1, 2, 3)),
('a_tuple', Tuple[str, ...], '1 2 3', ('1', '2', '3')),
('a_tuple', Tuple[Num, ...], 'one two', (Num.one, Num.two)),
('a_tuple', Tuple[float, ...], '1 2 3', (1.0, 2.0, 3.0)),
('a_tuple', Tuple[str, int], '1 2', ('1', 2)),
("a_tuple", tuple, "1 2 3", ("1", "2", "3")),
("a_tuple", Tuple[int, ...], "1 2 3", (1, 2, 3)),
("a_tuple", Tuple[str, ...], "1 2 3", ("1", "2", "3")),
("a_tuple", Tuple[Num, ...], "one two", (Num.one, Num.two)),
("a_tuple", Tuple[float, ...], "1 2 3", (1.0, 2.0, 3.0)),
("a_tuple", Tuple[str, int], "1 2", ("1", 2)),
(
'a_tuple',
"a_tuple",
Tuple[int, float, Decimal, complex, str],
'1 2 30 4+4j five',
(1, 2.0, Decimal(30), 4 + 4j, 'five'),
"1 2 30 4+4j five",
(1, 2.0, Decimal(30), 4 + 4j, "five"),
),
('a_list', list, '1 2 3', ['1', '2', '3']),
('a_list', List, '1 2 3', ['1', '2', '3']),
('a_list', List[str], '1 2 3', ['1', '2', '3']),
('a_list', List[Num], 'one two', [Num.one, Num.two]),
('a_list', List[int], '1 2 3', [1, 2, 3]),
('a_list', List[Decimal], '1 2 3', [Decimal(1), Decimal(2), Decimal(3)]),
('a_list', set, '1 2 3', {'1', '2', '3'}),
("a_list", list, "1 2 3", ["1", "2", "3"]),
("a_list", List, "1 2 3", ["1", "2", "3"]),
("a_list", List[str], "1 2 3", ["1", "2", "3"]),
("a_list", List[Num], "one two", [Num.one, Num.two]),
("a_list", List[int], "1 2 3", [1, 2, 3]),
("a_list", List[Decimal], "1 2 3", [Decimal(1), Decimal(2), Decimal(3)]),
("a_list", set, "1 2 3", {"1", "2", "3"}),
],
)
def test_arguments(parser, argument, input, expected, name):
Expand Down

0 comments on commit 12ab8c7

Please sign in to comment.