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,8 +11,6 @@
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."""
15
-
16
14
  import copy
17
15
  import inspect
18
16
  import io
@@ -23,8 +21,8 @@ import typing
23
21
  from typing import Any
24
22
  import unittest
25
23
 
26
- from pyglove.core import object_utils
27
24
  from pyglove.core import typing as pg_typing
25
+ from pyglove.core import utils
28
26
  from pyglove.core.symbolic import base
29
27
  from pyglove.core.symbolic import flags
30
28
  from pyglove.core.symbolic import inferred
@@ -39,9 +37,10 @@ from pyglove.core.symbolic.object import use_init_args as pg_use_init_args
39
37
  from pyglove.core.symbolic.origin import Origin
40
38
  from pyglove.core.symbolic.pure_symbolic import NonDeterministic
41
39
  from pyglove.core.symbolic.pure_symbolic import PureSymbolic
40
+ from pyglove.core.views.html import tree_view # pylint: disable=unused-import
42
41
 
43
42
 
44
- MISSING_VALUE = object_utils.MISSING_VALUE
43
+ MISSING_VALUE = utils.MISSING_VALUE
45
44
 
46
45
 
47
46
  class ObjectMetaTest(unittest.TestCase):
@@ -69,7 +68,7 @@ class ObjectMetaTest(unittest.TestCase):
69
68
  pass
70
69
 
71
70
  @pg_members([
72
- ('args', pg_typing.List(pg_typing.Str())),
71
+ ('args', pg_typing.List(pg_typing.Str(), default=[])),
73
72
  ], init_arg_list=['x', 'y', 'z', '*args'])
74
73
  class C(B):
75
74
  pass
@@ -87,7 +86,7 @@ class ObjectMetaTest(unittest.TestCase):
87
86
  ('z', pg_typing.List(pg_typing.Int(min_value=1))),
88
87
  ('p', pg_typing.Bool().freeze(True)),
89
88
  ('q', pg_typing.Bool(default=True)),
90
- ('args', pg_typing.List(pg_typing.Str())),
89
+ ('args', pg_typing.List(pg_typing.Str(), default=[])),
91
90
  ]),
92
91
  )
93
92
 
@@ -100,16 +99,14 @@ class ObjectMetaTest(unittest.TestCase):
100
99
  ('z', pg_typing.List(pg_typing.Int(min_value=1))),
101
100
  ('p', pg_typing.Bool().freeze(True)),
102
101
  ('q', pg_typing.Bool(default=True)),
103
- ('args', pg_typing.List(pg_typing.Str())),
104
- ]))
102
+ ('args', pg_typing.List(pg_typing.Str(), default=[])),
103
+ ])
104
+ )
105
105
 
106
106
  def test_init_arg_list(self):
107
- self.assertEqual(
108
- self._A.init_arg_list, ['x', 'y', 'z', 'p'])
109
- self.assertEqual(
110
- self._B.init_arg_list, ['x', 'y', 'z', 'p', 'q'])
111
- self.assertEqual(
112
- self._C.init_arg_list, ['x', 'y', 'z', '*args'])
107
+ self.assertEqual(self._A.init_arg_list, ['x', 'y', 'z', 'p'])
108
+ self.assertEqual(self._B.init_arg_list, ['x', 'y', 'z', 'q'])
109
+ self.assertEqual(self._C.init_arg_list, ['x', 'y', 'z', '*args'])
113
110
 
114
111
  def test_serialization_key(self):
115
112
  self.assertEqual(self._A.__serialization_key__, self._A.__type_name__)
@@ -206,7 +203,7 @@ class ObjectTest(unittest.TestCase):
206
203
  ])
207
204
  class A(Object):
208
205
 
209
- @object_utils.explicit_method_override
206
+ @utils.explicit_method_override
210
207
  def __init__(self, x):
211
208
  super().__init__(int(x))
212
209
 
@@ -215,7 +212,7 @@ class ObjectTest(unittest.TestCase):
215
212
 
216
213
  class B(A):
217
214
 
218
- @object_utils.explicit_method_override
215
+ @utils.explicit_method_override
219
216
  def __init__(self, x): # pylint: disable=super-init-not-called
220
217
  # Forgot to call super().__init__ will trigger error.
221
218
  self.x = x
@@ -325,8 +322,95 @@ class ObjectTest(unittest.TestCase):
325
322
  self.assertEqual(e.x, 1)
326
323
  self.assertEqual(e.y, 3)
327
324
 
325
+ class F(Object):
326
+ x: typing.Literal[1, 'a']
327
+
328
+ class G(F):
329
+ x: typing.Final[int] = 1
330
+ y: typing.ClassVar[int] = 2
331
+
332
+ self.assertEqual(G().x, 1)
333
+ self.assertEqual(G.y, 2)
334
+
335
+ with self.assertRaisesRegex(
336
+ ValueError, 'Frozen field is not assignable'):
337
+ G(x=2)
338
+
339
+ with self.assertRaisesRegex(
340
+ TypeError, 'Field x is marked as final but has no default value'):
341
+
342
+ class H(Object): # pylint: disable=unused-variable
343
+ x: typing.Final[int] # pylint: disable=invalid-name
344
+
345
+ def test_init_arg_list(self):
346
+
347
+ def _update_init_arg_list(cls, init_arg_list):
348
+ cls.__schema__.metadata['init_arg_list'] = init_arg_list
349
+ cls.apply_schema()
350
+
351
+ def _assert_init_arg_list(
352
+ cls,
353
+ updated_init_arg_list: typing.Optional[typing.List[str]],
354
+ arg_names: typing.List[str],
355
+ kwonly_names: typing.List[str],
356
+ vararg_name: typing.Optional[str] = None
357
+ ):
358
+ _update_init_arg_list(cls, updated_init_arg_list)
359
+ signature = pg_typing.signature(cls)
360
+ self.assertEqual(
361
+ [v.name for v in signature.args],
362
+ arg_names
363
+ )
364
+ self.assertEqual(
365
+ [v.name for v in signature.kwonlyargs],
366
+ kwonly_names
367
+ )
368
+ self.assertEqual(getattr(signature.varargs, 'name', None), vararg_name)
369
+
370
+ class A(Object):
371
+ x: int
372
+ y: str
373
+
374
+ _update_init_arg_list(A, ['x'])
375
+
376
+ class B(A):
377
+ pass
378
+
379
+ _update_init_arg_list(B, None)
380
+ self.assertEqual(B.init_arg_list, ['x'])
381
+
382
+ # Case 2: base has init_arg_list, child adds new fields.
383
+ class C(A):
384
+ z: str
385
+
386
+ _update_init_arg_list(C, None)
387
+ self.assertEqual(C.init_arg_list, ['x', 'y', 'z'])
388
+
389
+ # Case 3: base has no init_arg_list, child automatically figured it out.
390
+ class D(Object):
391
+ x: int
392
+ y: str
393
+
394
+ _update_init_arg_list(D, None)
395
+
396
+ class E(D):
397
+ pass
398
+
399
+ _update_init_arg_list(E, None)
400
+ self.assertEqual(E.init_arg_list, ['x', 'y'])
401
+
402
+ class F(Object):
403
+ x: int
404
+ y: typing.List[int]
405
+
406
+ _assert_init_arg_list(F, [], [], ['x', 'y'])
407
+ _assert_init_arg_list(F, ['x'], ['x'], ['y'])
408
+ _assert_init_arg_list(F, ['x', 'y'], ['x', 'y'], [])
409
+ _assert_init_arg_list(F, ['y', 'x'], ['y', 'x'], [])
410
+ _assert_init_arg_list(F, ['x', '*y'], ['x'], [], 'y')
411
+
328
412
  def test_forward_reference(self):
329
- self.assertIs(Foo.schema.get_field('p').value.cls, Foo)
413
+ self.assertIs(Foo.__schema__.get_field('p').value.cls, Foo)
330
414
 
331
415
  def test_update_of_default_values(self):
332
416
 
@@ -370,6 +454,9 @@ class ObjectTest(unittest.TestCase):
370
454
  def y(self):
371
455
  return self.sym_init_args.y * 2
372
456
 
457
+ self.assertTrue(H.__schema__.fields['x'].frozen)
458
+ self.assertFalse(H.__schema__.fields['y'].frozen)
459
+
373
460
  h = H(y=1)
374
461
  self.assertEqual(h.x(1), 3)
375
462
  self.assertEqual(h.y(), 2)
@@ -485,6 +572,28 @@ class ObjectTest(unittest.TestCase):
485
572
  a.inspect(where=lambda v: v == 1, file=s)
486
573
  self.assertEqual(s.getvalue(), '{\n \'x[0].x\': 1\n}\n')
487
574
 
575
+ def test_clone(self):
576
+ class X:
577
+ pass
578
+
579
+ @pg_members([
580
+ ('x', pg_typing.Any()),
581
+ ])
582
+ class A(Object):
583
+ pass
584
+
585
+ a = [dict(y=A([dict(), A(X())]))]
586
+ a2 = base.clone(a)
587
+ self.assertEqual(a, a2)
588
+
589
+ # Containers and symbolic objects are deeply copied.
590
+ self.assertIsNot(a, a2)
591
+ self.assertIsNot(a[0], a2[0])
592
+ self.assertIsNot(a[0]['y'].x[0], a2[0]['y'].x[0])
593
+ self.assertIsNot(a[0]['y'].x[1], a2[0]['y'].x[1])
594
+ # Regualr objects are shallowly copied.
595
+ self.assertIs(a[0]['y'].x[1].x, a2[0]['y'].x[1].x)
596
+
488
597
  def test_copy(self):
489
598
 
490
599
  class X:
@@ -691,8 +800,8 @@ class ObjectTest(unittest.TestCase):
691
800
  a = A(A(dict(y=A(1))))
692
801
  self.assertTrue(a.sym_has('x'))
693
802
  self.assertTrue(a.sym_has('x.x'))
694
- self.assertTrue(a.sym_has(object_utils.KeyPath.parse('x.x.y')))
695
- self.assertTrue(a.sym_has(object_utils.KeyPath.parse('x.x.y.x')))
803
+ self.assertTrue(a.sym_has(utils.KeyPath.parse('x.x.y')))
804
+ self.assertTrue(a.sym_has(utils.KeyPath.parse('x.x.y.x')))
696
805
  self.assertFalse(a.sym_has('y')) # `y` is not a symbolic field.
697
806
 
698
807
  def test_sym_get(self):
@@ -717,10 +826,10 @@ class ObjectTest(unittest.TestCase):
717
826
  self.assertIs(a.sym_get('x'), a.x)
718
827
  self.assertIs(a.sym_get('p'), a.sym_getattr('p'))
719
828
  self.assertIs(a.sym_get('x.x'), a.x.x)
720
- self.assertIs(a.sym_get(object_utils.KeyPath.parse('x.x.y')), a.x.x.y)
721
- self.assertIs(a.sym_get(object_utils.KeyPath.parse('x.x.y.x')), a.x.x.y.x)
829
+ self.assertIs(a.sym_get(utils.KeyPath.parse('x.x.y')), a.x.x.y)
830
+ self.assertIs(a.sym_get(utils.KeyPath.parse('x.x.y.x')), a.x.x.y.x)
722
831
  self.assertIs(
723
- a.sym_get(object_utils.KeyPath.parse('x.x.y.p')),
832
+ a.sym_get(utils.KeyPath.parse('x.x.y.p')),
724
833
  a.x.x.y.sym_getattr('p'),
725
834
  )
726
835
  self.assertIsNone(a.sym_get('x.x.y.q', use_inferred=True))
@@ -1014,8 +1123,6 @@ class ObjectTest(unittest.TestCase):
1014
1123
 
1015
1124
  # Origin is not tracked by default.
1016
1125
  a = builder_of_builder(1)
1017
- a1 = a() # a1 is a `builder`.
1018
- a2 = a() # a2 is an `A`.
1019
1126
  a3 = a.clone()
1020
1127
  a4 = a3.clone(deep=True)
1021
1128
  self.assertIsNone(a4.sym_origin)
@@ -1486,7 +1593,7 @@ class ObjectTest(unittest.TestCase):
1486
1593
  self.assertEqual(a.x.x.x.sym_path, 'x.x.x')
1487
1594
  self.assertEqual(a.x.x.x[0].sym_path, 'x.x.x[0]')
1488
1595
 
1489
- a.sym_setpath(object_utils.KeyPath('a'))
1596
+ a.sym_setpath(utils.KeyPath('a'))
1490
1597
  self.assertEqual(a.sym_path, 'a')
1491
1598
  self.assertEqual(a.x.sym_path, 'a.x')
1492
1599
  self.assertEqual(a.x.x.sym_path, 'a.x.x')
@@ -1745,13 +1852,67 @@ class MembersTest(unittest.TestCase):
1745
1852
  json_dict['_type'] = key
1746
1853
  self.assertEqual(base.from_json(json_dict), A(1))
1747
1854
 
1748
- def test_bad_cases(self):
1749
1855
 
1750
- with self.assertRaisesRegex(TypeError, 'Unsupported keyword arguments'):
1856
+ class InheritanceTest(unittest.TestCase):
1857
+ """Tests for `pg.Object` inheritance."""
1751
1858
 
1752
- @pg_members([], unsupported_arg=1)
1753
- class A(Object): # pylint: disable=unused-variable
1754
- pass
1859
+ def test_single_inheritance(self):
1860
+
1861
+ class A(Object):
1862
+ x: Any
1863
+
1864
+ class B(A):
1865
+ y: str
1866
+
1867
+ self.assertEqual(list(B.__schema__.keys()), ['x', 'y'])
1868
+
1869
+ class C(B):
1870
+ # Be more specific about x and y's type.
1871
+ x: int
1872
+ y: typing.Literal['a', 'b']
1873
+
1874
+ self.assertEqual(list(C.__schema__.keys()), ['x', 'y'])
1875
+ self.assertEqual(C.__schema__['x'].value, pg_typing.Int())
1876
+ self.assertEqual(
1877
+ C.__schema__['y'].value,
1878
+ pg_typing.Enum(pg_typing.MISSING_VALUE, ['a', 'b'])
1879
+ )
1880
+
1881
+ def test_bad_inheritance(self):
1882
+
1883
+ class A(Object):
1884
+ x: int
1885
+
1886
+ with self.assertRaisesRegex(TypeError, 'incompatible type'):
1887
+
1888
+ class B(A): # pylint: disable=unused-variable
1889
+ x: str
1890
+
1891
+ def test_multi_inheritance(self):
1892
+
1893
+ class A(Object):
1894
+ x: str
1895
+
1896
+ class B(A):
1897
+ x = 'foo'
1898
+
1899
+ def foo(self):
1900
+ return 'B'
1901
+
1902
+ class C(A):
1903
+ y: int
1904
+
1905
+ def bar(self):
1906
+ return 'C'
1907
+
1908
+ class D(B, C):
1909
+ pass
1910
+
1911
+ self.assertEqual(list(D.__schema__.keys()), ['x', 'y'])
1912
+ self.assertEqual(D.__schema__['x'].default_value, 'foo')
1913
+ d = D(y=2)
1914
+ self.assertEqual(d.x, 'foo')
1915
+ self.assertEqual(d.foo(), 'B')
1755
1916
 
1756
1917
 
1757
1918
  class InitSignatureTest(unittest.TestCase):
@@ -1912,7 +2073,7 @@ class InitSignatureTest(unittest.TestCase):
1912
2073
  class C(B):
1913
2074
  """Custom __init__."""
1914
2075
 
1915
- @object_utils.explicit_method_override
2076
+ @utils.explicit_method_override
1916
2077
  def __init__(self, a, b):
1917
2078
  super().__init__(b, x=a)
1918
2079
 
@@ -2206,7 +2367,7 @@ class RebindTest(unittest.TestCase):
2206
2367
  A(1).rebind({})
2207
2368
 
2208
2369
  with self.assertRaisesRegex(
2209
- KeyError, 'Key must be string type. Encountered 1'):
2370
+ KeyError, 'Key 1 is not allowed for .*'):
2210
2371
  A(1).rebind({1: 1})
2211
2372
 
2212
2373
  with self.assertRaisesRegex(
@@ -2287,44 +2448,51 @@ class EventsTest(unittest.TestCase):
2287
2448
  [
2288
2449
  # Set default value from outer space (parent List) for field d1.
2289
2450
  {
2290
- 'd1':
2291
- base.FieldUpdate(
2292
- path=object_utils.KeyPath.parse('a2.b1.c1[0].d1'),
2293
- target=sd.a2.b1.c1[0],
2294
- field=sd.a2.b1.c1[0].value_spec.schema['d1'],
2295
- old_value=MISSING_VALUE,
2296
- new_value='foo')
2451
+ 'd1': base.FieldUpdate(
2452
+ path=utils.KeyPath.parse('a2.b1.c1[0].d1'),
2453
+ target=sd.a2.b1.c1[0],
2454
+ field=sd.a2.b1.c1[0].value_spec.schema['d1'],
2455
+ old_value=MISSING_VALUE,
2456
+ new_value='foo',
2457
+ )
2297
2458
  },
2298
2459
  # Set default value from outer space (parent List) for field d2.
2299
2460
  {
2300
- 'd2':
2301
- base.FieldUpdate(
2302
- path=object_utils.KeyPath.parse('a2.b1.c1[0].d2'),
2303
- target=sd.a2.b1.c1[0],
2304
- field=sd.a2.b1.c1[0].value_spec.schema['d2'],
2305
- old_value=MISSING_VALUE,
2306
- new_value=True)
2307
- }
2308
- ])
2461
+ 'd2': base.FieldUpdate(
2462
+ path=utils.KeyPath.parse('a2.b1.c1[0].d2'),
2463
+ target=sd.a2.b1.c1[0],
2464
+ field=sd.a2.b1.c1[0].value_spec.schema['d2'],
2465
+ old_value=MISSING_VALUE,
2466
+ new_value=True,
2467
+ )
2468
+ },
2469
+ ],
2470
+ )
2309
2471
 
2310
2472
  # list get updated after bind with parent structures.
2311
- self.assertEqual(list_updates, [{
2312
- '[0].d1':
2313
- base.FieldUpdate(
2314
- path=object_utils.KeyPath.parse('a2.b1.c1[0].d1'),
2315
- target=sd.a2.b1.c1[0],
2316
- field=sd.a2.b1.c1[0].value_spec.schema['d1'],
2317
- old_value=MISSING_VALUE,
2318
- new_value='foo')
2319
- }, {
2320
- '[0].d2':
2321
- base.FieldUpdate(
2322
- path=object_utils.KeyPath.parse('a2.b1.c1[0].d2'),
2323
- target=sd.a2.b1.c1[0],
2324
- field=sd.a2.b1.c1[0].value_spec.schema['d2'],
2325
- old_value=MISSING_VALUE,
2326
- new_value=True)
2327
- }])
2473
+ self.assertEqual(
2474
+ list_updates,
2475
+ [
2476
+ {
2477
+ '[0].d1': base.FieldUpdate(
2478
+ path=utils.KeyPath.parse('a2.b1.c1[0].d1'),
2479
+ target=sd.a2.b1.c1[0],
2480
+ field=sd.a2.b1.c1[0].value_spec.schema['d1'],
2481
+ old_value=MISSING_VALUE,
2482
+ new_value='foo',
2483
+ )
2484
+ },
2485
+ {
2486
+ '[0].d2': base.FieldUpdate(
2487
+ path=utils.KeyPath.parse('a2.b1.c1[0].d2'),
2488
+ target=sd.a2.b1.c1[0],
2489
+ field=sd.a2.b1.c1[0].value_spec.schema['d2'],
2490
+ old_value=MISSING_VALUE,
2491
+ new_value=True,
2492
+ )
2493
+ },
2494
+ ],
2495
+ )
2328
2496
 
2329
2497
  # There are no updates in root.
2330
2498
  self.assertEqual(root_updates, [])
@@ -2347,28 +2515,28 @@ class EventsTest(unittest.TestCase):
2347
2515
  root_updates[0],
2348
2516
  {
2349
2517
  'a1': base.FieldUpdate(
2350
- path=object_utils.KeyPath.parse('a1'),
2518
+ path=utils.KeyPath.parse('a1'),
2351
2519
  target=sd,
2352
2520
  field=sd.value_spec.schema['a1'],
2353
2521
  old_value=MISSING_VALUE,
2354
2522
  new_value=1,
2355
2523
  ),
2356
2524
  'a2.b1.c1[0].d1': base.FieldUpdate(
2357
- path=object_utils.KeyPath.parse('a2.b1.c1[0].d1'),
2525
+ path=utils.KeyPath.parse('a2.b1.c1[0].d1'),
2358
2526
  target=sd.a2.b1.c1[0],
2359
2527
  field=sd.a2.b1.c1[0].value_spec.schema['d1'],
2360
2528
  old_value='foo',
2361
2529
  new_value='bar',
2362
2530
  ),
2363
2531
  'a2.b1.c1[0].d2': base.FieldUpdate(
2364
- path=object_utils.KeyPath.parse('a2.b1.c1[0].d2'),
2532
+ path=utils.KeyPath.parse('a2.b1.c1[0].d2'),
2365
2533
  target=sd.a2.b1.c1[0],
2366
2534
  field=sd.a2.b1.c1[0].value_spec.schema['d2'],
2367
2535
  old_value=True,
2368
2536
  new_value=False,
2369
2537
  ),
2370
2538
  'a2.b1.c1[0].d3.z': base.FieldUpdate(
2371
- path=object_utils.KeyPath.parse('a2.b1.c1[0].d3.z'),
2539
+ path=utils.KeyPath.parse('a2.b1.c1[0].d3.z'),
2372
2540
  target=sd.a2.b1.c1[0].d3,
2373
2541
  field=sd.a2.b1.c1[0].d3.__class__.__schema__['z'],
2374
2542
  old_value=MISSING_VALUE,
@@ -2384,21 +2552,21 @@ class EventsTest(unittest.TestCase):
2384
2552
  # Root object rebind.
2385
2553
  {
2386
2554
  '[0].d1': base.FieldUpdate(
2387
- path=object_utils.KeyPath.parse('a2.b1.c1[0].d1'),
2555
+ path=utils.KeyPath.parse('a2.b1.c1[0].d1'),
2388
2556
  target=sd.a2.b1.c1[0],
2389
2557
  field=sd.a2.b1.c1[0].value_spec.schema['d1'],
2390
2558
  old_value='foo',
2391
2559
  new_value='bar',
2392
2560
  ),
2393
2561
  '[0].d2': base.FieldUpdate(
2394
- path=object_utils.KeyPath.parse('a2.b1.c1[0].d2'),
2562
+ path=utils.KeyPath.parse('a2.b1.c1[0].d2'),
2395
2563
  target=sd.a2.b1.c1[0],
2396
2564
  field=sd.a2.b1.c1[0].value_spec.schema['d2'],
2397
2565
  old_value=True,
2398
2566
  new_value=False,
2399
2567
  ),
2400
2568
  '[0].d3.z': base.FieldUpdate(
2401
- path=object_utils.KeyPath.parse('a2.b1.c1[0].d3.z'),
2569
+ path=utils.KeyPath.parse('a2.b1.c1[0].d3.z'),
2402
2570
  target=sd.a2.b1.c1[0].d3,
2403
2571
  field=sd.a2.b1.c1[0].d3.__class__.__schema__['z'],
2404
2572
  old_value=MISSING_VALUE,
@@ -2414,29 +2582,30 @@ class EventsTest(unittest.TestCase):
2414
2582
  [
2415
2583
  # Root object rebind.
2416
2584
  {
2417
- 'd1':
2418
- base.FieldUpdate(
2419
- path=object_utils.KeyPath.parse('a2.b1.c1[0].d1'),
2420
- target=sd.a2.b1.c1[0],
2421
- field=sd.a2.b1.c1[0].value_spec.schema['d1'],
2422
- old_value='foo',
2423
- new_value='bar'),
2424
- 'd2':
2425
- base.FieldUpdate(
2426
- path=object_utils.KeyPath.parse('a2.b1.c1[0].d2'),
2427
- target=sd.a2.b1.c1[0],
2428
- field=sd.a2.b1.c1[0].value_spec.schema['d2'],
2429
- old_value=True,
2430
- new_value=False),
2431
- 'd3.z':
2432
- base.FieldUpdate(
2433
- path=object_utils.KeyPath.parse('a2.b1.c1[0].d3.z'),
2434
- target=sd.a2.b1.c1[0].d3,
2435
- field=sd.a2.b1.c1[0].d3.__class__.schema['z'],
2436
- old_value=MISSING_VALUE,
2437
- new_value='foo')
2585
+ 'd1': base.FieldUpdate(
2586
+ path=utils.KeyPath.parse('a2.b1.c1[0].d1'),
2587
+ target=sd.a2.b1.c1[0],
2588
+ field=sd.a2.b1.c1[0].value_spec.schema['d1'],
2589
+ old_value='foo',
2590
+ new_value='bar',
2591
+ ),
2592
+ 'd2': base.FieldUpdate(
2593
+ path=utils.KeyPath.parse('a2.b1.c1[0].d2'),
2594
+ target=sd.a2.b1.c1[0],
2595
+ field=sd.a2.b1.c1[0].value_spec.schema['d2'],
2596
+ old_value=True,
2597
+ new_value=False,
2598
+ ),
2599
+ 'd3.z': base.FieldUpdate(
2600
+ path=utils.KeyPath.parse('a2.b1.c1[0].d3.z'),
2601
+ target=sd.a2.b1.c1[0].d3,
2602
+ field=sd.a2.b1.c1[0].d3.__class__.__schema__['z'],
2603
+ old_value=MISSING_VALUE,
2604
+ new_value='foo',
2605
+ ),
2438
2606
  }
2439
- ])
2607
+ ],
2608
+ )
2440
2609
 
2441
2610
  def test_on_change_notification_order(self):
2442
2611
  change_order = []
@@ -2482,7 +2651,7 @@ class EventsTest(unittest.TestCase):
2482
2651
  y.x = A()
2483
2652
  self.assertIs(x.old_parent, y)
2484
2653
  self.assertIsNone(x.new_parent)
2485
- self.assertEqual(x.sym_path, object_utils.KeyPath())
2654
+ self.assertEqual(x.sym_path, utils.KeyPath())
2486
2655
 
2487
2656
  def test_on_path_change(self):
2488
2657
 
@@ -2493,8 +2662,8 @@ class EventsTest(unittest.TestCase):
2493
2662
  self.new_path = new_path
2494
2663
 
2495
2664
  x = A()
2496
- x.sym_setpath(object_utils.KeyPath('a'))
2497
- self.assertEqual(x.old_path, object_utils.KeyPath())
2665
+ x.sym_setpath(utils.KeyPath('a'))
2666
+ self.assertEqual(x.old_path, utils.KeyPath())
2498
2667
  self.assertEqual(x.new_path, 'a')
2499
2668
 
2500
2669
  y = Dict(x=x)
@@ -2896,7 +3065,7 @@ class SerializationTest(unittest.TestCase):
2896
3065
 
2897
3066
  def test_serialization_with_json_convertible(self):
2898
3067
 
2899
- class Y(object_utils.JSONConvertible):
3068
+ class Y(utils.JSONConvertible):
2900
3069
 
2901
3070
  TYPE_NAME = 'Y'
2902
3071
 
@@ -2916,7 +3085,7 @@ class SerializationTest(unittest.TestCase):
2916
3085
  def from_json(cls, json_dict, *args, **kwargs):
2917
3086
  return cls(json_dict.pop('value'))
2918
3087
 
2919
- object_utils.JSONConvertible.register(Y.TYPE_NAME, Y)
3088
+ utils.JSONConvertible.register(Y.TYPE_NAME, Y)
2920
3089
 
2921
3090
  a = self._A(Y(1), y=True)
2922
3091
  self.assertEqual(base.from_json_str(a.to_json_str()), a)
@@ -2935,18 +3104,38 @@ class SerializationTest(unittest.TestCase):
2935
3104
  Q.partial(P.partial()).to_json_str(), allow_partial=True),
2936
3105
  Q.partial(P.partial()))
2937
3106
 
2938
- def test_serialization_with_force_dict(self):
3107
+ def test_serialization_with_auto_dict(self):
2939
3108
 
2940
3109
  class P(Object):
3110
+ auto_register = False
2941
3111
  x: int
2942
3112
 
2943
3113
  class Q(Object):
3114
+ auto_register = False
2944
3115
  p: P
2945
3116
  y: str
2946
3117
 
2947
3118
  self.assertEqual(
2948
- base.from_json_str(Q(P(1), y='foo').to_json_str(), force_dict=True),
2949
- {'p': {'x': 1}, 'y': 'foo'}
3119
+ Q(P(1), y='foo').to_json(),
3120
+ {
3121
+ 'p': {
3122
+ '_type': P.__type_name__,
3123
+ 'x': 1
3124
+ },
3125
+ 'y': 'foo',
3126
+ '_type': Q.__type_name__,
3127
+ }
3128
+ )
3129
+ self.assertEqual(
3130
+ base.from_json_str(Q(P(1), y='foo').to_json_str(), auto_dict=True),
3131
+ {
3132
+ 'p': {
3133
+ 'type_name': P.__type_name__,
3134
+ 'x': 1
3135
+ },
3136
+ 'y': 'foo',
3137
+ 'type_name': Q.__type_name__,
3138
+ }
2950
3139
  )
2951
3140
 
2952
3141
  def test_serialization_with_converter(self):
@@ -2986,9 +3175,7 @@ class SerializationTest(unittest.TestCase):
2986
3175
  ValueError, 'Cannot encode opaque object .* with pickle'):
2987
3176
  base.to_json(self._A(w=Z(), y=True))
2988
3177
 
2989
- with self.assertRaisesRegex(
2990
- TypeError,
2991
- 'Type name \'.*\' is not registered with a .* subclass'):
3178
+ with self.assertRaisesRegex(TypeError, 'Cannot load class .*'):
2992
3179
  base.from_json_str('{"_type": "pyglove.core.symbolic.object_test.NotExisted", "a": 1}')
2993
3180
 
2994
3181
  def test_default_load_save_handler(self):
@@ -3105,7 +3292,7 @@ class FormatTest(unittest.TestCase):
3105
3292
 
3106
3293
  def test_compact_python_format(self):
3107
3294
  self.assertEqual(
3108
- self._a.format(compact=True, python_format=True, markdown=True),
3295
+ utils.format(self._a, compact=True, python_format=True, markdown=True),
3109
3296
  "`A(x=[A(x=1, y=None), A(x='foo', y={'a': A(x=True, y=1.0)})], "
3110
3297
  'y=MISSING_VALUE)`',
3111
3298
  )
@@ -3150,8 +3337,12 @@ class FormatTest(unittest.TestCase):
3150
3337
 
3151
3338
  def test_noncompact_python_format(self):
3152
3339
  self.assertEqual(
3153
- self._a.format(
3154
- compact=False, verbose=False, python_format=True, markdown=True
3340
+ utils.format(
3341
+ self._a,
3342
+ compact=False,
3343
+ verbose=False,
3344
+ python_format=True,
3345
+ markdown=True,
3155
3346
  ),
3156
3347
  inspect.cleandoc("""
3157
3348
  ```
@@ -3303,6 +3494,27 @@ class FormatTest(unittest.TestCase):
3303
3494
  ]
3304
3495
  )"""))
3305
3496
 
3497
+ def test_custom_format(self):
3498
+
3499
+ class Foo(Object): # pylint: disable=redefined-outer-name]
3500
+ pass
3501
+
3502
+ class Bar(Object):
3503
+ foo: Foo
3504
+
3505
+ def _method(attr_name):
3506
+ def fn(v, root_indent):
3507
+ del root_indent
3508
+ f = getattr(v, attr_name, None)
3509
+ return f() if f is not None else None
3510
+ return fn
3511
+
3512
+ with utils.str_format(custom_format=_method('_repr_xml_')):
3513
+ self.assertEqual(str(Bar(Foo())), 'Bar(\n foo = Foo()\n)')
3514
+
3515
+ with utils.str_format(custom_format=_method('_repr_html_')):
3516
+ self.assertIn('<html>', str(Bar(Foo())))
3517
+
3306
3518
 
3307
3519
  class Foo(Object):
3308
3520
  x: typing.List[typing.Dict[str, int]] = [dict(x=1)]