pyglove 0.4.5.dev202501030808__py3-none-any.whl → 0.4.5.dev202501060809__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 (91) hide show
  1. pyglove/core/__init__.py +24 -21
  2. pyglove/core/geno/base.py +53 -38
  3. pyglove/core/geno/base_test.py +2 -4
  4. pyglove/core/geno/categorical.py +36 -27
  5. pyglove/core/geno/custom.py +18 -15
  6. pyglove/core/geno/numerical.py +19 -16
  7. pyglove/core/geno/space.py +3 -4
  8. pyglove/core/hyper/base.py +6 -6
  9. pyglove/core/hyper/categorical.py +91 -52
  10. pyglove/core/hyper/custom.py +7 -7
  11. pyglove/core/hyper/custom_test.py +9 -10
  12. pyglove/core/hyper/derived.py +30 -22
  13. pyglove/core/hyper/derived_test.py +2 -4
  14. pyglove/core/hyper/dynamic_evaluation.py +3 -4
  15. pyglove/core/hyper/evolvable.py +57 -46
  16. pyglove/core/hyper/numerical.py +48 -24
  17. pyglove/core/hyper/numerical_test.py +9 -9
  18. pyglove/core/hyper/object_template.py +58 -46
  19. pyglove/core/logging_test.py +0 -2
  20. pyglove/core/patching/object_factory.py +4 -4
  21. pyglove/core/patching/pattern_based.py +4 -4
  22. pyglove/core/patching/rule_based.py +4 -3
  23. pyglove/core/symbolic/base.py +167 -131
  24. pyglove/core/symbolic/base_test.py +17 -19
  25. pyglove/core/symbolic/boilerplate.py +4 -5
  26. pyglove/core/symbolic/class_wrapper.py +9 -9
  27. pyglove/core/symbolic/compounding.py +2 -2
  28. pyglove/core/symbolic/compounding_test.py +2 -4
  29. pyglove/core/symbolic/dict.py +70 -54
  30. pyglove/core/symbolic/dict_test.py +117 -100
  31. pyglove/core/symbolic/diff.py +12 -12
  32. pyglove/core/symbolic/flags.py +1 -1
  33. pyglove/core/symbolic/functor.py +16 -15
  34. pyglove/core/symbolic/functor_test.py +2 -4
  35. pyglove/core/symbolic/inferred.py +2 -2
  36. pyglove/core/symbolic/list.py +70 -47
  37. pyglove/core/symbolic/list_test.py +117 -98
  38. pyglove/core/symbolic/object.py +42 -40
  39. pyglove/core/symbolic/object_test.py +95 -88
  40. pyglove/core/symbolic/origin.py +5 -7
  41. pyglove/core/symbolic/pure_symbolic.py +4 -3
  42. pyglove/core/symbolic/ref.py +12 -8
  43. pyglove/core/tuning/local_backend.py +2 -2
  44. pyglove/core/tuning/protocols.py +3 -3
  45. pyglove/core/typing/annotation_conversion.py +3 -3
  46. pyglove/core/typing/callable_ext.py +11 -13
  47. pyglove/core/typing/callable_signature.py +19 -18
  48. pyglove/core/typing/callable_signature_test.py +3 -5
  49. pyglove/core/typing/class_schema.py +48 -44
  50. pyglove/core/typing/class_schema_test.py +3 -5
  51. pyglove/core/typing/custom_typing.py +5 -4
  52. pyglove/core/typing/key_specs.py +5 -7
  53. pyglove/core/typing/key_specs_test.py +4 -4
  54. pyglove/core/typing/type_conversion.py +4 -5
  55. pyglove/core/typing/type_conversion_test.py +12 -12
  56. pyglove/core/typing/typed_missing.py +6 -7
  57. pyglove/core/typing/typed_missing_test.py +7 -8
  58. pyglove/core/typing/value_specs.py +210 -141
  59. pyglove/core/typing/value_specs_test.py +12 -13
  60. pyglove/core/utils/__init__.py +159 -0
  61. pyglove/core/{object_utils → utils}/common_traits_test.py +1 -3
  62. pyglove/core/{object_utils → utils}/docstr_utils_test.py +1 -3
  63. pyglove/core/{object_utils → utils}/error_utils.py +3 -3
  64. pyglove/core/{object_utils → utils}/error_utils_test.py +1 -1
  65. pyglove/core/{object_utils → utils}/formatting.py +1 -1
  66. pyglove/core/{object_utils → utils}/formatting_test.py +1 -2
  67. pyglove/core/{object_utils → utils}/hierarchical.py +23 -25
  68. pyglove/core/{object_utils → utils}/hierarchical_test.py +3 -5
  69. pyglove/core/{object_utils → utils}/json_conversion_test.py +1 -3
  70. pyglove/core/{object_utils → utils}/missing.py +2 -2
  71. pyglove/core/{object_utils → utils}/missing_test.py +2 -4
  72. pyglove/core/{object_utils → utils}/thread_local_test.py +1 -3
  73. pyglove/core/{object_utils → utils}/timing.py +3 -3
  74. pyglove/core/{object_utils → utils}/timing_test.py +2 -3
  75. pyglove/core/{object_utils → utils}/value_location.py +2 -2
  76. pyglove/core/{object_utils → utils}/value_location_test.py +2 -4
  77. pyglove/core/views/base.py +25 -29
  78. pyglove/core/views/html/base.py +14 -15
  79. pyglove/core/views/html/controls/base.py +5 -5
  80. pyglove/core/views/html/controls/progress_bar.py +3 -5
  81. pyglove/core/views/html/tree_view.py +37 -35
  82. {pyglove-0.4.5.dev202501030808.dist-info → pyglove-0.4.5.dev202501060809.dist-info}/METADATA +1 -1
  83. {pyglove-0.4.5.dev202501030808.dist-info → pyglove-0.4.5.dev202501060809.dist-info}/RECORD +90 -90
  84. {pyglove-0.4.5.dev202501030808.dist-info → pyglove-0.4.5.dev202501060809.dist-info}/WHEEL +1 -1
  85. pyglove/core/object_utils/__init__.py +0 -161
  86. /pyglove/core/{object_utils → utils}/common_traits.py +0 -0
  87. /pyglove/core/{object_utils → utils}/docstr_utils.py +0 -0
  88. /pyglove/core/{object_utils → utils}/json_conversion.py +0 -0
  89. /pyglove/core/{object_utils → utils}/thread_local.py +0 -0
  90. {pyglove-0.4.5.dev202501030808.dist-info → pyglove-0.4.5.dev202501060809.dist-info}/LICENSE +0 -0
  91. {pyglove-0.4.5.dev202501030808.dist-info → pyglove-0.4.5.dev202501060809.dist-info}/top_level.txt +0 -0
