pyglove 0.4.5.dev20240318__py3-none-any.whl → 0.4.5.dev202501132210__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. pyglove/core/__init__.py +54 -20
  2. pyglove/core/coding/__init__.py +42 -0
  3. pyglove/core/coding/errors.py +111 -0
  4. pyglove/core/coding/errors_test.py +98 -0
  5. pyglove/core/coding/execution.py +309 -0
  6. pyglove/core/coding/execution_test.py +333 -0
  7. pyglove/core/{object_utils/codegen.py → coding/function_generation.py} +10 -4
  8. pyglove/core/{object_utils/codegen_test.py → coding/function_generation_test.py} +5 -7
  9. pyglove/core/coding/parsing.py +153 -0
  10. pyglove/core/coding/parsing_test.py +150 -0
  11. pyglove/core/coding/permissions.py +100 -0
  12. pyglove/core/coding/permissions_test.py +93 -0
  13. pyglove/core/geno/base.py +54 -41
  14. pyglove/core/geno/base_test.py +2 -4
  15. pyglove/core/geno/categorical.py +37 -28
  16. pyglove/core/geno/custom.py +19 -16
  17. pyglove/core/geno/numerical.py +20 -17
  18. pyglove/core/geno/space.py +4 -5
  19. pyglove/core/hyper/base.py +6 -6
  20. pyglove/core/hyper/categorical.py +94 -55
  21. pyglove/core/hyper/custom.py +7 -7
  22. pyglove/core/hyper/custom_test.py +9 -10
  23. pyglove/core/hyper/derived.py +30 -22
  24. pyglove/core/hyper/derived_test.py +2 -4
  25. pyglove/core/hyper/dynamic_evaluation.py +5 -6
  26. pyglove/core/hyper/evolvable.py +57 -46
  27. pyglove/core/hyper/numerical.py +48 -24
  28. pyglove/core/hyper/numerical_test.py +9 -9
  29. pyglove/core/hyper/object_template.py +58 -46
  30. pyglove/core/io/__init__.py +1 -0
  31. pyglove/core/io/file_system.py +17 -7
  32. pyglove/core/io/file_system_test.py +2 -0
  33. pyglove/core/io/sequence.py +299 -0
  34. pyglove/core/io/sequence_test.py +124 -0
  35. pyglove/core/logging_test.py +0 -2
  36. pyglove/core/patching/object_factory.py +4 -4
  37. pyglove/core/patching/pattern_based.py +4 -4
  38. pyglove/core/patching/rule_based.py +17 -5
  39. pyglove/core/patching/rule_based_test.py +27 -4
  40. pyglove/core/symbolic/__init__.py +2 -7
  41. pyglove/core/symbolic/base.py +320 -183
  42. pyglove/core/symbolic/base_test.py +123 -19
  43. pyglove/core/symbolic/boilerplate.py +7 -13
  44. pyglove/core/symbolic/boilerplate_test.py +25 -23
  45. pyglove/core/symbolic/class_wrapper.py +48 -45
  46. pyglove/core/symbolic/class_wrapper_test.py +2 -2
  47. pyglove/core/symbolic/compounding.py +9 -15
  48. pyglove/core/symbolic/compounding_test.py +2 -4
  49. pyglove/core/symbolic/dict.py +154 -110
  50. pyglove/core/symbolic/dict_test.py +238 -130
  51. pyglove/core/symbolic/diff.py +199 -10
  52. pyglove/core/symbolic/diff_test.py +226 -0
  53. pyglove/core/symbolic/flags.py +1 -1
  54. pyglove/core/symbolic/functor.py +29 -26
  55. pyglove/core/symbolic/functor_test.py +102 -50
  56. pyglove/core/symbolic/inferred.py +2 -2
  57. pyglove/core/symbolic/list.py +81 -50
  58. pyglove/core/symbolic/list_test.py +119 -97
  59. pyglove/core/symbolic/object.py +225 -113
  60. pyglove/core/symbolic/object_test.py +320 -108
  61. pyglove/core/symbolic/origin.py +17 -14
  62. pyglove/core/symbolic/origin_test.py +4 -2
  63. pyglove/core/symbolic/pure_symbolic.py +4 -3
  64. pyglove/core/symbolic/ref.py +108 -21
  65. pyglove/core/symbolic/ref_test.py +93 -0
  66. pyglove/core/symbolic/symbolize_test.py +10 -2
  67. pyglove/core/tuning/local_backend.py +2 -2
  68. pyglove/core/tuning/protocols.py +3 -3
  69. pyglove/core/tuning/sample_test.py +3 -3
  70. pyglove/core/typing/__init__.py +14 -5
  71. pyglove/core/typing/annotation_conversion.py +43 -27
  72. pyglove/core/typing/annotation_conversion_test.py +23 -0
  73. pyglove/core/typing/callable_ext.py +241 -3
  74. pyglove/core/typing/callable_ext_test.py +255 -0
  75. pyglove/core/typing/callable_signature.py +510 -66
  76. pyglove/core/typing/callable_signature_test.py +619 -99
  77. pyglove/core/typing/class_schema.py +229 -154
  78. pyglove/core/typing/class_schema_test.py +149 -95
  79. pyglove/core/typing/custom_typing.py +5 -4
  80. pyglove/core/typing/inspect.py +63 -0
  81. pyglove/core/typing/inspect_test.py +39 -0
  82. pyglove/core/typing/key_specs.py +10 -11
  83. pyglove/core/typing/key_specs_test.py +7 -4
  84. pyglove/core/typing/type_conversion.py +4 -5
  85. pyglove/core/typing/type_conversion_test.py +12 -12
  86. pyglove/core/typing/typed_missing.py +6 -7
  87. pyglove/core/typing/typed_missing_test.py +7 -8
  88. pyglove/core/typing/value_specs.py +604 -362
  89. pyglove/core/typing/value_specs_test.py +328 -90
  90. pyglove/core/utils/__init__.py +164 -0
  91. pyglove/core/{object_utils → utils}/common_traits.py +3 -67
  92. pyglove/core/utils/common_traits_test.py +36 -0
  93. pyglove/core/{object_utils → utils}/docstr_utils.py +23 -0
  94. pyglove/core/{object_utils → utils}/docstr_utils_test.py +36 -4
  95. pyglove/core/{object_utils → utils}/error_utils.py +78 -9
  96. pyglove/core/{object_utils → utils}/error_utils_test.py +61 -5
  97. pyglove/core/utils/formatting.py +464 -0
  98. pyglove/core/utils/formatting_test.py +453 -0
  99. pyglove/core/{object_utils → utils}/hierarchical.py +23 -25
  100. pyglove/core/{object_utils → utils}/hierarchical_test.py +3 -5
  101. pyglove/core/{object_utils → utils}/json_conversion.py +177 -52
  102. pyglove/core/{object_utils → utils}/json_conversion_test.py +97 -16
  103. pyglove/core/{object_utils → utils}/missing.py +3 -3
  104. pyglove/core/{object_utils → utils}/missing_test.py +2 -4
  105. pyglove/core/utils/text_color.py +128 -0
  106. pyglove/core/utils/text_color_test.py +94 -0
  107. pyglove/core/{object_utils → utils}/thread_local_test.py +1 -3
  108. pyglove/core/utils/timing.py +236 -0
  109. pyglove/core/utils/timing_test.py +154 -0
  110. pyglove/core/{object_utils → utils}/value_location.py +275 -6
  111. pyglove/core/utils/value_location_test.py +707 -0
  112. pyglove/core/views/__init__.py +32 -0
  113. pyglove/core/views/base.py +804 -0
  114. pyglove/core/views/base_test.py +580 -0
  115. pyglove/core/views/html/__init__.py +27 -0
  116. pyglove/core/views/html/base.py +547 -0
  117. pyglove/core/views/html/base_test.py +830 -0
  118. pyglove/core/views/html/controls/__init__.py +35 -0
  119. pyglove/core/views/html/controls/base.py +275 -0
  120. pyglove/core/views/html/controls/label.py +207 -0
  121. pyglove/core/views/html/controls/label_test.py +157 -0
  122. pyglove/core/views/html/controls/progress_bar.py +183 -0
  123. pyglove/core/views/html/controls/progress_bar_test.py +97 -0
  124. pyglove/core/views/html/controls/tab.py +320 -0
  125. pyglove/core/views/html/controls/tab_test.py +87 -0
  126. pyglove/core/views/html/controls/tooltip.py +99 -0
  127. pyglove/core/views/html/controls/tooltip_test.py +99 -0
  128. pyglove/core/views/html/tree_view.py +1517 -0
  129. pyglove/core/views/html/tree_view_test.py +1461 -0
  130. {pyglove-0.4.5.dev20240318.dist-info → pyglove-0.4.5.dev202501132210.dist-info}/METADATA +18 -4
  131. pyglove-0.4.5.dev202501132210.dist-info/RECORD +214 -0
  132. {pyglove-0.4.5.dev20240318.dist-info → pyglove-0.4.5.dev202501132210.dist-info}/WHEEL +1 -1
  133. pyglove/core/object_utils/__init__.py +0 -154
  134. pyglove/core/object_utils/common_traits_test.py +0 -82
  135. pyglove/core/object_utils/formatting.py +0 -234
  136. pyglove/core/object_utils/formatting_test.py +0 -223
  137. pyglove/core/object_utils/value_location_test.py +0 -385
  138. pyglove/core/symbolic/schema_utils.py +0 -327
  139. pyglove/core/symbolic/schema_utils_test.py +0 -57
  140. pyglove/core/typing/class_schema_utils.py +0 -202
  141. pyglove/core/typing/class_schema_utils_test.py +0 -194
  142. pyglove-0.4.5.dev20240318.dist-info/RECORD +0 -185
  143. /pyglove/core/{object_utils → utils}/thread_local.py +0 -0
  144. {pyglove-0.4.5.dev20240318.dist-info → pyglove-0.4.5.dev202501132210.dist-info}/LICENSE +0 -0
  145. {pyglove-0.4.5.dev20240318.dist-info → pyglove-0.4.5.dev202501132210.dist-info}/top_level.txt +0 -0
