Skip to content

Commit

Permalink
Tests, more intuitive behavior for type narrowing
Browse files Browse the repository at this point in the history
  • Loading branch information
brentyi committed Aug 25, 2022
1 parent 2925576 commit e517015
Show file tree
Hide file tree
Showing 7 changed files with 146 additions and 5 deletions.
2 changes: 1 addition & 1 deletion dcargs/_fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ def _try_field_list_from_callable(
container_fields = _try_field_list_from_sequence(
contained_type, default_instance
)
elif f_origin is dict:
elif f_origin is dict or cls is dict:
container_fields = _try_field_list_from_dict(f, default_instance)

# Check if one of the container types matched.
Expand Down
2 changes: 1 addition & 1 deletion dcargs/_resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ def narrow_type(typ: TypeT, default_instance: Any) -> TypeT:
try:
potential_subclass = type(default_instance)
superclass = typ
if issubclass(potential_subclass, superclass): # type: ignore
if superclass is Any or issubclass(potential_subclass, superclass): # type: ignore
return cast(TypeT, potential_subclass)
except TypeError:
pass
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "dcargs"
version = "0.2.2"
version = "0.2.3"
description = "Strongly typed, zero-effort CLI interfaces"
authors = ["brentyi <[email protected]>"]
include = ["./dcargs/**/*"]
Expand Down
41 changes: 40 additions & 1 deletion tests/test_collections.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,18 @@
import collections
import dataclasses
import enum
from typing import Any, Deque, FrozenSet, List, Optional, Sequence, Set, Tuple, Union
from typing import (
Any,
Deque,
Dict,
FrozenSet,
List,
Optional,
Sequence,
Set,
Tuple,
Union,
)

import pytest
from typing_extensions import Literal
Expand Down Expand Up @@ -382,3 +393,31 @@ def main(
]
with pytest.raises(SystemExit):
dcargs.cli(main, args=["--help"])


def test_dict_no_annotation():
def main(x: Dict[str, Any] = {"int": 5, "str": "5"}):
return x

assert dcargs.cli(main, args=[]) == {"int": 5, "str": "5"}
assert dcargs.cli(main, args="--x.int 3 --x.str 7".split(" ")) == {
"int": 3,
"str": "7",
}


def test_double_dict_no_annotation():
def main(
x: Dict[str, Any] = {
"wow": {"int": 5, "str": "5"},
}
):
return x

assert dcargs.cli(main, args=[]) == {"wow": {"int": 5, "str": "5"}}
assert dcargs.cli(main, args="--x.wow.int 3 --x.wow.str 7".split(" ")) == {
"wow": {
"int": 3,
"str": "7",
}
}
1 change: 0 additions & 1 deletion tests/test_helptext.py
Original file line number Diff line number Diff line change
Expand Up @@ -515,7 +515,6 @@ class Something(


def test_unparsable():
@dataclasses.dataclass
class Struct:
a: int = 5
b: str = "7"
Expand Down
52 changes: 52 additions & 0 deletions tests/test_is_nested_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import dataclasses
import pathlib
from typing import Any, Dict, List, Tuple

from dcargs._fields import MISSING_NONPROP, is_nested_type


def test_is_nested_type_simple():
assert not is_nested_type(int, MISSING_NONPROP)
assert not is_nested_type(bool, MISSING_NONPROP)
assert not is_nested_type(str, MISSING_NONPROP)
assert not is_nested_type(pathlib.Path, MISSING_NONPROP)


def test_is_nested_type_containers():
assert not is_nested_type(List[int], MISSING_NONPROP)
assert not is_nested_type(List[bool], MISSING_NONPROP)
assert not is_nested_type(List[str], MISSING_NONPROP)
assert not is_nested_type(List[pathlib.Path], MISSING_NONPROP)


@dataclasses.dataclass
class Color:
r: int
g: int
b: int


def test_is_nested_type_actually_nested():
assert is_nested_type(Color, Color(255, 0, 0))


def test_is_nested_type_actually_nested_narrowing():
assert is_nested_type(Any, Color(255, 0, 0))
assert is_nested_type(object, Color(255, 0, 0))
assert not is_nested_type(int, Color(255, 0, 0))


def test_is_nested_type_actually_nested_in_container():
assert is_nested_type(Tuple[Color, Color], MISSING_NONPROP)
assert is_nested_type(Tuple[object, ...], (Color(255, 0, 0),))
assert is_nested_type(Tuple[Any, ...], (Color(255, 0, 0),))
assert is_nested_type(tuple, (Color(255, 0, 0),))
assert not is_nested_type(tuple, (1, 2, 3))
assert is_nested_type(tuple, (1, Color(255, 0, 0), 3))
assert is_nested_type(List[Any], [Color(255, 0, 0)])


def test_nested_dict():
assert is_nested_type(Dict[str, int], {"x": 5})
assert is_nested_type(dict, {"x": 5})
assert is_nested_type(Any, {"x": 5})
51 changes: 51 additions & 0 deletions tests/test_nested_in_containers.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,14 @@ def main(x: List[object] = [Color(255, 0, 0)]) -> Any:
assert dcargs.cli(main, args="--x.0.r 127".split(" ")) == [Color(127, 0, 0)]


def test_list_any():
def main(x: List[Any] = [Color(255, 0, 0)]) -> Any:
return x

assert dcargs.cli(main, args=[]) == [Color(255, 0, 0)]
assert dcargs.cli(main, args="--x.0.r 127".split(" ")) == [Color(127, 0, 0)]


def test_tuple_in_list():
def main(x: List[Tuple[Color]] = [(Color(255, 0, 0),)]) -> Any:
return x
Expand Down Expand Up @@ -320,3 +328,46 @@ def main(
main,
args="--x.int.g 0".split(" "),
) == {"float": GenericColor(0.5, 0.2, 0.3), "int": GenericColor(25, 0, 3)}


def test_generic_in_double_nested_dict_with_default():
ScalarType = TypeVar("ScalarType", int, float)

@dataclasses.dataclass
class GenericColor(Generic[ScalarType]):
r: ScalarType
g: ScalarType
b: ScalarType

def main(
x: Dict[str, Dict[str, GenericColor]] = {
"hello": {
"float": GenericColor(0.5, 0.2, 0.3),
"int": GenericColor[int](25, 2, 3),
}
}
) -> Any:
return x

assert dcargs.cli(main, args="--x.hello.float.g 0.1".split(" "),)["hello"][
"float"
] == GenericColor(0.5, 0.1, 0.3)
assert dcargs.cli(main, args="--x.hello.int.g 0".split(" "),) == {
"hello": {"float": GenericColor(0.5, 0.2, 0.3), "int": GenericColor(25, 0, 3)}
}


def test_double_nested_dict_with_inferred_type():
def main(
x: Dict[str, Any] = {
"hello": {
"a": Color(5, 2, 3),
"b": Color(25, 2, 3),
}
}
) -> Any:
return x

assert dcargs.cli(main, args="--x.hello.a.g 1".split(" "),)["hello"][
"a"
] == Color(5, 1, 3)

0 comments on commit e517015

Please sign in to comment.