@@ -18,8 +18,8 @@ import math
18
18
  import numbers
19
19
  import typing
20
20
  from typing import Any, Callable, Dict, Iterable, Iterator, Optional, Tuple, Union
21
- from pyglove.core import object_utils
22
21
  from pyglove.core import typing as pg_typing
22
+ from pyglove.core import utils
23
23
  from pyglove.core.symbolic import base
24
24
  from pyglove.core.symbolic import flags
25
25
 
@@ -74,13 +74,16 @@ class List(list, base.Symbolic, pg_typing.CustomTyping):
74
74
  """
75
75
 
76
76
  @classmethod
77
- def partial(cls,
78
- items: Optional[Iterable[Any]] = None,
79
- *,
80
- value_spec: Optional[pg_typing.List] = None,
81
- onchange_callback: Optional[Callable[
82
- [Dict[object_utils.KeyPath, base.FieldUpdate]], None]] = None,
83
- **kwargs) -> 'List':
77
+ def partial(
78
+ cls,
79
+ items: Optional[Iterable[Any]] = None,
80
+ *,
81
+ value_spec: Optional[pg_typing.List] = None,
82
+ onchange_callback: Optional[
83
+ Callable[[Dict[utils.KeyPath, base.FieldUpdate]], None]
84
+ ] = None,
85
+ **kwargs,
86
+ ) -> 'List':
84
87
  """Class method that creates a partial List object."""
85
88
  return cls(items,
86
89
  value_spec=value_spec,
@@ -89,13 +92,15 @@ class List(list, base.Symbolic, pg_typing.CustomTyping):
89
92
  **kwargs)
90
93
 
91
94
  @classmethod
92
- def from_json(cls,
93
- json_value: Any,
94
- *,
95
- value_spec: Optional[pg_typing.List] = None,
96
- allow_partial: bool = False,
97
- root_path: Optional[object_utils.KeyPath] = None,
98
- **kwargs) -> 'List':
95
+ def from_json(
96
+ cls,
97
+ json_value: Any,
98
+ *,
99
+ value_spec: Optional[pg_typing.List] = None,
100
+ allow_partial: bool = False,
101
+ root_path: Optional[utils.KeyPath] = None,
102
+ **kwargs,
103
+ ) -> 'List':
99
104
  """Class method that load an symbolic List from a JSON value.
