pyglove 0.4.5.dev20240319__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.dev20240319.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.dev20240319.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.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.dev202501132210.dist-info}/LICENSE +0 -0
  145. {pyglove-0.4.5.dev20240319.dist-info → pyglove-0.4.5.dev202501132210.dist-info}/top_level.txt +0 -0
@@ -13,17 +13,18 @@
13
13
  # limitations under the License.
14
14
  """Symbolic differences."""
15
15
 
16
- from typing import Any, Callable, Tuple, Union
16
+ from typing import Any, Callable, Optional, Sequence, Tuple, Union
17
17
 
18
- from pyglove.core import object_utils
19
18
  from pyglove.core import typing as pg_typing
19
+ from pyglove.core import utils
20
20
  from pyglove.core.symbolic import base
21
21
  from pyglove.core.symbolic import list as pg_list
22
22
  from pyglove.core.symbolic import object as pg_object
23
23
  from pyglove.core.symbolic.pure_symbolic import PureSymbolic
24
+ from pyglove.core.views.html import tree_view
24
25
 
25
26
 
26
- class Diff(PureSymbolic, pg_object.Object):
27
+ class Diff(PureSymbolic, pg_object.Object, tree_view.HtmlTreeView.Extension):
27
28
  """A value diff between two objects: a 'left' object and a 'right' object.
28
29
 
29
30
  If one of them is missing, it may be represented by pg.Diff.MISSING
@@ -111,8 +112,7 @@ class Diff(PureSymbolic, pg_object.Object):
111
112
  return 'No diff'
112
113
  # When there is no diff, but the same value needs to be displayed
113
114
  # we simply return the value.
114
- return object_utils.format(
115
- self.value, compact, verbose, root_indent, **kwargs)
115
+ return utils.format(self.value, compact, verbose, root_indent, **kwargs)
116
116
  if self.is_leaf:
117
117
  exclude_keys = kwargs.pop('exclude_keys', None)
118
118
  exclude_keys = exclude_keys or set()
@@ -128,7 +128,7 @@ class Diff(PureSymbolic, pg_object.Object):
128
128
  verbose=verbose,
129
129
  root_indent=root_indent,
130
130
  cls_name='',
131
- bracket_type=object_utils.BracketType.SQUARE,
131
+ bracket_type=utils.BracketType.SQUARE,
132
132
  **kwargs,
133
133
  )
134
134
  if self.left is self.right:
@@ -140,10 +140,198 @@ class Diff(PureSymbolic, pg_object.Object):
140
140
  verbose=verbose,
141
141
  root_indent=root_indent,
142
142
  cls_name=cls_name,
143
- bracket_type=object_utils.BracketType.ROUND,
143
+ bracket_type=utils.BracketType.ROUND,
144
144
  **kwargs,
145
145
  )
146
146
 
147
+ # pytype: disable=annotation-type-mismatch
148
+ def _html_tree_view_summary(
149
+ self,
150
+ *,
151
+ view: tree_view.HtmlTreeView,
152
+ css_classes: Optional[Sequence[str]] = None,
153
+ title: Optional[str] = None,
154
+ max_summary_len_for_str: int = 80,
155
+ **kwargs,
156
+ ) -> Optional[tree_view.Html]:
157
+ # pytype: enable=annotation-type-mismatch
158
+ if not bool(self):
159
+ v = self.value
160
+ if (isinstance(v, (int, float, bool, type(None)))
161
+ or (isinstance(v, str) and len(v) <= max_summary_len_for_str)):
162
+ return None
163
+
164
+ if v == Diff.MISSING:
165
+ return view.summary(
166
+ self,
167
+ title=title or 'Diff',
168
+ css_classes=css_classes,
169
+ max_summary_len_for_str=max_summary_len_for_str,
170
+ **kwargs
171
+ )
172
+ return view.summary(
173
+ self.value,
174
+ title=title,
175
+ css_classes=css_classes,
176
+ max_summary_len_for_str=max_summary_len_for_str,
177
+ **kwargs
178
+ )
179
+ elif self.is_leaf:
180
+ return None
181
+ else:
182
+ assert isinstance(self.left, type), self.left
183
+ assert isinstance(self.right, type), self.right
184
+ if self.left is self.right:
185
+ diff_title = self.left.__name__
186
+ else:
187
+ diff_title = f'{self.left.__name__} | {self.right.__name__}'
188
+ return view.summary(
189
+ self,
190
+ title=title or diff_title,
191
+ css_classes=css_classes,
192
+ max_summary_len_for_str=max_summary_len_for_str,
193
+ **kwargs
194
+ )
195
+
196
+ def _html_tree_view_content(
197
+ self,
198
+ *,
199
+ view: tree_view.HtmlTreeView,
200
+ parent: Any = None,
201
+ root_path: Optional[utils.KeyPath] = None,
202
+ css_classes: Optional[Sequence[str]] = None,
203
+ **kwargs,
204
+ ) -> tree_view.Html:
205
+ root_path = root_path or utils.KeyPath()
206
+ if not bool(self):
207
+ if self.value == Diff.MISSING:
208
+ root = tree_view.Html.element(
209
+ 'span',
210
+ # CSS class already defined in HtmlTreeView.
211
+ css_classes=['diff-empty']
212
+ )
213
+ else:
214
+ # When there is no diff, but the same value needs to be displayed
215
+ # we simply return the value.
216
+ root = view.content(
217
+ self.value, parent=parent, root_path=root_path,
218
+ css_classes=['no-diff', view.css_class_name(self.value)],
219
+ **kwargs,
220
+ )
221
+ elif self.is_leaf:
222
+ root = tree_view.Html.element(
223
+ 'div',
224
+ [
225
+ view.render( # pylint: disable=g-long-ternary
226
+ self.left, root_path=root_path + 'left',
227
+ css_classes=['diff-left'], **kwargs
228
+ ) if self.left != Diff.MISSING else None,
229
+ view.render( # pylint: disable=g-long-ternary
230
+ self.right, root_path=root_path + 'right',
231
+ css_classes=['diff-right'], **kwargs
232
+ ) if self.right != Diff.MISSING else None,
233
+ ],
234
+ css_classes=['diff-value']
235
+ )
236
+ else:
237
+ assert isinstance(self.left, type)
238
+ assert isinstance(self.right, type)
239
+
240
+ if issubclass(self.left, list):
241
+ key_fn = int
242
+ else:
243
+ key_fn = lambda k: k
244
+
245
+ s = tree_view.Html()
246
+ for k, v in self.children.items():
247
+ k = key_fn(k)
248
+ child_path = root_path + k
249
+ has_diff = bool(v)
250
+ s.write('<tr><td>')
251
+ # Print Key.
252
+ s.write(
253
+ view.object_key(
254
+ child_path, value=v, parent=self,
255
+ css_classes=None if has_diff else ['no-diff'],
256
+ ),
257
+ )
258
+ # Print Value.
259
+ s.write(
260
+ '</td><td>',
261
+ view.render(
262
+ v, root_path=child_path, **kwargs
263
+ ),
264
+ '</td></tr>'
265
+ )
266
+ root = tree_view.Html.element(
267
+ 'div',
268
+ [
269
+ '<table>', s, '</table>'
270
+ ],
271
+ css_classes=[
272
+ 'complex-value', view.css_class_name(self.left), css_classes,
273
+ ]
274
+ )
275
+ return root
276
+
277
+ def _html_tree_view_config(self) -> dict[str, Any]:
278
+ return tree_view.HtmlTreeView.get_kwargs(
279
+ super()._html_tree_view_config(),
280
+ dict(
281
+ css_classes=[
282
+ 'has-diff' if bool(self) else 'no-diff'
283
+ ]
284
+ )
285
+ )
286
+
287
+ @classmethod
288
+ def _html_tree_view_css_styles(cls) -> list[str]:
289
+ return super()._html_tree_view_css_styles() + [
290
+ """
291
+ /* Diff styles. */
292
+ .has-diff.summary-title::after {
293
+ content: ' (diff)';
294
+ color: #aaa;
295
+ }
296
+ .has-diff.summary-title {
297
+ background-color: yellow;
298
+ }
299
+ .no-diff.summary-title::after {
300
+ content: ' (no diff)';
301
+ font-style: italic;
302
+ font-weight: normal;
303
+ color: #aaa;
304
+ }
305
+ .no-diff {
306
+ opacity: 0.6;
307
+ }
308
+ .no-diff.simple_value::after {
309
+ content: '(no diff)';
310
+ margin-left: 0.5em;
311
+ color: #aaa;
312
+ font-style: italic;
313
+ }
314
+ .diff-empty::before {
315
+ content: '(empty)';
316
+ font-style: italic;
317
+ margin-left: 0.5em;
318
+ color: #aaa;
319
+ }
320
+ .diff-left.summary-title::before, .diff-left.simple-value::before{
321
+ content: '🇱';
322
+ }
323
+ .diff-left {
324
+ background-color: #ffcccc;
325
+ }
326
+ .diff-right.summary-title::before, .diff-right.simple-value::before{
327
+ content: '🇷';
328
+ }
329
+ .diff-right {
330
+ background-color: #ccffcc;
331
+ }
332
+ """
333
+ ]
334
+
147
335
 
148
336
  # NOTE(daiyip): we add the symbolic attribute to Diff after its declaration
149
337
  # since we need to access Diff.MISSING as the default value for `left` and
@@ -164,7 +352,8 @@ def diff(
164
352
  right: Any,
165
353
  flatten: bool = False,
166
354
  collapse: Union[bool, str, Callable[[Any, Any], bool]] = 'same_type',
167
- mode: str = 'diff') -> object_utils.Nestable[Diff]:
355
+ mode: str = 'diff',
356
+ ) -> utils.Nestable[Diff]:
168
357
  """Inspect the symbolic diff between two objects.
