pyglove 0.4.5.dev202411132359__py3-none-any.whl → 0.4.5.dev202501250807__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 (118) hide show
  1. pyglove/core/__init__.py +40 -21
  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 +312 -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 +53 -38
  14. pyglove/core/geno/base_test.py +2 -4
  15. pyglove/core/geno/categorical.py +36 -27
  16. pyglove/core/geno/custom.py +18 -15
  17. pyglove/core/geno/numerical.py +19 -16
  18. pyglove/core/geno/space.py +3 -4
  19. pyglove/core/hyper/base.py +6 -6
  20. pyglove/core/hyper/categorical.py +91 -52
  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 +3 -5
  25. pyglove/core/hyper/dynamic_evaluation.py +3 -4
  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/logging_test.py +0 -2
  31. pyglove/core/patching/object_factory.py +4 -4
  32. pyglove/core/patching/pattern_based.py +4 -4
  33. pyglove/core/patching/rule_based.py +4 -3
  34. pyglove/core/symbolic/__init__.py +4 -0
  35. pyglove/core/symbolic/base.py +200 -136
  36. pyglove/core/symbolic/base_test.py +17 -19
  37. pyglove/core/symbolic/boilerplate.py +4 -5
  38. pyglove/core/symbolic/class_wrapper.py +10 -14
  39. pyglove/core/symbolic/class_wrapper_test.py +2 -2
  40. pyglove/core/symbolic/compounding.py +2 -2
  41. pyglove/core/symbolic/compounding_test.py +2 -4
  42. pyglove/core/symbolic/contextual_object.py +288 -0
  43. pyglove/core/symbolic/contextual_object_test.py +327 -0
  44. pyglove/core/symbolic/dict.py +115 -87
  45. pyglove/core/symbolic/dict_test.py +188 -131
  46. pyglove/core/symbolic/diff.py +12 -12
  47. pyglove/core/symbolic/flags.py +1 -1
  48. pyglove/core/symbolic/functor.py +16 -15
  49. pyglove/core/symbolic/functor_test.py +2 -4
  50. pyglove/core/symbolic/inferred.py +2 -2
  51. pyglove/core/symbolic/list.py +70 -47
  52. pyglove/core/symbolic/list_test.py +117 -98
  53. pyglove/core/symbolic/object.py +59 -58
  54. pyglove/core/symbolic/object_test.py +143 -90
  55. pyglove/core/symbolic/origin.py +5 -7
  56. pyglove/core/symbolic/pure_symbolic.py +4 -3
  57. pyglove/core/symbolic/ref.py +33 -16
  58. pyglove/core/symbolic/ref_test.py +17 -0
  59. pyglove/core/tuning/local_backend.py +2 -2
  60. pyglove/core/tuning/protocols.py +3 -3
  61. pyglove/core/typing/annotation_conversion.py +8 -3
  62. pyglove/core/typing/annotation_conversion_test.py +8 -0
  63. pyglove/core/typing/callable_ext.py +11 -13
  64. pyglove/core/typing/callable_signature.py +22 -19
  65. pyglove/core/typing/callable_signature_test.py +3 -5
  66. pyglove/core/typing/class_schema.py +93 -54
  67. pyglove/core/typing/class_schema_test.py +4 -5
  68. pyglove/core/typing/custom_typing.py +5 -4
  69. pyglove/core/typing/key_specs.py +5 -7
  70. pyglove/core/typing/key_specs_test.py +4 -4
  71. pyglove/core/typing/type_conversion.py +4 -5
  72. pyglove/core/typing/type_conversion_test.py +12 -12
  73. pyglove/core/typing/typed_missing.py +6 -7
  74. pyglove/core/typing/typed_missing_test.py +7 -8
  75. pyglove/core/typing/value_specs.py +287 -144
  76. pyglove/core/typing/value_specs_test.py +148 -25
  77. pyglove/core/utils/__init__.py +172 -0
  78. pyglove/core/{object_utils → utils}/common_traits.py +2 -2
  79. pyglove/core/{object_utils → utils}/common_traits_test.py +1 -3
  80. pyglove/core/utils/contextual.py +147 -0
  81. pyglove/core/utils/contextual_test.py +88 -0
  82. pyglove/core/{object_utils → utils}/docstr_utils_test.py +1 -3
  83. pyglove/core/{object_utils → utils}/error_utils.py +3 -3
  84. pyglove/core/{object_utils → utils}/error_utils_test.py +1 -1
  85. pyglove/core/{object_utils → utils}/formatting.py +1 -1
  86. pyglove/core/{object_utils → utils}/formatting_test.py +1 -2
  87. pyglove/core/{object_utils → utils}/hierarchical.py +23 -25
  88. pyglove/core/{object_utils → utils}/hierarchical_test.py +3 -5
  89. pyglove/core/{object_utils → utils}/json_conversion.py +1 -1
  90. pyglove/core/{object_utils → utils}/json_conversion_test.py +1 -3
  91. pyglove/core/{object_utils → utils}/missing.py +2 -2
  92. pyglove/core/{object_utils → utils}/missing_test.py +2 -4
  93. pyglove/core/utils/text_color.py +128 -0
  94. pyglove/core/utils/text_color_test.py +94 -0
  95. pyglove/core/{object_utils → utils}/thread_local_test.py +1 -3
  96. pyglove/core/{object_utils → utils}/timing.py +21 -10
  97. pyglove/core/{object_utils → utils}/timing_test.py +14 -12
  98. pyglove/core/{object_utils → utils}/value_location.py +2 -2
  99. pyglove/core/{object_utils → utils}/value_location_test.py +2 -4
  100. pyglove/core/views/base.py +25 -29
  101. pyglove/core/views/html/base.py +15 -16
  102. pyglove/core/views/html/controls/base.py +46 -9
  103. pyglove/core/views/html/controls/label.py +13 -2
  104. pyglove/core/views/html/controls/label_test.py +27 -8
  105. pyglove/core/views/html/controls/progress_bar.py +3 -5
  106. pyglove/core/views/html/controls/progress_bar_test.py +2 -2
  107. pyglove/core/views/html/controls/tab.py +217 -66
  108. pyglove/core/views/html/controls/tab_test.py +46 -15
  109. pyglove/core/views/html/tree_view.py +39 -37
  110. {pyglove-0.4.5.dev202411132359.dist-info → pyglove-0.4.5.dev202501250807.dist-info}/METADATA +17 -3
  111. pyglove-0.4.5.dev202501250807.dist-info/RECORD +218 -0
  112. {pyglove-0.4.5.dev202411132359.dist-info → pyglove-0.4.5.dev202501250807.dist-info}/WHEEL +1 -1
  113. pyglove/core/object_utils/__init__.py +0 -164
  114. pyglove-0.4.5.dev202411132359.dist-info/RECORD +0 -203
  115. /pyglove/core/{object_utils → utils}/docstr_utils.py +0 -0
  116. /pyglove/core/{object_utils → utils}/thread_local.py +0 -0
  117. {pyglove-0.4.5.dev202411132359.dist-info → pyglove-0.4.5.dev202501250807.dist-info}/LICENSE +0 -0
  118. {pyglove-0.4.5.dev202411132359.dist-info → pyglove-0.4.5.dev202501250807.dist-info}/top_level.txt +0 -0