100
105
 
101
106
  Example::
@@ -136,10 +141,11 @@ class List(list, base.Symbolic, pg_typing.CustomTyping):
136
141
  [
137
142
  base.from_json(
138
143
  v,
139
- root_path=object_utils.KeyPath(i, root_path),
144
+ root_path=utils.KeyPath(i, root_path),
140
145
  allow_partial=allow_partial,
141
- **kwargs
142
- ) for i, v in enumerate(json_value)
146
+ **kwargs,
147
+ )
148
+ for i, v in enumerate(json_value)
143
149
  ],
144
150
  value_spec=value_spec,
145
151
  root_path=root_path,
@@ -151,12 +157,14 @@ class List(list, base.Symbolic, pg_typing.CustomTyping):
151
157
  items: Optional[Iterable[Any]] = None,
152
158
  *,
153
159
  value_spec: Optional[pg_typing.List] = None,
154
- onchange_callback: Optional[Callable[
155
- [Dict[object_utils.KeyPath, base.FieldUpdate]], None]] = None,
160
+ onchange_callback: Optional[
161
+ Callable[[Dict[utils.KeyPath, base.FieldUpdate]], None]
162
+ ] = None,
156
163
  allow_partial: bool = False,
157
164
  accessor_writable: bool = True,
158
165
  sealed: bool = False,
159
- root_path: Optional[object_utils.KeyPath] = None):
166
+ root_path: Optional[utils.KeyPath] = None,
167
+ ):
160
168
  """Constructor.
161
169
 
162
170
  Args:
@@ -337,8 +345,8 @@ class List(list, base.Symbolic, pg_typing.CustomTyping):
337
345
  return missing
338
346
 
339
347
  def _sym_rebind(
340
- self, path_value_pairs: typing.Dict[object_utils.KeyPath, Any]
341
- ) -> typing.List[base.FieldUpdate]:
348
+ self, path_value_pairs: typing.Dict[utils.KeyPath, Any]
349
+ ) -> typing.List[base.FieldUpdate]:
342
350
  """Subclass specific rebind implementation."""
343
351
  updates = []
344
352
 
@@ -378,14 +386,13 @@ class List(list, base.Symbolic, pg_typing.CustomTyping):
378
386
  return self
379
387
 
380
388
  def _update_children_paths(
381
- self,
382
- old_path: object_utils.KeyPath,
383
- new_path: object_utils.KeyPath) -> None:
389
+ self, old_path: utils.KeyPath, new_path: utils.KeyPath
390
+ ) -> None:
384
391
  """Update children paths according to root_path of current node."""
385
392
  del old_path
386
393
  for idx, item in self.sym_items():
387
394
  if isinstance(item, base.TopologyAware):
388
- item.sym_setpath(object_utils.KeyPath(idx, new_path))
395
+ item.sym_setpath(utils.KeyPath(idx, new_path))
389
396
 
390
397
  def _set_item_without_permission_check( # pytype: disable=signature-mismatch # overriding-parameter-type-checks
391
398
  self, key: int, value: Any) -> Optional[base.FieldUpdate]:
@@ -432,13 +439,15 @@ class List(list, base.Symbolic, pg_typing.CustomTyping):
432
439
  value = base.from_json(
433
440
  value,
434
441
  allow_partial=allow_partial,
435
- root_path=object_utils.KeyPath(idx, self.sym_path))
442
+ root_path=utils.KeyPath(idx, self.sym_path),
443
+ )
436
444
  if self._value_spec and flags.is_type_check_enabled():
437
445
  value = self._value_spec.element.apply(
438
446
  value,
439
447
  allow_partial=allow_partial,
440
448
  transform_fn=base.symbolic_transform_fn(self._allow_partial),
441
- root_path=object_utils.KeyPath(idx, self.sym_path))
449
+ root_path=utils.KeyPath(idx, self.sym_path),
450
+ )
442
451
  return self._relocate_if_symbolic(idx, value)
443
452
 
444
453
  @property
@@ -446,8 +455,7 @@ class List(list, base.Symbolic, pg_typing.CustomTyping):
446
455
  """Returns True if current list subscribes field updates."""
447
456
  return self._onchange_callback is not None
448
457
 
449
- def _on_change(self,
450
- field_updates: Dict[object_utils.KeyPath, base.FieldUpdate]):
458
+ def _on_change(self, field_updates: Dict[utils.KeyPath, base.FieldUpdate]):
451
459
  """On change event of List."""
452
460
  # Do nothing for now to handle changes of List.
453
461
 
@@ -463,7 +471,7 @@ class List(list, base.Symbolic, pg_typing.CustomTyping):
463
471
  # Update paths for children.
464
472
  for idx, item in self.sym_items():
465
473
  if isinstance(item, base.TopologyAware) and item.sym_path.key != idx:
466
- item.sym_setpath(object_utils.KeyPath(idx, self.sym_path))
474
+ item.sym_setpath(utils.KeyPath(idx, self.sym_path))
467
475
 
468
476
  if self._onchange_callback is not None:
469
477
  self._onchange_callback(field_updates)
@@ -723,11 +731,12 @@ class List(list, base.Symbolic, pg_typing.CustomTyping):
723
731
 
724
732
  def custom_apply(
725
733
  self,
726
- path: object_utils.KeyPath,
734
+ path: utils.KeyPath,
727
735
  value_spec: pg_typing.ValueSpec,
728
736
  allow_partial: bool,
729
737
  child_transform: Optional[
730
- Callable[[object_utils.KeyPath, pg_typing.Field, Any], Any]] = None
738
+ Callable[[utils.KeyPath, pg_typing.Field, Any], Any]
739
+ ] = None,
731
740
  ) -> Tuple[bool, 'List']:
732
741
  """Implement pg.typing.CustomTyping interface.
