test_traitlets_enum.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381
  1. # pylint: disable=missing-docstring, too-few-public-methods
  2. """
  3. Test the trait-type ``UseEnum``.
  4. """
  5. from __future__ import annotations
  6. import enum
  7. import unittest
  8. from traitlets import CaselessStrEnum, Enum, FuzzyEnum, HasTraits, TraitError, UseEnum
  9. # -----------------------------------------------------------------------------
  10. # TEST SUPPORT:
  11. # -----------------------------------------------------------------------------
  12. class Color(enum.Enum):
  13. red = 1
  14. green = 2
  15. blue = 3
  16. yellow = 4
  17. class OtherColor(enum.Enum):
  18. red = 0
  19. green = 1
  20. class CSColor(enum.Enum):
  21. red = 1
  22. Green = 2
  23. BLUE = 3
  24. YeLLoW = 4
  25. color_choices = "red Green BLUE YeLLoW".split()
  26. # -----------------------------------------------------------------------------
  27. # TESTSUITE:
  28. # -----------------------------------------------------------------------------
  29. class TestUseEnum(unittest.TestCase):
  30. # pylint: disable=invalid-name
  31. class Example(HasTraits):
  32. color = UseEnum(Color, help="Color enum")
  33. def test_assign_enum_value(self):
  34. example = self.Example()
  35. example.color = Color.green
  36. self.assertEqual(example.color, Color.green)
  37. def test_assign_all_enum_values(self):
  38. # pylint: disable=no-member
  39. enum_values = list(Color.__members__.values())
  40. for value in enum_values:
  41. self.assertIsInstance(value, Color)
  42. example = self.Example()
  43. example.color = value
  44. self.assertEqual(example.color, value)
  45. self.assertIsInstance(value, Color)
  46. def test_assign_enum_value__with_other_enum_raises_error(self):
  47. example = self.Example()
  48. with self.assertRaises(TraitError):
  49. example.color = OtherColor.green
  50. def test_assign_enum_name_1(self):
  51. # -- CONVERT: string => Enum value (item)
  52. example = self.Example()
  53. example.color = "red"
  54. self.assertEqual(example.color, Color.red)
  55. def test_assign_enum_value_name(self):
  56. # -- CONVERT: string => Enum value (item)
  57. # pylint: disable=no-member
  58. enum_names = [enum_val.name for enum_val in Color.__members__.values()]
  59. for value in enum_names:
  60. self.assertIsInstance(value, str)
  61. example = self.Example()
  62. enum_value = Color.__members__.get(value)
  63. example.color = value
  64. self.assertIs(example.color, enum_value)
  65. self.assertEqual(example.color.name, value) # type:ignore
  66. def test_assign_scoped_enum_value_name(self):
  67. # -- CONVERT: string => Enum value (item)
  68. scoped_names = ["Color.red", "Color.green", "Color.blue", "Color.yellow"]
  69. for value in scoped_names:
  70. example = self.Example()
  71. example.color = value
  72. self.assertIsInstance(example.color, Color)
  73. self.assertEqual(str(example.color), value)
  74. def test_assign_bad_enum_value_name__raises_error(self):
  75. # -- CONVERT: string => Enum value (item)
  76. bad_enum_names = ["UNKNOWN_COLOR", "RED", "Green", "blue2"]
  77. for value in bad_enum_names:
  78. example = self.Example()
  79. with self.assertRaises(TraitError):
  80. example.color = value
  81. def test_assign_enum_value_number_1(self):
  82. # -- CONVERT: number => Enum value (item)
  83. example = self.Example()
  84. example.color = 1 # == Color.red.value
  85. example.color = Color.red.value
  86. self.assertEqual(example.color, Color.red)
  87. def test_assign_enum_value_number(self):
  88. # -- CONVERT: number => Enum value (item)
  89. # pylint: disable=no-member
  90. enum_numbers = [enum_val.value for enum_val in Color.__members__.values()]
  91. for value in enum_numbers:
  92. self.assertIsInstance(value, int)
  93. example = self.Example()
  94. example.color = value
  95. self.assertIsInstance(example.color, Color)
  96. self.assertEqual(example.color.value, value) # type:ignore
  97. def test_assign_bad_enum_value_number__raises_error(self):
  98. # -- CONVERT: number => Enum value (item)
  99. bad_numbers = [-1, 0, 5]
  100. for value in bad_numbers:
  101. self.assertIsInstance(value, int)
  102. assert UseEnum(Color).select_by_number(value, None) is None
  103. example = self.Example()
  104. with self.assertRaises(TraitError):
  105. example.color = value
  106. def test_ctor_without_default_value(self):
  107. # -- IMPLICIT: default_value = Color.red (first enum-value)
  108. class Example2(HasTraits):
  109. color = UseEnum(Color)
  110. example = Example2()
  111. self.assertEqual(example.color, Color.red)
  112. def test_ctor_with_default_value_as_enum_value(self):
  113. # -- CONVERT: number => Enum value (item)
  114. class Example2(HasTraits):
  115. color = UseEnum(Color, default_value=Color.green)
  116. example = Example2()
  117. self.assertEqual(example.color, Color.green)
  118. def test_ctor_with_default_value_none_and_not_allow_none(self):
  119. # -- IMPLICIT: default_value = Color.red (first enum-value)
  120. class Example2(HasTraits):
  121. color1 = UseEnum(Color, default_value=None, allow_none=False)
  122. color2 = UseEnum(Color, default_value=None)
  123. example = Example2()
  124. self.assertEqual(example.color1, Color.red)
  125. self.assertEqual(example.color2, Color.red)
  126. def test_ctor_with_default_value_none_and_allow_none(self):
  127. class Example2(HasTraits):
  128. color1 = UseEnum(Color, default_value=None, allow_none=True)
  129. color2 = UseEnum(Color, allow_none=True)
  130. example = Example2()
  131. self.assertIs(example.color1, None)
  132. self.assertIs(example.color2, None)
  133. def test_assign_none_without_allow_none_resets_to_default_value(self):
  134. class Example2(HasTraits):
  135. color1 = UseEnum(Color, allow_none=False)
  136. color2 = UseEnum(Color)
  137. example = Example2()
  138. example.color1 = None
  139. example.color2 = None
  140. self.assertIs(example.color1, Color.red)
  141. self.assertIs(example.color2, Color.red)
  142. def test_assign_none_to_enum_or_none(self):
  143. class Example2(HasTraits):
  144. color = UseEnum(Color, allow_none=True)
  145. example = Example2()
  146. example.color = None
  147. self.assertIs(example.color, None)
  148. def test_assign_bad_value_with_to_enum_or_none(self):
  149. class Example2(HasTraits):
  150. color = UseEnum(Color, allow_none=True)
  151. example = Example2()
  152. with self.assertRaises(TraitError):
  153. example.color = "BAD_VALUE"
  154. def test_info(self):
  155. choices = color_choices
  156. class Example(HasTraits):
  157. enum1 = Enum(choices, allow_none=False)
  158. enum2 = CaselessStrEnum(choices, allow_none=False)
  159. enum3 = FuzzyEnum(choices, allow_none=False)
  160. enum4 = UseEnum(CSColor, allow_none=False)
  161. for i in range(1, 5):
  162. attr = "enum%s" % i
  163. enum = getattr(Example, attr)
  164. enum.allow_none = True
  165. info = enum.info()
  166. self.assertEqual(len(info.split(", ")), len(choices), info.split(", "))
  167. self.assertIn("or None", info)
  168. info = enum.info_rst()
  169. self.assertEqual(len(info.split("|")), len(choices), info.split("|"))
  170. self.assertIn("or `None`", info)
  171. # Check no single `\` exists.
  172. self.assertNotRegex(info, r"\b\\\b")
  173. enum.allow_none = False
  174. info = enum.info()
  175. self.assertEqual(len(info.split(", ")), len(choices), info.split(", "))
  176. self.assertNotIn("None", info)
  177. info = enum.info_rst()
  178. self.assertEqual(len(info.split("|")), len(choices), info.split("|"))
  179. self.assertNotIn("None", info)
  180. # Check no single `\` exists.
  181. self.assertNotRegex(info, r"\b\\\b")
  182. # -----------------------------------------------------------------------------
  183. # TESTSUITE:
  184. # -----------------------------------------------------------------------------
  185. class TestFuzzyEnum(unittest.TestCase):
  186. # Check mostly `validate()`, Ctor must be checked on generic `Enum`
  187. # or `CaselessStrEnum`.
  188. def test_search_all_prefixes__overwrite(self):
  189. class FuzzyExample(HasTraits):
  190. color = FuzzyEnum(color_choices, help="Color enum")
  191. example = FuzzyExample()
  192. for color in color_choices:
  193. for wlen in range(1, len(color)):
  194. value = color[:wlen]
  195. example.color = value
  196. self.assertEqual(example.color, color)
  197. example.color = value.upper()
  198. self.assertEqual(example.color, color)
  199. example.color = value.lower()
  200. self.assertEqual(example.color, color)
  201. def test_search_all_prefixes__ctor(self):
  202. class FuzzyExample(HasTraits):
  203. color = FuzzyEnum(color_choices, help="Color enum")
  204. for color in color_choices:
  205. for wlen in range(1, len(color)):
  206. value = color[:wlen]
  207. example = FuzzyExample()
  208. example.color = value
  209. self.assertEqual(example.color, color)
  210. example = FuzzyExample()
  211. example.color = value.upper()
  212. self.assertEqual(example.color, color)
  213. example = FuzzyExample()
  214. example.color = value.lower()
  215. self.assertEqual(example.color, color)
  216. def test_search_substrings__overwrite(self):
  217. class FuzzyExample(HasTraits):
  218. color = FuzzyEnum(color_choices, help="Color enum", substring_matching=True)
  219. example = FuzzyExample()
  220. for color in color_choices:
  221. for wlen in range(2):
  222. value = color[wlen:]
  223. example.color = value
  224. self.assertEqual(example.color, color)
  225. example.color = value.upper()
  226. self.assertEqual(example.color, color)
  227. example.color = value.lower()
  228. self.assertEqual(example.color, color)
  229. def test_search_substrings__ctor(self):
  230. class FuzzyExample(HasTraits):
  231. color = FuzzyEnum(color_choices, help="Color enum", substring_matching=True)
  232. color = color_choices[-1] # 'YeLLoW'
  233. for end in (-1, len(color)):
  234. for start in range(1, len(color) - 2):
  235. value = color[start:end]
  236. example = FuzzyExample()
  237. example.color = value
  238. self.assertEqual(example.color, color)
  239. example = FuzzyExample()
  240. example.color = value.upper()
  241. self.assertEqual(example.color, color)
  242. def test_assign_other_raises(self):
  243. def new_trait_class(case_sensitive, substring_matching):
  244. class Example(HasTraits):
  245. color = FuzzyEnum(
  246. color_choices,
  247. case_sensitive=case_sensitive,
  248. substring_matching=substring_matching,
  249. )
  250. return Example
  251. example = new_trait_class(case_sensitive=False, substring_matching=False)()
  252. with self.assertRaises(TraitError):
  253. example.color = ""
  254. with self.assertRaises(TraitError):
  255. example.color = "BAD COLOR"
  256. with self.assertRaises(TraitError):
  257. example.color = "ed"
  258. example = new_trait_class(case_sensitive=True, substring_matching=False)()
  259. with self.assertRaises(TraitError):
  260. example.color = ""
  261. with self.assertRaises(TraitError):
  262. example.color = "Red" # not 'red'
  263. example = new_trait_class(case_sensitive=True, substring_matching=True)()
  264. with self.assertRaises(TraitError):
  265. example.color = ""
  266. with self.assertRaises(TraitError):
  267. example.color = "BAD COLOR"
  268. with self.assertRaises(TraitError):
  269. example.color = "green" # not 'Green'
  270. with self.assertRaises(TraitError):
  271. example.color = "lue" # not (b)'LUE'
  272. with self.assertRaises(TraitError):
  273. example.color = "lUE" # not (b)'LUE'
  274. example = new_trait_class(case_sensitive=False, substring_matching=True)()
  275. with self.assertRaises(TraitError):
  276. example.color = ""
  277. with self.assertRaises(TraitError):
  278. example.color = "BAD COLOR"
  279. def test_ctor_with_default_value(self):
  280. def new_trait_class(default_value, case_sensitive, substring_matching):
  281. class Example(HasTraits):
  282. color = FuzzyEnum(
  283. color_choices,
  284. default_value=default_value,
  285. case_sensitive=case_sensitive,
  286. substring_matching=substring_matching,
  287. )
  288. return Example
  289. for color in color_choices:
  290. example = new_trait_class(color, False, False)()
  291. self.assertEqual(example.color, color)
  292. example = new_trait_class(color.upper(), False, False)()
  293. self.assertEqual(example.color, color)
  294. color = color_choices[-1] # 'YeLLoW'
  295. example = new_trait_class(color, True, False)()
  296. self.assertEqual(example.color, color)
  297. # FIXME: default value not validated!
  298. # with self.assertRaises(TraitError):
  299. # example = new_trait_class(color.lower(), True, False)