Source code for pythonwrench.argparse

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from functools import partial
from typing import (
    Any,
    Callable,
    Iterable,
    List,
    Optional,
    Type,
    TypeVar,
    Union,
    get_args,
    get_origin,
)

from pythonwrench.typing.classes import NoneType

T = TypeVar("T")


DEFAULT_TRUE_VALUES = ("True", "t", "yes", "y", "1")
DEFAULT_FALSE_VALUES = ("False", "f", "no", "n", "0")
DEFAULT_NONE_VALUES = ("None", "null")


[docs] def parse_to( target_type: Type[T], *, case_sensitive: bool = False, true_values: Union[str, Iterable[str]] = DEFAULT_TRUE_VALUES, false_values: Union[str, Iterable[str]] = DEFAULT_FALSE_VALUES, none_values: Union[str, Iterable[str]] = DEFAULT_NONE_VALUES, ) -> Callable[[str], T]: """Returns a callable that convert string value to target type safely. Intended for argparse arguments. """ return partial( str_to_type, target_type=target_type, case_sensitive=case_sensitive, true_values=true_values, false_values=false_values, none_values=none_values, )
[docs] def str_to_type( x: str, target_type: Type[T], *, case_sensitive: bool = False, true_values: Union[str, Iterable[str]] = DEFAULT_TRUE_VALUES, false_values: Union[str, Iterable[str]] = DEFAULT_FALSE_VALUES, none_values: Union[str, Iterable[str]] = DEFAULT_NONE_VALUES, ) -> T: """Convert string values to target type safely. Intended for argparse arguments. - True values: 'True', 'T', 'yes', 'y', '1'. - False values: 'False', 'F', 'no', 'n', '0'. - None values: 'None', 'null' - Other raises ValueError. """ result = _str_to_type_impl( x, target_type, case_sensitive=case_sensitive, true_values=true_values, false_values=false_values, none_values=none_values, ) if isinstance(result, Exception): raise result else: return result
[docs] def str_to_bool( x: str, *, case_sensitive: bool = False, true_values: Union[str, Iterable[str]] = DEFAULT_TRUE_VALUES, false_values: Union[str, Iterable[str]] = DEFAULT_FALSE_VALUES, ) -> bool: """Convert string values to bool safely. Intended for argparse arguments. - True values: 'True', 'T', 'yes', 'y', '1'. - False values: 'False', 'F', 'no', 'n', '0'. - Other raises ValueError. """ return str_to_type( x, bool, case_sensitive=case_sensitive, true_values=true_values, false_values=false_values, )
[docs] def str_to_none( x: str, *, case_sensitive: bool = False, none_values: Union[str, Iterable[str]] = DEFAULT_NONE_VALUES, ) -> None: """Convert string values to None safely. Intended for argparse arguments. - None values: 'None', 'null' - Other raises ValueError. """ return str_to_type( x, NoneType, case_sensitive=case_sensitive, none_values=none_values )
[docs] def str_to_optional_bool( x: str, *, case_sensitive: bool = False, true_values: Union[str, Iterable[str]] = DEFAULT_TRUE_VALUES, false_values: Union[str, Iterable[str]] = DEFAULT_FALSE_VALUES, none_values: Union[str, Iterable[str]] = DEFAULT_NONE_VALUES, ) -> Optional[bool]: """Convert string values to optional bool safely. Intended for argparse arguments. - True values: 'True', 'T', 'yes', 'y', '1'. - False values: 'False', 'F', 'no', 'n', '0'. - None values: 'None', 'null' - Other raises ValueError. """ return str_to_type( x, Optional[bool], case_sensitive=case_sensitive, none_values=none_values )
[docs] def str_to_optional_float( x: str, *, case_sensitive: bool = False, none_values: Union[str, Iterable[str]] = DEFAULT_NONE_VALUES, ) -> Optional[float]: """Convert string values to optional float safely. Intended for argparse arguments.""" return str_to_type( x, Optional[float], case_sensitive=case_sensitive, none_values=none_values )
[docs] def str_to_optional_int( x: str, *, case_sensitive: bool = False, none_values: Union[str, Iterable[str]] = DEFAULT_NONE_VALUES, ) -> Optional[int]: """Convert string values to optional int safely. Intended for argparse arguments.""" return str_to_type( x, Optional[int], case_sensitive=case_sensitive, none_values=none_values )
[docs] def str_to_optional_str( x: str, *, case_sensitive: bool = False, none_values: Union[str, Iterable[str]] = DEFAULT_NONE_VALUES, ) -> Optional[str]: """Convert string values to optional str safely. Intended for argparse arguments.""" return str_to_type( x, Optional[str], case_sensitive=case_sensitive, none_values=none_values )
def _str_to_type_impl( x: str, target_type: Type[T], *, case_sensitive: bool = False, true_values: Union[str, Iterable[str]] = DEFAULT_TRUE_VALUES, false_values: Union[str, Iterable[str]] = DEFAULT_FALSE_VALUES, none_values: Union[str, Iterable[str]] = DEFAULT_NONE_VALUES, ) -> Union[T, Exception]: if target_type in (str, int, float, None, NoneType, bool): return _str_to_scalar_impl( x, target_type, case_sensitive=case_sensitive, true_values=true_values, false_values=false_values, none_values=none_values, ) origin = get_origin(target_type) if getattr(target_type, "__name__", None) == "Optional": args = (None,) + get_args(target_type) elif origin == Union or origin.__name__ in ("Union", "UnionType"): # type: ignore args = get_args(target_type) else: msg = f"Invalid argument {target_type=}. (unsupported type)" raise ValueError(msg) # str is always at the end def key_fn(xi: Any) -> int: if xi is str: return 1 else: return 0 args = sorted(args, key=key_fn) for arg in args: result = _str_to_type_impl( x, arg, # type: ignore case_sensitive=case_sensitive, true_values=true_values, false_values=false_values, ) if not isinstance(result, Exception): return result return ValueError(f"Invalid argument {x=} with {target_type=}.") def _str_to_scalar_impl( x: str, target_type: Type[T], *, case_sensitive: bool = False, true_values: Union[str, Iterable[str]] = DEFAULT_TRUE_VALUES, false_values: Union[str, Iterable[str]] = DEFAULT_FALSE_VALUES, none_values: Union[str, Iterable[str]] = DEFAULT_NONE_VALUES, ) -> Any: if target_type is str: return x elif target_type is int: try: return int(x) except ValueError as err: return err elif target_type is float: try: return float(x) except ValueError as err: return err elif target_type in (None, NoneType): return _str_to_none_impl( x, case_sensitive=case_sensitive, none_values=none_values ) elif target_type is bool: return _str_to_bool_impl( x, case_sensitive=case_sensitive, true_values=true_values, false_values=false_values, ) else: raise ValueError(f"Invalid argument {target_type=}. (unsupported type)") def _str_to_bool_impl( x: str, *, case_sensitive: bool = False, true_values: Union[str, Iterable[str]] = DEFAULT_TRUE_VALUES, false_values: Union[str, Iterable[str]] = DEFAULT_FALSE_VALUES, ) -> Union[bool, Exception]: true_values = _sanitize_values(true_values) if _str_in(x, true_values, case_sensitive): return True false_values = _sanitize_values(false_values) if _str_in(x, false_values, case_sensitive): return False values = tuple(true_values + false_values) err = ValueError(f"Invalid argument '{x}'. (expected one of {values})") return err def _str_to_none_impl( x: str, *, case_sensitive: bool = False, none_values: Union[str, Iterable[str]] = DEFAULT_NONE_VALUES, ) -> Union[None, Exception]: """Convert string values to None safely. Intended for argparse arguments. - None values: 'None', 'null' - Other raises ValueError. """ none_values = _sanitize_values(none_values) if _str_in(x, none_values, case_sensitive): return None values = tuple(none_values) err = ValueError(f"Invalid argument '{x}'. (expected one of {values})") return err def _sanitize_values(values: Union[str, Iterable[str]]) -> List[str]: if isinstance(values, str): values = [values] else: values = list(values) return values def _str_in(x: str, values: List[str], case_sensitive: bool) -> bool: if case_sensitive: return x in values else: return x.lower() in map(str.lower, values)