You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

163 lines
3.8 KiB

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