codekingpro/portable-devtools
114k
1# SPDX-License-Identifier: MIT2 3"""4Commonly useful converters.5"""6 7import typing8 9from ._compat import _AnnotationExtractor10from ._make import NOTHING, Converter, Factory, pipe11 12 13__all__ = [14 "default_if_none",15 "optional",16 "pipe",17 "to_bool",18]19 20 21def optional(converter):22 """23 A converter that allows an attribute to be optional. An optional attribute24 is one which can be set to `None`.25 26 Type annotations will be inferred from the wrapped converter's, if it has27 any.28 29 Args:30 converter (typing.Callable):31 the converter that is used for non-`None` values.32 33 .. versionadded:: 17.1.034 """35 36 if isinstance(converter, Converter):37 38 def optional_converter(val, inst, field):39 if val is None:40 return None41 return converter(val, inst, field)42 43 else:44 45 def optional_converter(val):46 if val is None:47 return None48 return converter(val)49 50 xtr = _AnnotationExtractor(converter)51 52 t = xtr.get_first_param_type()53 if t:54 optional_converter.__annotations__["val"] = typing.Optional[t]55 56 rt = xtr.get_return_type()57 if rt:58 optional_converter.__annotations__["return"] = typing.Optional[rt]59 60 if isinstance(converter, Converter):61 return Converter(optional_converter, takes_self=True, takes_field=True)62 63 return optional_converter64 65 66def default_if_none(default=NOTHING, factory=None):67 """68 A converter that allows to replace `None` values by *default* or the result69 of *factory*.70 71 Args:72 default:73 Value to be used if `None` is passed. Passing an instance of74 `attrs.Factory` is supported, however the ``takes_self`` option is75 *not*.76 77 factory (typing.Callable):78 A callable that takes no parameters whose result is used if `None`79 is passed.80 81 Raises:82 TypeError: If **neither** *default* or *factory* is passed.83 84 TypeError: If **both** *default* and *factory* are passed.85 86 ValueError:87 If an instance of `attrs.Factory` is passed with88 ``takes_self=True``.89 90 .. versionadded:: 18.2.091 """92 if default is NOTHING and factory is None:93 msg = "Must pass either `default` or `factory`."94 raise TypeError(msg)95 96 if default is not NOTHING and factory is not None:97 msg = "Must pass either `default` or `factory` but not both."98 raise TypeError(msg)99 100 if factory is not None:101 default = Factory(factory)102 103 if isinstance(default, Factory):104 if default.takes_self:105 msg = "`takes_self` is not supported by default_if_none."106 raise ValueError(msg)107 108 def default_if_none_converter(val):109 if val is not None:110 return val111 112 return default.factory()113 114 else:115 116 def default_if_none_converter(val):117 if val is not None:118 return val119 120 return default121 122 return default_if_none_converter123 124 125def to_bool(val):126 """127 Convert "boolean" strings (for example, from environment variables) to real128 booleans.129 130 Values mapping to `True`:131 132 - ``True``133 - ``"true"`` / ``"t"``134 - ``"yes"`` / ``"y"``135 - ``"on"``136 - ``"1"``137 - ``1``138 139 Values mapping to `False`:140 141 - ``False``142 - ``"false"`` / ``"f"``143 - ``"no"`` / ``"n"``144 - ``"off"``145 - ``"0"``146 - ``0``147 148 Raises:149 ValueError: For any other value.150 151 .. versionadded:: 21.3.0152 """153 if isinstance(val, str):154 val = val.lower()155 156 if val in (True, "true", "t", "yes", "y", "on", "1", 1):157 return True158 if val in (False, "false", "f", "no", "n", "off", "0", 0):159 return False160 161 msg = f"Cannot convert value to bool: {val!r}"162 raise ValueError(msg)163 