pyglove 0.4.5.dev20240319__py3-none-any.whl → 0.4.5.dev202501140808__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.dev20240319.dist-info → pyglove-0.4.5.dev202501140808.dist-info}/METADATA +18 -4
  131. pyglove-0.4.5.dev202501140808.dist-info/RECORD +214 -0
  132. {pyglove-0.4.5.dev20240319.dist-info → pyglove-0.4.5.dev202501140808.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.dev20240319.dist-info/RECORD +0 -185
  143. /pyglove/core/{object_utils → utils}/thread_local.py +0 -0
  144. {pyglove-0.4.5.dev20240319.dist-info → pyglove-0.4.5.dev202501140808.dist-info}/LICENSE +0 -0
  145. {pyglove-0.4.5.dev20240319.dist-info → pyglove-0.4.5.dev202501140808.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::
@@ -132,22 +137,34 @@ class List(list, base.Symbolic, pg_typing.CustomTyping):
132
137
  Returns:
133
138
  A schema-less symbolic list, but its items maybe symbolic.
134
139
  """
135
- return cls(json_value,
136
- value_spec=value_spec,
137
- allow_partial=allow_partial,
138
- root_path=root_path)
140
+ return cls(
141
+ [
142
+ base.from_json(
143
+ v,
144
+ root_path=utils.KeyPath(i, root_path),
145
+ allow_partial=allow_partial,
146
+ **kwargs,
147
+ )
148
+ for i, v in enumerate(json_value)
149
+ ],
150
+ value_spec=value_spec,
151
+ root_path=root_path,
152
+ allow_partial=allow_partial,
153
+ )
139
154
 
140
155
  def __init__(
141
156
  self,
142
157
  items: Optional[Iterable[Any]] = None,
143
158
  *,
144
159
  value_spec: Optional[pg_typing.List] = None,
145
- onchange_callback: Optional[Callable[
146
- [Dict[object_utils.KeyPath, base.FieldUpdate]], None]] = None,
160
+ onchange_callback: Optional[
161
+ Callable[[Dict[utils.KeyPath, base.FieldUpdate]], None]
162
+ ] = None,
147
163
  allow_partial: bool = False,
148
164
  accessor_writable: bool = True,
149
165
  sealed: bool = False,
150
- root_path: Optional[object_utils.KeyPath] = None):
166
+ root_path: Optional[utils.KeyPath] = None,
167
+ ):
151
168
  """Constructor.
152
169
 
153
170
  Args:
@@ -328,8 +345,8 @@ class List(list, base.Symbolic, pg_typing.CustomTyping):
328
345
  return missing
329
346
 
330
347
  def _sym_rebind(
331
- self, path_value_pairs: typing.Dict[object_utils.KeyPath, Any]
332
- ) -> typing.List[base.FieldUpdate]:
348
+ self, path_value_pairs: typing.Dict[utils.KeyPath, Any]
349
+ ) -> typing.List[base.FieldUpdate]:
333
350
  """Subclass specific rebind implementation."""
334
351
  updates = []
335
352
 
@@ -369,14 +386,13 @@ class List(list, base.Symbolic, pg_typing.CustomTyping):
369
386
  return self
370
387
 
371
388
  def _update_children_paths(
372
- self,
373
- old_path: object_utils.KeyPath,
374
- new_path: object_utils.KeyPath) -> None:
389
+ self, old_path: utils.KeyPath, new_path: utils.KeyPath
390
+ ) -> None:
375
391
  """Update children paths according to root_path of current node."""
376
392
  del old_path
377
393
  for idx, item in self.sym_items():
378
394
  if isinstance(item, base.TopologyAware):
379
- item.sym_setpath(object_utils.KeyPath(idx, new_path))
395
+ item.sym_setpath(utils.KeyPath(idx, new_path))
380
396
 