@@ -11,33 +11,35 @@
11
11
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
- """Tests for pyglove.core.typing.value_specs."""
15
-
16
14
  import contextlib
15
+ import datetime
16
+ import inspect
17
17
  import sys
18
18
  import typing
19
19
  import unittest
20
20
 
21
- from pyglove.core import object_utils
21
+ from pyglove.core import utils
22
22
  from pyglove.core.typing import annotation_conversion # pylint: disable=unused-import
23
23
  from pyglove.core.typing import callable_signature
24
24
  from pyglove.core.typing import class_schema
25
25
  from pyglove.core.typing import custom_typing
26
- from pyglove.core.typing import inspect as pg_inspect
27
- from pyglove.core.typing import typed_missing
28
26
  from pyglove.core.typing import key_specs as ks
27
+ from pyglove.core.typing import typed_missing
29
28
  from pyglove.core.typing import value_specs as vs
30
29
 
31
30
 
31
+ Argument = callable_signature.Argument
32
+ Signature = callable_signature.Signature
33
+
34
+
32
35
  class ValueSpecTest(unittest.TestCase):
33
36
  """Base class for value spec test."""
34
37
 
35
38
  def assert_json_conversion(self, v):
36
- self.assertEqual(object_utils.from_json(v.to_json()), v)
39
+ self.assertEqual(utils.from_json(v.to_json()), v)
37
40
 
38
41
  def assert_json_conversion_key(self, v, key):
39
- self.assertEqual(
40
- v.to_json()[object_utils.JSONConvertible.TYPE_NAME_KEY], key)
42
+ self.assertEqual(v.to_json()[utils.JSONConvertible.TYPE_NAME_KEY], key)
41
43
 
42
44
 
43
45
  class BoolTest(ValueSpecTest):
@@ -60,15 +62,15 @@ class BoolTest(ValueSpecTest):
60
62
  self.assertFalse(vs.Bool().is_noneable)
61
63
  self.assertTrue(vs.Bool().noneable().is_noneable)
62
64
 
63
- def test_str(self):
64
- self.assertEqual(str(vs.Bool()), 'Bool()')
65
- self.assertEqual(str(vs.Bool(True)), 'Bool(default=True)')
66
- self.assertEqual(str(vs.Bool(True).freeze()),
65
+ def test_repr(self):
66
+ self.assertEqual(repr(vs.Bool()), 'Bool()')
67
+ self.assertEqual(repr(vs.Bool(True)), 'Bool(default=True)')
68
+ self.assertEqual(repr(vs.Bool(True).freeze()),
67
69
  'Bool(default=True, frozen=True)')
68
70
  self.assertEqual(
69
- str(vs.Bool().noneable()), 'Bool(default=None, noneable=True)')
71
+ repr(vs.Bool().noneable()), 'Bool(default=None, noneable=True)')
70
72
  self.assertEqual(
71
- str(vs.Bool(True).noneable()), 'Bool(default=True, noneable=True)')
73
+ repr(vs.Bool(True).noneable()), 'Bool(default=True, noneable=True)')
72
74
 
73
75
  def test_annotation(self):
74
76
  self.assertEqual(vs.Bool().annotation, bool)
@@ -96,6 +98,13 @@ class BoolTest(ValueSpecTest):
96
98
  with self.assertRaisesRegex(ValueError, 'Value cannot be None'):
97
99
  vs.Bool().apply(None)
98
100
 
101
+ def test_instantiation(self):
102
+ self.assertTrue(vs.Bool()(True))
103
+ self.assertFalse(vs.Bool()(False))
104
+ self.assertIsNone(vs.Bool().noneable()())
105
+ self.assertFalse(vs.Bool()())
106
+ self.assertTrue(vs.Bool().freeze(True)(False))
107
+
99
108
  def test_is_compatible(self):
100
109
  v = vs.Bool()
101
110
  self.assertTrue(v.is_compatible(v))
@@ -114,6 +123,12 @@ class BoolTest(ValueSpecTest):
114
123
  # Child may extend a noneable base into non-noneable.
115
124
  self.assertFalse(vs.Bool().extend(vs.Bool().noneable()).is_noneable)
116
125
 
126
+ # A frozen child may extend a enum with its value as candidate.
127
+ self.assertEqual(
128
+ vs.Bool().freeze(True).extend(vs.Enum(2, [2, True])),
129
+ vs.Enum(True, [2, True]).freeze(),
130
+ )
131
+
117
132
  # Child cannot extend a base with different type.
118
133
  with self.assertRaisesRegex(
119
134
  TypeError, '.* cannot extend .*: incompatible type.'):
@@ -124,6 +139,11 @@ class BoolTest(ValueSpecTest):
124
139
  TypeError, '.* cannot extend .*: None is not allowed in base spec.'):
125
140
  vs.Bool().noneable().extend(vs.Bool())
126
141
 