169
358
 
170
359
  For example::
@@ -290,7 +479,7 @@ def diff(
290
479
  assert isinstance(container, base.Symbolic)
291
480
  return container.sym_hasattr, container.sym_getattr, container.sym_items
292
481
 
293
- def _diff(x, y) -> Tuple[object_utils.Nestable[Diff], bool]:
482
+ def _diff(x, y) -> Tuple[utils.Nestable[Diff], bool]:
294
483
  if x is y or x == y:
295
484
  return (Diff(x, y), False)
296
485
  if not _should_collapse(x, y):
@@ -344,5 +533,5 @@ def diff(
344
533
  if not has_diff and mode == 'diff':
345
534
  diff_value = Diff()
346
535
  if flatten:
347
- diff_value = object_utils.flatten(diff_value)
536
+ diff_value = utils.flatten(diff_value)
348
537
  return diff_value
@@ -13,6 +13,8 @@
13
13
  # limitations under the License.
14
14
  """Tests for pyglove.diff."""
15
15
 
16
+ import inspect
17
+ from typing import Any
16
18
  import unittest
17
19
 
18
20
  from pyglove.core import typing as pg_typing
@@ -337,6 +339,230 @@ class DiffTest(unittest.TestCase):
337
339
  '_type': Diff(B, C),
338
340
  })