@@ -18,7 +18,7 @@ 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
@@ -36,11 +36,10 @@ class ValueSpecTest(unittest.TestCase):
36
36
  """Base class for value spec test."""
37
37
 
38
38
  def assert_json_conversion(self, v):
39
- self.assertEqual(object_utils.from_json(v.to_json()), v)
39
+ self.assertEqual(utils.from_json(v.to_json()), v)
40
40
 
41
41
  def assert_json_conversion_key(self, v, key):
42
- self.assertEqual(
43
- v.to_json()[object_utils.JSONConvertible.TYPE_NAME_KEY], key)
42
+ self.assertEqual(v.to_json()[utils.JSONConvertible.TYPE_NAME_KEY], key)
44
43
 
45
44
 
46
45
  class BoolTest(ValueSpecTest):
@@ -99,6 +98,13 @@ class BoolTest(ValueSpecTest):
99
98
  with self.assertRaisesRegex(ValueError, 'Value cannot be None'):
100
99
  vs.Bool().apply(None)
101
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
+
102
108
  def test_is_compatible(self):
103
109
  v = vs.Bool()
104
110
  self.assertTrue(v.is_compatible(v))
@@ -117,6 +123,12 @@ class BoolTest(ValueSpecTest):
117
123
  # Child may extend a noneable base into non-noneable.
118
124
  self.assertFalse(vs.Bool().extend(vs.Bool().noneable()).is_noneable)
119
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
+
120
132
  # Child cannot extend a base with different type.
121
133
  with self.assertRaisesRegex(
122
134
  TypeError, '.* cannot extend .*: incompatible type.'):
@@ -127,6 +139,11 @@ class BoolTest(ValueSpecTest):
127
139
  TypeError, '.* cannot extend .*: None is not allowed in base spec.'):
128
140
  vs.Bool().noneable().extend(vs.Bool())
129
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
+
130
147
  def test_freeze(self):