142
+ # Child cannot extend a non-noneable base to noneable.
143
+ with self.assertRaisesRegex(
144
+ TypeError, '.* cannot extend .* with incompatible frozen value'):
145
+ vs.Bool().freeze(True).extend(vs.Enum(False, [2, False]))
146
+
127
147
  def test_freeze(self):
128
148
  self.assertFalse(vs.Bool().frozen)
129
149
 
@@ -141,7 +161,7 @@ class BoolTest(ValueSpecTest):
141
161
  self.assertTrue(v.default)
142
162
 
143
163
  with self.assertRaisesRegex(
144
- TypeError, 'Cannot extend a frozen value spec.'):
164
+ TypeError, '.* cannot extend a frozen value spec'):
145
165
  vs.Bool().extend(v)
146
166
 
147
167
  with self.assertRaisesRegex(
@@ -193,14 +213,14 @@ class StrTest(ValueSpecTest):
193
213
  self.assertFalse(vs.Str().is_noneable)
194
214
  self.assertTrue(vs.Str().noneable().is_noneable)
195
215
 
196
- def test_str(self):
197
- self.assertEqual(str(vs.Str()), 'Str()')
216
+ def test_repr(self):
217
+ self.assertEqual(repr(vs.Str()), 'Str()')
198
218
  self.assertEqual(
199
- str(vs.Str().noneable()), 'Str(default=None, noneable=True)')
200
- self.assertEqual(str(vs.Str('a')), 'Str(default=\'a\')')
201
- self.assertEqual(str(vs.Str('a').freeze()),
219
+ repr(vs.Str().noneable()), 'Str(default=None, noneable=True)')
220
+ self.assertEqual(repr(vs.Str('a')), 'Str(default=\'a\')')
221
+ self.assertEqual(repr(vs.Str('a').freeze()),
202
222
  'Str(default=\'a\', frozen=True)')
203
- self.assertEqual(str(vs.Str(regex='.*')), 'Str(regex=\'.*\')')
223
+ self.assertEqual(repr(vs.Str(regex='.*')), 'Str(regex=\'.*\')')
204
224
 
205
225
  def test_annotation(self):
206
226
  self.assertEqual(vs.Str().annotation, str)
@@ -221,6 +241,12 @@ class StrTest(ValueSpecTest):
221
241
  self.assertNotEqual(vs.Str(), vs.Str(regex='.*'))
222
242
  self.assertNotEqual(vs.Str(regex='a'), vs.Str(regex='.*'))
223
243
 
244
+ def test_instantiation(self):
245
+ self.assertEqual(vs.Str()('abc'), 'abc')
246
+ self.assertIsNone(vs.Str().noneable()())
247
+ self.assertEqual(vs.Str()(), '')
248
+ self.assertEqual(vs.Str().freeze('abc')('def'), 'abc')
249
+
224
250
  def test_apply(self):
225
251
  self.assertEqual(vs.Str().apply('a'), 'a')
226
252
  self.assertEqual(vs.Str(regex='a.*').apply('a1'), 'a1')
@@ -263,6 +289,12 @@ class StrTest(ValueSpecTest):
263
289
  # Child may extend a noneable base into non-noneable.
264
290
  self.assertFalse(vs.Str().extend(vs.Str().noneable()).is_noneable)
265
291
 
292
+ # A frozen child may extend a enum with its value as candidate.
293
+ self.assertEqual(
294
+ vs.Str().freeze('a').extend(vs.Enum(2, [2, 'a'])),
295
+ vs.Enum('a', [2, 'a']).freeze(),
296
+ )
297
+
266
298
  # Child cannot extend a base of different type.
267
299
  with self.assertRaisesRegex(
268
300
  TypeError, '.* cannot extend .*: incompatible type.'):
@@ -273,6 +305,11 @@ class StrTest(ValueSpecTest):
273
305
  TypeError, '.* cannot extend .*: None is not allowed in base spec.'):
274
306
  vs.Str().noneable().extend(vs.Str())
275
307
 
308
+ # Child cannot extend a non-noneable base to noneable.
309
+ with self.assertRaisesRegex(
310
+ TypeError, '.* cannot extend .* with incompatible frozen value'):
311
+ vs.Str().freeze('b').extend(vs.Enum('a', ['a', False]))
312
+
276
313
  def test_freeze(self):
277
314
  self.assertFalse(vs.Str().frozen)
278
315
 
@@ -290,7 +327,7 @@ class StrTest(ValueSpecTest):
290
327
  self.assertEqual(v.default, 'foo')
291
328
 
292
329
  with self.assertRaisesRegex(
293
- TypeError, 'Cannot extend a frozen value spec.'):
330
+ TypeError, '.* cannot extend a frozen value spec'):
294
331
  vs.Str().extend(v)
295
332
 
296
333
  with self.assertRaisesRegex(
@@ -304,6 +341,7 @@ class StrTest(ValueSpecTest):
304
341
  self.assert_json_conversion(vs.Str().noneable().freeze('abc'))
305
342
  self.assert_json_conversion_key(vs.Str(), 'pyglove.typing.Str')
306
343
 
344
+
307
345
  class IntTest(ValueSpecTest):
308
346
  """Tests for `Int`."""
309
347
 
@@ -334,17 +372,17 @@ class IntTest(ValueSpecTest):
334
372
  self.assertFalse(vs.Int().is_noneable)
335
373
  self.assertTrue(vs.Int().noneable().is_noneable)
336
374
 
337
- def test_str(self):
338
- self.assertEqual(str(vs.Int()), 'Int()')
339
- self.assertEqual(str(vs.Int(1)), 'Int(default=1)')
340
- self.assertEqual(str(vs.Int(1).freeze()),
375
+ def test_repr(self):
376
+ self.assertEqual(repr(vs.Int()), 'Int()')
377
+ self.assertEqual(repr(vs.Int(1)), 'Int(default=1)')
378
+ self.assertEqual(repr(vs.Int(1).freeze()),
341
379
  'Int(default=1, frozen=True)')
342
380
  self.assertEqual(
343
- str(vs.Int().noneable()), 'Int(default=None, noneable=True)')
381
+ repr(vs.Int().noneable()), 'Int(default=None, noneable=True)')
344
382
  self.assertEqual(
345
- str(vs.Int(1).noneable()), 'Int(default=1, noneable=True)')
383
+ repr(vs.Int(1).noneable()), 'Int(default=1, noneable=True)')
346
384
  self.assertEqual(
347
- str(vs.Int(min_value=0, max_value=1)), 'Int(min=0, max=1)')
385
+ repr(vs.Int(min_value=0, max_value=1)), 'Int(min=0, max=1)')
348
386
 
349
387
  def test_annotation(self):
350
388
  self.assertEqual(vs.Int().annotation, int)
@@ -374,6 +412,16 @@ class IntTest(ValueSpecTest):
374
412
  ValueError, '"max_value" must be equal or greater than "min_value".'):
375
413
  vs.Int(min_value=1, max_value=0)
376
414
 
415
+ def test_instantiation(self):
416
+ self.assertEqual(vs.Int()(1), 1)
417
+ self.assertEqual(vs.Int()(), 0)
418
+ self.assertIsNone(vs.Int().noneable()())
419
+ self.assertEqual(vs.Int().freeze(1)(0), 1)
420
+ with self.assertRaisesRegex(
421
+ ValueError, 'Value .* is out of range'
422
+ ):
423
+ vs.Int(min_value=1)()
424
+
377
425
  def test_apply(self):
378
426
  self.assertEqual(vs.Int().apply(1), 1)
379
427
  self.assertEqual(vs.Int(min_value=1, max_value=1).apply(1), 1)
