diff --git a/README.md b/README.md index f5a9e3a..3450f48 100644 --- a/README.md +++ b/README.md @@ -68,10 +68,21 @@ if __name__ == "__main__": parse_args_and_run(func) ``` -See the docstring for `auto_argparse.make_parser` for more details. +See the docstring for [`auto_argparse.make_parser`] for more details. + +## Supported Types + +The following types should be fully supported, but any annotation `T` should work if `T(cli_string)` gives the desired value, where `cli_string` is the string entered at the command line. +* `int` +* `float` +* `str` +* `bool` +* `List[T]`, `Sequence[T]` where `T` is any of (`int`, `float`, `str`) or as described in the paragraph above +* `Optional[T]` where `T` could additionally be `List` or `Sequence`. Note that there's no way to explicitly enter a `None` value from the command-line though it can be the default value. ## Alternatives * [`defopt`] is a more mature library which has the same aims as `auto-argparse` but with a slightly different implementation (e.g. `auto-argparse` adds short names, makes all arguments keyword-only, and puts the part of the doc string for each argument into its help string) +[`auto_argparse.make_parser`]: auto_argparse/auto_argparse.py [`defopt`]: https://github.com/anntzer/defopt diff --git a/auto_argparse/auto_argparse.py b/auto_argparse/auto_argparse.py index 0ae3e93..448f76b 100644 --- a/auto_argparse/auto_argparse.py +++ b/auto_argparse/auto_argparse.py @@ -2,7 +2,7 @@ import re from argparse import ArgumentParser, ArgumentTypeError from collections.abc import Sequence -from typing import Callable, Optional, TypeVar, Union +from typing import Callable, TypeVar, Union T = TypeVar("T") @@ -17,8 +17,8 @@ def make_parser(func: Callable, add_short_args: bool = True) -> ArgumentParser: * types: use type annotations. The only supported types from `typing` are listed below. * `bool` uses `str2bool`; values have to be entered like `--debug True` * `List[type]` and `Sequence[type]` will use `nargs="+", type=type`. - * `Optional[type]` converts an input `s` to None if `s.strip().lower() == "none"`. - Any other inputs are converted normally using `type`. + * `Optional[type]` converts inputs using `type`; a `None` is only possible if this + is the default value. * defaults: just use defaults * required params: this is just the parameters with no default values @@ -51,12 +51,18 @@ def make_parser(func: Callable, add_short_args: bool = True) -> ArgumentParser: kwargs = {} anno = param.annotation origin = getattr(anno, "__origin__", None) - if origin == list or origin == Sequence: # e.g. List[int] + if origin in (list, Sequence): # e.g. List[int] kwargs["type"] = anno.__args__[0] kwargs["nargs"] = "+" elif origin == Union: # Optional[T] is converted to Union[T, None] if len(anno.__args__) == 2 and anno.__args__[1] == type(None): - kwargs["type"] = make_optional(anno.__args__[0]) + anno = anno.__args__[0] + origin = getattr(anno, "__origin__", None) + if origin in (list, Sequence): + kwargs["nargs"] = "+" + kwargs["type"] = anno.__args__[0] + else: + kwargs["type"] = anno else: if anno is not param.empty: if anno == bool: @@ -102,28 +108,3 @@ def str2bool(v: str) -> bool: if v == "false": return False raise ArgumentTypeError("Boolean value expected.") - - -def make_optional(type_: Callable[[str], T]) -> Callable[[str], Optional[T]]: - """ - Convert `type_` into a callable which returns an instance of type T or None. - For an input `s`, if `s.strip().lower() == "none"` then None is returned. - Otherwise, `type_(s)` is returned. - """ - - def parse_to_type(cli_string: str) -> Optional[T]: - return None if cli_string.strip().lower() == "none" else type_(cli_string) - - # If there's an error parsing the type, argparse says - # "invalid value: ". Saying "invalid parse_to_type value" is - # confusing, so we rename the function based on the type for clarity. - # Handle a few special cases to make things look nicer (this way we get, e.g., - # "invalid int value" instead of "invalid value"). - pretty_strings = { - int: "int", - float: "float", - str: "str", - } - parse_to_type.__name__ = pretty_strings.get(type_, str(type_)) - - return parse_to_type diff --git a/setup.py b/setup.py index 121c4a9..68308c0 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setup( name="auto_argparse", - version="0.0.4", + version="0.0.5", url="https://github.com/neighthan/auto-argparse", author="Nathan Hunt", author_email="neighthan.hunt@gmail.com", diff --git a/tests/test.py b/tests/test.py index 1bd3996..5fc6f40 100644 --- a/tests/test.py +++ b/tests/test.py @@ -5,7 +5,7 @@ def func( x: int, - things: Sequence[int], + things: Optional[Sequence[int]] = None, y: str = "test", z: bool = False, maybe: Optional[float] = 5,