733
742
 
@@ -746,9 +755,12 @@ class List(list, base.Symbolic, pg_typing.CustomTyping):
746
755
  if self._value_spec:
747
756
  if value_spec and not value_spec.is_compatible(self._value_spec):
748
757
  raise ValueError(
749
- object_utils.message_on_path(
758
+ utils.message_on_path(
750
759
  f'List (spec={self._value_spec!r}) cannot be assigned to an '
751
- f'incompatible field (spec={value_spec!r}).', path))
760
+ f'incompatible field (spec={value_spec!r}).',
761
+ path,
762
+ )
763
+ )
752
764
  if self._allow_partial == allow_partial:
753
765
  proceed_with_standard_apply = False
754
766
  else:
@@ -758,9 +770,8 @@ class List(list, base.Symbolic, pg_typing.CustomTyping):
758
770
  return (proceed_with_standard_apply, self)
759
771
 
760
772
  def sym_jsonify(
761
- self,
762
- use_inferred: bool = False,
763
- **kwargs) -> object_utils.JSONValueType:
773
+ self, use_inferred: bool = False, **kwargs
774
+ ) -> utils.JSONValueType:
764
775
  """Converts current list to a list of plain Python objects."""
765
776
  def json_item(idx):
766
777
  v = self.sym_getattr(idx)
@@ -778,7 +789,7 @@ class List(list, base.Symbolic, pg_typing.CustomTyping):
778
789
  python_format: bool = False,
779
790
  use_inferred: bool = False,
780
791
  cls_name: Optional[str] = None,
781
- bracket_type: object_utils.BracketType = object_utils.BracketType.SQUARE,
792
+ bracket_type: utils.BracketType = utils.BracketType.SQUARE,
782
793
  **kwargs,
783
794
  ) -> str:
784
795
  """Formats this List."""
@@ -787,16 +798,22 @@ class List(list, base.Symbolic, pg_typing.CustomTyping):
787
798
  return ' ' * 2 * indent + text
788
799
 
789
800
  cls_name = cls_name or ''
790
- open_bracket, close_bracket = object_utils.bracket_chars(bracket_type)
801
+ open_bracket, close_bracket = utils.bracket_chars(bracket_type)
791
802
  s = [f'{cls_name}{open_bracket}']