@@ -431,6 +479,12 @@ class IntTest(ValueSpecTest):
431
479
  vs.Int(min_value=1),
432
480
  )
433
481
 
482
+ # A frozen child may extend a enum with its value as candidate.
483
+ self.assertEqual(
484
+ vs.Int().freeze(2).extend(vs.Enum(2, [2, 'a'])),
485
+ vs.Enum(2, [2, 'a']).freeze(),
486
+ )
487
+
434
488
  with self.assertRaisesRegex(TypeError,
435
489
  '.* cannot extend .*: incompatible type.'):
436
490
  vs.Int().extend(vs.Bool())
@@ -463,6 +517,11 @@ class IntTest(ValueSpecTest):
463
517
  TypeError, '.* cannot extend .*: no compatible type found in Union.'):
464
518
  vs.Int().extend(vs.Union([vs.Bool(), vs.Str()]))
465
519
 
520
+ # Child cannot extend a non-noneable base to noneable.
521
+ with self.assertRaisesRegex(
522
+ TypeError, '.* cannot extend .* with incompatible frozen value'):
523
+ vs.Int().freeze(1).extend(vs.Enum('a', ['a', False]))
524
+
466
525
  def test_freeze(self):
467
526
  self.assertFalse(vs.Int().frozen)
468
527
 
@@ -480,7 +539,7 @@ class IntTest(ValueSpecTest):
480
539
  self.assertEqual(v.default, 1)
481
540
 
482
541
  with self.assertRaisesRegex(
483
- TypeError, 'Cannot extend a frozen value spec.'):
542
+ TypeError, '.* cannot extend a frozen value spec'):
484
543
  vs.Int().extend(v)
485
544
 
486
545
  with self.assertRaisesRegex(
@@ -526,16 +585,16 @@ class FloatTest(ValueSpecTest):
526
585
  self.assertFalse(vs.Float().is_noneable)
527
586
  self.assertTrue(vs.Float().noneable().is_noneable)
528
587
 
529
- def test_str(self):
588
+ def test_repr(self):
530
589
  self.assertEqual(str(vs.Float()), 'Float()')
531
590
  self.assertEqual(
532
- str(vs.Float().noneable()), 'Float(default=None, noneable=True)')
591
+ repr(vs.Float().noneable()), 'Float(default=None, noneable=True)')
533
592
  self.assertEqual(
534
- str(vs.Float(1.0).freeze()), 'Float(default=1.0, frozen=True)')
593
+ repr(vs.Float(1.0).freeze()), 'Float(default=1.0, frozen=True)')
535
594
  self.assertEqual(
536
- str(vs.Float(1.0).noneable()), 'Float(default=1.0, noneable=True)')
595
+ repr(vs.Float(1.0).noneable()), 'Float(default=1.0, noneable=True)')
537
596
  self.assertEqual(
538
- str(vs.Float(default=1., min_value=0., max_value=1.).noneable()),
597
+ repr(vs.Float(default=1., min_value=0., max_value=1.).noneable()),
539
598
  'Float(default=1.0, min=0.0, max=1.0, noneable=True)')
540
599
 
541
600
  def test_annotation(self):
@@ -568,6 +627,16 @@ class FloatTest(ValueSpecTest):
568
627
  ValueError, '"max_value" must be equal or greater than "min_value".'):
569
628
  vs.Float(min_value=1., max_value=0.)
570
629
 
630
+ def test_instantiation(self):
631
+ self.assertEqual(vs.Float()(1), 1.0)
632
+ self.assertEqual(vs.Float()(), 0.0)
633
+ self.assertIsNone(vs.Float().noneable()())
634
+ self.assertEqual(vs.Float().freeze(1.0)(0), 1.0)
635
+ with self.assertRaisesRegex(
636
+ ValueError, 'Value .* is out of range'
637
+ ):
638
+ vs.Float(min_value=1)()
639
+
571
640
  def test_apply(self):
572
641
  self.assertEqual(vs.Float().apply(1.), 1.)
573
642
  self.assertEqual(vs.Float().apply(1), 1.)
@@ -614,6 +683,12 @@ class FloatTest(ValueSpecTest):
614
683
  # Child may extend a noneable base into non-noneable.
615
684
  self.assertFalse(vs.Float().extend(vs.Float().noneable()).is_noneable)
616
685
 
686
+ # A frozen child may extend a enum with its value as candidate.
687
+ self.assertEqual(
688
+ vs.Float().freeze(2.5).extend(vs.Enum(2.5, [2.5, 'a'])),
689
+ vs.Enum(2.5, [2.5, 'a']).freeze(),
690
+ )
691
+
617
692
  with self.assertRaisesRegex(
618
693
  TypeError, '.* cannot extend .*: incompatible type.'):
619
694
  vs.Float().extend(vs.Int())
@@ -642,6 +717,11 @@ class FloatTest(ValueSpecTest):
642
717
  'min_value .* is greater than max_value .* after extension'):
643
718
  vs.Float(min_value=1.).extend(vs.Float(max_value=0.))
644
719
 
720
+ # Child cannot extend a non-noneable base to noneable.
721
+ with self.assertRaisesRegex(
722
+ TypeError, '.* cannot extend .* with incompatible frozen value'):
723
+ vs.Float().freeze(1.0).extend(vs.Enum('a', ['a', False]))
724
+
645
725
  def test_freeze(self):
646
726
  self.assertFalse(vs.Float().frozen)
647
727
 
@@ -659,7 +739,7 @@ class FloatTest(ValueSpecTest):
659
739
  self.assertEqual(v.default, 1.0)
660
740
 
661
741
  with self.assertRaisesRegex(
662
- TypeError, 'Cannot extend a frozen value spec.'):
742
+ TypeError, '.* cannot extend a frozen value spec'):
663
743
  vs.Float().extend(v)
664
744
 
665
745
  with self.assertRaisesRegex(
@@ -730,13 +810,13 @@ class EnumTest(ValueSpecTest):
730
810
  vs.Enum('a', ['a', 'b']).noneable(),
731
811
  vs.Enum('a', ['a', 'b', None]))
732
812
 
733
- def test_str(self):
813
+ def test_repr(self):
734
814
  self.assertEqual(
735
- str(vs.Enum('a', ['a', 'b', 'c'])),
815
+ repr(vs.Enum('a', ['a', 'b', 'c'])),
736
816
  'Enum(default=\'a\', values=[\'a\', \'b\', \'c\'])')
737
817
 
738
818
  self.assertEqual(
739
- str(vs.Enum('a', ['a', 'b', 'c']).freeze()),
819
+ repr(vs.Enum('a', ['a', 'b', 'c']).freeze()),
740
820
  'Enum(default=\'a\', values=[\'a\', \'b\', \'c\'], frozen=True)')
741
821
 
742
822
  def test_annotation(self):
@@ -765,6 +845,11 @@ class EnumTest(ValueSpecTest):
765
845
  ValueError, 'Enum default value \'a\' is not in candidate list.'):
766
846
  vs.Enum('a', ['b'])
767
847
 
848
+ def test_instantiation(self):
849
+ self.assertEqual(vs.Enum(1, [1, 2, 3])(), 1)
850
+ self.assertEqual(vs.Enum(1, [1, 2, 3])(2), 2)
851
+ self.assertEqual(vs.Enum(1, [1, 2, 3]).freeze(2)(3), 2)
852
+
768
853
  def test_apply(self):
769
854
  self.assertEqual(vs.Enum('a', ['a']).apply('a'), 'a')
