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
@@ -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
@@ -42,7 +40,7 @@ from pyglove.core.symbolic.pure_symbolic import PureSymbolic
42
40
  from pyglove.core.views.html import tree_view # pylint: disable=unused-import
43
41
 
44
42
 
45
- MISSING_VALUE = object_utils.MISSING_VALUE
43
+ MISSING_VALUE = utils.MISSING_VALUE
46
44
 
47
45
 
48
46
  class ObjectMetaTest(unittest.TestCase):
@@ -205,7 +203,7 @@ class ObjectTest(unittest.TestCase):
205
203
  ])
206
204
  class A(Object):
207
205
 
208
- @object_utils.explicit_method_override
206
+ @utils.explicit_method_override
209
207
  def __init__(self, x):
210
208
  super().__init__(int(x))
211
209
 
@@ -214,7 +212,7 @@ class ObjectTest(unittest.TestCase):
214
212
 
215
213
  class B(A):
216
214
 
217
- @object_utils.explicit_method_override
215
+ @utils.explicit_method_override
218
216
  def __init__(self, x): # pylint: disable=super-init-not-called
219
217
  # Forgot to call super().__init__ will trigger error.
220
218
  self.x = x
@@ -324,6 +322,26 @@ class ObjectTest(unittest.TestCase):
324
322
  self.assertEqual(e.x, 1)
325
323
  self.assertEqual(e.y, 3)
326
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
+
327
345
  def test_init_arg_list(self):
328
346
 
329
347
  def _update_init_arg_list(cls, init_arg_list):
@@ -392,7 +410,7 @@ class ObjectTest(unittest.TestCase):
392
410
  _assert_init_arg_list(F, ['x', '*y'], ['x'], [], 'y')
393
411
 
394
412
  def test_forward_reference(self):
395
- self.assertIs(Foo.schema.get_field('p').value.cls, Foo)
413
+ self.assertIs(Foo.__schema__.get_field('p').value.cls, Foo)
396
414
 
397
415
  def test_update_of_default_values(self):
398
416
 
@@ -782,8 +800,8 @@ class ObjectTest(unittest.TestCase):
782
800
  a = A(A(dict(y=A(1))))
783
801
  self.assertTrue(a.sym_has('x'))
784
802
  self.assertTrue(a.sym_has('x.x'))
785
- self.assertTrue(a.sym_has(object_utils.KeyPath.parse('x.x.y')))
786
- 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')))
787
805
  self.assertFalse(a.sym_has('y')) # `y` is not a symbolic field.
788
806
 
789
807
  def test_sym_get(self):
@@ -808,10 +826,10 @@ class ObjectTest(unittest.TestCase):
808
826
  self.assertIs(a.sym_get('x'), a.x)
809
827
  self.assertIs(a.sym_get('p'), a.sym_getattr('p'))
810
828
  self.assertIs(a.sym_get('x.x'), a.x.x)
811
- self.assertIs(a.sym_get(object_utils.KeyPath.parse('x.x.y')), a.x.x.y)
812
- 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)
813
831
  self.assertIs(
814
- a.sym_get(object_utils.KeyPath.parse('x.x.y.p')),
832
+ a.sym_get(utils.KeyPath.parse('x.x.y.p')),
815
833
  a.x.x.y.sym_getattr('p'),
816
834
  )
817
835
  self.assertIsNone(a.sym_get('x.x.y.q', use_inferred=True))
@@ -1575,7 +1593,7 @@ class ObjectTest(unittest.TestCase):
1575
1593
  self.assertEqual(a.x.x.x.sym_path, 'x.x.x')
1576
1594
  self.assertEqual(a.x.x.x[0].sym_path, 'x.x.x[0]')
1577
1595
 
1578
- a.sym_setpath(object_utils.KeyPath('a'))
1596
+ a.sym_setpath(utils.KeyPath('a'))
1579
1597
  self.assertEqual(a.sym_path, 'a')
1580
1598
  self.assertEqual(a.x.sym_path, 'a.x')
1581
1599
  self.assertEqual(a.x.x.sym_path, 'a.x.x')
@@ -1896,6 +1914,32 @@ class InheritanceTest(unittest.TestCase):
1896
1914
  self.assertEqual(d.x, 'foo')