131
148
  self.assertFalse(vs.Bool().frozen)
132
149
 
@@ -144,7 +161,7 @@ class BoolTest(ValueSpecTest):
144
161
  self.assertTrue(v.default)
145
162
 
146
163
  with self.assertRaisesRegex(
147
- TypeError, 'Cannot extend a frozen value spec.'):
164
+ TypeError, '.* cannot extend a frozen value spec'):
148
165
  vs.Bool().extend(v)
149
166
 
150
167
  with self.assertRaisesRegex(
@@ -224,6 +241,12 @@ class StrTest(ValueSpecTest):
224
241
  self.assertNotEqual(vs.Str(), vs.Str(regex='.*'))
225
242
  self.assertNotEqual(vs.Str(regex='a'), vs.Str(regex='.*'))
226
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
+
227
250
  def test_apply(self):
228
251
  self.assertEqual(vs.Str().apply('a'), 'a')
229
252
  self.assertEqual(vs.Str(regex='a.*').apply('a1'), 'a1')
@@ -266,6 +289,12 @@ class StrTest(ValueSpecTest):
266
289
  # Child may extend a noneable base into non-noneable.
267
290
  self.assertFalse(vs.Str().extend(vs.Str().noneable()).is_noneable)
268
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
+
269
298
  # Child cannot extend a base of different type.
270
299
  with self.assertRaisesRegex(
271
300
  TypeError, '.* cannot extend .*: incompatible type.'):
@@ -276,6 +305,11 @@ class StrTest(ValueSpecTest):
276
305
  TypeError, '.* cannot extend .*: None is not allowed in base spec.'):
277
306
  vs.Str().noneable().extend(vs.Str())
278
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
+
279
313
  def test_freeze(self):
280
314
  self.assertFalse(vs.Str().frozen)
281
315
 
@@ -293,7 +327,7 @@ class StrTest(ValueSpecTest):
293
327
  self.assertEqual(v.default, 'foo')
294
328
 
295
329
  with self.assertRaisesRegex(
296
- TypeError, 'Cannot extend a frozen value spec.'):
330
+ TypeError, '.* cannot extend a frozen value spec'):
297
331
  vs.Str().extend(v)
298
332
 
299
333
  with self.assertRaisesRegex(
@@ -307,6 +341,7 @@ class StrTest(ValueSpecTest):
307
341
  self.assert_json_conversion(vs.Str().noneable().freeze('abc'))
308
342
  self.assert_json_conversion_key(vs.Str(), 'pyglove.typing.Str')
309
343
 
344
+
310
345
  class IntTest(ValueSpecTest):
311
346
  """Tests for `Int`."""
312
347
 
@@ -377,6 +412,16 @@ class IntTest(ValueSpecTest):
377
412
  ValueError, '"max_value" must be equal or greater than "min_value".'):
378
413
  vs.Int(min_value=1, max_value=0)
379
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
+
380
425
  def test_apply(self):
381
426
  self.assertEqual(vs.Int().apply(1), 1)
382
427
  self.assertEqual(vs.Int(min_value=1, max_value=1).apply(1), 1)
@@ -434,6 +479,12 @@ class IntTest(ValueSpecTest):
434
479
  vs.Int(min_value=1),
435
480
  )
436
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
+
437
488
  with self.assertRaisesRegex(TypeError,
438
489
  '.* cannot extend .*: incompatible type.'):
439
490
  vs.Int().extend(vs.Bool())
@@ -466,6 +517,11 @@ class IntTest(ValueSpecTest):
466
517
  TypeError, '.* cannot extend .*: no compatible type found in Union.'):
467
518
  vs.Int().extend(vs.Union([vs.Bool(), vs.Str()]))
468
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
+
469
525
  def test_freeze(self):
470
526
  self.assertFalse(vs.Int().frozen)
471
527
 
@@ -483,7 +539,7 @@ class IntTest(ValueSpecTest):
483
539
  self.assertEqual(v.default, 1)
484
540
 
485
541
  with self.assertRaisesRegex(
486
- TypeError, 'Cannot extend a frozen value spec.'):
542
+ TypeError, '.* cannot extend a frozen value spec'):
487
543
  vs.Int().extend(v)
488
544
 
489
545
  with self.assertRaisesRegex(
@@ -571,6 +627,16 @@ class FloatTest(ValueSpecTest):
571
627
  ValueError, '"max_value" must be equal or greater than "min_value".'):
572
628
  vs.Float(min_value=1., max_value=0.)
573
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
+
574
640
  def test_apply(self):
575
641
  self.assertEqual(vs.Float().apply(1.), 1.)
576
642
  self.assertEqual(vs.Float().apply(1), 1.)