770
855
  self.assertIsNone(vs.Enum('a', ['a', None]).apply(None))
@@ -781,6 +866,8 @@ class EnumTest(ValueSpecTest):
781
866
  self.assertTrue(
782
867
  vs.Enum(0, [0, 1]).is_compatible(vs.Enum(0, [0, 1])))
783
868
  self.assertTrue(vs.Enum(0, [0, 1]).is_compatible(vs.Enum(0, [0])))
869
+ self.assertTrue(vs.Enum(0, [0, 'a']).is_compatible(vs.Int().freeze(0)))
870
+ self.assertTrue(vs.Enum(0, [0, 'a']).is_compatible(vs.Str().freeze('a')))
784
871
  self.assertFalse(vs.Enum(0, [0]).is_compatible(vs.Enum(0, [0, 1])))
785
872
  self.assertFalse(vs.Enum(0, [0]).is_compatible(vs.Int()))
786
873
 
@@ -791,7 +878,7 @@ class EnumTest(ValueSpecTest):
791
878
 
792
879
  # Child cannot extend a non-noneable base to noneable.
793
880
  with self.assertRaisesRegex(
794
- TypeError, '.* cannot extend .*: values in base should be super set.'):
881
+ TypeError, '.* cannot extend .*: \'b\' is not an acceptable value.'):
795
882
  vs.Enum('a', ['a', 'b']).extend(vs.Enum('a', ['a']))
796
883
 
797
884
  def test_freeze(self):
@@ -811,7 +898,7 @@ class EnumTest(ValueSpecTest):
811
898
  self.assertEqual(v.default, 'a')
812
899
 
813
900
  with self.assertRaisesRegex(
814
- TypeError, 'Cannot extend a frozen value spec.'):
901
+ TypeError, '.* cannot extend a frozen value spec'):
815
902
  vs.Enum('c', ['a', 'b', 'c']).extend(v)
816
903
 
817
904
  def test_json_conversion(self):
@@ -939,15 +1026,21 @@ class ListTest(ValueSpecTest):
939
1026
  'Either "size" or "min_size"/"max_size" pair can be specified.'):
940
1027
  vs.List(vs.Int(), size=5, min_size=1)
941
1028
 
1029
+ def test_instantiation(self):
1030
+ self.assertEqual(vs.List(vs.Int())([1]), [1])
1031
+ self.assertEqual(vs.List(vs.Int())(), [])
1032
+ self.assertIsNone(vs.List(vs.Int()).noneable()())
1033
+ self.assertEqual(vs.List(vs.Int()).freeze([0])([]), [0])
1034
+
942
1035
  def test_apply(self):
943
1036
  self.assertEqual(vs.List(vs.Int()).apply([]), [])
944
1037
  self.assertEqual(vs.List(vs.Int()).apply([1]), [1])
945
1038
  self.assertEqual(vs.List(vs.Int().noneable()).apply([1, None]), [1, None])
946
1039
  # Automatic conversion: str -> KeyPath is a registered conversion.
947
1040
  # See 'type_conversion.py'.
948
- l = vs.List(vs.Object(object_utils.KeyPath)).apply(['a.b.c'])
949
- self.assertIsInstance(l[0], object_utils.KeyPath)
950
- self.assertEqual(l, [object_utils.KeyPath.parse('a.b.c')])
1041
+ l = vs.List(vs.Object(utils.KeyPath)).apply(['a.b.c'])
1042
+ self.assertIsInstance(l[0], utils.KeyPath)
1043
+ self.assertEqual(l, [utils.KeyPath.parse('a.b.c')])
951
1044
  self.assertEqual(
952
1045
  vs.List(vs.Int()).apply(
953
1046
  typed_missing.MISSING_VALUE, allow_partial=True),
@@ -1104,7 +1197,7 @@ class ListTest(ValueSpecTest):
1104
1197
  self.assertEqual(v.default, [1])
1105
1198
 
1106
1199
  with self.assertRaisesRegex(
1107
- TypeError, 'Cannot extend a frozen value spec.'):
1200
+ TypeError, '.* cannot extend a frozen value spec'):
1108
1201
  vs.List(vs.Int()).extend(v)
1109
1202
 
1110
1203
  with self.assertRaisesRegex(
@@ -1298,6 +1391,12 @@ class TupleTest(ValueSpecTest):
1298
1391
  '<(type|class) \'int\'>.'):
1299
1392
  vs.Tuple([vs.Int()], default=1)
1300
1393
 
1394
+ def test_instantiation(self):
1395
+ self.assertEqual(vs.Tuple(vs.Int())([1]), (1,))
1396
+ self.assertEqual(vs.Tuple(vs.Int())(), ())
1397
+ self.assertIsNone(vs.Tuple(vs.Int()).noneable()())
1398
+ self.assertEqual(vs.Tuple(vs.Int()).freeze((0,))((1, 2)), (0,))
1399
+
1301
1400
  def test_apply(self):
1302
1401
  self.assertEqual(vs.Tuple(vs.Int()).apply(tuple()), tuple())
1303
1402
  self.assertEqual(vs.Tuple(vs.Int()).apply((1, 1, 1)), (1, 1, 1))
@@ -1530,7 +1629,7 @@ class TupleTest(ValueSpecTest):
1530
1629
  self.assertEqual(v.default, (1,))
1531
1630
 
1532
1631
  with self.assertRaisesRegex(
1533
- TypeError, 'Cannot extend a frozen value spec.'):
1632
+ TypeError, '.* cannot extend a frozen value spec'):
1534
1633
  vs.Tuple(vs.Int()).extend(v)
1535
1634
 
1536
1635
  with self.assertRaisesRegex(
@@ -1574,6 +1673,9 @@ class DictTest(ValueSpecTest):
1574
1673
  ('z', vs.Int(min_value=0, max_value=None), 'field z.', dict(foo=1))
1575
1674
  ]))
1576
1675
 
1676
+ self.assertEqual(vs.Dict(vs.Int()), vs.Dict([(ks.StrKey(), vs.Int())]))
1677
+ self.assertEqual(vs.Dict(int), vs.Dict([(ks.StrKey(), vs.Int())]))
1678
+
1577
1679
  with self.assertRaisesRegex(
1578
1680
  TypeError,
1579
1681
  '`pg.typing.Dict` accepts 1 dict type argument as the schema'):
@@ -1657,7 +1759,12 @@ class DictTest(ValueSpecTest):
1657
1759
  ('b', 1, 'field 1'),
1658
1760
  ('a', vs.Str(), 'field 2'),
1659
1761
  ]).noneable()),
1660
- 'Dict({b=Int(default=1), a=Str()}, noneable=True)')
1762
+ (
1763
+ 'Dict(fields=[Field(key=b, value=Int(default=1), '
1764
+ 'description=\'field 1\'), Field(key=a, value=Str(), '
1765
+ 'description=\'field 2\')], noneable=True)'
1766
+ )
1767
+ )
1661
1768
 
1662
1769
  self.assertEqual(
1663
1770
  repr(
@@ -1665,7 +1772,42 @@ class DictTest(ValueSpecTest):
1665
1772
  ('b', 1, 'field 1'),
1666
1773
  ('a', vs.Str('abc'), 'field 2'),
1667
1774
  ]).freeze()),