381
397
  def _set_item_without_permission_check( # pytype: disable=signature-mismatch # overriding-parameter-type-checks
382
398
  self, key: int, value: Any) -> Optional[base.FieldUpdate]:
@@ -423,13 +439,15 @@ class List(list, base.Symbolic, pg_typing.CustomTyping):
423
439
  value = base.from_json(
424
440
  value,
425
441
  allow_partial=allow_partial,
426
- root_path=object_utils.KeyPath(idx, self.sym_path))
442
+ root_path=utils.KeyPath(idx, self.sym_path),
443
+ )
427
444
  if self._value_spec and flags.is_type_check_enabled():
428
445
  value = self._value_spec.element.apply(
429
446
  value,
430
447
  allow_partial=allow_partial,
431
448
  transform_fn=base.symbolic_transform_fn(self._allow_partial),
432
- root_path=object_utils.KeyPath(idx, self.sym_path))
449
+ root_path=utils.KeyPath(idx, self.sym_path),
450
+ )
433
451
  return self._relocate_if_symbolic(idx, value)
434
452
 
435
453
  @property
@@ -437,8 +455,7 @@ class List(list, base.Symbolic, pg_typing.CustomTyping):
437
455
  """Returns True if current list subscribes field updates."""
438
456
  return self._onchange_callback is not None
439
457
 
440
- def _on_change(self,
441
- field_updates: Dict[object_utils.KeyPath, base.FieldUpdate]):
458
+ def _on_change(self, field_updates: Dict[utils.KeyPath, base.FieldUpdate]):
442
459
  """On change event of List."""
443
460
  # Do nothing for now to handle changes of List.
444
461
 
@@ -454,7 +471,7 @@ class List(list, base.Symbolic, pg_typing.CustomTyping):
454
471
  # Update paths for children.
455
472
  for idx, item in self.sym_items():
456
473
  if isinstance(item, base.TopologyAware) and item.sym_path.key != idx:
457
- item.sym_setpath(object_utils.KeyPath(idx, self.sym_path))
474
+ item.sym_setpath(utils.KeyPath(idx, self.sym_path))
458
475
 
459
476
  if self._onchange_callback is not None:
460
477
  self._onchange_callback(field_updates)
@@ -714,11 +731,12 @@ class List(list, base.Symbolic, pg_typing.CustomTyping):
714
731
 
715
732
  def custom_apply(
716
733
  self,
717
- path: object_utils.KeyPath,
734
+ path: utils.KeyPath,
718
735
  value_spec: pg_typing.ValueSpec,
719
736
  allow_partial: bool,
720
737
  child_transform: Optional[
721
- Callable[[object_utils.KeyPath, pg_typing.Field, Any], Any]] = None
738
+ Callable[[utils.KeyPath, pg_typing.Field, Any], Any]
739
+ ] = None,
722
740
  ) -> Tuple[bool, 'List']:
