convert.py 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. from __future__ import annotations
  2. import logging
  3. import os
  4. from typing import ClassVar
  5. LOGGER = logging.getLogger(__name__)
  6. class TypeData:
  7. def __init__(self, default_type, as_type) -> None:
  8. self.default_type = default_type
  9. self.as_type = as_type
  10. def __repr__(self) -> str:
  11. return f"{self.__class__.__name__}(base={self.default_type}, as={self.as_type})"
  12. def convert(self, value):
  13. return self.default_type(value)
  14. class BoolType(TypeData):
  15. BOOLEAN_STATES: ClassVar[dict[str, bool]] = {
  16. "1": True,
  17. "yes": True,
  18. "true": True,
  19. "on": True,
  20. "0": False,
  21. "no": False,
  22. "false": False,
  23. "off": False,
  24. }
  25. def convert(self, value):
  26. if value.lower() not in self.BOOLEAN_STATES:
  27. msg = f"Not a boolean: {value}"
  28. raise ValueError(msg)
  29. return self.BOOLEAN_STATES[value.lower()]
  30. class NoneType(TypeData):
  31. def convert(self, value):
  32. if not value:
  33. return None
  34. return str(value)
  35. class ListType(TypeData):
  36. def _validate(self):
  37. """no op."""
  38. def convert(self, value, flatten=True): # noqa: ARG002, FBT002
  39. values = self.split_values(value)
  40. result = []
  41. for a_value in values:
  42. sub_values = a_value.split(os.pathsep)
  43. result.extend(sub_values)
  44. return [self.as_type(i) for i in result]
  45. def split_values(self, value):
  46. """
  47. Split the provided value into a list.
  48. First this is done by newlines. If there were no newlines in the text,
  49. then we next try to split by comma.
  50. """
  51. if isinstance(value, (str, bytes)):
  52. # Use `splitlines` rather than a custom check for whether there is
  53. # more than one line. This ensures that the full `splitlines()`
  54. # logic is supported here.
  55. values = value.splitlines()
  56. if len(values) <= 1:
  57. values = value.split(",")
  58. values = filter(None, [x.strip() for x in values])
  59. else:
  60. values = list(value)
  61. return values
  62. def convert(value, as_type, source):
  63. """Convert the value as a given type where the value comes from the given source."""
  64. try:
  65. return as_type.convert(value)
  66. except Exception as exception:
  67. LOGGER.warning("%s failed to convert %r as %r because %r", source, value, as_type, exception)
  68. raise
  69. _CONVERT = {bool: BoolType, type(None): NoneType, list: ListType}
  70. def get_type(action):
  71. default_type = type(action.default)
  72. as_type = default_type if action.type is None else action.type
  73. return _CONVERT.get(default_type, TypeData)(default_type, as_type)
  74. __all__ = [
  75. "convert",
  76. "get_type",
  77. ]