1668
- 'Dict({b=Int(default=1), a=Str(default=\'abc\')}, frozen=True)')
1775
+ (
1776
+ 'Dict(fields=[Field(key=b, value=Int(default=1), '
1777
+ 'description=\'field 1\'), Field(key=a, '
1778
+ 'value=Str(default=\'abc\'), description=\'field 2\')], '
1779
+ 'frozen=True)'
1780
+ )
1781
+ )
1782
+
1783
+ def test_str(self):
1784
+ self.assertEqual(str(vs.Dict()), 'Dict()')
1785
+ self.assertEqual(
1786
+ str(
1787
+ vs.Dict([
1788
+ ('b', 1, 'field 1'),
1789
+ ('a', vs.Str(), 'field 2'),
1790
+ ]).noneable()),
1791
+ inspect.cleandoc('''
1792
+ Dict(
1793
+ fields=[
1794
+ Field(
1795
+ key=b,
1796
+ value=Int(
1797
+ default=1
1798
+ ),
1799
+ description='field 1'
1800
+ ),
1801
+ Field(
1802
+ key=a,
1803
+ value=Str(),
1804
+ description='field 2'
1805
+ )
1806
+ ],
1807
+ noneable=True
1808
+ )
1809
+ ''')
1810
+ )
1669
1811
 
1670
1812
  def test_annotation(self):
1671
1813
  self.assertEqual(vs.Dict().annotation, typing.Dict[str, typing.Any])
@@ -1695,11 +1837,6 @@ class DictTest(ValueSpecTest):
1695
1837
  vs.Dict(class_schema.create_schema([('a', vs.Int())])),
1696
1838
  vs.Dict([('a', vs.Int())]))
1697
1839
 
1698
- with self.assertRaisesRegex(
1699
- TypeError,
1700
- 'Schema definition should be a dict .* a list .*'):
1701
- vs.Dict(int)
1702
-
1703
1840
  with self.assertRaisesRegex(
1704
1841
  TypeError, 'The 1st element of field definition should be of '
1705
1842
  '<(type|class) \'str\'>'):
@@ -1715,6 +1852,12 @@ class DictTest(ValueSpecTest):
1715
1852
  'should be a dict of objects.'):
1716
1853
  vs.Dict([('key', 1, 'field 1', 123)])
1717
1854
 
1855
+ def test_instantiation(self):
1856
+ self.assertEqual(vs.Dict()(), {})
1857
+ self.assertEqual(vs.Dict()({'x': 1, 2: 2}), {'x': 1, 2: 2})
1858
+ self.assertEqual(vs.Dict()(x=1), dict(x=1))
1859
+ self.assertEqual(vs.Dict({'a': int, 'b': 1})(a=1), dict(a=1, b=1))
1860
+
1718
1861
  def test_apply(self):
1719
1862
  self.assertEqual(vs.Dict().apply({'a': 1}), {'a': 1})