@@ -617,6 +683,12 @@ class FloatTest(ValueSpecTest):
617
683
  # Child may extend a noneable base into non-noneable.
618
684
  self.assertFalse(vs.Float().extend(vs.Float().noneable()).is_noneable)
619
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
+
620
692
  with self.assertRaisesRegex(
621
693
  TypeError, '.* cannot extend .*: incompatible type.'):
622
694
  vs.Float().extend(vs.Int())
@@ -645,6 +717,11 @@ class FloatTest(ValueSpecTest):
645
717
  'min_value .* is greater than max_value .* after extension'):
646
718
  vs.Float(min_value=1.).extend(vs.Float(max_value=0.))
647
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
+
648
725
  def test_freeze(self):
649
726
  self.assertFalse(vs.Float().frozen)
650
727
 
@@ -662,7 +739,7 @@ class FloatTest(ValueSpecTest):
662
739
  self.assertEqual(v.default, 1.0)
663
740
 
664
741
  with self.assertRaisesRegex(
665
- TypeError, 'Cannot extend a frozen value spec.'):
742
+ TypeError, '.* cannot extend a frozen value spec'):
666
743
  vs.Float().extend(v)
667
744
 
668
745
  with self.assertRaisesRegex(
@@ -768,6 +845,11 @@ class EnumTest(ValueSpecTest):
768
845
  ValueError, 'Enum default value \'a\' is not in candidate list.'):
769
846
  vs.Enum('a', ['b'])
770
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
+
771
853
  def test_apply(self):
772
854
  self.assertEqual(vs.Enum('a', ['a']).apply('a'), 'a')
773
855
  self.assertIsNone(vs.Enum('a', ['a', None]).apply(None))
@@ -784,6 +866,8 @@ class EnumTest(ValueSpecTest):
784
866
  self.assertTrue(
785
867
  vs.Enum(0, [0, 1]).is_compatible(vs.Enum(0, [0, 1])))
786
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')))
787
871
  self.assertFalse(vs.Enum(0, [0]).is_compatible(vs.Enum(0, [0, 1])))
788
872
  self.assertFalse(vs.Enum(0, [0]).is_compatible(vs.Int()))
789
873
 
@@ -814,7 +898,7 @@ class EnumTest(ValueSpecTest):
814
898
  self.assertEqual(v.default, 'a')
815
899
 
816
900
  with self.assertRaisesRegex(
817
- TypeError, 'Cannot extend a frozen value spec.'):
901
+ TypeError, '.* cannot extend a frozen value spec'):
818
902
  vs.Enum('c', ['a', 'b', 'c']).extend(v)
819
903
 
820
904
  def test_json_conversion(self):
@@ -942,15 +1026,21 @@ class ListTest(ValueSpecTest):
942
1026
  'Either "size" or "min_size"/"max_size" pair can be specified.'):
943
1027
  vs.List(vs.Int(), size=5, min_size=1)
944
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
+
945
1035
  def test_apply(self):
946
1036
  self.assertEqual(vs.List(vs.Int()).apply([]), [])
947
1037
  self.assertEqual(vs.List(vs.Int()).apply([1]), [1])
948
1038
  self.assertEqual(vs.List(vs.Int().noneable()).apply([1, None]), [1, None])
949
1039
  # Automatic conversion: str -> KeyPath is a registered conversion.
950
1040
  # See 'type_conversion.py'.
951
- l = vs.List(vs.Object(object_utils.KeyPath)).apply(['a.b.c'])
952
- self.assertIsInstance(l[0], object_utils.KeyPath)
953
- 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')])
954
1044
  self.assertEqual(
955
1045
  vs.List(vs.Int()).apply(
956
1046
  typed_missing.MISSING_VALUE, allow_partial=True),
@@ -1107,7 +1197,7 @@ class ListTest(ValueSpecTest):
1107
1197
  self.assertEqual(v.default, [1])
1108
1198
 
1109
1199
  with self.assertRaisesRegex(
1110
- TypeError, 'Cannot extend a frozen value spec.'):
1200
+ TypeError, '.* cannot extend a frozen value spec'):
1111
1201
  vs.List(vs.Int()).extend(v)
1112
1202
 
1113
1203
  with self.assertRaisesRegex(
@@ -1301,6 +1391,12 @@ class TupleTest(ValueSpecTest):
1301
1391
  '<(type|class) \'int\'>.'):
1302
1392
  vs.Tuple([vs.Int()], default=1)
1303
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
+
1304
1400
  def test_apply(self):
1305
1401
  self.assertEqual(vs.Tuple(vs.Int()).apply(tuple()), tuple())
