Skip to content

Commit

Permalink
Merge branch 'dev'; bump version to 0.3.1
Browse files Browse the repository at this point in the history
  • Loading branch information
MamoruDS committed Dec 4, 2023
2 parents 8c41925 + 0d98ac1 commit 6f423b2
Show file tree
Hide file tree
Showing 14 changed files with 380 additions and 195 deletions.
21 changes: 21 additions & 0 deletions examples/00_quick_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from typed_cap import Cap


class Args:
"""description here"""

config: str | None
"""file path to config file"""

depth: int
"""depth of search"""

dry_run: bool = True
"""run without making any changes"""


cap = Cap(Args)
parsed = cap.parse()

print(parsed.args.__dict__)
print(parsed.argv)
48 changes: 48 additions & 0 deletions examples/01_cmt_params.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
from enum import Enum

from typed_cap import Cap


class CoinFlip(Enum):
head = 0
tail = 1


class Args:
"""
docstring of T
"""

# @alias=a
all: bool = False
"""
write counts for all files, not just directories
"""

# @alias=c
total: bool = False

# @alias=d @hide_default
max_depth: int = -1
"""
print the total for a directory (or file, with --all) only if it is N or fewer levels below the command line argument
"""

# @alias=h
human_readable: bool | None
"""
print sizes in human readable format (e.g., 1K 234M 2G)
"""

# @alias=m @none_delimiter
message: list[str] | None

# @enum_on_value
flip: CoinFlip


cap = Cap(Args)
parsed = cap.parse()

print(parsed.args.__dict__)
print(parsed.argv)
40 changes: 40 additions & 0 deletions examples/02_custom_unit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from PIL import Image
from typed_cap import Cap
from typed_cap.typing import ValidUnit, ValidRes, ValidVal


class Args:
image: Image.Image
"""path to image wanted to be loaded"""


def valid_image(vv, t, val, cvt):
v = ValidRes[Image.Image]()
if cvt:
try:
v.some(Image.open(val))
v.valid()
except Exception as err:
v.error(err)
else:
if isinstance(val, Image.Image):
v.some(val)
v.valid()
return v


cap = Cap(
Args,
extra_validator_units={
"image": ValidUnit(
exact=Image.Image,
type_of=None,
class_of=None,
valid_fn=valid_image,
),
},
)
parsed = cap.parse()
# python 02_custom_unit.py --image demo.jpg

print(parsed.args.image)
10 changes: 6 additions & 4 deletions tests/items/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -267,13 +267,15 @@ class T(B):
@pytest.mark.skipif(
sys.version_info < (3, 10), reason="requires Python 3.10 or higher"
)
def test_uniontypes():
def test_uniontypes_queue_list():
class T(B):
numbers: list[int] | None
foo: list[int] | None
bar: list[int] | None

cap = Cap(T)
res = cap.parse(cmd("--numbers 5,6,10"))
assert G(res.args, "numbers") == [5, 6, 10]
res = cap.parse(cmd("--foo 5,6,10 --bar 1 --bar 2"))
assert G(res.args, "foo") == [5, 6, 10]
assert G(res.args, "bar") == [1, 2]


def test_default_none_union():
Expand Down
11 changes: 11 additions & 0 deletions tests/test_cap_dict_based.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,17 @@
CFG.cur = "dict-based"


def test_unpack():
class T(TypedDict):
verbose: bool

cap = Cap(T)
parsed = cap.parse(cmd("--verbose foo"))
argv, args = parsed.unpack()
assert isinstance(argv, list)
assert isinstance(args, dict)


def test_default_dict_based():
class T(TypedDict):
silent: bool
Expand Down
11 changes: 11 additions & 0 deletions tests/test_cap_obj_based.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,17 @@
CFG.cur = "object-based"


def test_unpack():
class T:
verbose: bool

cap = Cap(T)
parsed = cap.parse(cmd("--verbose foo"))
argv, args = parsed.unpack()
assert isinstance(argv, list)
assert isinstance(args, T)


def test_default_obj_based():
class T:
silent: bool = False
Expand Down
9 changes: 4 additions & 5 deletions typed_cap/args_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,10 +165,9 @@ def _extract(k: str, v: List[Union[str, bool]]) -> List[Union[str, bool]]:
raise Exception(f"val of {k} is not a list")
return v