723
741
  """Implement pg.typing.CustomTyping interface.
724
742
 
@@ -737,9 +755,12 @@ class List(list, base.Symbolic, pg_typing.CustomTyping):
737
755
  if self._value_spec:
738
756
  if value_spec and not value_spec.is_compatible(self._value_spec):
739
757
  raise ValueError(
740
- object_utils.message_on_path(
758
+ utils.message_on_path(
741
759
  f'List (spec={self._value_spec!r}) cannot be assigned to an '
742
- f'incompatible field (spec={value_spec!r}).', path))
760
+ f'incompatible field (spec={value_spec!r}).',
761
+ path,
762
+ )
763
+ )
743
764
  if self._allow_partial == allow_partial:
744
765
  proceed_with_standard_apply = False
745
766
  else:
@@ -749,9 +770,8 @@ class List(list, base.Symbolic, pg_typing.CustomTyping):
749
770
  return (proceed_with_standard_apply, self)
750
771
 
751
772
  def sym_jsonify(
752
- self,
753
- use_inferred: bool = False,
754
- **kwargs) -> object_utils.JSONValueType:
773
+ self, use_inferred: bool = False, **kwargs
774
+ ) -> utils.JSONValueType:
755
775
  """Converts current list to a list of plain Python objects."""
756
776
  def json_item(idx):
757
777
  v = self.sym_getattr(idx)
@@ -767,10 +787,9 @@ class List(list, base.Symbolic, pg_typing.CustomTyping):
767
787
  root_indent: int = 0,
768
788
  *,
769
789
  python_format: bool = False,
770
- markdown: bool = False,
771
790
  use_inferred: bool = False,
772
791
  cls_name: Optional[str] = None,
773
- bracket_type: object_utils.BracketType = object_utils.BracketType.SQUARE,
792
+ bracket_type: utils.BracketType = utils.BracketType.SQUARE,
774
793
  **kwargs,
775
794
  ) -> str:
776
795
  """Formats this List."""
@@ -779,16 +798,22 @@ class List(list, base.Symbolic, pg_typing.CustomTyping):
779
798
  return ' ' * 2 * indent + text
780
799
 
781
800
  cls_name = cls_name or ''
782
- open_bracket, close_bracket = object_utils.bracket_chars(bracket_type)
801
+ open_bracket, close_bracket = utils.bracket_chars(bracket_type)
783
802
  s = [f'{cls_name}{open_bracket}']
784
803
  if compact:
785
804
  kv_strs = []
786
805
  for idx, elem in self.sym_items():
787
806
  if use_inferred and isinstance(elem, base.Inferential):
788
807
  elem = self.sym_inferred(idx, default=elem)
789
- v_str = object_utils.format(
790
- elem, compact, verbose, root_indent + 1,
791
- 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
+ )
792
817
  if python_format:
793
818
  kv_strs.append(v_str)
794
819
  else:
@@ -804,9 +829,15 @@ class List(list, base.Symbolic, pg_typing.CustomTyping):
804
829
  s.append('\n')
805
830
  else:
806
831
  s.append(',\n')
807
- v_str = object_utils.format(
808
- elem, compact, verbose, root_indent + 1,
809
- 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
+ )
810
841
  if python_format:
811
842
  s.append(_indent(v_str, root_indent + 1))
812
843
  else:
@@ -815,7 +846,7 @@ class List(list, base.Symbolic, pg_typing.CustomTyping):
815
846
  s.append(_indent(close_bracket, root_indent))
816
847
  else:
817
848
  s.append(close_bracket)
818
- return object_utils.maybe_markdown_quote(''.join(s), markdown)
849
+ return ''.join(s)
819
850
 
820
851
  def __copy__(self) -> 'List':
821
852
  """List.copy."""
@@ -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,9 @@ class FormatTest(unittest.TestCase):
1716
1732
 
1717
1733
  def test_compact_python_format(self):
1718
1734
  self.assertEqual(
1719
- self._list.format(compact=True, python_format=True, markdown=True),
1735
+ utils.format(
1736
+ self._list, compact=True, python_format=True, markdown=True
1737
+ ),
1720
1738
  "`[{'a1': 1, 'a2': {'b1': {'c1': [{'d1': MISSING_VALUE, "
1721
1739
  "'d2': True, 'd3': A(x=2, y=MISSING_VALUE, z={'p': [None, True], "
1722
1740
  "'q': 'foo', 't': 'foo'})}]}}}]`",
@@ -1724,8 +1742,12 @@ class FormatTest(unittest.TestCase):
1724
1742
 
1725
1743
  def test_noncompact_python_format(self):
1726
1744
  self.assertEqual(
1727
- self._list.format(
1728
- compact=False, verbose=False, python_format=True, markdown=True
1745
+ utils.format(
1746
+ self._list,
1747
+ compact=False,
1748
+ verbose=False,
1749
+ python_format=True,
1750
+ markdown=True,
1729
1751
  ),
1730
1752
  inspect.cleandoc("""
1731
1753
  ```