1897
1915
  self.assertEqual(d.foo(), 'B')
1898
1916
 
1917
+ def test_multi_inheritance2(self):
1918
+
1919
+ class A(Object):
1920
+ x: typing.Callable[[], Any]
1921
+
1922
+ class B(A):
1923
+ def x(self):
1924
+ return 1
1925
+
1926
+ class C(A):
1927
+ pass
1928
+
1929
+ class D(C, B):
1930
+ pass
1931
+
1932
+ self.assertIs(D.__schema__['x'].default_value, B.x)
1933
+
1934
+ class E(A):
1935
+ def x(self):
1936
+ return 2
1937
+
1938
+ class F(E, B):
1939
+ pass
1940
+
1941
+ self.assertIs(F.__schema__['x'].default_value, E.x)
1942
+
1899
1943
 
1900
1944
  class InitSignatureTest(unittest.TestCase):
1901
1945
  """Tests for `pg.Object.__init__` signature."""
@@ -2055,7 +2099,7 @@ class InitSignatureTest(unittest.TestCase):
2055
2099
  class C(B):
2056
2100
  """Custom __init__."""
2057
2101
 
2058
- @object_utils.explicit_method_override
2102
+ @utils.explicit_method_override
2059
2103
  def __init__(self, a, b):
2060
2104
  super().__init__(b, x=a)
2061
2105
 
@@ -2349,7 +2393,7 @@ class RebindTest(unittest.TestCase):
2349
2393
  A(1).rebind({})
2350
2394
 
2351
2395
  with self.assertRaisesRegex(
2352
- KeyError, 'Key must be string type. Encountered 1'):
2396
+ KeyError, 'Key 1 is not allowed for .*'):
2353
2397
  A(1).rebind({1: 1})
2354
2398
 
2355
2399
  with self.assertRaisesRegex(
@@ -2430,44 +2474,51 @@ class EventsTest(unittest.TestCase):
2430
2474
  [
2431
2475
  # Set default value from outer space (parent List) for field d1.
2432
2476
  {
2433
- 'd1':
2434
- base.FieldUpdate(
2435
- path=object_utils.KeyPath.parse('a2.b1.c1[0].d1'),
2436
- target=sd.a2.b1.c1[0],
2437
- field=sd.a2.b1.c1[0].value_spec.schema['d1'],
2438
- old_value=MISSING_VALUE,
2439
- new_value='foo')
2477
+ '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
+ )
2440
2484
  },
2441
2485
  # Set default value from outer space (parent List) for field d2.
2442
2486
  {
2443
- 'd2':
2444
- base.FieldUpdate(
2445
- path=object_utils.KeyPath.parse('a2.b1.c1[0].d2'),
2446
- target=sd.a2.b1.c1[0],
2447
- field=sd.a2.b1.c1[0].value_spec.schema['d2'],
2448
- old_value=MISSING_VALUE,
2449
- new_value=True)
2450
- }
2451
- ])
2487
+ 'd2': base.FieldUpdate(
2488
+ path=utils.KeyPath.parse('a2.b1.c1[0].d2'),
2489
+ target=sd.a2.b1.c1[0],
2490
+ field=sd.a2.b1.c1[0].value_spec.schema['d2'],
2491
+ old_value=MISSING_VALUE,
2492
+ new_value=True,
2493
+ )
2494
+ },
2495
+ ],
2496
+ )
2452
2497
 
2453
2498
  # list get updated after bind with parent structures.