parsed_args: ArgsParserResults = {
"args": parsed.pop("_"),
"options": dict(
return ArgsParserResults(
argv=parsed.pop("_"),
options=dict(
map(lambda it: (it[0], _extract(it[0], it[1])), parsed.items())
),
}
return parsed_args
)
63 changes: 35 additions & 28 deletions typed_cap/cap.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from __future__ import annotations
import inspect
import json
import sys
from copy import deepcopy
from typing import (
Any,
Callable,
Expand Down Expand Up @@ -45,15 +45,17 @@
)
from .typing import (
BasedType,
ParsedQueueType,
ValidatorNotFound,
ValidUnit,
ValidVal,
get_based,
get_optional_candidates,
get_queue_type,
get_type_candidates,
argstyping_parse,
)
from .typing.default import VALIDATOR
from .typing.default import PREDEFINED_UNITS
from .utils import (
flatten,
get_terminal_width,
Expand All @@ -66,7 +68,7 @@
get_annotations,
get_docs_from_annotations,
)
from .utils.color import Colors, fg
from .utils.color import BasicColors, fg
from .utils.option import Option


Expand All @@ -76,10 +78,12 @@
class _ParsedVal(TypedDict):
val: List[List[Any]]
default_val: Option
queue_type: Optional[Literal["list", "tuple"]]
queue_type: ParsedQueueType


T = TypeVar("T", bound=Union[TypedDict, object])
U = TypeVar("U", bound=Union[TypedDict, Dict[str, Any]])
K = TypeVar("K", bound=str)


class _GVCS(Generic[T]):
Expand Down Expand Up @@ -144,9 +148,9 @@ def args(self) -> T:
pv = flatten(pv)
if len(pv) == 0:
gvc.setVal(key, parsed["default_val"].unwrap())
elif parsed["queue_type"] == "list":
elif parsed["queue_type"] is ParsedQueueType.LIST:
gvc.setVal(key, flatten(pv))
elif parsed["queue_type"] == "tuple":
elif parsed["queue_type"] is ParsedQueueType.TUPLE:
gvc.setVal(key, pv[-1])
else:
gvc.setVal(key, pv[-1])
Expand All @@ -162,25 +166,16 @@ def val(self) -> T:
"""deprecated; use `args` instead"""
return self.value

def unpack(self) -> Tuple[List[str], T]:
return self.argv, self.args

def count(self, name: str) -> int:
parsed = self._parsed_map.get(name)
if parsed is not None:
return len(parsed["val"])
else:
panic(f'Parsed.count: cannot find option with name "{name}"')

def __json__(self, indent: Optional[int]) -> str:
j = {}
j["arguments"] = self.arguments
j["value"] = self.value
return json.dumps(j, indent=indent)

def toJSON(self, indent: Optional[int] = None) -> str:
return self.__json__(indent=indent)


U = TypeVar("U", bound=Union[TypedDict, Dict[str, Any]])
K = TypeVar("K", bound=str)

CAP_ERR = Union[
ArgsParserKeyError,
Expand Down Expand Up @@ -331,16 +326,16 @@ def colorize_text_t_type(t: Type) -> str:
tn: str = t.__name__
except AttributeError:
tn = str(t)
return str(fg(tn, Colors.Blue))
return str(fg(tn, BasicColors.Blue))


def colorize_text_t_option_name(key: str) -> str:
return str(fg(key, Colors.Yellow))
return str(fg(key, BasicColors.Yellow))


def colorize_text_t_value(val: Any) -> str:
try:
return str(fg(val, Colors.Red))
return str(fg(val, BasicColors.Red))
except Exception as err:
print(err)
return "[...]"
Expand All @@ -354,6 +349,7 @@ class Cap(Generic[K, T, U]):
_about: Optional[str]
_delimiter: Option[Optional[str]]
_name: Optional[str]
_val_validator: ValidVal
_version: Optional[str]
_raw_err: bool
_preset_helper_used: bool
Expand All @@ -373,6 +369,7 @@ def __init__(
use_anno_doc_as_about: bool = True,
use_anno_cmt_params: bool = True,
add_helper_help: bool = True,
extra_validator_units: Optional[Dict[str, ValidUnit]] = None,
) -> None:
self._attributes = {}
self._argstype = argstype
Expand Down Expand Up @@ -418,6 +415,10 @@ def __init__(

self._add_helper_help = add_helper_help

self._val_validator = ValidVal(deepcopy(PREDEFINED_UNITS))
if extra_validator_units is not None:
self._val_validator._registry.update(extra_validator_units)

def _get_key(self, name: str) -> Union[NoReturn, str]:
for key, opt in self._args.items():
if key == name:
Expand Down Expand Up @@ -592,7 +593,9 @@ def default_strict(self, value: T) -> Cap:
for arg, val in value.items(): # type: ignore
try:
t = self._args[arg].type
valid, _, _ = VALIDATOR.extract(t, val, cvt=False).unwrap()
valid, _, _ = self._val_validator.extract(
t, val, cvt=False
).unwrap()
if valid:
self._args[arg].val = Option.Some(val)
else:
Expand Down Expand Up @@ -665,7 +668,7 @@ def parse(
self._before_parse()

if validator is None:
validator = VALIDATOR
validator = self._val_validator

validator.delimiter = self._delimiter
validator.attributes = self._attributes
Expand Down Expand Up @@ -715,14 +718,14 @@ def _is_flag(t: Type) -> bool:

parsed_map: Dict[str, _ParsedVal] = {}
# extract process
for name, val in out["options"].items():
for name, val in out.options.items():
key = self._get_key(name)
parsed: _ParsedVal = parsed_map.get(
key,
{
"val": [],
"default_val": Option.NONE(),
"queue_type": None,
"queue_type": ParsedQueueType.NONE,
},
)
opt = self._args[key] # TODO:
Expand All @@ -734,13 +737,17 @@ def _is_flag(t: Type) -> bool:
temp_delimiter = opt.local_delimiter

try:
valid, v_got, err = validator.extract(
res = validator.extract(
t,
v,
cvt=True,
temp_delimiter=temp_delimiter,
leave_scope=True,
).unwrap()
)
if not res.is_valid():
# TODO: err handling
raise res._error.unwrap()
valid, v_got, err = res.unwrap()
except ValidatorNotFound as err:
self._panic(
f"validator for type {colorize_text_t_type(err.type)} not found",
Expand Down Expand Up @@ -820,4 +827,4 @@ def _is_flag(t: Type) -> bool:
else:
parsed_map[key]["default_val"] = Option.Some(None)

return Parsed(self._argstype, out["args"], parsed_map, args_obj)
return Parsed(self._argstype, out.argv, parsed_map, args_obj)
4 changes: 2 additions & 2 deletions typed_cap/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,8 @@ class ArgsParserOptions(TypedDict, total=False):
disable_hyphen_conversion: bool


class ArgsParserResults(TypedDict):
args: List[str]
class ArgsParserResults(NamedTuple):
argv: List[str]
options: Dict[str, List[Union[str, bool]]]


Expand Down
Loading

0 comments on commit 6f423b2

Please sign in to comment.