339
341
 
342
+ def test_to_html(self):
343
+
344
+ class Foo(Object):
345
+ x: Any
346
+ y: Any
347
+
348
+ class Bar(Foo):
349
+ pass
350
+
351
+ def assert_style(html, expected):
352
+ expected = inspect.cleandoc(expected).strip()
353
+ actual = html.style_section.strip()
354
+ if expected not in actual:
355
+ print(actual)
356
+ self.assertIn(expected, actual)
357
+
358
+ def assert_content(html, expected):
359
+ expected = inspect.cleandoc(expected).strip()
360
+ actual = html.content.strip()
361
+ if actual != expected:
362
+ print(actual)
363
+ self.assertEqual(actual.strip(), expected)
364
+
365
+ assert_style(
366
+ pg_diff(1, 1).to_html(),
367
+ """
368
+ /* Diff styles. */
369
+ .has-diff.summary-title::after {
370
+ content: ' (diff)';
371
+ color: #aaa;
372
+ }
373
+ .has-diff.summary-title {
374
+ background-color: yellow;
375
+ }
376
+ .no-diff.summary-title::after {
377
+ content: ' (no diff)';
378
+ font-style: italic;
379
+ font-weight: normal;
380
+ color: #aaa;
381
+ }
382
+ .no-diff {
383
+ opacity: 0.6;
384
+ }
385
+ .no-diff.simple_value::after {
386
+ content: '(no diff)';
387
+ margin-left: 0.5em;
388
+ color: #aaa;
389
+ font-style: italic;
390
+ }
391
+ .diff-empty::before {
392
+ content: '(empty)';
393
+ font-style: italic;
394
+ margin-left: 0.5em;
395
+ color: #aaa;
396
+ }
397
+ .diff-left.summary-title::before, .diff-left.simple-value::before{
398
+ content: '🇱';
399
+ }
400
+ .diff-left {
401
+ background-color: #ffcccc;
402
+ }
403
+ .diff-right.summary-title::before, .diff-right.simple-value::before{
404
+ content: '🇷';
405
+ }
406
+ .diff-right {
407
+ background-color: #ccffcc;
408
+ }
409
+ """
410
+ )
411
+
412
+ # No diff leaf value (diff only)
413
+ assert_content(
414
+ pg_diff(1, 1).to_html(),
415
+ """
416
+ <details open class="pyglove diff no-diff"><summary><div class="summary-title no-diff">Diff</div><span class="tooltip no-diff">No diff</span></summary><span class="diff-empty"></span></details>
417
+ """
418
+ )
419
+
420
+ # No diff leaf value (keep both)
421
+ assert_content(
422
+ pg_diff(1, 1, mode='both').to_html(),
423
+ """
424
+ <span class="simple-value int no-diff">1</span>
425
+ """
426
+ )
427
+ # No diff complex value (diff only)
428
+ assert_content(
429
+ pg_diff(
430
+ Foo(x=1, y=[Foo(x=2, y=3)]),
431
+ Foo(x=1, y=[Foo(x=2, y=3)])
432
+ ).to_html(
433
+ enable_summary_tooltip=False,
434
+ enable_key_tooltip=False,
435
+ ),
436
+ """
437
+ <details open class="pyglove diff no-diff"><summary><div class="summary-title no-diff">Diff</div></summary><span class="diff-empty"></span></details>
438
+ """
439
+ )
440
+ # No diff complex value.
441
+ assert_content(
442
+ pg_diff(
443
+ Foo(x=1, y=[Foo(x=2, y=3)]),
444
+ Foo(x=1, y=[Foo(x=2, y=3)]), mode='both'
445
+ ).to_html(
446
+ enable_summary_tooltip=False,
447
+ enable_key_tooltip=False,
448
+ ),
449
+ """
450
+ <details open class="pyglove diff no-diff"><summary><div class="summary-title no-diff">Foo(...)</div></summary><div class="complex-value foo no-diff"><details open class="pyglove int"><summary><div class="summary-name">x</div><div class="summary-title">int</div></summary><span class="simple-value int">1</span></details><details class="pyglove list"><summary><div class="summary-name">y</div><div class="summary-title">List(...)</div></summary><div class="complex-value list"><table><tr><td><span class="object-key int">0</span></td><td><details class="pyglove foo"><summary><div class="summary-title">Foo(...)</div></summary><div class="complex-value foo"><details open class="pyglove int"><summary><div class="summary-name">x</div><div class="summary-title">int</div></summary><span class="simple-value int">2</span></details><details open class="pyglove int"><summary><div class="summary-name">y</div><div class="summary-title">int</div></summary><span class="simple-value int">3</span></details></div></details></td></tr></table></div></details></div></details>
451
+ """
452
+ )
453
+
454
+ # Diff on simple value.
455
+ assert_content(
456
+ pg_diff(1, 2).to_html(),
457
+ """
458
+ <div class="diff-value"><span class="simple-value int diff-left">1</span><span class="simple-value int diff-right">2</span></div>
459
+ """
460
+ )
461
+
462
+ # Diff on list.
463
+ assert_content(
464
+ pg_diff([0, 1, 2], [0, 2], mode='both').to_html(
465
+ enable_summary_tooltip=False,
466
+ enable_key_tooltip=False,
467
+ ),
468
+ """
469
+ <details open class="pyglove diff has-diff"><summary><div class="summary-title has-diff">List</div></summary><div class="complex-value list-class"><table><tr><td><span class="object-key int no-diff">0</span><span class="tooltip">[0]</span></td><td><span class="simple-value int no-diff">0</span></td></tr><tr><td><span class="object-key int">1</span><span class="tooltip">[1]</span></td><td><div class="diff-value"><span class="simple-value int diff-left">1</span><span class="simple-value int diff-right">2</span></div></td></tr><tr><td><span class="object-key int">2</span><span class="tooltip">[2]</span></td><td><div class="diff-value"><span class="simple-value int diff-left">2</span></div></td></tr></table></div></details>
470
+ """
471
+ )
472
+
473
+ # Diff on dict.
474
+ assert_content(
475
+ pg_diff(dict(x=1, y=2, z=3), dict(x=1, y=3, w=4), mode='both').to_html(
476
+ enable_summary_tooltip=False,
477
+ enable_key_tooltip=False,
478
+ ),
479
+ """
480
+ <details open class="pyglove diff has-diff"><summary><div class="summary-title has-diff">dict</div></summary><div class="complex-value dict-class"><table><tr><td><span class="object-key str no-diff">x</span><span class="tooltip">x</span></td><td><span class="simple-value int no-diff">1</span></td></tr><tr><td><span class="object-key str">y</span><span class="tooltip">y</span></td><td><div class="diff-value"><span class="simple-value int diff-left">2</span><span class="simple-value int diff-right">3</span></div></td></tr><tr><td><span class="object-key str">z</span><span class="tooltip">z</span></td><td><div class="diff-value"><span class="simple-value int diff-left">3</span></div></td></tr><tr><td><span class="object-key str">w</span><span class="tooltip">w</span></td><td><div class="diff-value"><span class="simple-value int diff-right">4</span></div></td></tr></table></div></details>
481
+ """
482
+ )
483
+
484
+ # Diff on symbolic objects of the same type.
485
+ assert_content(
486
+ pg_diff(
487
+ Foo(x=2, y=Foo(x=3, y=3)),
488
+ Foo(x=1, y=Foo(x=2, y=3)),
489
+ mode='both',
490
+ ).to_html(
491
+ enable_summary_tooltip=False,
492
+ enable_key_tooltip=False,
493
+ ),
494
+ """
495
+ <details open class="pyglove diff has-diff"><summary><div class="summary-title has-diff">Foo</div></summary><div class="complex-value foo-class"><table><tr><td><span class="object-key str">x</span><span class="tooltip">x</span></td><td><div class="diff-value"><span class="simple-value int diff-left">2</span><span class="simple-value int diff-right">1</span></div></td></tr><tr><td><span class="object-key str">y</span><span class="tooltip">y</span></td><td><details open class="pyglove diff has-diff"><summary><div class="summary-title has-diff">Foo</div></summary><div class="complex-value foo-class"><table><tr><td><span class="object-key str">x</span><span class="tooltip">y.x</span></td><td><div class="diff-value"><span class="simple-value int diff-left">3</span><span class="simple-value int diff-right">2</span></div></td></tr><tr><td><span class="object-key str no-diff">y</span><span class="tooltip">y.y</span></td><td><span class="simple-value int no-diff">3</span></td></tr></table></div></details></td></tr></table></div></details>
496
+ """
497
+ )
498
+
499
+ # Diff on symbolic objects of different types.
500
+ assert_content(
501
+ pg_diff(
502
+ Foo(x=2, y=Foo(x=3, y=3)),
503
+ Bar(x=2, y=Foo(x=3, y=3)),
504
+ mode='both',
505
+ ).to_html(
506
+ enable_summary_tooltip=False,
507
+ enable_key_tooltip=False,
508
+ ),
509
+ """
510
+ <div class="diff-value"><details open class="pyglove foo diff-left"><summary><div class="summary-title diff-left">Foo(...)</div></summary><div class="complex-value foo"><details open class="pyglove int"><summary><div class="summary-name">x</div><div class="summary-title">int</div></summary><span class="simple-value int">2</span></details><details class="pyglove foo"><summary><div class="summary-name">y</div><div class="summary-title">Foo(...)</div></summary><div class="complex-value foo"><details open class="pyglove int"><summary><div class="summary-name">x</div><div class="summary-title">int</div></summary><span class="simple-value int">3</span></details><details open class="pyglove int"><summary><div class="summary-name">y</div><div class="summary-title">int</div></summary><span class="simple-value int">3</span></details></div></details></div></details><details open class="pyglove bar diff-right"><summary><div class="summary-title diff-right">Bar(...)</div></summary><div class="complex-value bar"><details open class="pyglove int"><summary><div class="summary-name">x</div><div class="summary-title">int</div></summary><span class="simple-value int">2</span></details><details class="pyglove foo"><summary><div class="summary-name">y</div><div class="summary-title">Foo(...)</div></summary><div class="complex-value foo"><details open class="pyglove int"><summary><div class="summary-name">x</div><div class="summary-title">int</div></summary><span class="simple-value int">3</span></details><details open class="pyglove int"><summary><div class="summary-name">y</div><div class="summary-title">int</div></summary><span class="simple-value int">3</span></details></div></details></div></details></div>
511
+ """
512
+ )
513
+
514
+ # Different types but same values, with value collapsing.
515
+ assert_content(
516
+ pg_diff(
517
+ Foo(x=2, y=Foo(x=3, y=3)),
518
+ Bar(x=2, y=Bar(x=3, y=3)),
519
+ mode='both', collapse=True,
520
+ ).to_html(
521
+ enable_summary_tooltip=False,
522
+ enable_key_tooltip=False,
523
+ ),
524
+ """
525
+ <details open class="pyglove diff has-diff"><summary><div class="summary-title has-diff">Foo | Bar</div></summary><div class="complex-value foo-class"><table><tr><td><span class="object-key str no-diff">x</span><span class="tooltip">x</span></td><td><span class="simple-value int no-diff">2</span></td></tr><tr><td><span class="object-key str">y</span><span class="tooltip">y</span></td><td><details open class="pyglove diff has-diff"><summary><div class="summary-title has-diff">Foo | Bar</div></summary><div class="complex-value foo-class"><table><tr><td><span class="object-key str no-diff">x</span><span class="tooltip">y.x</span></td><td><span class="simple-value int no-diff">3</span></td></tr><tr><td><span class="object-key str no-diff">y</span><span class="tooltip">y.y</span></td><td><span class="simple-value int no-diff">3</span></td></tr></table></div></details></td></tr></table></div></details>
526
+ """
527
+ )
528
+
529
+ # Different types and different values, with value collapsing.
530
+ assert_content(
531
+ pg_diff(
532
+ Foo(x=2, y=Foo(x=3, y=3)),
533
+ Bar(x=3, y=Bar(x=2, y=3)),
534
+ mode='both', collapse=True,
535
+ ).to_html(
536
+ enable_summary_tooltip=False,
537
+ enable_key_tooltip=False,
538
+ ),
539
+ """
540
+ <details open class="pyglove diff has-diff"><summary><div class="summary-title has-diff">Foo | Bar</div></summary><div class="complex-value foo-class"><table><tr><td><span class="object-key str">x</span><span class="tooltip">x</span></td><td><div class="diff-value"><span class="simple-value int diff-left">2</span><span class="simple-value int diff-right">3</span></div></td></tr><tr><td><span class="object-key str">y</span><span class="tooltip">y</span></td><td><details open class="pyglove diff has-diff"><summary><div class="summary-title has-diff">Foo | Bar</div></summary><div class="complex-value foo-class"><table><tr><td><span class="object-key str">x</span><span class="tooltip">y.x</span></td><td><div class="diff-value"><span class="simple-value int diff-left">3</span><span class="simple-value int diff-right">2</span></div></td></tr><tr><td><span class="object-key str no-diff">y</span><span class="tooltip">y.y</span></td><td><span class="simple-value int no-diff">3</span></td></tr></table></div></details></td></tr></table></div></details>
541
+ """
542
+ )
543
+
544
+ # Diff with uncollapsing UI.
545
+ assert_content(
546
+ pg_diff(
547
+ [
548
+ Foo(1, 2), Foo(1, 2), None,
549
+ Foo(x=1, y=Foo(2, 3)), [dict(x=1, y=2)]
550
+ ],
551
+ [
552
+ Foo(1, 2), Bar(1, 2), dict(x=1),
553
+ [1, 2], Foo(x=2, y=Foo(2, 4)), [dict(x=3)]
554
+ ],
555
+ mode='both'
556
+ ).to_html(
557
+ enable_summary_tooltip=False,
558
+ enable_key_tooltip=False,
559
+ uncollapse=['[0]', '[1].right', '[3].left.y'],
560
+ ),
561
+ """
562
+ <details open class="pyglove diff has-diff"><summary><div class="summary-title has-diff">List</div></summary><div class="complex-value list-class"><table><tr><td><span class="object-key int no-diff">0</span><span class="tooltip">[0]</span></td><td><details open class="pyglove diff no-diff"><summary><div class="summary-title no-diff">Foo(...)</div></summary><div class="complex-value foo no-diff"><details open class="pyglove int"><summary><div class="summary-name">x</div><div class="summary-title">int</div></summary><span class="simple-value int">1</span></details><details open class="pyglove int"><summary><div class="summary-name">y</div><div class="summary-title">int</div></summary><span class="simple-value int">2</span></details></div></details></td></tr><tr><td><span class="object-key int">1</span><span class="tooltip">[1]</span></td><td><div class="diff-value"><details open class="pyglove foo diff-left"><summary><div class="summary-title diff-left">Foo(...)</div></summary><div class="complex-value foo"><details open class="pyglove int"><summary><div class="summary-name">x</div><div class="summary-title">int</div></summary><span class="simple-value int">1</span></details><details open class="pyglove int"><summary><div class="summary-name">y</div><div class="summary-title">int</div></summary><span class="simple-value int">2</span></details></div></details><details open class="pyglove bar diff-right"><summary><div class="summary-title diff-right">Bar(...)</div></summary><div class="complex-value bar"><details open class="pyglove int"><summary><div class="summary-name">x</div><div class="summary-title">int</div></summary><span class="simple-value int">1</span></details><details open class="pyglove int"><summary><div class="summary-name">y</div><div class="summary-title">int</div></summary><span class="simple-value int">2</span></details></div></details></div></td></tr><tr><td><span class="object-key int">2</span><span class="tooltip">[2]</span></td><td><div class="diff-value"><span class="simple-value none-type diff-left">None</span><details open class="pyglove dict diff-right"><summary><div class="summary-title diff-right">Dict(...)</div></summary><div class="complex-value dict"><details open class="pyglove int"><summary><div class="summary-name">x</div><div class="summary-title">int</div></summary><span class="simple-value int">1</span></details></div></details></div></td></tr><tr><td><span class="object-key int">3</span><span class="tooltip">[3]</span></td><td><div class="diff-value"><details open class="pyglove foo diff-left"><summary><div class="summary-title diff-left">Foo(...)</div></summary><div class="complex-value foo"><details open class="pyglove int"><summary><div class="summary-name">x</div><div class="summary-title">int</div></summary><span class="simple-value int">1</span></details><details open class="pyglove foo"><summary><div class="summary-name">y</div><div class="summary-title">Foo(...)</div></summary><div class="complex-value foo"><details open class="pyglove int"><summary><div class="summary-name">x</div><div class="summary-title">int</div></summary><span class="simple-value int">2</span></details><details open class="pyglove int"><summary><div class="summary-name">y</div><div class="summary-title">int</div></summary><span class="simple-value int">3</span></details></div></details></div></details><details open class="pyglove list diff-right"><summary><div class="summary-title diff-right">List(...)</div></summary><div class="complex-value list"><table><tr><td><span class="object-key int">0</span></td><td><span class="simple-value int">1</span></td></tr><tr><td><span class="object-key int">1</span></td><td><span class="simple-value int">2</span></td></tr></table></div></details></div></td></tr><tr><td><span class="object-key int">4</span><span class="tooltip">[4]</span></td><td><div class="diff-value"><details open class="pyglove list diff-left"><summary><div class="summary-title diff-left">List(...)</div></summary><div class="complex-value list"><table><tr><td><span class="object-key int">0</span></td><td><details class="pyglove dict"><summary><div class="summary-title">Dict(...)</div></summary><div class="complex-value dict"><details open class="pyglove int"><summary><div class="summary-name">x</div><div class="summary-title">int</div></summary><span class="simple-value int">1</span></details><details open class="pyglove int"><summary><div class="summary-name">y</div><div class="summary-title">int</div></summary><span class="simple-value int">2</span></details></div></details></td></tr></table></div></details><details open class="pyglove foo diff-right"><summary><div class="summary-title diff-right">Foo(...)</div></summary><div class="complex-value foo"><details open class="pyglove int"><summary><div class="summary-name">x</div><div class="summary-title">int</div></summary><span class="simple-value int">2</span></details><details class="pyglove foo"><summary><div class="summary-name">y</div><div class="summary-title">Foo(...)</div></summary><div class="complex-value foo"><details open class="pyglove int"><summary><div class="summary-name">x</div><div class="summary-title">int</div></summary><span class="simple-value int">2</span></details><details open class="pyglove int"><summary><div class="summary-name">y</div><div class="summary-title">int</div></summary><span class="simple-value int">4</span></details></div></details></div></details></div></td></tr><tr><td><span class="object-key int">5</span><span class="tooltip">[5]</span></td><td><div class="diff-value"><details open class="pyglove list diff-right"><summary><div class="summary-title diff-right">List(...)</div></summary><div class="complex-value list"><table><tr><td><span class="object-key int">0</span></td><td><details class="pyglove dict"><summary><div class="summary-title">Dict(...)</div></summary><div class="complex-value dict"><details open class="pyglove int"><summary><div class="summary-name">x</div><div class="summary-title">int</div></summary><span class="simple-value int">3</span></details></div></details></td></tr></table></div></details></div></td></tr></table></div></details>
563
+ """
564
+ )
565
+
340
566
 
341
567
  if __name__ == '__main__':
342
568
  unittest.main()
@@ -14,7 +14,7 @@
14
14
  """Global, thread-local and scoped flags for handling symbolic objects."""
15
15
 
16
16
  from typing import Any, Callable, ContextManager, Optional
17
- from pyglove.core.object_utils import thread_local
17
+ from pyglove.core.utils import thread_local
18
18
 
19
19
 
20
20
  #