1306
1402
  self.assertEqual(vs.Tuple(vs.Int()).apply((1, 1, 1)), (1, 1, 1))
@@ -1533,7 +1629,7 @@ class TupleTest(ValueSpecTest):
1533
1629
  self.assertEqual(v.default, (1,))
1534
1630
 
1535
1631
  with self.assertRaisesRegex(
1536
- TypeError, 'Cannot extend a frozen value spec.'):
1632
+ TypeError, '.* cannot extend a frozen value spec'):
1537
1633
  vs.Tuple(vs.Int()).extend(v)
1538
1634
 
1539
1635
  with self.assertRaisesRegex(
@@ -1756,6 +1852,12 @@ class DictTest(ValueSpecTest):
1756
1852
  'should be a dict of objects.'):
1757
1853
  vs.Dict([('key', 1, 'field 1', 123)])
1758
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
+
1759
1861
  def test_apply(self):
1760
1862
  self.assertEqual(vs.Dict().apply({'a': 1}), {'a': 1})
1761
1863
  self.assertEqual(
@@ -1943,7 +2045,7 @@ class DictTest(ValueSpecTest):
1943
2045
  x = vs.Dict([
1944
2046
  ('a', int, 'field 1', dict(x=1)),
1945
2047
  ]).freeze(dict(a=1))
1946
- y = object_utils.from_json(x.to_json())
2048
+ y = utils.from_json(x.to_json())
1947
2049
  self.assert_json_conversion(
1948
2050
  vs.Dict([
1949
2051
  ('a', int, 'field 1', dict(x=1)),
@@ -1986,7 +2088,7 @@ class ObjectTest(ValueSpecTest):
1986
2088
  class C(A):
1987
2089
  pass
1988
2090
 
1989
- class D(C, object_utils.MaybePartial):
2091
+ class D(C, utils.MaybePartial):
1990
2092
 
1991
2093
  def missing_values(self):
1992
2094
  return {'SOME_KEY': 'SOME_VALUE'}
@@ -2108,6 +2210,10 @@ class ObjectTest(ValueSpecTest):
2108
2210
  TypeError, '<(type|class) \'object\'> is too general for Object spec.'):
2109
2211
  vs.Object(object)
2110
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
+
2111
2217
  def test_apply(self):
2112
2218
  a = self.A()
2113
2219
  self.assertEqual(vs.Object(self.A).apply(a), a)
@@ -2230,7 +2336,7 @@ class ObjectTest(ValueSpecTest):
2230
2336
  self.assertIs(v.default, b)
2231
2337
 
2232
2338
  with self.assertRaisesRegex(
2233
- TypeError, 'Cannot extend a frozen value spec.'):
2339
+ TypeError, '.* cannot extend a frozen value spec'):
2234
2340
  vs.Object(self.A).extend(v)
2235
2341
 
2236
2342
  with self.assertRaisesRegex(
@@ -2271,7 +2377,7 @@ class CallableTest(ValueSpecTest):
2271
2377
 
2272
2378
  def test_value_type(self):
2273
2379
  self.assertIsNone(vs.Callable().value_type)
2274
- self.assertEqual(vs.Functor().annotation, object_utils.Functor)
2380
+ self.assertEqual(vs.Functor().annotation, utils.Functor)
2275
2381
 
2276
2382
  def test_forward_refs(self):
2277
2383
  self.assertEqual(vs.Callable().forward_refs, set())
@@ -2427,6 +2533,10 @@ class CallableTest(ValueSpecTest):
2427
2533
  TypeError, '.* only take 0 positional arguments, while 1 is required'):
2428
2534
  vs.Callable([vs.Int()]).apply(f)
2429
2535
 
2536
+ def test_instantiation(self):
2537
+ with self.assertRaisesRegex(TypeError, '.* cannot be instantiated'):
2538
+ vs.Callable()()
2539
+
2430
2540
  def test_apply_on_callable_object(self):
2431
2541
 
2432
2542
  class CallableObject:
@@ -2473,7 +2583,7 @@ class CallableTest(ValueSpecTest):
2473
2583
 
2474
2584
  def test_apply_on_functor(self):
2475
2585
 
2476
- class FunctorWithRegularArgs(object_utils.Functor):
2586
+ class FunctorWithRegularArgs(utils.Functor):
2477
2587
 
2478
2588
  __signature__ = Signature(
2479
2589
  callable_type=callable_signature.CallableType.FUNCTION,
@@ -2519,7 +2629,7 @@ class CallableTest(ValueSpecTest):
2519
2629
 
2520
2630
  def test_apply_on_functor_with_varargs(self):
2521
2631
 
2522
- class FunctorWithVarArgs(object_utils.Functor):
2632
+ class FunctorWithVarArgs(utils.Functor):
2523
2633
 
2524
2634
  __signature__ = Signature(
2525
2635
  callable_type=callable_signature.CallableType.FUNCTION,
@@ -2652,7 +2762,7 @@ class CallableTest(ValueSpecTest):
2652
2762
  self.assertIs(v.default, f)
2653
2763
 
2654
2764
  with self.assertRaisesRegex(
2655
- TypeError, 'Cannot extend a frozen value spec.'):
2765
+ TypeError, '.* cannot extend a frozen value spec'):
2656
2766
  vs.Callable().extend(v)
2657
2767
 
2658
2768
  with self.assertRaisesRegex(
@@ -2670,7 +2780,7 @@ class CallableTest(ValueSpecTest):
2670
2780
  )
2671
2781
  )
2672
2782
  x = vs.Callable([vs.Int()], default=lambda x: x + 1).noneable()
2673
- y = object_utils.from_json(x.to_json())
2783
+ y = utils.from_json(x.to_json())
2674
2784
  self.assert_json_conversion(
2675
2785
  vs.Callable([vs.Int()], default=lambda x: x + 1).noneable()
2676
2786
  )
@@ -2797,6 +2907,9 @@ class TypeTest(ValueSpecTest):
2797
2907
  self.assertNotEqual(
2798
2908
  vs.Type(Exception), vs.Type(Exception, default=ValueError))
2799
2909
 
2910
+ def test_instantiate(self):
2911
+ self.assertIs(vs.Type[str](), str)
2912
+
2800
2913
  def test_apply(self):
2801
2914
  self.assertEqual(vs.Type(Exception).apply(Exception), Exception)
2802
2915
  self.assertEqual(vs.Type(Exception).apply(ValueError), ValueError)
@@ -2904,7 +3017,7 @@ class TypeTest(ValueSpecTest):
2904
3017
  self.assertIs(v.default, e)
2905
3018
 
2906
3019
  with self.assertRaisesRegex(
2907
- TypeError, 'Cannot extend a frozen value spec.'):
3020
+ TypeError, '.* cannot extend a frozen value spec'):
2908
3021
  vs.Type(Exception).extend(v)
2909
3022
 
2910
3023
  with self.assertRaisesRegex(
@@ -3111,6 +3224,12 @@ class UnionTest(ValueSpecTest):
3111
3224
  vs.Union([vs.Callable(), vs.Int()]).get_candidate(vs.Any()),
3112
3225
  vs.Callable())
3113
3226
 
3227
+ def test_instantiate(self):
3228
+ with self.assertRaisesRegex(
3229
+ TypeError, '.* cannot be instantiated'
3230
+ ):
3231
+ vs.Union[int, str]()
3232
+
3114
3233
  def test_apply(self):
3115
3234
  self.assertEqual(vs.Union([vs.Int(), vs.Str()]).apply(1), 1)
3116
3235
  self.assertEqual(
@@ -3261,7 +3380,7 @@ class UnionTest(ValueSpecTest):
3261
3380
  self.assertEqual(v.default, 'foo')
3262
3381
 
3263
3382
  with self.assertRaisesRegex(
3264
- TypeError, 'Cannot extend a frozen value spec.'):
3383
+ TypeError, '.* cannot extend a frozen value spec.'):
3265
3384
  vs.Str().extend(v)
3266
3385
 
3267
3386
  with self.assertRaisesRegex(
@@ -3343,6 +3462,10 @@ class AnyTest(ValueSpecTest):
3343
3462
  self.assertNotEqual(vs.Any(), vs.Int())
3344
3463
  self.assertNotEqual(vs.Any(True), vs.Any())
3345
3464
 
3465
+ def test_instantiate(self):
3466
+ with self.assertRaisesRegex(TypeError, '.* cannot be instantiated'):
3467
+ vs.Any()()
3468
+
3346
3469
  def test_apply(self):
3347
3470
  self.assertEqual(vs.Any().apply(True), True)
3348
3471
  self.assertEqual(vs.Any().apply(1), 1)
@@ -3392,7 +3515,7 @@ class AnyTest(ValueSpecTest):
3392
3515
  self.assertEqual(v.default, 'foo')
3393
3516
 
3394
3517
  with self.assertRaisesRegex(
3395
- TypeError, 'Cannot extend a frozen value spec.'):
3518
+ TypeError, '.* cannot extend a frozen value spec'):
3396
3519
  vs.Any().extend(v)
3397
3520
 
3398
3521
  with self.assertRaisesRegex(
@@ -0,0 +1,172 @@
1
+ # Copyright 2022 The PyGlove Authors
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ # pylint: disable=line-too-long
15
+ """Utility library that provides common traits for objects in Python.
16
+
17
+ Overview
18
+ --------
19
+
20
+ ``pg.utils`` sits at the bottom of all PyGlove modules and empowers other
21
+ modules with the following features:
22
+
23
+ +---------------------+--------------------------------------------+
24
+ | Functionality | API |
25
+ +=====================+============================================+
26
+ | Formatting | :class:`pg.Formattable`, |
27
+ | | |
28
+ | | :func:`pg.format`, |
29
+ | | |
30
+ | | :func:`pg.print`, |
31
+ | | |
32
+ | | :func:`pg.utils.kvlist_str`, |
33
+ | | |
34
+ | | :func:`pg.utils.quote_if_str`, |
35
+ | | |
36
+ | | :func:`pg.utils.message_on_path` |
37
+ +---------------------+--------------------------------------------+
38
+ | Serialization | :class:`pg.JSONConvertible`, |
39
+ | | |
40
+ | | :func:`pg.registered_types`, |
41
+ | | |
42
+ | | :func:`pg.utils.to_json`, |
43
+ | | |
44
+ | | :func:`pg.utils.from_json`, |
45
+ +---------------------+--------------------------------------------+
46
+ | Partial construction| :class:`pg.MaybePartial`, |
47
+ | | |
48
+ | | :const:`pg.MISSING_VALUE`. |
49
+ +---------------------+--------------------------------------------+
50
+ | Hierarchical key | :class:`pg.KeyPath` |
51
+ | representation | |
52
+ +---------------------+--------------------------------------------+
53
+ | Hierarchical object | :func:`pg.utils.traverse` |
54
+ | traversal | |
55
+ +---------------------+--------------------------------------------+
56
+ | Hierarchical object | :func:`pg.utils.transform`, |
57
+ | transformation | |
58
+ | | :func:`pg.utils.merge`, |
59
+ | | |
60
+ | | :func:`pg.utils.canonicalize`, |
61
+ | | |
62
+ | | :func:`pg.utils.flatten` |
63
+ +---------------------+--------------------------------------------+
64
+ | Docstr handling | :class:`pg.docstr`, |
65
+ +---------------------+--------------------------------------------+
66
+ | Error handling | :class:`pg.catch_errors`, |
67
+ +---------------------+--------------------------------------------+
68
+ """
69
+ # pylint: enable=line-too-long
70
+ # pylint: disable=g-bad-import-order
71
+ # pylint: disable=g-importing-member
72
+
73
+ # Handling JSON conversion.
74
+ from pyglove.core.utils.json_conversion import Nestable
75
+ from pyglove.core.utils.json_conversion import JSONValueType
76
+
77
+ from pyglove.core.utils.json_conversion import JSONConvertible
78
+ from pyglove.core.utils.json_conversion import from_json
79
+ from pyglove.core.utils.json_conversion import to_json
80
+ from pyglove.core.utils.json_conversion import registered_types
81
+
82
+ # Handling formatting.
83
+ from pyglove.core.utils.formatting import Formattable
84
+ from pyglove.core.utils.formatting import format # pylint: disable=redefined-builtin
85
+ from pyglove.core.utils.formatting import printv as print # pylint: disable=redefined-builtin
86
+ from pyglove.core.utils.formatting import kvlist_str
87
+ from pyglove.core.utils.formatting import quote_if_str
88
+ from pyglove.core.utils.formatting import maybe_markdown_quote
89
+ from pyglove.core.utils.formatting import comma_delimited_str
90
+ from pyglove.core.utils.formatting import camel_to_snake
91
+ from pyglove.core.utils.formatting import auto_plural
92
+ from pyglove.core.utils.formatting import BracketType
93
+ from pyglove.core.utils.formatting import bracket_chars
94
+ from pyglove.core.utils.formatting import RawText
95
+
96
+ # Context managers for defining the default format for __str__ and __repr__.
97
+ from pyglove.core.utils.formatting import str_format
98
+ from pyglove.core.utils.formatting import repr_format
99
+
100
+ # Value location.
101
+ from pyglove.core.utils.value_location import KeyPath
102
+ from pyglove.core.utils.value_location import KeyPathSet
103
+ from pyglove.core.utils.value_location import StrKey
104
+ from pyglove.core.utils.value_location import message_on_path
105
+
106
+ # Value markers.
107
+ from pyglove.core.utils.missing import MissingValue
108
+ from pyglove.core.utils.missing import MISSING_VALUE
109
+
110
+ # Handling hierarchical.
111
+ from pyglove.core.utils.hierarchical import traverse
112
+ from pyglove.core.utils.hierarchical import transform
113
+ from pyglove.core.utils.hierarchical import flatten
114
+ from pyglove.core.utils.hierarchical import canonicalize
115
+ from pyglove.core.utils.hierarchical import merge
116
+ from pyglove.core.utils.hierarchical import merge_tree
117
+ from pyglove.core.utils.hierarchical import is_partial
118
+ from pyglove.core.utils.hierarchical import try_listify_dict_with_int_keys
119
+
120
+ # Common traits.
121
+ from pyglove.core.utils.common_traits import MaybePartial
122
+ from pyglove.core.utils.common_traits import Functor
123
+
124
+ from pyglove.core.utils.common_traits import explicit_method_override
125
+ from pyglove.core.utils.common_traits import ensure_explicit_method_override
126
+
127
+ # Handling thread local values.
128
+ from pyglove.core.utils.thread_local import thread_local_value_scope
129
+ from pyglove.core.utils.thread_local import thread_local_has
130
+ from pyglove.core.utils.thread_local import thread_local_set
131
+ from pyglove.core.utils.thread_local import thread_local_get
132
+ from pyglove.core.utils.thread_local import thread_local_del
133
+ from pyglove.core.utils.thread_local import thread_local_increment
134
+ from pyglove.core.utils.thread_local import thread_local_decrement
135
+ from pyglove.core.utils.thread_local import thread_local_push
136
+ from pyglove.core.utils.thread_local import thread_local_pop
137
+ from pyglove.core.utils.thread_local import thread_local_peek
138
+
139
+ # Handling docstrings.
140
+ from pyglove.core.utils.docstr_utils import DocStr
141
+ from pyglove.core.utils.docstr_utils import DocStrStyle
142
+ from pyglove.core.utils.docstr_utils import DocStrEntry
143
+ from pyglove.core.utils.docstr_utils import DocStrExample
144
+ from pyglove.core.utils.docstr_utils import DocStrArgument
145
+ from pyglove.core.utils.docstr_utils import DocStrReturns
146
+ from pyglove.core.utils.docstr_utils import DocStrRaises
147
+ from pyglove.core.utils.docstr_utils import docstr
148
+
149
+ # Handling exceptions.
150
+ from pyglove.core.utils.error_utils import catch_errors
151
+ from pyglove.core.utils.error_utils import CatchErrorsContext
152
+ from pyglove.core.utils.error_utils import ErrorInfo
153
+
154
+ # Timing.
155
+ from pyglove.core.utils.timing import timeit
156
+ from pyglove.core.utils.timing import TimeIt
157
+
158
+ # Value override from context manager.
159
+ from pyglove.core.utils.contextual import ContextualOverride
160
+ from pyglove.core.utils.contextual import contextual_override
161
+ from pyglove.core.utils.contextual import with_contextual_override
162
+ from pyglove.core.utils.contextual import get_contextual_override
163
+ from pyglove.core.utils.contextual import contextual_value
164
+ from pyglove.core.utils.contextual import all_contextual_values
165
+
166
+ # Text color.
167
+ from pyglove.core.utils.text_color import colored
168
+ from pyglove.core.utils.text_color import colored_block
169
+ from pyglove.core.utils.text_color import decolor
170
+
171
+ # pylint: enable=g-importing-member
172
+ # pylint: enable=g-bad-import-order
@@ -18,7 +18,7 @@ object, for example, partiality (MaybePartial), functor (Functor).
18
18
  """
19
19
 
20
20
  import abc
21
- from typing import Any, Dict, Optional
21
+ from typing import Any, Dict, Optional, Union
22
22
 
23
23
 
24
24
  class MaybePartial(metaclass=abc.ABCMeta):
@@ -47,7 +47,7 @@ class MaybePartial(metaclass=abc.ABCMeta):
47
47
  return len(self.missing_values()) > 0 # pylint: disable=g-explicit-length-test
48
48
 
49
49
  @abc.abstractmethod
50
- def missing_values(self, flatten: bool = True) -> Dict[str, Any]: # pylint: disable=redefined-outer-name
50
+ def missing_values(self, flatten: bool = True) -> Dict[Union[str, int], Any]: # pylint: disable=redefined-outer-name
51
51
  """Returns missing values from this object.
52
52
 
53
53
  Args:
@@ -11,10 +11,8 @@
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.object_utils.common_traits."""
15
-
16
14
  import unittest
17
- from pyglove.core.object_utils import common_traits
15
+ from pyglove.core.utils import common_traits
18
16
 
19
17
 
20
18
  class ExplicitlyOverrideTest(unittest.TestCase):