2454
- self.assertEqual(list_updates, [{
2455
- '[0].d1':
2456
- base.FieldUpdate(
2457
- path=object_utils.KeyPath.parse('a2.b1.c1[0].d1'),
2458
- target=sd.a2.b1.c1[0],
2459
- field=sd.a2.b1.c1[0].value_spec.schema['d1'],
2460
- old_value=MISSING_VALUE,
2461
- new_value='foo')
2462
- }, {
2463
- '[0].d2':
2464
- base.FieldUpdate(
2465
- path=object_utils.KeyPath.parse('a2.b1.c1[0].d2'),
2466
- target=sd.a2.b1.c1[0],
2467
- field=sd.a2.b1.c1[0].value_spec.schema['d2'],
2468
- old_value=MISSING_VALUE,
2469
- new_value=True)
2470
- }])
2499
+ self.assertEqual(
2500
+ list_updates,
2501
+ [
2502
+ {
2503
+ '[0].d1': base.FieldUpdate(
2504
+ path=utils.KeyPath.parse('a2.b1.c1[0].d1'),
2505
+ target=sd.a2.b1.c1[0],
2506
+ field=sd.a2.b1.c1[0].value_spec.schema['d1'],
2507
+ old_value=MISSING_VALUE,
2508
+ new_value='foo',
2509
+ )
2510
+ },
2511
+ {
2512
+ '[0].d2': base.FieldUpdate(
2513
+ path=utils.KeyPath.parse('a2.b1.c1[0].d2'),
2514
+ target=sd.a2.b1.c1[0],
2515
+ field=sd.a2.b1.c1[0].value_spec.schema['d2'],
2516
+ old_value=MISSING_VALUE,
2517
+ new_value=True,
2518
+ )
2519
+ },
2520
+ ],
2521
+ )
2471
2522
 
2472
2523
  # There are no updates in root.
2473
2524
  self.assertEqual(root_updates, [])
@@ -2490,28 +2541,28 @@ class EventsTest(unittest.TestCase):
2490
2541
  root_updates[0],