1720
1863
  self.assertEqual(
@@ -1902,7 +2045,7 @@ class DictTest(ValueSpecTest):
1902
2045
  x = vs.Dict([
1903
2046
  ('a', int, 'field 1', dict(x=1)),
1904
2047
  ]).freeze(dict(a=1))
1905
- y = object_utils.from_json(x.to_json())
2048
+ y = utils.from_json(x.to_json())
1906
2049
  self.assert_json_conversion(
1907
2050
  vs.Dict([
1908
2051
  ('a', int, 'field 1', dict(x=1)),
@@ -1945,7 +2088,7 @@ class ObjectTest(ValueSpecTest):
1945
2088
  class C(A):
1946
2089
  pass
1947
2090
 
1948
- class D(C, object_utils.MaybePartial):
2091
+ class D(C, utils.MaybePartial):
1949
2092
 
1950
2093
  def missing_values(self):
1951
2094
  return {'SOME_KEY': 'SOME_VALUE'}
@@ -2001,6 +2144,8 @@ class ObjectTest(ValueSpecTest):
2001
2144
  def test_forward_refs(self):
2002
2145
  self.assertEqual(vs.Object(self.A).forward_refs, set())
2003
2146
  self.assertEqual(vs.Object('Foo').forward_refs, set([forward_ref('Foo')]))
2147
+ self.assertEqual(
2148
+ vs.Object(forward_ref('Foo')).forward_refs, set([forward_ref('Foo')]))
2004
2149
 
2005
2150
  def test_default(self):
2006
2151
  self.assertEqual(vs.Object(self.A).default, typed_missing.MISSING_VALUE)
@@ -2012,14 +2157,14 @@ class ObjectTest(ValueSpecTest):
2012
2157
  self.assertTrue(vs.Object(self.A).noneable().is_noneable)
2013
2158
  self.assertTrue(vs.Object('Foo').noneable().is_noneable)
2014
2159
 
2015
- def test_str(self):
2016
- self.assertEqual(str(vs.Object(self.A)), 'Object(A)')
2017
- self.assertEqual(str(vs.Object('Foo')), 'Object(Foo)')
2160
+ def test_repr(self):
2161
+ self.assertEqual(repr(vs.Object(self.A)), 'Object(A)')
2162
+ self.assertEqual(repr(vs.Object('Foo')), 'Object(Foo)')
2018
2163
  self.assertEqual(
2019
- str(vs.Object(self.A).noneable()),
2164
+ repr(vs.Object(self.A).noneable()),
2020
2165
  'Object(A, default=None, noneable=True)')
2021
2166
  self.assertEqual(
2022
- str(vs.Object(self.A).noneable().freeze()),
2167
+ repr(vs.Object(self.A).noneable().freeze()),
2023
2168
  'Object(A, default=None, noneable=True, frozen=True)')
2024
2169
 
2025
2170
  def test_annotation(self):
@@ -2065,6 +2210,10 @@ class ObjectTest(ValueSpecTest):
2065
2210
  TypeError, '<(type|class) \'object\'> is too general for Object spec.'):
2066
2211
  vs.Object(object)
2067
2212
 
2213
+ def test_instantiation(self):
2214
+ self.assertIsInstance(vs.Object(self.A)(), self.A)
2215
+ self.assertIsInstance(vs.Object(self.B)(1), self.B)
2216
+
2068
2217
  def test_apply(self):
2069
2218
  a = self.A()
2070
2219
  self.assertEqual(vs.Object(self.A).apply(a), a)
@@ -2187,7 +2336,7 @@ class ObjectTest(ValueSpecTest):
2187
2336
  self.assertIs(v.default, b)
2188
2337
 
2189
2338
  with self.assertRaisesRegex(
2190
- TypeError, 'Cannot extend a frozen value spec.'):
2339
+ TypeError, '.* cannot extend a frozen value spec'):
2191
2340
  vs.Object(self.A).extend(v)
2192
2341
 
2193
2342
  with self.assertRaisesRegex(
@@ -2228,7 +2377,7 @@ class CallableTest(ValueSpecTest):
2228
2377
 
2229
2378
  def test_value_type(self):
2230
2379
  self.assertIsNone(vs.Callable().value_type)
2231
- self.assertEqual(vs.Functor().annotation, object_utils.Functor)
2380
+ self.assertEqual(vs.Functor().annotation, utils.Functor)
2232
2381
 
2233
2382
  def test_forward_refs(self):
2234
2383
  self.assertEqual(vs.Callable().forward_refs, set())
@@ -2268,10 +2417,10 @@ class CallableTest(ValueSpecTest):
2268
2417
  self.assertFalse(vs.Callable().is_noneable)
2269
2418
  self.assertTrue(vs.Callable().noneable().is_noneable)
2270
2419
 
2271
- def test_str(self):
2272
- self.assertEqual(str(vs.Callable()), 'Callable()')
2420
+ def test_repr(self):
2421
+ self.assertEqual(repr(vs.Callable()), 'Callable()')
2273
2422
  self.assertEqual(
2274
- str(
2423
+ repr(
2275
2424
  vs.Callable(
2276
2425
  args=[vs.Int(), vs.Int()],
2277
2426
  kw=[('a', vs.Str().noneable())],
@@ -2279,7 +2428,7 @@ class CallableTest(ValueSpecTest):
2279
2428
  'Callable(args=[Int(), Int()], kw=[(\'a\', '
2280
2429
  'Str(default=None, noneable=True))], returns=Int())')
2281
2430
  self.assertEqual(
2282
- str(
2431
+ repr(
2283
2432
  vs.Callable(
2284
2433
  args=[vs.Int(), vs.Int()],
2285
2434
  kw=[('a', vs.Str().noneable())],
@@ -2384,6 +2533,10 @@ class CallableTest(ValueSpecTest):
2384
2533
  TypeError, '.* only take 0 positional arguments, while 1 is required'):
2385
2534
  vs.Callable([vs.Int()]).apply(f)
2386
2535
 
2536
+ def test_instantiation(self):
2537
+ with self.assertRaisesRegex(TypeError, '.* cannot be instantiated'):
2538
+ vs.Callable()()
2539
+
2387
2540
  def test_apply_on_callable_object(self):
2388
2541
 
2389
2542
  class CallableObject:
@@ -2430,15 +2583,15 @@ class CallableTest(ValueSpecTest):
2430
2583
 
2431
2584
  def test_apply_on_functor(self):
2432
2585
 
2433
- class FunctorWithRegularArgs(object_utils.Functor):
2586
+ class FunctorWithRegularArgs(utils.Functor):
2434
2587
 
2435
- __signature__ = callable_signature.Signature(
2588
+ __signature__ = Signature(
2436
2589
  callable_type=callable_signature.CallableType.FUNCTION,
2437
2590
  name='foo',
2438
2591
  module_name='__main__',
2439
2592
  args=[
2440
- callable_signature.Argument('a', vs.Int()),
2441
- callable_signature.Argument('b', vs.Str())
2593
+ Argument('a', Argument.Kind.POSITIONAL_OR_KEYWORD, vs.Int()),
2594
+ Argument('b', Argument.Kind.POSITIONAL_OR_KEYWORD, vs.Str()),
2442
2595
  ])
2443
2596
 
2444
2597
  def __init__(self, value):
@@ -2476,18 +2629,28 @@ class CallableTest(ValueSpecTest):
2476
2629
 
2477
2630
  def test_apply_on_functor_with_varargs(self):
2478
2631
 
2479
- class FunctorWithVarArgs(object_utils.Functor):
2632
+ class FunctorWithVarArgs(utils.Functor):
2480
2633
 
2481
- __signature__ = callable_signature.Signature(
2634
+ __signature__ = Signature(
2482
2635
  callable_type=callable_signature.CallableType.FUNCTION,
2483
2636
  name='foo',
2484
2637
  module_name='__main__',
2485
2638
  args=[
2486
- callable_signature.Argument('a', vs.Int()),
2487
- callable_signature.Argument('b', vs.Str())
2639
+ Argument(
2640
+ 'a', Argument.Kind.POSITIONAL_OR_KEYWORD, vs.Int()
2641
+ ),
2642
+ Argument(
2643
+ 'b', Argument.Kind.POSITIONAL_OR_KEYWORD, vs.Str()
2644
+ )
2488
2645
  ],
2489
- varargs=callable_signature.Argument('args', vs.Int()),
2490
- varkw=callable_signature.Argument('kwargs', vs.Int()),
2646
+ varargs=Argument(
2647
+ 'args', Argument.Kind.VAR_POSITIONAL, vs.List(vs.Int())
2648
+ ),
2649
+ varkw=Argument(
2650
+ 'kwargs',
2651
+ Argument.Kind.VAR_KEYWORD,
2652
+ vs.Dict([(ks.StrKey(), vs.Int())])
2653
+ ),
2491
2654
  return_value=vs.Object(ValueError))
2492
2655
 
2493
2656
  def __init__(self, value):
@@ -2599,7 +2762,7 @@ class CallableTest(ValueSpecTest):
2599
2762
  self.assertIs(v.default, f)
2600
2763
 
2601
2764
  with self.assertRaisesRegex(
2602
- TypeError, 'Cannot extend a frozen value spec.'):
2765
+ TypeError, '.* cannot extend a frozen value spec'):
2603
2766
  vs.Callable().extend(v)
2604
2767
 
2605
2768
  with self.assertRaisesRegex(
@@ -2617,7 +2780,7 @@ class CallableTest(ValueSpecTest):
2617
2780
  )
2618
2781
  )
2619
2782
  x = vs.Callable([vs.Int()], default=lambda x: x + 1).noneable()
2620
- y = object_utils.from_json(x.to_json())
2783
+ y = utils.from_json(x.to_json())
2621
2784
  self.assert_json_conversion(
2622
2785
  vs.Callable([vs.Int()], default=lambda x: x + 1).noneable()
2623
2786
  )
@@ -2699,14 +2862,16 @@ class TypeTest(ValueSpecTest):
2699
2862
  self.assertFalse(vs.Type(Exception).is_noneable)
2700
2863
  self.assertTrue(vs.Type(Exception).noneable().is_noneable)
2701
2864
 
2702
- def test_str(self):
2703
- self.assertEqual(str(vs.Type(Exception)), 'Type(<class \'Exception\'>)')
2865
+ def test_repr(self):
2866
+ self.assertEqual(repr(vs.Type(Exception)), 'Type(<class \'Exception\'>)')
2704
2867
  self.assertEqual(
2705
- str(vs.Type(Exception).noneable()),
2706
- 'Type(<class \'Exception\'>, default=None, noneable=True)')
2868
+ repr(vs.Type(Exception).noneable()),
2869
+ 'Type(<class \'Exception\'>, default=None, noneable=True)'
2870
+ )
2707
2871
  self.assertEqual(
2708
- str(vs.Type(Exception).noneable().freeze()),
2709
- 'Type(<class \'Exception\'>, default=None, noneable=True, frozen=True)')
2872
+ repr(vs.Type(Exception).noneable().freeze()),
2873
+ 'Type(<class \'Exception\'>, default=None, noneable=True, frozen=True)'
2874
+ )
2710
2875
 
2711
2876
  def test_annotation(self):
2712
2877
  self.assertEqual(vs.Type(Exception).annotation, typing.Type[Exception])
@@ -2742,6 +2907,9 @@ class TypeTest(ValueSpecTest):
2742
2907
  self.assertNotEqual(
2743
2908
  vs.Type(Exception), vs.Type(Exception, default=ValueError))
2744
2909
 
2910
+ def test_instantiate(self):
2911
+ self.assertIs(vs.Type[str](), str)
2912
+
2745
2913
  def test_apply(self):
2746
2914
  self.assertEqual(vs.Type(Exception).apply(Exception), Exception)
2747
2915
  self.assertEqual(vs.Type(Exception).apply(ValueError), ValueError)
@@ -2849,7 +3017,7 @@ class TypeTest(ValueSpecTest):
2849
3017
  self.assertIs(v.default, e)
2850
3018
 
2851
3019
  with self.assertRaisesRegex(
2852
- TypeError, 'Cannot extend a frozen value spec.'):
3020
+ TypeError, '.* cannot extend a frozen value spec'):
2853
3021
  vs.Type(Exception).extend(v)
2854
3022
 
2855
3023
  with self.assertRaisesRegex(
@@ -3056,6 +3224,12 @@ class UnionTest(ValueSpecTest):
3056
3224
  vs.Union([vs.Callable(), vs.Int()]).get_candidate(vs.Any()),
3057
3225
  vs.Callable())
3058
3226
 
3227
+ def test_instantiate(self):
3228
+ with self.assertRaisesRegex(
3229
+ TypeError, '.* cannot be instantiated'
3230
+ ):
3231
+ vs.Union[int, str]()
3232
+
3059
3233
  def test_apply(self):
3060
3234
  self.assertEqual(vs.Union([vs.Int(), vs.Str()]).apply(1), 1)
3061
3235
  self.assertEqual(
@@ -3085,6 +3259,15 @@ class UnionTest(ValueSpecTest):
3085
3259
  with self.assertRaisesRegex(TypeError, 'Expect .* but encountered .*'):
3086
3260
  _ = v.apply('foo')
3087
3261
 
3262
+ # Union with strong-type and non-strong-type candidates.
3263
+ self.assertEqual(
3264
+ vs.Union([typing.Callable[[int], int], str]).apply('foo'), 'foo'
3265
+ )
3266
+ # Union with type conversion.
3267
+ self.assertIsInstance(
3268
+ vs.Union([int, str]).apply(datetime.datetime.now()), int
3269
+ )
3270
+
3088
3271
  # Bad cases.
3089
3272
  with self.assertRaisesRegex(ValueError, 'Value cannot be None'):
3090
3273
  vs.Union([vs.Int(), vs.Str()]).apply(None)
@@ -3093,6 +3276,11 @@ class UnionTest(ValueSpecTest):
3093
3276
  TypeError, 'Expect \\(.*\\) but encountered <(type|class) \'list\'>.'):
3094
3277
  vs.Union([vs.Int(), vs.Str()]).apply([])
3095
3278
 
3279
+ with self.assertRaisesRegex(
3280
+ TypeError, '1 does not match any candidate of .*'
3281
+ ):
3282
+ vs.Union([typing.Callable[[int], int], str]).apply(1)
3283
+
3096
3284
  def test_is_compatible(self):
3097
3285
  self.assertTrue(
3098
3286
  vs.Union([vs.Int(), vs.Bool()]).is_compatible(
@@ -3160,7 +3348,7 @@ class UnionTest(ValueSpecTest):
3160
3348
 
3161
3349
  # Test enum of different values cannot be extended.
3162
3350
  with self.assertRaisesRegex(
3163
- TypeError, '.* cannot extend .*: values in base should be super set.'):
3351
+ TypeError, '.* cannot extend .*: 1 is not an acceptable value'):
3164
3352
  vs.Union([vs.Enum(1, [1, 2]), vs.Int()]).extend(
3165
3353
  vs.Union([vs.Enum('a', ['a', 'b']), vs.Int()]))
3166
3354
 
@@ -3192,7 +3380,7 @@ class UnionTest(ValueSpecTest):
3192
3380
  self.assertEqual(v.default, 'foo')
3193
3381
 
3194
3382
  with self.assertRaisesRegex(
3195
- TypeError, 'Cannot extend a frozen value spec.'):
3383
+ TypeError, '.* cannot extend a frozen value spec.'):
3196
3384
  vs.Str().extend(v)
3197
3385
 
3198
3386
  with self.assertRaisesRegex(
@@ -3249,10 +3437,17 @@ class AnyTest(ValueSpecTest):
3249
3437
  def test_noneable(self):
3250
3438
  self.assertTrue(vs.Any().is_noneable)
3251
3439
 
3440
+ def test_repr(self):
3441
+ self.assertEqual(repr(vs.Any()), 'Any()')
3442
+ self.assertEqual(repr(vs.Any(1)), 'Any(default=1)')
3443
+ self.assertEqual(repr(vs.Any(1).freeze()), 'Any(default=1, frozen=True)')
3444
+
3252
3445
  def test_str(self):
3253
3446
  self.assertEqual(str(vs.Any()), 'Any()')
3254
- self.assertEqual(str(vs.Any(1)), 'Any(default=1)')
3255
- self.assertEqual(str(vs.Any(1).freeze()), 'Any(default=1, frozen=True)')
3447
+ self.assertEqual(str(vs.Any(1)), 'Any(\n default=1\n)')
3448
+ self.assertEqual(
3449
+ str(vs.Any(1).freeze()), 'Any(\n default=1,\n frozen=True\n)'
3450
+ )
3256
3451
 
3257
3452
  def test_annotation(self):
3258
3453
  self.assertEqual(vs.Any().annotation, typed_missing.MISSING_VALUE)
@@ -3267,6 +3462,10 @@ class AnyTest(ValueSpecTest):
3267
3462
  self.assertNotEqual(vs.Any(), vs.Int())
3268
3463
  self.assertNotEqual(vs.Any(True), vs.Any())
3269
3464
 
3465
+ def test_instantiate(self):
3466
+ with self.assertRaisesRegex(TypeError, '.* cannot be instantiated'):
3467
+ vs.Any()()
3468
+
3270
3469
  def test_apply(self):
3271
3470
  self.assertEqual(vs.Any().apply(True), True)
3272
3471
  self.assertEqual(vs.Any().apply(1), 1)
@@ -3316,7 +3515,7 @@ class AnyTest(ValueSpecTest):
3316
3515
  self.assertEqual(v.default, 'foo')
3317
3516
 
3318
3517
  with self.assertRaisesRegex(
3319
- TypeError, 'Cannot extend a frozen value spec.'):
3518
+ TypeError, '.* cannot extend a frozen value spec'):
3320
3519
  vs.Any().extend(v)
3321
3520
 
3322
3521
  with self.assertRaisesRegex(
@@ -3387,5 +3586,44 @@ def forward_ref(name):
3387
3586
  return class_schema.ForwardRef(sys.modules[__name__], name)
3388
3587
 
3389
3588
 
3589
+ class EnsureValueSpecTest(unittest.TestCase):
3590
+ """Tests for `ensure_value_spec`."""
3591
+
3592
+ def test_basics(self):
3593
+ self.assertEqual(
3594
+ vs.ensure_value_spec(
3595
+ vs.Int(min_value=1), vs.Int()),
3596
+ vs.Int(min_value=1)
3597
+ )
3598
+
3599
+ self.assertEqual(
3600
+ vs.ensure_value_spec(
3601
+ vs.Int(min_value=1), vs.Number(int)
3602
+ ),
3603
+ vs.Int(min_value=1)
3604
+ )
3605
+
3606
+ with self.assertRaisesRegex(
3607
+ TypeError, 'Source spec .* is not compatible with destination spec'):
3608
+ vs.ensure_value_spec(vs.Int(min_value=1), vs.Bool())
3609
+
3610
+ def test_union(self):
3611
+ self.assertEqual(
3612
+ vs.ensure_value_spec(
3613
+ vs.Union([vs.Int(), vs.Str(regex='a.*')]),
3614
+ vs.Str()), vs.Str(regex='a.*')
3615
+ )
3616
+
3617
+ with self.assertRaisesRegex(
3618
+ TypeError, 'Source spec .* is not compatible with destination spec'):
3619
+ vs.ensure_value_spec(
3620
+ vs.Union([vs.Int(), vs.Str()]),
3621
+ vs.Bool()
3622
+ )
3623
+
3624
+ def test_any(self):
3625
+ self.assertIsNone(vs.ensure_value_spec(vs.Any(), vs.Int()))
3626
+
3627
+
3390
3628
  if __name__ == '__main__':
3391
3629
  unittest.main()