converters.py (3622B)
1 # SPDX-License-Identifier: MIT 2 3 """ 4 Commonly useful converters. 5 """ 6 7 8 import typing 9 10 from ._compat import _AnnotationExtractor 11 from ._make import NOTHING, Factory, pipe 12 13 14 __all__ = [ 15 "default_if_none", 16 "optional", 17 "pipe", 18 "to_bool", 19 ] 20 21 22 def optional(converter): 23 """ 24 A converter that allows an attribute to be optional. An optional attribute 25 is one which can be set to ``None``. 26 27 Type annotations will be inferred from the wrapped converter's, if it 28 has any. 29 30 :param callable converter: the converter that is used for non-``None`` 31 values. 32 33 .. versionadded:: 17.1.0 34 """ 35 36 def optional_converter(val): 37 if val is None: 38 return None 39 return converter(val) 40 41 xtr = _AnnotationExtractor(converter) 42 43 t = xtr.get_first_param_type() 44 if t: 45 optional_converter.__annotations__["val"] = typing.Optional[t] 46 47 rt = xtr.get_return_type() 48 if rt: 49 optional_converter.__annotations__["return"] = typing.Optional[rt] 50 51 return optional_converter 52 53 54 def default_if_none(default=NOTHING, factory=None): 55 """ 56 A converter that allows to replace ``None`` values by *default* or the 57 result of *factory*. 58 59 :param default: Value to be used if ``None`` is passed. Passing an instance 60 of `attrs.Factory` is supported, however the ``takes_self`` option 61 is *not*. 62 :param callable factory: A callable that takes no parameters whose result 63 is used if ``None`` is passed. 64 65 :raises TypeError: If **neither** *default* or *factory* is passed. 66 :raises TypeError: If **both** *default* and *factory* are passed. 67 :raises ValueError: If an instance of `attrs.Factory` is passed with 68 ``takes_self=True``. 69 70 .. versionadded:: 18.2.0 71 """ 72 if default is NOTHING and factory is None: 73 msg = "Must pass either `default` or `factory`." 74 raise TypeError(msg) 75 76 if default is not NOTHING and factory is not None: 77 msg = "Must pass either `default` or `factory` but not both." 78 raise TypeError(msg) 79 80 if factory is not None: 81 default = Factory(factory) 82 83 if isinstance(default, Factory): 84 if default.takes_self: 85 msg = "`takes_self` is not supported by default_if_none." 86 raise ValueError(msg) 87 88 def default_if_none_converter(val): 89 if val is not None: 90 return val 91 92 return default.factory() 93 94 else: 95 96 def default_if_none_converter(val): 97 if val is not None: 98 return val 99 100 return default 101 102 return default_if_none_converter 103 104 105 def to_bool(val): 106 """ 107 Convert "boolean" strings (e.g., from env. vars.) to real booleans. 108 109 Values mapping to :code:`True`: 110 111 - :code:`True` 112 - :code:`"true"` / :code:`"t"` 113 - :code:`"yes"` / :code:`"y"` 114 - :code:`"on"` 115 - :code:`"1"` 116 - :code:`1` 117 118 Values mapping to :code:`False`: 119 120 - :code:`False` 121 - :code:`"false"` / :code:`"f"` 122 - :code:`"no"` / :code:`"n"` 123 - :code:`"off"` 124 - :code:`"0"` 125 - :code:`0` 126 127 :raises ValueError: for any other value. 128 129 .. versionadded:: 21.3.0 130 """ 131 if isinstance(val, str): 132 val = val.lower() 133 truthy = {True, "true", "t", "yes", "y", "on", "1", 1} 134 falsy = {False, "false", "f", "no", "n", "off", "0", 0} 135 try: 136 if val in truthy: 137 return True 138 if val in falsy: 139 return False 140 except TypeError: 141 # Raised when "val" is not hashable (e.g., lists) 142 pass 143 msg = f"Cannot convert value to bool: {val}" 144 raise ValueError(msg)