792
803
  if compact:
793
804
  kv_strs = []
794
805
  for idx, elem in self.sym_items():
795
806
  if use_inferred and isinstance(elem, base.Inferential):
796
807
  elem = self.sym_inferred(idx, default=elem)
797
- v_str = object_utils.format(
798
- elem, compact, verbose, root_indent + 1,
799
- python_format=python_format, use_inferred=use_inferred, **kwargs)
808
+ v_str = utils.format(
809
+ elem,
810
+ compact,
811
+ verbose,
812
+ root_indent + 1,
813
+ python_format=python_format,
814
+ use_inferred=use_inferred,
815
+ **kwargs,
816
+ )
800
817
  if python_format:
801
818
  kv_strs.append(v_str)
802
819
  else:
@@ -812,9 +829,15 @@ class List(list, base.Symbolic, pg_typing.CustomTyping):
812
829
  s.append('\n')
813
830
  else:
814
831
  s.append(',\n')
815
- v_str = object_utils.format(
816
- elem, compact, verbose, root_indent + 1,
817
- python_format=python_format, use_inferred=use_inferred, **kwargs)
832
+ v_str = utils.format(
833
+ elem,
834
+ compact,
835
+ verbose,
836
+ root_indent + 1,
837
+ python_format=python_format,
838
+ use_inferred=use_inferred,
839
+ **kwargs,
840
+ )
818
841
  if python_format:
819
842
  s.append(_indent(v_str, root_indent + 1))
820
843
  else:
@@ -20,8 +20,8 @@ import pickle
20
20
  from typing import Any
21
21
  import unittest
22
22
 
23
- from pyglove.core import object_utils
24
23
  from pyglove.core import typing as pg_typing
24
+ from pyglove.core import utils
25
25
  from pyglove.core.symbolic import base
26
26
  from pyglove.core.symbolic import flags
27
27
  from pyglove.core.symbolic import inferred
@@ -34,7 +34,7 @@ from pyglove.core.symbolic.pure_symbolic import NonDeterministic
34
34
  from pyglove.core.symbolic.pure_symbolic import PureSymbolic
35
35
 
36
36
 
37
- MISSING_VALUE = object_utils.MISSING_VALUE
37
+ MISSING_VALUE = utils.MISSING_VALUE
38
38
 
39
39
 
40
40
  class ListTest(unittest.TestCase):
@@ -685,7 +685,7 @@ class ListTest(unittest.TestCase):
685
685
  self.assertTrue(sl.sym_has('[0].x'))
686
686
  self.assertTrue(sl.sym_has('[0].x[0]'))
687
687
  self.assertTrue(sl.sym_has('[0].x[0].y'))
688
- self.assertTrue(sl.sym_has(object_utils.KeyPath.parse('[0].x[0].y')))
688
+ self.assertTrue(sl.sym_has(utils.KeyPath.parse('[0].x[0].y')))
689
689
 
690
690
  def test_sym_get(self):
691
691
  sl = List([dict(x=[dict(y=1)])])
@@ -1100,7 +1100,7 @@ class ListTest(unittest.TestCase):
1100
1100
  self.assertEqual(sl[1].sym_path, '[1]')
1101
1101
  self.assertEqual(sl[1][0].b.sym_path, '[1][0].b')
1102
1102
 
1103
- sl.sym_setpath(object_utils.KeyPath('a'))
1103
+ sl.sym_setpath(utils.KeyPath('a'))
1104
1104
  self.assertEqual(sl.sym_path, 'a')
1105
1105
  self.assertEqual(sl[0].sym_path, 'a[0]')
1106
1106
  self.assertEqual(sl[0].a.sym_path, 'a[0].a')
@@ -1412,96 +1412,112 @@ class RebindTest(unittest.TestCase):
1412
1412
  '[3]': 'foo', # Unchanged.
1413
1413
  '[4]': Insertion('bar')
1414
1414
  })