2491
2542
  {
2492
2543
  'a1': base.FieldUpdate(
2493
- path=object_utils.KeyPath.parse('a1'),
2544
+ path=utils.KeyPath.parse('a1'),
2494
2545
  target=sd,
2495
2546
  field=sd.value_spec.schema['a1'],
2496
2547
  old_value=MISSING_VALUE,
2497
2548
  new_value=1,
2498
2549
  ),
2499
2550
  'a2.b1.c1[0].d1': base.FieldUpdate(
2500
- path=object_utils.KeyPath.parse('a2.b1.c1[0].d1'),
2551
+ path=utils.KeyPath.parse('a2.b1.c1[0].d1'),
2501
2552
  target=sd.a2.b1.c1[0],
2502
2553
  field=sd.a2.b1.c1[0].value_spec.schema['d1'],
2503
2554
  old_value='foo',
2504
2555
  new_value='bar',
2505
2556
  ),
2506
2557
  'a2.b1.c1[0].d2': base.FieldUpdate(
2507
- path=object_utils.KeyPath.parse('a2.b1.c1[0].d2'),
2558
+ path=utils.KeyPath.parse('a2.b1.c1[0].d2'),
2508
2559
  target=sd.a2.b1.c1[0],
2509
2560
  field=sd.a2.b1.c1[0].value_spec.schema['d2'],
2510
2561
  old_value=True,
2511
2562
  new_value=False,
2512
2563
  ),
2513
2564
  'a2.b1.c1[0].d3.z': base.FieldUpdate(
2514
- path=object_utils.KeyPath.parse('a2.b1.c1[0].d3.z'),
2565
+ path=utils.KeyPath.parse('a2.b1.c1[0].d3.z'),
2515
2566
  target=sd.a2.b1.c1[0].d3,
2516
2567
  field=sd.a2.b1.c1[0].d3.__class__.__schema__['z'],
2517
2568
  old_value=MISSING_VALUE,
@@ -2527,21 +2578,21 @@ class EventsTest(unittest.TestCase):
2527
2578
  # Root object rebind.
2528
2579
  {
2529
2580
  '[0].d1': base.FieldUpdate(
2530
- path=object_utils.KeyPath.parse('a2.b1.c1[0].d1'),
2581
+ path=utils.KeyPath.parse('a2.b1.c1[0].d1'),
2531
2582
  target=sd.a2.b1.c1[0],
2532
2583
  field=sd.a2.b1.c1[0].value_spec.schema['d1'],
2533
2584
  old_value='foo',
2534
2585
  new_value='bar',
2535
2586
  ),
2536
2587
  '[0].d2': base.FieldUpdate(
2537
- path=object_utils.KeyPath.parse('a2.b1.c1[0].d2'),
2588
+ path=utils.KeyPath.parse('a2.b1.c1[0].d2'),
2538
2589
  target=sd.a2.b1.c1[0],
2539
2590
  field=sd.a2.b1.c1[0].value_spec.schema['d2'],
2540
2591
  old_value=True,
2541
2592
  new_value=False,
2542
2593
  ),
2543
2594
  '[0].d3.z': base.FieldUpdate(
2544
- path=object_utils.KeyPath.parse('a2.b1.c1[0].d3.z'),
2595
+ path=utils.KeyPath.parse('a2.b1.c1[0].d3.z'),
2545
2596
  target=sd.a2.b1.c1[0].d3,
2546
2597
  field=sd.a2.b1.c1[0].d3.__class__.__schema__['z'],
2547
2598
  old_value=MISSING_VALUE,
@@ -2557,29 +2608,30 @@ class EventsTest(unittest.TestCase):
2557
2608
  [
2558
2609
  # Root object rebind.
2559
2610
  {
2560
- 'd1':
2561
- base.FieldUpdate(
2562
- path=object_utils.KeyPath.parse('a2.b1.c1[0].d1'),
2563
- target=sd.a2.b1.c1[0],
2564
- field=sd.a2.b1.c1[0].value_spec.schema['d1'],
2565
- old_value='foo',
2566
- new_value='bar'),
2567
- 'd2':
2568
- base.FieldUpdate(
2569
- path=object_utils.KeyPath.parse('a2.b1.c1[0].d2'),
2570
- target=sd.a2.b1.c1[0],
2571
- field=sd.a2.b1.c1[0].value_spec.schema['d2'],
2572
- old_value=True,
2573
- new_value=False),
2574
- 'd3.z':
2575
- base.FieldUpdate(
2576
- path=object_utils.KeyPath.parse('a2.b1.c1[0].d3.z'),
2577
- target=sd.a2.b1.c1[0].d3,
2578
- field=sd.a2.b1.c1[0].d3.__class__.schema['z'],
2579
- old_value=MISSING_VALUE,
2580
- new_value='foo')
2611
+ 'd1': base.FieldUpdate(
2612
+ path=utils.KeyPath.parse('a2.b1.c1[0].d1'),
2613
+ target=sd.a2.b1.c1[0],
2614
+ field=sd.a2.b1.c1[0].value_spec.schema['d1'],
2615
+ old_value='foo',
2616
+ new_value='bar',
2617
+ ),
2618
+ 'd2': base.FieldUpdate(
2619
+ path=utils.KeyPath.parse('a2.b1.c1[0].d2'),
2620
+ target=sd.a2.b1.c1[0],
2621
+ field=sd.a2.b1.c1[0].value_spec.schema['d2'],
2622
+ old_value=True,
2623
+ new_value=False,
2624
+ ),
2625
+ 'd3.z': base.FieldUpdate(
2626
+ path=utils.KeyPath.parse('a2.b1.c1[0].d3.z'),
2627
+ target=sd.a2.b1.c1[0].d3,
2628
+ field=sd.a2.b1.c1[0].d3.__class__.__schema__['z'],
2629
+ old_value=MISSING_VALUE,
2630
+ new_value='foo',
2631
+ ),
2581
2632
  }
2582
- ])
2633
+ ],
2634
+ )
2583
2635
 
2584
2636
  def test_on_change_notification_order(self):
2585
2637
  change_order = []
@@ -2625,7 +2677,7 @@ class EventsTest(unittest.TestCase):
2625
2677
  y.x = A()
2626
2678
  self.assertIs(x.old_parent, y)
2627
2679
  self.assertIsNone(x.new_parent)
2628
- self.assertEqual(x.sym_path, object_utils.KeyPath())
2680
+ self.assertEqual(x.sym_path, utils.KeyPath())
2629
2681
 
2630
2682
  def test_on_path_change(self):
2631
2683
 
@@ -2636,8 +2688,8 @@ class EventsTest(unittest.TestCase):
2636
2688
  self.new_path = new_path
2637
2689
 
2638
2690
  x = A()
2639
- x.sym_setpath(object_utils.KeyPath('a'))
2640
- self.assertEqual(x.old_path, object_utils.KeyPath())
2691
+ x.sym_setpath(utils.KeyPath('a'))
2692
+ self.assertEqual(x.old_path, utils.KeyPath())
2641
2693
  self.assertEqual(x.new_path, 'a')
2642
2694
 
2643
2695
  y = Dict(x=x)
@@ -3039,7 +3091,7 @@ class SerializationTest(unittest.TestCase):
3039
3091
 
3040
3092
  def test_serialization_with_json_convertible(self):
3041
3093
 
3042
- class Y(object_utils.JSONConvertible):
3094
+ class Y(utils.JSONConvertible):
3043
3095
 
3044
3096
  TYPE_NAME = 'Y'
3045
3097
 
@@ -3059,7 +3111,7 @@ class SerializationTest(unittest.TestCase):
3059
3111
  def from_json(cls, json_dict, *args, **kwargs):
3060
3112
  return cls(json_dict.pop('value'))
3061
3113
 
3062
- object_utils.JSONConvertible.register(Y.TYPE_NAME, Y)
3114
+ utils.JSONConvertible.register(Y.TYPE_NAME, Y)
3063
3115
 
3064
3116
  a = self._A(Y(1), y=True)
3065
3117
  self.assertEqual(base.from_json_str(a.to_json_str()), a)
@@ -3266,9 +3318,7 @@ class FormatTest(unittest.TestCase):
3266
3318
 
3267
3319
  def test_compact_python_format(self):
3268
3320
  self.assertEqual(
3269
- object_utils.format(
3270
- self._a, compact=True, python_format=True, markdown=True
3271
- ),
3321
+ utils.format(self._a, compact=True, python_format=True, markdown=True),
3272
3322
  "`A(x=[A(x=1, y=None), A(x='foo', y={'a': A(x=True, y=1.0)})], "
3273
3323
  'y=MISSING_VALUE)`',
3274
3324
  )
@@ -3313,9 +3363,12 @@ class FormatTest(unittest.TestCase):
3313
3363
 
3314
3364
  def test_noncompact_python_format(self):
3315
3365
  self.assertEqual(
3316
- object_utils.format(
3317
- self._a, compact=False, verbose=False, python_format=True,
3318
- markdown=True
3366
+ utils.format(
3367
+ self._a,
3368
+ compact=False,
3369
+ verbose=False,
3370
+ python_format=True,
3371
+ markdown=True,
3319
3372
  ),
3320
3373
  inspect.cleandoc("""
3321
3374
  ```
@@ -3482,10 +3535,10 @@ class FormatTest(unittest.TestCase):
3482
3535
  return f() if f is not None else None
3483
3536
  return fn
3484
3537
 
3485
- with object_utils.str_format(custom_format=_method('_repr_xml_')):
3538
+ with utils.str_format(custom_format=_method('_repr_xml_')):
3486
3539
  self.assertEqual(str(Bar(Foo())), 'Bar(\n foo = Foo()\n)')
3487
3540
 
3488
- with object_utils.str_format(custom_format=_method('_repr_html_')):
3541
+ with utils.str_format(custom_format=_method('_repr_html_')):
3489
3542
  self.assertIn('<html>', str(Bar(Foo())))
3490
3543
 
3491
3544
 
@@ -16,11 +16,11 @@
16
16
  import traceback
17
17
  from typing import Any, Callable, List, Optional
18
18
 
19
- from pyglove.core import object_utils
19
+ from pyglove.core import utils
20
20
  from pyglove.core.symbolic import flags
21
21
 
22
22
 
23
- class Origin(object_utils.Formattable):
23
+ class Origin(utils.Formattable):
24
24
  """Class that represents the origin of a symbolic value.
25
25
 
26
26
  Origin is used for debugging the creation chain of a symbolic value, as
@@ -158,14 +158,12 @@ class Origin(object_utils.Formattable):
158
158
  if isinstance(self._source, (str, type(None))):
159
159
  source_str = self._source
160
160
  else:
161
- source_info = object_utils.format(
161
+ source_info = utils.format(
162
162
  self._source, compact, verbose, root_indent + 1, **kwargs
163
163
  )
164
- source_str = object_utils.RawText(
165
- f'{source_info} at 0x{id(self._source):8x}'
166
- )
164
+ source_str = utils.RawText(f'{source_info} at 0x{id(self._source):8x}')
167
165
 
168
- return object_utils.kvlist_str(
166
+ return utils.kvlist_str(
169
167
  [
170
168
  ('tag', self._tag, None),
171
169
  ('source', source_str, None),
@@ -14,8 +14,8 @@
14
14
  """Interfaces for pure symbolic objects."""
15
15
 
16
16
  from typing import Any, Callable, Optional, Tuple
17
- from pyglove.core import object_utils
18
17
  from pyglove.core import typing as pg_typing
18
+ from pyglove.core import utils
19
19
 
20
20
 
21
21
  class PureSymbolic(pg_typing.CustomTyping):
@@ -37,11 +37,12 @@ class PureSymbolic(pg_typing.CustomTyping):
37
37
 
38
38
  def custom_apply(
39
39
  self,
40
- path: object_utils.KeyPath,
40
+ path: utils.KeyPath,
41
41
  value_spec: pg_typing.ValueSpec,
42
42
  allow_partial: bool,
43
43
  child_transform: Optional[
44
- Callable[[object_utils.KeyPath, pg_typing.Field, Any], Any]] = None
44
+ Callable[[utils.KeyPath, pg_typing.Field, Any], Any]
45
+ ] = None,
45
46
  ) -> Tuple[bool, Any]:
46
47
  """Custom apply on a value based on its original value spec.
47
48
 
@@ -14,16 +14,30 @@
14
14
  """Symbolic reference."""
15
15
 
16
16
  import functools
17
- import numbers
18
- from typing import Any, Callable, List, Optional, Tuple
19
- from pyglove.core import object_utils
17
+ import typing
18
+ from typing import Any, Callable, List, Optional, Tuple, Type
20
19
  from pyglove.core import typing as pg_typing
20
+ from pyglove.core import utils
21
21
  from pyglove.core.symbolic import base
22
- from pyglove.core.symbolic.object import Object
22
+ from pyglove.core.symbolic import object as pg_object
23
23
  from pyglove.core.views.html import tree_view
24
24
 
25
25
 
26
- class Ref(Object, base.Inferential, tree_view.HtmlTreeView.Extension):
26
+ class RefMeta(pg_object.ObjectMeta):
27
+ """Metaclass for Ref."""
28
+
29
+ def __getitem__(cls, type_arg: Type[Any]) -> Any: # pylint: disable=no-self-argument
30
+ if typing.TYPE_CHECKING:
31
+ return type_arg
32
+ return pg_typing.Object(type_arg, transform=Ref)
33
+
34
+
35
+ class Ref(
36
+ pg_object.Object,
37
+ base.Inferential,
38
+ tree_view.HtmlTreeView.Extension,
39
+ metaclass=RefMeta
40
+ ):
27
41
  """Symbolic reference.
28
42
 
29
43
  When adding a symbolic node to a symbolic tree, it undergoes a copy operation
@@ -82,11 +96,11 @@ class Ref(Object, base.Inferential, tree_view.HtmlTreeView.Extension):
82
96
 
83
97
  def __new__(cls, value: Any, **kwargs):
84
98
  del kwargs
85
- if isinstance(value, (bool, numbers.Number, str)):
86
- return value
87
- return object.__new__(cls)
99
+ if isinstance(value, (base.Symbolic, list, dict)):
100
+ return object.__new__(cls)
101
+ return value
88
102
 
89
- @object_utils.explicit_method_override
103
+ @utils.explicit_method_override
90
104
  def __init__(self, value: Any, **kwargs) -> None:
91
105
  super().__init__(**kwargs)
92
106
  if isinstance(value, Ref):
@@ -113,12 +127,13 @@ class Ref(Object, base.Inferential, tree_view.HtmlTreeView.Extension):
113
127
 
114
128
  def custom_apply(
115
129
  self,
116
- path: object_utils.KeyPath,
130
+ path: utils.KeyPath,
117
131
  value_spec: pg_typing.ValueSpec,
118
132
  allow_partial: bool = False,
119
- child_transform: Optional[Callable[
120
- [object_utils.KeyPath, pg_typing.Field, Any], Any]] = None
121
- ) -> Tuple[bool, Any]:
133
+ child_transform: Optional[
134
+ Callable[[utils.KeyPath, pg_typing.Field, Any], Any]
135
+ ] = None,
136
+ ) -> Tuple[bool, Any]:
122
137
  """Validate candidates during value_spec binding time."""
123
138
  del child_transform
124
139
  # Check if the field being assigned could accept the referenced value.
@@ -152,9 +167,12 @@ class Ref(Object, base.Inferential, tree_view.HtmlTreeView.Extension):
152
167
  root_indent: int = 0,
153
168
  **kwargs: Any,
154
169
  ) -> str:
155
- value_str = object_utils.format(
170
+ value_str = utils.format(
156
171
  self._value,
157
- compact=compact, verbose=verbose, root_indent=root_indent + 1)
172
+ compact=compact,
173
+ verbose=verbose,
174
+ root_indent=root_indent + 1,
175
+ )
158
176
  if compact:
159
177
  return f'{self.__class__.__name__}({value_str})'
160
178
  else:
@@ -241,4 +259,3 @@ def deref(value: base.Symbolic, recursive: bool = False) -> Any:
241
259
  return v
242
260
  return value.rebind(_deref, raise_on_no_change=False)
243
261
  return value
244
-
@@ -16,6 +16,7 @@
16
16
  import copy
17
17
  import inspect
18
18
  import pickle
19
+ import typing
19
20
  from typing import Any
20
21
  import unittest
21
22
 
@@ -202,6 +203,22 @@ class RefTest(unittest.TestCase):
202
203
  """
203
204
  )
204
205
 
206
+ def test_annotation(self):
207
+ typing.TYPE_CHECKING = True
208
+ assert ref.Ref[ValueError] is ValueError
209
+ typing.TYPE_CHECKING = False
210
+
211
+ class Bar(Object):
212
+ x: int
213
+
214
+ class Foo(Object):
215
+ y: ref.Ref[Bar]
216
+
217
+ f = Foo(Bar(1))
218
+ self.assertIsInstance(f.sym_getattr('y'), ref.Ref)
219
+ self.assertEqual(f.y.sym_path, '')
220
+ self.assertIsInstance(ref.Ref[Bar](1), ref.Ref)
221
+
205
222
 
206
223
  if __name__ == '__main__':
207
224
  unittest.main()
@@ -21,8 +21,8 @@ from typing import Any, Callable, Dict, List, Optional, Sequence
21
21
 
22
22
  from pyglove.core import geno
23
23
  from pyglove.core import logging
24
- from pyglove.core import object_utils
25
24
  from pyglove.core import symbolic
25
+ from pyglove.core import utils
26
26
  from pyglove.core.tuning import backend
27
27
  from pyglove.core.tuning.early_stopping import EarlyStoppingPolicy
28
28
  from pyglove.core.tuning.protocols import Feedback
@@ -278,7 +278,7 @@ class _InMemoryResult(Result):
278
278
  ('step', self._best_trial.final_measurement.step),
279
279
  ('dna', self._best_trial.dna.format(compact=True))
280
280
  ])
281
- return object_utils.format(json_repr, compact, False, root_indent, **kwargs)
281
+ return utils.format(json_repr, compact, False, root_indent, **kwargs)
282
282
 
283
283
 
284
284
  @backend.add_backend('in-memory')
@@ -22,9 +22,9 @@ from typing import Any, Dict, List, Optional, Sequence, Tuple, Type, Union
22
22
 
23
23
  from pyglove.core import geno
24
24
  from pyglove.core import logging
25
- from pyglove.core import object_utils
26
25
  from pyglove.core import symbolic
27
26
  from pyglove.core import typing as pg_typing
27
+ from pyglove.core import utils
28
28
 
29
29
 
30
30
  class _DataEntity(symbolic.Object):
@@ -116,7 +116,7 @@ class Trial(_DataEntity):
116
116
  return tuple(metric_values) if len(metric_values) > 1 else metric_values[0]
117
117
 
118
118
 
119
- class Result(object_utils.Formattable):
119
+ class Result(utils.Formattable):
120
120
  """Interface for tuning result."""
121
121
 
122
122
  @property
@@ -416,7 +416,7 @@ class Feedback(metaclass=abc.ABCMeta):
416
416
  error_stack = traceback.format_exc()
417
417
  logging.warning('Skipping trial on unhandled exception: %s', error_stack)
418
418
  self.skip(error_stack)
419
- return object_utils.catch_errors(exceptions, skip_on_exception)
419
+ return utils.catch_errors(exceptions, skip_on_exception)
420
420
 
421
421
  @contextlib.contextmanager
422
422
  def ignore_race_condition(self):