1415
- self.assertEqual(updates, [
1416
- { # Notification to `sl[2][0]`.
1417
- 'p': base.FieldUpdate(
1418
- object_utils.KeyPath.parse('[2][0].p'),
1419
- target=sl[2][0],
1420
- field=None,
1421
- old_value=1,
1422
- new_value=MISSING_VALUE),
1423
- 'q': base.FieldUpdate(
1424
- object_utils.KeyPath.parse('[2][0].q'),
1425
- target=sl[2][0],
1426
- field=None,
1427
- old_value=MISSING_VALUE,
1428
- new_value=2),
1429
- },
1430
- { # Notification to `sl.c`.
1431
- '[0].p': base.FieldUpdate(
1432
- object_utils.KeyPath.parse('[2][0].p'),
1433
- target=sl[2][0],
1434
- field=None,
1435
- old_value=1,
1436
- new_value=MISSING_VALUE),
1437
- '[0].q': base.FieldUpdate(
1438
- object_utils.KeyPath.parse('[2][0].q'),
1439
- target=sl[2][0],
1440
- field=None,
1441
- old_value=MISSING_VALUE,
1442
- new_value=2),
1443
- },
1444
- { # Notification to `sl[1].y`.
1445
- 'z': base.FieldUpdate(
1446
- object_utils.KeyPath.parse('[1].y.z'),
1447
- target=sl[1].y,
1448
- field=None,
1449
- old_value=MISSING_VALUE,
1450
- new_value=1),
1451
- },
1452
- { # Notification to `sl.b`.
1453
- 'x': base.FieldUpdate(
1454
- object_utils.KeyPath.parse('[1].x'),
1455
- target=sl[1],
1456
- field=None,
1457
- old_value=1,
1458
- new_value=2),
1459
- 'y.z': base.FieldUpdate(
1460
- object_utils.KeyPath.parse('[1].y.z'),
1461
- target=sl[1].y,
1462
- field=None,
1463
- old_value=MISSING_VALUE,
1464
- new_value=1),
1465
- },
1466
- { # Notification to `sl`.
1467
- '[0]': base.FieldUpdate(
1468
- object_utils.KeyPath.parse('[0]'),
1469
- target=sl,
1470
- field=None,
1471
- old_value=1,
1472
- new_value=2),
1473
- '[1].x': base.FieldUpdate(
1474
- object_utils.KeyPath.parse('[1].x'),
1475
- target=sl[1],
1476
- field=None,
1477
- old_value=1,
1478
- new_value=2),
1479
- '[1].y.z': base.FieldUpdate(
1480
- object_utils.KeyPath.parse('[1].y.z'),
1481
- target=sl[1].y,
1482
- field=None,
1483
- old_value=MISSING_VALUE,
1484
- new_value=1),
1485
- '[2][0].p': base.FieldUpdate(
1486
- object_utils.KeyPath.parse('[2][0].p'),
1487
- target=sl[2][0],
1488
- field=None,
1489
- old_value=1,
1490
- new_value=MISSING_VALUE),
1491
- '[2][0].q': base.FieldUpdate(
1492
- object_utils.KeyPath.parse('[2][0].q'),
1493
- target=sl[2][0],
1494
- field=None,
1495
- old_value=MISSING_VALUE,
1496
- new_value=2),
1497
- '[4]': base.FieldUpdate(
1498
- object_utils.KeyPath.parse('[4]'),
1499
- target=sl,
1500
- field=None,
1501
- old_value=MISSING_VALUE,
1502
- new_value='bar')
1503
- }
1504
- ])
1415
+ self.assertEqual(
1416
+ updates,
1417
+ [
1418
+ { # Notification to `sl[2][0]`.
1419
+ 'p': base.FieldUpdate(
1420
+ utils.KeyPath.parse('[2][0].p'),
1421
+ target=sl[2][0],
1422
+ field=None,
1423
+ old_value=1,
1424
+ new_value=MISSING_VALUE,
1425
+ ),
1426
+ 'q': base.FieldUpdate(
1427
+ utils.KeyPath.parse('[2][0].q'),
1428
+ target=sl[2][0],
1429
+ field=None,
1430
+ old_value=MISSING_VALUE,
1431
+ new_value=2,
1432
+ ),
1433
+ },
1434
+ { # Notification to `sl.c`.
1435
+ '[0].p': base.FieldUpdate(
1436
+ utils.KeyPath.parse('[2][0].p'),
1437
+ target=sl[2][0],
1438
+ field=None,
1439
+ old_value=1,
1440
+ new_value=MISSING_VALUE,
1441
+ ),
1442
+ '[0].q': base.FieldUpdate(
1443
+ utils.KeyPath.parse('[2][0].q'),
1444
+ target=sl[2][0],
1445
+ field=None,
1446
+ old_value=MISSING_VALUE,
1447
+ new_value=2,
1448
+ ),
1449
+ },
1450
+ { # Notification to `sl[1].y`.
1451
+ 'z': base.FieldUpdate(
1452
+ utils.KeyPath.parse('[1].y.z'),
1453
+ target=sl[1].y,
1454
+ field=None,
1455
+ old_value=MISSING_VALUE,
1456
+ new_value=1,
1457
+ ),
1458
+ },
1459
+ { # Notification to `sl.b`.
1460
+ 'x': base.FieldUpdate(
1461
+ utils.KeyPath.parse('[1].x'),
1462
+ target=sl[1],
1463
+ field=None,
1464
+ old_value=1,
1465
+ new_value=2,
1466
+ ),
1467
+ 'y.z': base.FieldUpdate(
1468
+ utils.KeyPath.parse('[1].y.z'),
1469
+ target=sl[1].y,
1470
+ field=None,
1471
+ old_value=MISSING_VALUE,
1472
+ new_value=1,
1473
+ ),
1474
+ },
1475
+ { # Notification to `sl`.
1476
+ '[0]': base.FieldUpdate(
1477
+ utils.KeyPath.parse('[0]'),
1478
+ target=sl,
1479
+ field=None,
1480
+ old_value=1,
1481
+ new_value=2,
1482
+ ),
1483
+ '[1].x': base.FieldUpdate(
1484
+ utils.KeyPath.parse('[1].x'),
1485
+ target=sl[1],
1486
+ field=None,
1487
+ old_value=1,
1488
+ new_value=2,
1489
+ ),
1490
+ '[1].y.z': base.FieldUpdate(
1491
+ utils.KeyPath.parse('[1].y.z'),
1492
+ target=sl[1].y,
1493
+ field=None,
1494
+ old_value=MISSING_VALUE,
1495
+ new_value=1,
1496
+ ),
1497
+ '[2][0].p': base.FieldUpdate(
1498
+ utils.KeyPath.parse('[2][0].p'),
1499
+ target=sl[2][0],
1500
+ field=None,
1501
+ old_value=1,
1502
+ new_value=MISSING_VALUE,
1503
+ ),
1504
+ '[2][0].q': base.FieldUpdate(
1505
+ utils.KeyPath.parse('[2][0].q'),
1506
+ target=sl[2][0],
1507
+ field=None,
1508
+ old_value=MISSING_VALUE,
1509
+ new_value=2,
1510
+ ),
1511
+ '[4]': base.FieldUpdate(
1512
+ utils.KeyPath.parse('[4]'),
1513
+ target=sl,
1514
+ field=None,
1515
+ old_value=MISSING_VALUE,
1516
+ new_value='bar',
1517
+ ),
1518
+ },
1519
+ ],
1520
+ )
1505
1521
 
1506
1522
  def test_rebind_with_fn(self):
1507
1523
  sl = List([0, dict(x=1, y='foo', z=[2, 3, 4])])
@@ -1716,7 +1732,7 @@ class FormatTest(unittest.TestCase):
1716
1732
 
1717
1733
  def test_compact_python_format(self):
1718
1734
  self.assertEqual(
1719
- object_utils.format(
1735
+ utils.format(
1720
1736
  self._list, compact=True, python_format=True, markdown=True
1721
1737
  ),
1722
1738
  "`[{'a1': 1, 'a2': {'b1': {'c1': [{'d1': MISSING_VALUE, "
@@ -1726,9 +1742,12 @@ class FormatTest(unittest.TestCase):
1726
1742
 
1727
1743
  def test_noncompact_python_format(self):
1728
1744
  self.assertEqual(
1729
- object_utils.format(
1730
- self._list, compact=False, verbose=False,
1731
- python_format=True, markdown=True
1745
+ utils.format(
1746
+ self._list,
1747
+ compact=False,
1748
+ verbose=False,
1749
+ python_format=True,
1750
+ markdown=True,
1732
1751
  ),
1733
1752
  inspect.cleandoc("""
1734
1753
  ```