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
@@ -0,0 +1,1461 @@
1
+ # Copyright 2024 The PyGlove Authors
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ import inspect
15
+ from typing import Any
16
+ import unittest
17
+
18
+ from pyglove.core.views.html import base
19
+ from pyglove.core.views.html import tree_view
20
+
21
+ Html = base.Html
22
+ KeyPath = tree_view.KeyPath
23
+ KeyPathSet = tree_view.KeyPathSet
24
+
25
+
26
+ class TestCase(unittest.TestCase):
27
+
28
+ def setUp(self):
29
+ super().setUp()
30
+ self._view = tree_view.HtmlTreeView()
31
+
32
+ def assert_style(self, html, expected):
33
+ expected = inspect.cleandoc(expected).strip()
34
+ actual = html.style_section.strip()
35
+ if actual != expected:
36
+ print(actual)
37
+ self.assertEqual(actual.strip(), expected)
38
+
39
+ def assert_content(self, html, expected):
40
+ expected = inspect.cleandoc(expected).strip()
41
+ actual = html.content.strip()
42
+ if actual != expected:
43
+ print(actual)
44
+ self.assertEqual(actual.strip(), expected)
45
+
46
+ def assert_count(self, html, part, expected):
47
+ content = html.content.strip()
48
+ actual = content.count(part)
49
+ if actual != expected:
50
+ print(content)
51
+ self.assertEqual(actual, expected)
52
+
53
+
54
+ class TooltipTest(TestCase):
55
+
56
+ def test_style(self):
57
+ self.assert_style(
58
+ self._view.tooltip(
59
+ 'This <hello><world></world></hello>.',
60
+ name='name',
61
+ ),
62
+ """
63
+ <style>
64
+ /* Tooltip styles. */
65
+ span.tooltip {
66
+ visibility: hidden;
67
+ white-space: pre-wrap;
68
+ font-weight: normal;
69
+ background-color: #484848;
70
+ color: #fff;
71
+ padding: 10px;
72
+ border-radius: 6px;
73
+ position: absolute;
74
+ z-index: 1;
75
+ }
76
+ span.tooltip:hover {
77
+ visibility: visible;
78
+ }
79
+ </style>
80
+ """
81
+ )
82
+
83
+ def test_render(self):
84
+ self.assert_content(
85
+ self._view.tooltip(
86
+ 'This <hello><world></world></hello>.',
87
+ name='name',
88
+ ),
89
+ """
90
+ <span class="tooltip">&#x27;This &lt;hello&gt;&lt;world&gt;&lt;/world&gt;&lt;/hello&gt;.&#x27;</span>
91
+ """
92
+ )
93
+ self.assert_content(
94
+ self._view.tooltip(
95
+ 1,
96
+ content=Html.element('div', ['hello']),
97
+ css_classes=['int'],
98
+ name='name',
99
+ ),
100
+ """
101
+ <span class="tooltip int"><div>hello</div></span>
102
+ """
103
+ )
104
+
105
+
106
+ class SummaryTest(TestCase):
107
+
108
+ def test_style(self):
109
+ self.assert_style(
110
+ self._view.summary(
111
+ 1, enable_summary=True,
112
+ ),
113
+ """
114
+ <style>
115
+ /* Tooltip styles. */
116
+ span.tooltip {
117
+ visibility: hidden;
118
+ white-space: pre-wrap;
119
+ font-weight: normal;
120
+ background-color: #484848;
121
+ color: #fff;
122
+ padding: 10px;
123
+ border-radius: 6px;
124
+ position: absolute;
125
+ z-index: 1;
126
+ }
127
+ span.tooltip:hover {
128
+ visibility: visible;
129
+ }
130
+ /* Summary styles. */
131
+ details.pyglove summary {
132
+ font-weight: bold;
133
+ margin: -0.5em -0.5em 0;
134
+ padding: 0.5em;
135
+ }
136
+ .summary-name {
137
+ display: inline;
138
+ padding: 3px 5px 3px 5px;
139
+ margin: 0 5px;
140
+ border-radius: 3px;
141
+ }
142
+ .summary-title {
143
+ display: inline;
144
+ }
145
+ .summary-name + div.summary-title {
146
+ display: inline;
147
+ color: #aaa;
148
+ }
149
+ .summary-title:hover + span.tooltip {
150
+ visibility: visible;
151
+ }
152
+ .summary-name:hover > span.tooltip {
153
+ visibility: visible;
154
+ background-color: darkblue;
155
+ }
156
+ </style>
157
+ """
158
+ )
159
+
160
+ def test_summary_title(self):
161
+ self.assert_content(
162
+ self._view.summary(
163
+ 1, enable_summary=True, enable_summary_tooltip=False
164
+ ),
165
+ """
166
+ <summary><div class="summary-title">int</div></summary>
167
+ """
168
+ )
169
+ self.assert_content(
170
+ self._view.summary(
171
+ int, enable_summary=True, enable_summary_tooltip=False
172
+ ),
173
+ """
174
+ <summary><div class="summary-title">type</div></summary>
175
+ """
176
+ )
177
+ self.assert_content(
178
+ self._view.summary(
179
+ [0, 1], enable_summary=True, enable_summary_tooltip=False
180
+ ),
181
+ """
182
+ <summary><div class="summary-title">list(...)</div></summary>
183
+ """
184
+ )
185
+
186
+ def test_enable_summary(self):
187
+ self.assertIsNone(
188
+ self._view.summary(1, enable_summary=None),
189
+ )
190
+ self.assertIsNone(
191
+ self._view.summary('foo', enable_summary=False),
192
+ )
193
+ self.assertIsNone(
194
+ self._view.summary('foo', enable_summary=None),
195
+ )
196
+ self.assert_content(
197
+ self._view.summary('foo', enable_summary=True),
198
+ """
199
+ <summary><div class="summary-title">str</div><span class="tooltip">&#x27;foo&#x27;</span></summary>
200
+ """
201
+ )
202
+ self.assert_content(
203
+ self._view.summary(
204
+ 'foo', enable_summary=None, max_summary_len_for_str=1
205
+ ),
206
+ """
207
+ <summary><div class="summary-title">str</div><span class="tooltip">&#x27;foo&#x27;</span></summary>
208
+ """
209
+ )
210
+ self.assertIsNone(
211
+ self._view.summary(
212
+ 'foo', enable_summary=None, enable_summary_for_str=False
213
+ ),
214
+ )
215
+
216
+ def test_css_styles(self):
217
+ self.assert_content(
218
+ self._view.summary(
219
+ 'foo', name='x', css_classes=['bar'], enable_summary=True
220
+ ),
221
+ """
222
+ <summary><div class="summary-name bar">x<span class="tooltip bar"></span></div><div class="summary-title bar">str</div><span class="tooltip bar">&#x27;foo&#x27;</span></summary>
223
+ """
224
+ )
225
+
226
+ def test_max_summary_len_for_str(self):
227
+ self.assert_content(
228
+ self._view.summary(
229
+ 'abcdefg',
230
+ max_summary_len_for_str=5
231
+ ),
232
+ """
233
+ <summary><div class="summary-title">str</div><span class="tooltip">&#x27;abcdefg&#x27;</span></summary>
234
+ """
235
+ )
236
+
237
+ def test_name(self):
238
+ self.assert_content(
239
+ self._view.summary('foo', name='x'),
240
+ """
241
+ <summary><div class="summary-name">x<span class="tooltip"></span></div><div class="summary-title">str</div><span class="tooltip">&#x27;foo&#x27;</span></summary>
242
+ """
243
+ )
244
+
245
+ def test_enable_key_tooltip(self):
246
+ self.assert_content(
247
+ self._view.summary(
248
+ 'foo',
249
+ name='x',
250
+ root_path=KeyPath.parse('y.a.x'),
251
+ enable_summary_tooltip=False,
252
+ enable_key_tooltip=True
253
+ ),
254
+ """
255
+ <summary><div class="summary-name">x<span class="tooltip">y.a.x</span></div><div class="summary-title">str</div></summary>
256
+ """
257
+ )
258
+ self.assert_content(
259
+ self._view.summary(
260
+ 'foo',
261
+ name='x',
262
+ root_path=KeyPath.parse('y.a.x'),
263
+ enable_summary_tooltip=False,
264
+ enable_key_tooltip=False
265
+ ),
266
+ """
267
+ <summary><div class="summary-name">x</div><div class="summary-title">str</div></summary>
268
+ """
269
+ )
270
+
271
+ def test_summary_color(self):
272
+ self.assert_content(
273
+ self._view.summary(
274
+ 'foo',
275
+ name='x',
276
+ summary_color=('white', 'red'),
277
+ enable_summary_tooltip=False
278
+ ),
279
+ """
280
+ <summary><div class="summary-name" style="color:white;background-color:red;">x<span class="tooltip"></span></div><div class="summary-title">str</div></summary>
281
+ """
282
+ )
283
+
284
+ def test_enable_summary_tooltip(self):
285
+ self.assert_content(
286
+ self._view.summary(
287
+ 'foo', enable_summary=True, enable_summary_tooltip=True
288
+ ),
289
+ """
290
+ <summary><div class="summary-title">str</div><span class="tooltip">&#x27;foo&#x27;</span></summary>
291
+ """
292
+ )
293
+ self.assert_content(
294
+ self._view.summary(
295
+ 'foo', enable_summary=True, enable_summary_tooltip=False
296
+ ),
297
+ """
298
+ <summary><div class="summary-title">str</div></summary>
299
+ """
300
+ )
301
+
302
+
303
+ class ContentTest(TestCase):
304
+
305
+ def test_style(self):
306
+ self.assert_style(
307
+ self._view.content(dict(x=1)),
308
+ """
309
+ <style>
310
+ /* Tooltip styles. */
311
+ span.tooltip {
312
+ visibility: hidden;
313
+ white-space: pre-wrap;
314
+ font-weight: normal;
315
+ background-color: #484848;
316
+ color: #fff;
317
+ padding: 10px;
318
+ border-radius: 6px;
319
+ position: absolute;
320
+ z-index: 1;
321
+ }
322
+ span.tooltip:hover {
323
+ visibility: visible;
324
+ }
325
+ /* Summary styles. */
326
+ details.pyglove summary {
327
+ font-weight: bold;
328
+ margin: -0.5em -0.5em 0;
329
+ padding: 0.5em;
330
+ }
331
+ .summary-name {
332
+ display: inline;
333
+ padding: 3px 5px 3px 5px;
334
+ margin: 0 5px;
335
+ border-radius: 3px;
336
+ }
337
+ .summary-title {
338
+ display: inline;
339
+ }
340
+ .summary-name + div.summary-title {
341
+ display: inline;
342
+ color: #aaa;
343
+ }
344
+ .summary-title:hover + span.tooltip {
345
+ visibility: visible;
346
+ }
347
+ .summary-name:hover > span.tooltip {
348
+ visibility: visible;
349
+ background-color: darkblue;
350
+ }
351
+ /* Simple value styles. */
352
+ .simple-value {
353
+ color: blue;
354
+ display: inline-block;
355
+ white-space: pre-wrap;
356
+ padding: 0.2em;
357
+ margin-top: 0.15em;
358
+ }
359
+ .simple-value.str {
360
+ color: darkred;
361
+ font-style: italic;
362
+ }
363
+ .simple-value.int, .simple-value.float {
364
+ color: darkblue;
365
+ }
366
+ /* Value details styles. */
367
+ details.pyglove {
368
+ border: 1px solid #aaa;
369
+ border-radius: 4px;
370
+ padding: 0.5em 0.5em 0;
371
+ margin: 0.25em 0;
372
+ }
373
+ details.pyglove[open] {
374
+ padding: 0.5em 0.5em 0.5em;
375
+ }
376
+ .highlight {
377
+ background-color: Mark;
378
+ }
379
+ .lowlight {
380
+ opacity: 0.2;
381
+ }
382
+ /* Complex value styles. */
383
+ span.empty-container::before {
384
+ content: '(empty)';
385
+ font-style: italic;
386
+ margin-left: 0.5em;
387
+ color: #aaa;
388
+ }
389
+ </style>
390
+ """
391
+ )
392
+
393
+ def test_simple_types(self):
394
+ self.assert_content(
395
+ self._view.content(1),
396
+ """
397
+ <span class="simple-value int">1</span>
398
+ """
399
+ )
400
+ self.assert_content(
401
+ self._view.content(1.5),
402
+ """
403
+ <span class="simple-value float">1.5</span>
404
+ """
405
+ )
406
+ self.assert_content(
407
+ self._view.content(True),
408
+ """
409
+ <span class="simple-value bool">True</span>
410
+ """
411
+ )
412
+ self.assert_content(
413
+ self._view.content(None),
414
+ """
415
+ <span class="simple-value none-type">None</span>
416
+ """
417
+ )
418
+ self.assert_content(
419
+ self._view.content('<foo>'),
420
+ """
421
+ <span class="simple-value str">&#x27;&lt;foo&gt;&#x27;</span>
422
+ """
423
+ )
424
+ self.assert_content(
425
+ self._view.content(
426
+ '<hello><world> \nto everyone.', max_summary_len_for_str=10
427
+ ),
428
+ """
429
+ <span class="simple-value str">&lt;hello&gt;&lt;world&gt;
430
+ to everyone.</span>
431
+ """
432
+ )
433
+
434
+ def test_list(self):
435
+ self.assert_content(
436
+ self._view.content([1, 2, 'abc']),
437
+ """
438
+ <div class="complex-value list"><table><tr><td><span class="object-key int">0</span><span class="tooltip">[0]</span></td><td><span class="simple-value int">1</span></td></tr><tr><td><span class="object-key int">1</span><span class="tooltip">[1]</span></td><td><span class="simple-value int">2</span></td></tr><tr><td><span class="object-key int">2</span><span class="tooltip">[2]</span></td><td><span class="simple-value str">&#x27;abc&#x27;</span></td></tr></table></div>
439
+ """
440
+ )
441
+ self.assert_content(
442
+ self._view.content([1, 2, 'abc'], key_style='label'),
443
+ """
444
+ <div class="complex-value list"><table><tr><td><span class="object-key int">0</span><span class="tooltip">[0]</span></td><td><span class="simple-value int">1</span></td></tr><tr><td><span class="object-key int">1</span><span class="tooltip">[1]</span></td><td><span class="simple-value int">2</span></td></tr><tr><td><span class="object-key int">2</span><span class="tooltip">[2]</span></td><td><span class="simple-value str">&#x27;abc&#x27;</span></td></tr></table></div>
445
+ """
446
+ )
447
+ self.assert_content(
448
+ self._view.content([]),
449
+ """
450
+ <div class="complex-value list"><span class="empty-container"></span></div>
451
+ """
452
+ )
453
+
454
+ def test_tuple(self):
455
+ self.assert_content(
456
+ self._view.content((1, True)),
457
+ """
458
+ <div class="complex-value tuple"><table><tr><td><span class="object-key int">0</span><span class="tooltip">[0]</span></td><td><span class="simple-value int">1</span></td></tr><tr><td><span class="object-key int">1</span><span class="tooltip">[1]</span></td><td><span class="simple-value bool">True</span></td></tr></table></div>
459
+ """
460
+ )
461
+ self.assert_content(
462
+ self._view.content((1, True), key_style='label'),
463
+ """
464
+ <div class="complex-value tuple"><table><tr><td><span class="object-key int">0</span><span class="tooltip">[0]</span></td><td><span class="simple-value int">1</span></td></tr><tr><td><span class="object-key int">1</span><span class="tooltip">[1]</span></td><td><span class="simple-value bool">True</span></td></tr></table></div>
465
+ """
466
+ )
467
+ self.assert_content(
468
+ self._view.content(()),
469
+ """
470
+ <div class="complex-value tuple"><span class="empty-container"></span></div>
471
+ """
472
+ )
473
+
474
+ def test_dict(self):
475
+ self.assert_content(
476
+ self._view.content(dict(x=1, y='foo')),
477
+ """
478
+ <div class="complex-value dict"><details open class="pyglove int"><summary><div class="summary-name">x<span class="tooltip">x</span></div><div class="summary-title">int</div><span class="tooltip">1</span></summary><span class="simple-value int">1</span></details><details open class="pyglove str"><summary><div class="summary-name">y<span class="tooltip">y</span></div><div class="summary-title">str</div><span class="tooltip">&#x27;foo&#x27;</span></summary><span class="simple-value str">&#x27;foo&#x27;</span></details></div>
479
+ """
480
+ )
481
+ self.assert_content(
482
+ self._view.content(dict(x=1, y='foo'), key_style='label'),
483
+ """
484
+ <div class="complex-value dict"><table><tr><td><span class="object-key str">x</span><span class="tooltip">x</span></td><td><span class="simple-value int">1</span></td></tr><tr><td><span class="object-key str">y</span><span class="tooltip">y</span></td><td><span class="simple-value str">&#x27;foo&#x27;</span></td></tr></table></div>
485
+ """
486
+ )
487
+ self.assert_content(
488
+ self._view.content({}),
489
+ """
490
+ <div class="complex-value dict"><span class="empty-container"></span></div>
491
+ """
492
+ )
493
+
494
+ def test_custom_types(self):
495
+
496
+ class Foo:
497
+ def __str__(self):
498
+ return '<Foo></Foo>'
499
+
500
+ self.assert_content(
501
+ self._view.content(Foo()),
502
+ """
503
+ <span class="simple-value foo">&lt;Foo&gt;&lt;/Foo&gt;</span>
504
+ """
505
+ )
506
+
507
+ def test_custom_key_value_render(self):
508
+ def render_key(view, key, **kwargs):
509
+ del view, kwargs
510
+ return Html.element('span', [f'custom {key}'])
511
+
512
+ def render_value(view, *, value, **kwargs):
513
+ del view, kwargs
514
+ return Html.element('span', [f'custom {value}'])
515
+
516
+ self.assert_content(
517
+ self._view.complex_value(
518
+ dict(x=1, y='foo'),
519
+ parent=None,
520
+ root_path=KeyPath(),
521
+ render_key_fn=render_key,
522
+ render_value_fn=render_value,
523
+ ),
524
+ """
525
+ <div class="complex-value none-type"><span>custom 1</span><span>custom foo</span></div>
526
+ """
527
+ )
528
+ self.assert_content(
529
+ self._view.complex_value(
530
+ dict(x=1, y='foo'),
531
+ parent=None,
532
+ key_style='label',
533
+ root_path=KeyPath(),
534
+ render_key_fn=render_key,
535
+ render_value_fn=render_value,
536
+ ),
537
+ """
538
+ <div class="complex-value none-type"><table><tr><td><span>custom x</span></td><td><span>custom 1</span></td></tr><tr><td><span>custom y</span></td><td><span>custom foo</span></td></tr></table></div>
539
+ """
540
+ )
541
+
542
+ def test_key_style_with_nesting(self):
543
+
544
+ class Foo:
545
+ def __str__(self):
546
+ return '<Foo></Foo>'
547
+
548
+ self.assert_content(
549
+ self._view.content(
550
+ [
551
+ dict(
552
+ x=[(1, 2)],
553
+ y=['b', Foo()],
554
+ ),
555
+ 1,
556
+ [1, dict(xx=1, yy='a')]
557
+ ]
558
+ ),
559
+ """
560
+ <div class="complex-value list"><table><tr><td><span class="object-key int">0</span><span class="tooltip">[0]</span></td><td><details class="pyglove dict"><summary><div class="summary-title">dict(...)</div><span class="tooltip">{
561
+ &#x27;x&#x27;: [(1, 2)],
562
+ &#x27;y&#x27;: [&#x27;b&#x27;, &lt;Foo&gt;&lt;/Foo&gt;]
563
+ }</span></summary><div class="complex-value dict"><details class="pyglove list"><summary><div class="summary-name">x<span class="tooltip">[0].x</span></div><div class="summary-title">list(...)</div><span class="tooltip">[(1, 2)]</span></summary><div class="complex-value list"><table><tr><td><span class="object-key int">0</span><span class="tooltip">[0].x[0]</span></td><td><details class="pyglove tuple"><summary><div class="summary-title">tuple(...)</div><span class="tooltip">(1, 2)</span></summary><div class="complex-value tuple"><table><tr><td><span class="object-key int">0</span><span class="tooltip">[0].x[0][0]</span></td><td><span class="simple-value int">1</span></td></tr><tr><td><span class="object-key int">1</span><span class="tooltip">[0].x[0][1]</span></td><td><span class="simple-value int">2</span></td></tr></table></div></details></td></tr></table></div></details><details class="pyglove list"><summary><div class="summary-name">y<span class="tooltip">[0].y</span></div><div class="summary-title">list(...)</div><span class="tooltip">[&#x27;b&#x27;, &lt;Foo&gt;&lt;/Foo&gt;]</span></summary><div class="complex-value list"><table><tr><td><span class="object-key int">0</span><span class="tooltip">[0].y[0]</span></td><td><span class="simple-value str">&#x27;b&#x27;</span></td></tr><tr><td><span class="object-key int">1</span><span class="tooltip">[0].y[1]</span></td><td><details class="pyglove foo"><summary><div class="summary-title">Foo(...)</div><span class="tooltip">&lt;Foo&gt;&lt;/Foo&gt;</span></summary><span class="simple-value foo">&lt;Foo&gt;&lt;/Foo&gt;</span></details></td></tr></table></div></details></div></details></td></tr><tr><td><span class="object-key int">1</span><span class="tooltip">[1]</span></td><td><span class="simple-value int">1</span></td></tr><tr><td><span class="object-key int">2</span><span class="tooltip">[2]</span></td><td><details class="pyglove list"><summary><div class="summary-title">list(...)</div><span class="tooltip">[1, {
564
+ &#x27;xx&#x27;: 1,
565
+ &#x27;yy&#x27;: &#x27;a&#x27;
566
+ }]</span></summary><div class="complex-value list"><table><tr><td><span class="object-key int">0</span><span class="tooltip">[2][0]</span></td><td><span class="simple-value int">1</span></td></tr><tr><td><span class="object-key int">1</span><span class="tooltip">[2][1]</span></td><td><details class="pyglove dict"><summary><div class="summary-title">dict(...)</div><span class="tooltip">{
567
+ &#x27;xx&#x27;: 1,
568
+ &#x27;yy&#x27;: &#x27;a&#x27;
569
+ }</span></summary><div class="complex-value dict"><details open class="pyglove int"><summary><div class="summary-name">xx<span class="tooltip">[2][1].xx</span></div><div class="summary-title">int</div><span class="tooltip">1</span></summary><span class="simple-value int">1</span></details><details open class="pyglove str"><summary><div class="summary-name">yy<span class="tooltip">[2][1].yy</span></div><div class="summary-title">str</div><span class="tooltip">&#x27;a&#x27;</span></summary><span class="simple-value str">&#x27;a&#x27;</span></details></div></details></td></tr></table></div></details></td></tr></table></div>
570
+ """
571
+ )
572
+
573
+ self.assert_content(
574
+ self._view.content(
575
+ [
576
+ dict(
577
+ x=[(1, 2)],
578
+ y=['b', Foo()],
579
+ ),
580
+ 1,
581
+ [1, dict(xx=1, yy='a')]
582
+ ],
583
+ key_style='label',
584
+ ),
585
+ """
586
+ <div class="complex-value list"><table><tr><td><span class="object-key int">0</span><span class="tooltip">[0]</span></td><td><details class="pyglove dict"><summary><div class="summary-title">dict(...)</div><span class="tooltip">{
587
+ &#x27;x&#x27;: [(1, 2)],
588
+ &#x27;y&#x27;: [&#x27;b&#x27;, &lt;Foo&gt;&lt;/Foo&gt;]
589
+ }</span></summary><div class="complex-value dict"><table><tr><td><span class="object-key str">x</span><span class="tooltip">[0].x</span></td><td><details class="pyglove list"><summary><div class="summary-title">list(...)</div><span class="tooltip">[(1, 2)]</span></summary><div class="complex-value list"><table><tr><td><span class="object-key int">0</span><span class="tooltip">[0].x[0]</span></td><td><details class="pyglove tuple"><summary><div class="summary-title">tuple(...)</div><span class="tooltip">(1, 2)</span></summary><div class="complex-value tuple"><table><tr><td><span class="object-key int">0</span><span class="tooltip">[0].x[0][0]</span></td><td><span class="simple-value int">1</span></td></tr><tr><td><span class="object-key int">1</span><span class="tooltip">[0].x[0][1]</span></td><td><span class="simple-value int">2</span></td></tr></table></div></details></td></tr></table></div></details></td></tr><tr><td><span class="object-key str">y</span><span class="tooltip">[0].y</span></td><td><details class="pyglove list"><summary><div class="summary-title">list(...)</div><span class="tooltip">[&#x27;b&#x27;, &lt;Foo&gt;&lt;/Foo&gt;]</span></summary><div class="complex-value list"><table><tr><td><span class="object-key int">0</span><span class="tooltip">[0].y[0]</span></td><td><span class="simple-value str">&#x27;b&#x27;</span></td></tr><tr><td><span class="object-key int">1</span><span class="tooltip">[0].y[1]</span></td><td><details class="pyglove foo"><summary><div class="summary-title">Foo(...)</div><span class="tooltip">&lt;Foo&gt;&lt;/Foo&gt;</span></summary><span class="simple-value foo">&lt;Foo&gt;&lt;/Foo&gt;</span></details></td></tr></table></div></details></td></tr></table></div></details></td></tr><tr><td><span class="object-key int">1</span><span class="tooltip">[1]</span></td><td><span class="simple-value int">1</span></td></tr><tr><td><span class="object-key int">2</span><span class="tooltip">[2]</span></td><td><details class="pyglove list"><summary><div class="summary-title">list(...)</div><span class="tooltip">[1, {
590
+ &#x27;xx&#x27;: 1,
591
+ &#x27;yy&#x27;: &#x27;a&#x27;
592
+ }]</span></summary><div class="complex-value list"><table><tr><td><span class="object-key int">0</span><span class="tooltip">[2][0]</span></td><td><span class="simple-value int">1</span></td></tr><tr><td><span class="object-key int">1</span><span class="tooltip">[2][1]</span></td><td><details class="pyglove dict"><summary><div class="summary-title">dict(...)</div><span class="tooltip">{
593
+ &#x27;xx&#x27;: 1,
594
+ &#x27;yy&#x27;: &#x27;a&#x27;
595
+ }</span></summary><div class="complex-value dict"><table><tr><td><span class="object-key str">xx</span><span class="tooltip">[2][1].xx</span></td><td><span class="simple-value int">1</span></td></tr><tr><td><span class="object-key str">yy</span><span class="tooltip">[2][1].yy</span></td><td><span class="simple-value str">&#x27;a&#x27;</span></td></tr></table></div></details></td></tr></table></div></details></td></tr></table></div>
596
+ """
597
+ )
598
+
599
+ def _key_style(k, v, p):
600
+ del v, p
601
+ if k and k.key in ('x', 'xx'):
602
+ return 'label'
603
+ return 'summary'
604
+
605
+ self.assert_content(
606
+ self._view.content(
607
+ [
608
+ dict(
609
+ x=[(1, 2)],
610
+ y=['b', Foo()],
611
+ ),
612
+ 1,
613
+ [1, dict(xx=1, yy='a')]
614
+ ],
615
+ key_style=_key_style,
616
+ ),
617
+ """
618
+ <div class="complex-value list"><table><tr><td><span class="object-key int">0</span><span class="tooltip">[0]</span></td><td><details class="pyglove dict"><summary><div class="summary-title">dict(...)</div><span class="tooltip">{
619
+ &#x27;x&#x27;: [(1, 2)],
620
+ &#x27;y&#x27;: [&#x27;b&#x27;, &lt;Foo&gt;&lt;/Foo&gt;]
621
+ }</span></summary><div class="complex-value dict"><details class="pyglove list"><summary><div class="summary-name">y<span class="tooltip">[0].y</span></div><div class="summary-title">list(...)</div><span class="tooltip">[&#x27;b&#x27;, &lt;Foo&gt;&lt;/Foo&gt;]</span></summary><div class="complex-value list"><table><tr><td><span class="object-key int">0</span><span class="tooltip">[0].y[0]</span></td><td><span class="simple-value str">&#x27;b&#x27;</span></td></tr><tr><td><span class="object-key int">1</span><span class="tooltip">[0].y[1]</span></td><td><details class="pyglove foo"><summary><div class="summary-title">Foo(...)</div><span class="tooltip">&lt;Foo&gt;&lt;/Foo&gt;</span></summary><span class="simple-value foo">&lt;Foo&gt;&lt;/Foo&gt;</span></details></td></tr></table></div></details><table><tr><td><span class="object-key str">x</span><span class="tooltip">[0].x</span></td><td><details class="pyglove list"><summary><div class="summary-title">list(...)</div><span class="tooltip">[(1, 2)]</span></summary><div class="complex-value list"><table><tr><td><span class="object-key int">0</span><span class="tooltip">[0].x[0]</span></td><td><details class="pyglove tuple"><summary><div class="summary-title">tuple(...)</div><span class="tooltip">(1, 2)</span></summary><div class="complex-value tuple"><table><tr><td><span class="object-key int">0</span><span class="tooltip">[0].x[0][0]</span></td><td><span class="simple-value int">1</span></td></tr><tr><td><span class="object-key int">1</span><span class="tooltip">[0].x[0][1]</span></td><td><span class="simple-value int">2</span></td></tr></table></div></details></td></tr></table></div></details></td></tr></table></div></details></td></tr><tr><td><span class="object-key int">1</span><span class="tooltip">[1]</span></td><td><span class="simple-value int">1</span></td></tr><tr><td><span class="object-key int">2</span><span class="tooltip">[2]</span></td><td><details class="pyglove list"><summary><div class="summary-title">list(...)</div><span class="tooltip">[1, {
622
+ &#x27;xx&#x27;: 1,
623
+ &#x27;yy&#x27;: &#x27;a&#x27;
624
+ }]</span></summary><div class="complex-value list"><table><tr><td><span class="object-key int">0</span><span class="tooltip">[2][0]</span></td><td><span class="simple-value int">1</span></td></tr><tr><td><span class="object-key int">1</span><span class="tooltip">[2][1]</span></td><td><details class="pyglove dict"><summary><div class="summary-title">dict(...)</div><span class="tooltip">{
625
+ &#x27;xx&#x27;: 1,
626
+ &#x27;yy&#x27;: &#x27;a&#x27;
627
+ }</span></summary><div class="complex-value dict"><details open class="pyglove str"><summary><div class="summary-name">yy<span class="tooltip">[2][1].yy</span></div><div class="summary-title">str</div><span class="tooltip">&#x27;a&#x27;</span></summary><span class="simple-value str">&#x27;a&#x27;</span></details><table><tr><td><span class="object-key str">xx</span><span class="tooltip">[2][1].xx</span></td><td><span class="simple-value int">1</span></td></tr></table></div></details></td></tr></table></div></details></td></tr></table></div>
628
+ """
629
+ )
630
+
631
+ def test_key_color(self):
632
+ self.assert_content(
633
+ self._view.content(
634
+ dict(
635
+ x=[(1, 2)],
636
+ y=['b', dict(y=1)],
637
+ ),
638
+ key_color=('white', 'red'),
639
+ enable_summary_tooltip=False,
640
+ ),
641
+ """
642
+ <div class="complex-value dict"><details class="pyglove list"><summary><div class="summary-name">x<span class="tooltip">x</span></div><div class="summary-title">list(...)</div></summary><div class="complex-value list"><table><tr><td><span class="object-key int" style="color:white;background-color:red;">0</span><span class="tooltip">x[0]</span></td><td><details class="pyglove tuple"><summary><div class="summary-title">tuple(...)</div></summary><div class="complex-value tuple"><table><tr><td><span class="object-key int" style="color:white;background-color:red;">0</span><span class="tooltip">x[0][0]</span></td><td><span class="simple-value int">1</span></td></tr><tr><td><span class="object-key int" style="color:white;background-color:red;">1</span><span class="tooltip">x[0][1]</span></td><td><span class="simple-value int">2</span></td></tr></table></div></details></td></tr></table></div></details><details class="pyglove list"><summary><div class="summary-name">y<span class="tooltip">y</span></div><div class="summary-title">list(...)</div></summary><div class="complex-value list"><table><tr><td><span class="object-key int" style="color:white;background-color:red;">0</span><span class="tooltip">y[0]</span></td><td><span class="simple-value str">&#x27;b&#x27;</span></td></tr><tr><td><span class="object-key int" style="color:white;background-color:red;">1</span><span class="tooltip">y[1]</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">y<span class="tooltip">y[1].y</span></div><div class="summary-title">int</div></summary><span class="simple-value int">1</span></details></div></details></td></tr></table></div></details></div>
643
+ """
644
+ )
645
+ self.assert_content(
646
+ self._view.content(
647
+ dict(
648
+ x=[(1, 2)],
649
+ y=['b', dict(y=1)],
650
+ ),
651
+ key_style='label',
652
+ enable_summary_tooltip=False,
653
+ key_color=(
654
+ lambda k, v, p: ('white', 'red')
655
+ if k.key == 'y' else (None, None)
656
+ ),
657
+ ),
658
+ """
659
+ <div class="complex-value dict"><table><tr><td><span class="object-key str">x</span><span class="tooltip">x</span></td><td><details class="pyglove list"><summary><div class="summary-title">list(...)</div></summary><div class="complex-value list"><table><tr><td><span class="object-key int">0</span><span class="tooltip">x[0]</span></td><td><details class="pyglove tuple"><summary><div class="summary-title">tuple(...)</div></summary><div class="complex-value tuple"><table><tr><td><span class="object-key int">0</span><span class="tooltip">x[0][0]</span></td><td><span class="simple-value int">1</span></td></tr><tr><td><span class="object-key int">1</span><span class="tooltip">x[0][1]</span></td><td><span class="simple-value int">2</span></td></tr></table></div></details></td></tr></table></div></details></td></tr><tr><td><span class="object-key str" style="color:white;background-color:red;">y</span><span class="tooltip">y</span></td><td><details class="pyglove list"><summary><div class="summary-title">list(...)</div></summary><div class="complex-value list"><table><tr><td><span class="object-key int">0</span><span class="tooltip">y[0]</span></td><td><span class="simple-value str">&#x27;b&#x27;</span></td></tr><tr><td><span class="object-key int">1</span><span class="tooltip">y[1]</span></td><td><details class="pyglove dict"><summary><div class="summary-title">dict(...)</div></summary><div class="complex-value dict"><table><tr><td><span class="object-key str" style="color:white;background-color:red;">y</span><span class="tooltip">y[1].y</span></td><td><span class="simple-value int">1</span></td></tr></table></div></details></td></tr></table></div></details></td></tr></table></div>
660
+ """
661
+ )
662
+
663
+ def test_enable_disable_tooltip(self):
664
+
665
+ class Foo:
666
+ def __str__(self):
667
+ return '<Foo></Foo>'
668
+
669
+ value = [
670
+ dict(
671
+ x=[(1, 2)],
672
+ y=['b', Foo()],
673
+ ),
674
+ 1,
675
+ [1, dict(xx=1, yy='a')]
676
+ ]
677
+ self.assert_content(
678
+ self._view.content(value, enable_summary_tooltip=False),
679
+ """
680
+ <div class="complex-value list"><table><tr><td><span class="object-key int">0</span><span class="tooltip">[0]</span></td><td><details class="pyglove dict"><summary><div class="summary-title">dict(...)</div></summary><div class="complex-value dict"><details class="pyglove list"><summary><div class="summary-name">x<span class="tooltip">[0].x</span></div><div class="summary-title">list(...)</div></summary><div class="complex-value list"><table><tr><td><span class="object-key int">0</span><span class="tooltip">[0].x[0]</span></td><td><details class="pyglove tuple"><summary><div class="summary-title">tuple(...)</div></summary><div class="complex-value tuple"><table><tr><td><span class="object-key int">0</span><span class="tooltip">[0].x[0][0]</span></td><td><span class="simple-value int">1</span></td></tr><tr><td><span class="object-key int">1</span><span class="tooltip">[0].x[0][1]</span></td><td><span class="simple-value int">2</span></td></tr></table></div></details></td></tr></table></div></details><details class="pyglove list"><summary><div class="summary-name">y<span class="tooltip">[0].y</span></div><div class="summary-title">list(...)</div></summary><div class="complex-value list"><table><tr><td><span class="object-key int">0</span><span class="tooltip">[0].y[0]</span></td><td><span class="simple-value str">&#x27;b&#x27;</span></td></tr><tr><td><span class="object-key int">1</span><span class="tooltip">[0].y[1]</span></td><td><details class="pyglove foo"><summary><div class="summary-title">Foo(...)</div></summary><span class="simple-value foo">&lt;Foo&gt;&lt;/Foo&gt;</span></details></td></tr></table></div></details></div></details></td></tr><tr><td><span class="object-key int">1</span><span class="tooltip">[1]</span></td><td><span class="simple-value int">1</span></td></tr><tr><td><span class="object-key int">2</span><span class="tooltip">[2]</span></td><td><details class="pyglove list"><summary><div class="summary-title">list(...)</div></summary><div class="complex-value list"><table><tr><td><span class="object-key int">0</span><span class="tooltip">[2][0]</span></td><td><span class="simple-value int">1</span></td></tr><tr><td><span class="object-key int">1</span><span class="tooltip">[2][1]</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">xx<span class="tooltip">[2][1].xx</span></div><div class="summary-title">int</div></summary><span class="simple-value int">1</span></details><details open class="pyglove str"><summary><div class="summary-name">yy<span class="tooltip">[2][1].yy</span></div><div class="summary-title">str</div></summary><span class="simple-value str">&#x27;a&#x27;</span></details></div></details></td></tr></table></div></details></td></tr></table></div>
681
+ """
682
+ )
683
+ self.assert_content(
684
+ self._view.content(value, enable_key_tooltip=False),
685
+ """
686
+ <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><span class="tooltip">{
687
+ &#x27;x&#x27;: [(1, 2)],
688
+ &#x27;y&#x27;: [&#x27;b&#x27;, &lt;Foo&gt;&lt;/Foo&gt;]
689
+ }</span></summary><div class="complex-value dict"><details class="pyglove list"><summary><div class="summary-name">x</div><div class="summary-title">list(...)</div><span class="tooltip">[(1, 2)]</span></summary><div class="complex-value list"><table><tr><td><span class="object-key int">0</span></td><td><details class="pyglove tuple"><summary><div class="summary-title">tuple(...)</div><span class="tooltip">(1, 2)</span></summary><div class="complex-value tuple"><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></td></tr></table></div></details><details class="pyglove list"><summary><div class="summary-name">y</div><div class="summary-title">list(...)</div><span class="tooltip">[&#x27;b&#x27;, &lt;Foo&gt;&lt;/Foo&gt;]</span></summary><div class="complex-value list"><table><tr><td><span class="object-key int">0</span></td><td><span class="simple-value str">&#x27;b&#x27;</span></td></tr><tr><td><span class="object-key int">1</span></td><td><details class="pyglove foo"><summary><div class="summary-title">Foo(...)</div><span class="tooltip">&lt;Foo&gt;&lt;/Foo&gt;</span></summary><span class="simple-value foo">&lt;Foo&gt;&lt;/Foo&gt;</span></details></td></tr></table></div></details></div></details></td></tr><tr><td><span class="object-key int">1</span></td><td><span class="simple-value int">1</span></td></tr><tr><td><span class="object-key int">2</span></td><td><details class="pyglove list"><summary><div class="summary-title">list(...)</div><span class="tooltip">[1, {
690
+ &#x27;xx&#x27;: 1,
691
+ &#x27;yy&#x27;: &#x27;a&#x27;
692
+ }]</span></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><details class="pyglove dict"><summary><div class="summary-title">dict(...)</div><span class="tooltip">{
693
+ &#x27;xx&#x27;: 1,
694
+ &#x27;yy&#x27;: &#x27;a&#x27;
695
+ }</span></summary><div class="complex-value dict"><details open class="pyglove int"><summary><div class="summary-name">xx</div><div class="summary-title">int</div><span class="tooltip">1</span></summary><span class="simple-value int">1</span></details><details open class="pyglove str"><summary><div class="summary-name">yy</div><div class="summary-title">str</div><span class="tooltip">&#x27;a&#x27;</span></summary><span class="simple-value str">&#x27;a&#x27;</span></details></div></details></td></tr></table></div></details></td></tr></table></div>
696
+ """
697
+ )
698
+ self.assert_content(
699
+ self._view.content(
700
+ value, enable_summary_tooltip=False, enable_key_tooltip=False
701
+ ),
702
+ """
703
+ <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 class="pyglove list"><summary><div class="summary-name">x</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 tuple"><summary><div class="summary-title">tuple(...)</div></summary><div class="complex-value tuple"><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></td></tr></table></div></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><span class="simple-value str">&#x27;b&#x27;</span></td></tr><tr><td><span class="object-key int">1</span></td><td><details class="pyglove foo"><summary><div class="summary-title">Foo(...)</div></summary><span class="simple-value foo">&lt;Foo&gt;&lt;/Foo&gt;</span></details></td></tr></table></div></details></div></details></td></tr><tr><td><span class="object-key int">1</span></td><td><span class="simple-value int">1</span></td></tr><tr><td><span class="object-key int">2</span></td><td><details class="pyglove list"><summary><div class="summary-title">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><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">xx</div><div class="summary-title">int</div></summary><span class="simple-value int">1</span></details><details open class="pyglove str"><summary><div class="summary-name">yy</div><div class="summary-title">str</div></summary><span class="simple-value str">&#x27;a&#x27;</span></details></div></details></td></tr></table></div></details></td></tr></table></div>
704
+ """
705
+ )
706
+
707
+ def test_include_exclude_immediate_child_keys(self):
708
+ self.assert_count(
709
+ self._view.content(
710
+ dict(a='x', b=dict(e='y'), c='z', d='w', e='v', f='u'),
711
+ include_keys=['a', 'b', 'c', 'd'],
712
+ exclude_keys=['c', 'e'],
713
+ enable_summary_tooltip=False,
714
+ enable_key_tooltip=False,
715
+ key_style='label',
716
+ ),
717
+ 'object-key',
718
+ 3 + 1 # 'a', 'b', 'd' at the first level and 'e' at the second level.
719
+ )
720
+
721
+ def test_include_nodes_in_subtree(self):
722
+ self.assert_count(
723
+ self._view.content(
724
+ dict(a='x', b=dict(e='y'), c='z', d='w', e='v', f='u'),
725
+ include_keys=(lambda k, v, p: k.key == 'e' or isinstance(v, dict)),
726
+ enable_summary_tooltip=False,
727
+ enable_key_tooltip=False,
728
+ key_style='label',
729
+ ),
730
+ 'object-key',
731
+ 3 # 'b', 'b.e', 'e'.
732
+ )
733
+
734
+ def test_exclude_nodes_in_subtree(self):
735
+ self.assert_count(
736
+ self._view.content(
737
+ dict(a='x', b=dict(e='y'), c='z', d='w', e='v', f='u'),
738
+ exclude_keys=(lambda k, v, p: k.key == 'e'),
739
+ enable_summary_tooltip=False,
740
+ enable_key_tooltip=False,
741
+ key_style='label',
742
+ ),
743
+ 'object-key',
744
+ 5 # 'a', 'b', 'c', 'd', 'f'.
745
+ )
746
+
747
+ def test_highlight_lowlight(self):
748
+ self.assert_content(
749
+ self._view.content(
750
+ dict(a=1, b=dict(e='y'), c=2, d='w', e=3, f='u'),
751
+ enable_summary_tooltip=False,
752
+ enable_key_tooltip=False,
753
+ highlight=(lambda k, v, p: isinstance(v, int)),
754
+ lowlight=(lambda k, v, p: isinstance(v, str)),
755
+ ),
756
+ """
757
+ <div class="complex-value dict"><div class="highlight"><details open class="pyglove int"><summary><div class="summary-name">a</div><div class="summary-title">int</div></summary><span class="simple-value int">1</span></details></div><details class="pyglove dict"><summary><div class="summary-name">b</div><div class="summary-title">dict(...)</div></summary><div class="complex-value dict"><div class="lowlight"><details open class="pyglove str"><summary><div class="summary-name">e</div><div class="summary-title">str</div></summary><span class="simple-value str">&#x27;y&#x27;</span></details></div></div></details><div class="highlight"><details open class="pyglove int"><summary><div class="summary-name">c</div><div class="summary-title">int</div></summary><span class="simple-value int">2</span></details></div><div class="lowlight"><details open class="pyglove str"><summary><div class="summary-name">d</div><div class="summary-title">str</div></summary><span class="simple-value str">&#x27;w&#x27;</span></details></div><div class="highlight"><details open class="pyglove int"><summary><div class="summary-name">e</div><div class="summary-title">int</div></summary><span class="simple-value int">3</span></details></div><div class="lowlight"><details open class="pyglove str"><summary><div class="summary-name">f</div><div class="summary-title">str</div></summary><span class="simple-value str">&#x27;u&#x27;</span></details></div></div>
758
+ """
759
+ )
760
+
761
+ def test_collapse_level(self):
762
+ x = dict(
763
+ a=dict(
764
+ b=dict(
765
+ c=dict(
766
+ d=dict()
767
+ )
768
+ )
769
+ ),
770
+ aa=dict(
771
+ bb=dict(
772
+ cc=dict(
773
+ dd=dict()
774
+ )
775
+ )
776
+ )
777
+ )
778
+ self.assert_count(
779
+ self._view.content(x, collapse_level=0),
780
+ 'open',
781
+ 0
782
+ )
783
+ # There is no summary section, so there is no root.
784
+ self.assert_count(
785
+ self._view.content(x, collapse_level=1),
786
+ 'open',
787
+ 0
788
+ )
789
+ self.assert_count(
790
+ self._view.content(x, collapse_level=2),
791
+ 'open',
792
+ 2
793
+ )
794
+ self.assert_count(
795
+ self._view.content(x, collapse_level=3),
796
+ 'open',
797
+ 4
798
+ )
799
+ self.assert_count(
800
+ self._view.content(x, collapse_level=4),
801
+ 'open',
802
+ 6
803
+ )
804
+ self.assert_count(
805
+ self._view.content(x, collapse_level=None),
806
+ 'open',
807
+ 8
808
+ )
809
+
810
+ def test_uncollapse(self):
811
+ x = dict(
812
+ a=dict(
813
+ b=dict(
814
+ c=dict(
815
+ d=dict()
816
+ )
817
+ )
818
+ ),
819
+ aa=dict(
820
+ bb=dict(
821
+ cc=dict(
822
+ dd=dict()
823
+ )
824
+ )
825
+ )
826
+ )
827
+ self.assert_count(
828
+ self._view.content(x, uncollapse=['a.b.c']),
829
+ 'open',
830
+ 3
831
+ )
832
+ self.assert_count(
833
+ self._view.content(x, uncollapse=['aa.bb.cc.dd', 'a.b']),
834
+ 'open',
835
+ 6
836
+ )
837
+ # Use both collapse_level and uncollapse.
838
+ self.assert_count(
839
+ self._view.content(x, collapse_level=2, uncollapse=['aa.bb.cc']),
840
+ 'open',
841
+ 4
842
+ )
843
+ self.assert_count(
844
+ self._view.content(
845
+ x, collapse_level=0, uncollapse=lambda k, v, p: len(k) < 3
846
+ ),
847
+ 'open',
848
+ 4
849
+ )
850
+
851
+
852
+ class RenderTest(TestCase):
853
+
854
+ def test_render_html_convertible(self):
855
+ class Foo(base.HtmlConvertible):
856
+ def to_html(self, **kwargs):
857
+ return base.Html('<span>foo</span>')
858
+ self.assert_content(base.to_html(Foo()), '<span>foo</span>')
859
+
860
+ def test_render(self):
861
+ class Foo:
862
+ def __str__(self):
863
+ return '<Foo></Foo>'
864
+
865
+ self.assert_content(
866
+ self._view.render(
867
+ [
868
+ dict(
869
+ x=[(1, 2)],
870
+ y=['b', Foo()],
871
+ ),
872
+ 1,
873
+ [1, dict(xx=1, yy='a')]
874
+ ],
875
+ ),
876
+ """
877
+ <details open class="pyglove list"><summary><div class="summary-title">list(...)</div><span class="tooltip">[
878
+ {
879
+ &#x27;x&#x27;: [(1, 2)],
880
+ &#x27;y&#x27;: [&#x27;b&#x27;, &lt;Foo&gt;&lt;/Foo&gt;]
881
+ },
882
+ 1,
883
+ [1, {
884
+ &#x27;xx&#x27;: 1,
885
+ &#x27;yy&#x27;: &#x27;a&#x27;
886
+ }]
887
+ ]</span></summary><div class="complex-value list"><table><tr><td><span class="object-key int">0</span><span class="tooltip">[0]</span></td><td><details class="pyglove dict"><summary><div class="summary-title">dict(...)</div><span class="tooltip">{
888
+ &#x27;x&#x27;: [(1, 2)],
889
+ &#x27;y&#x27;: [&#x27;b&#x27;, &lt;Foo&gt;&lt;/Foo&gt;]
890
+ }</span></summary><div class="complex-value dict"><details class="pyglove list"><summary><div class="summary-name">x<span class="tooltip">[0].x</span></div><div class="summary-title">list(...)</div><span class="tooltip">[(1, 2)]</span></summary><div class="complex-value list"><table><tr><td><span class="object-key int">0</span><span class="tooltip">[0].x[0]</span></td><td><details class="pyglove tuple"><summary><div class="summary-title">tuple(...)</div><span class="tooltip">(1, 2)</span></summary><div class="complex-value tuple"><table><tr><td><span class="object-key int">0</span><span class="tooltip">[0].x[0][0]</span></td><td><span class="simple-value int">1</span></td></tr><tr><td><span class="object-key int">1</span><span class="tooltip">[0].x[0][1]</span></td><td><span class="simple-value int">2</span></td></tr></table></div></details></td></tr></table></div></details><details class="pyglove list"><summary><div class="summary-name">y<span class="tooltip">[0].y</span></div><div class="summary-title">list(...)</div><span class="tooltip">[&#x27;b&#x27;, &lt;Foo&gt;&lt;/Foo&gt;]</span></summary><div class="complex-value list"><table><tr><td><span class="object-key int">0</span><span class="tooltip">[0].y[0]</span></td><td><span class="simple-value str">&#x27;b&#x27;</span></td></tr><tr><td><span class="object-key int">1</span><span class="tooltip">[0].y[1]</span></td><td><details class="pyglove foo"><summary><div class="summary-title">Foo(...)</div><span class="tooltip">&lt;Foo&gt;&lt;/Foo&gt;</span></summary><span class="simple-value foo">&lt;Foo&gt;&lt;/Foo&gt;</span></details></td></tr></table></div></details></div></details></td></tr><tr><td><span class="object-key int">1</span><span class="tooltip">[1]</span></td><td><span class="simple-value int">1</span></td></tr><tr><td><span class="object-key int">2</span><span class="tooltip">[2]</span></td><td><details class="pyglove list"><summary><div class="summary-title">list(...)</div><span class="tooltip">[1, {
891
+ &#x27;xx&#x27;: 1,
892
+ &#x27;yy&#x27;: &#x27;a&#x27;
893
+ }]</span></summary><div class="complex-value list"><table><tr><td><span class="object-key int">0</span><span class="tooltip">[2][0]</span></td><td><span class="simple-value int">1</span></td></tr><tr><td><span class="object-key int">1</span><span class="tooltip">[2][1]</span></td><td><details class="pyglove dict"><summary><div class="summary-title">dict(...)</div><span class="tooltip">{
894
+ &#x27;xx&#x27;: 1,
895
+ &#x27;yy&#x27;: &#x27;a&#x27;
896
+ }</span></summary><div class="complex-value dict"><details open class="pyglove int"><summary><div class="summary-name">xx<span class="tooltip">[2][1].xx</span></div><div class="summary-title">int</div><span class="tooltip">1</span></summary><span class="simple-value int">1</span></details><details open class="pyglove str"><summary><div class="summary-name">yy<span class="tooltip">[2][1].yy</span></div><div class="summary-title">str</div><span class="tooltip">&#x27;a&#x27;</span></summary><span class="simple-value str">&#x27;a&#x27;</span></details></div></details></td></tr></table></div></details></td></tr></table></div></details>
897
+ """
898
+ )
899
+
900
+ def test_debug(self):
901
+ self.assertIn(
902
+ inspect.cleandoc(
903
+ """
904
+ .debug-info-trigger {
905
+ display: inline-flex;
906
+ cursor: pointer;
907
+ font-size: 0.6em;
908
+ background-color: red;
909
+ color: white;
910
+ padding: 5px;
911
+ border-radius: 3px;
912
+ margin: 5px 0 5px 0;
913
+ }
914
+ .debug-info-trigger:hover + span.tooltip {
915
+ visibility: visible;
916
+ }
917
+ """
918
+ ),
919
+ self._view.render(dict(x=dict(y=1)), debug=True).style_section,
920
+ )
921
+ self.assert_content(
922
+ self._view.render(dict(x=1), debug=True),
923
+ """
924
+ <details open class="pyglove dict"><summary><div class="summary-title">dict(...)</div><span class="tooltip">{
925
+ &#x27;x&#x27;: 1
926
+ }</span></summary><div><span class="debug-info-trigger">DEBUG</span><span class="tooltip debug-info">{
927
+ &#x27;css_classes&#x27;: None,
928
+ &#x27;collapse_level&#x27;: 1,
929
+ &#x27;uncollapse&#x27;: KeyPathSet(),
930
+ &#x27;extra_flags&#x27;: {},
931
+ &#x27;child_config&#x27;: {},
932
+ &#x27;key_style&#x27;: &#x27;summary&#x27;,
933
+ &#x27;key_color&#x27;: None,
934
+ &#x27;include_keys&#x27;: None,
935
+ &#x27;exclude_keys&#x27;: None,
936
+ &#x27;summary_color&#x27;: None,
937
+ &#x27;enable_summary&#x27;: None,
938
+ &#x27;enable_summary_for_str&#x27;: True,
939
+ &#x27;max_summary_len_for_str&#x27;: 80,
940
+ &#x27;enable_summary_tooltip&#x27;: True,
941
+ &#x27;enable_key_tooltip&#x27;: True
942
+ }</span></div><div class="complex-value dict"><details open class="pyglove int"><summary><div class="summary-name">x<span class="tooltip">x</span></div><div class="summary-title">int</div><span class="tooltip">1</span></summary><div><span class="debug-info-trigger">DEBUG</span><span class="tooltip debug-info">{
943
+ &#x27;css_classes&#x27;: None,
944
+ &#x27;collapse_level&#x27;: 0,
945
+ &#x27;uncollapse&#x27;: KeyPathSet(),
946
+ &#x27;extra_flags&#x27;: {},
947
+ &#x27;child_config&#x27;: {},
948
+ &#x27;key_style&#x27;: &#x27;summary&#x27;,
949
+ &#x27;key_color&#x27;: None,
950
+ &#x27;include_keys&#x27;: None,
951
+ &#x27;exclude_keys&#x27;: None,
952
+ &#x27;summary_color&#x27;: None,
953
+ &#x27;enable_summary&#x27;: None,
954
+ &#x27;enable_summary_for_str&#x27;: True,
955
+ &#x27;max_summary_len_for_str&#x27;: 80,
956
+ &#x27;enable_summary_tooltip&#x27;: True,
957
+ &#x27;enable_key_tooltip&#x27;: True
958
+ }</span></div><span class="simple-value int">1</span></details></div></details>
959
+ """
960
+ )
961
+
962
+
963
+ class ExtensionTest(TestCase):
964
+
965
+ def test_no_overrides(self):
966
+
967
+ class Foo(tree_view.HtmlTreeView.Extension):
968
+
969
+ def __str__(self):
970
+ return 'Foo()'
971
+
972
+ self.assert_content(
973
+ self._view.summary(
974
+ Foo(),
975
+ enable_summary=True,
976
+ max_summary_len_for_str=10,
977
+ enable_summary_tooltip=True,
978
+ ),
979
+ """
980
+ <summary><div class="summary-title">Foo(...)</div><span class="tooltip">Foo()</span></summary>
981
+ """
982
+ )
983
+ self.assert_content(
984
+ self._view.content(
985
+ Foo(),
986
+ ),
987
+ """
988
+ <span class="simple-value foo">Foo()</span>
989
+ """
990
+ )
991
+
992
+ def test_config_override(self):
993
+
994
+ class Foo(tree_view.HtmlTreeView.Extension):
995
+
996
+ def __str__(self):
997
+ return 'Foo()'
998
+
999
+ @classmethod
1000
+ def _html_tree_view_css_styles(cls) -> list[str]:
1001
+ return [
1002
+ """
1003
+ /* Foo style */
1004
+ """
1005
+ ]
1006
+
1007
+ @classmethod
1008
+ def _html_tree_view_config(cls) -> dict[str, Any]:
1009
+ return dict(
1010
+ css_classes=['foo', 'bar'],
1011
+ include_keys=['x', 'y', 'z', 'w'],
1012
+ exclude_keys=['z'],
1013
+ key_style='label',
1014
+ key_color=('white', 'red'),
1015
+ collapse_level=2,
1016
+ uncollapse=KeyPathSet(['x.a'], include_intermediate=True),
1017
+ child_config={
1018
+ 'x': dict(
1019
+ summary_color=('white', 'blue'),
1020
+ key_style='label',
1021
+ key_color=('white', 'blue'),
1022
+ collapse_level=1,
1023
+ ),
1024
+ 'y': dict(
1025
+ uncollapse=KeyPathSet(['e']),
1026
+ )
1027
+ }
1028
+ )
1029
+
1030
+ def _html_tree_view_content(
1031
+ self,
1032
+ *,
1033
+ view: tree_view.HtmlTreeView,
1034
+ parent: Any,
1035
+ root_path: KeyPath,
1036
+ **kwargs) -> base.Html:
1037
+ return view.complex_value(
1038
+ kv=dict(
1039
+ x=dict(a=dict(foo=2)),
1040
+ y=dict(b=dict(bar=3), e=dict(srt=6)),
1041
+ z=dict(c=dict(baz=4)),
1042
+ w=dict(d=dict(qux=5)),
1043
+ ),
1044
+ parent=self,
1045
+ root_path=root_path,
1046
+ **kwargs
1047
+ )
1048
+
1049
+ self.assertIn(
1050
+ '/* Foo style */',
1051
+ self._view.render(
1052
+ Foo(),
1053
+ enable_summary_tooltip=False,
1054
+ enable_key_tooltip=False,
1055
+ collapse_level=0,
1056
+ ).styles.content,
1057
+ )
1058
+ self.assert_content(
1059
+ self._view.render(
1060
+ [Foo()],
1061
+ enable_summary_tooltip=False,
1062
+ enable_key_tooltip=False,
1063
+ collapse_level=0,
1064
+ ),
1065
+ """
1066
+ <details class="pyglove list"><summary><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 open class="pyglove foo bar"><summary><div class="summary-title foo bar">Foo(...)</div></summary><div class="complex-value foo"><table><tr><td><span class="object-key str" style="color:white;background-color:blue;">x</span></td><td><details open class="pyglove dict"><summary><div class="summary-title">dict(...)</div></summary><div class="complex-value dict"><table><tr><td><span class="object-key str" style="color:white;background-color:blue;">a</span></td><td><details open class="pyglove dict"><summary><div class="summary-title">dict(...)</div></summary><div class="complex-value dict"><table><tr><td><span class="object-key str" style="color:white;background-color:blue;">foo</span></td><td><span class="simple-value int">2</span></td></tr></table></div></details></td></tr></table></div></details></td></tr><tr><td><span class="object-key str" style="color:white;background-color:red;">y</span></td><td><details open class="pyglove dict"><summary><div class="summary-title">dict(...)</div></summary><div class="complex-value dict"><table><tr><td><span class="object-key str" style="color:white;background-color:red;">b</span></td><td><details class="pyglove dict"><summary><div class="summary-title">dict(...)</div></summary><div class="complex-value dict"><table><tr><td><span class="object-key str" style="color:white;background-color:red;">bar</span></td><td><span class="simple-value int">3</span></td></tr></table></div></details></td></tr><tr><td><span class="object-key str" style="color:white;background-color:red;">e</span></td><td><details open class="pyglove dict"><summary><div class="summary-title">dict(...)</div></summary><div class="complex-value dict"><table><tr><td><span class="object-key str" style="color:white;background-color:red;">srt</span></td><td><span class="simple-value int">6</span></td></tr></table></div></details></td></tr></table></div></details></td></tr><tr><td><span class="object-key str" style="color:white;background-color:red;">w</span></td><td><details open class="pyglove dict"><summary><div class="summary-title">dict(...)</div></summary><div class="complex-value dict"><table><tr><td><span class="object-key str" style="color:white;background-color:red;">d</span></td><td><details class="pyglove dict"><summary><div class="summary-title">dict(...)</div></summary><div class="complex-value dict"><table><tr><td><span class="object-key str" style="color:white;background-color:red;">qux</span></td><td><span class="simple-value int">5</span></td></tr></table></div></details></td></tr></table></div></details></td></tr></table></div></details></td></tr></table></div></details>
1067
+ """
1068
+ )
1069
+ self.assert_content(
1070
+ self._view.render(
1071
+ [Foo()],
1072
+ enable_summary_tooltip=False,
1073
+ enable_key_tooltip=False,
1074
+ collapse_level=1,
1075
+ ),
1076
+ """
1077
+ <details open class="pyglove list"><summary><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 open class="pyglove foo bar"><summary><div class="summary-title foo bar">Foo(...)</div></summary><div class="complex-value foo"><table><tr><td><span class="object-key str" style="color:white;background-color:blue;">x</span></td><td><details open class="pyglove dict"><summary><div class="summary-title">dict(...)</div></summary><div class="complex-value dict"><table><tr><td><span class="object-key str" style="color:white;background-color:blue;">a</span></td><td><details open class="pyglove dict"><summary><div class="summary-title">dict(...)</div></summary><div class="complex-value dict"><table><tr><td><span class="object-key str" style="color:white;background-color:blue;">foo</span></td><td><span class="simple-value int">2</span></td></tr></table></div></details></td></tr></table></div></details></td></tr><tr><td><span class="object-key str" style="color:white;background-color:red;">y</span></td><td><details open class="pyglove dict"><summary><div class="summary-title">dict(...)</div></summary><div class="complex-value dict"><table><tr><td><span class="object-key str" style="color:white;background-color:red;">b</span></td><td><details class="pyglove dict"><summary><div class="summary-title">dict(...)</div></summary><div class="complex-value dict"><table><tr><td><span class="object-key str" style="color:white;background-color:red;">bar</span></td><td><span class="simple-value int">3</span></td></tr></table></div></details></td></tr><tr><td><span class="object-key str" style="color:white;background-color:red;">e</span></td><td><details open class="pyglove dict"><summary><div class="summary-title">dict(...)</div></summary><div class="complex-value dict"><table><tr><td><span class="object-key str" style="color:white;background-color:red;">srt</span></td><td><span class="simple-value int">6</span></td></tr></table></div></details></td></tr></table></div></details></td></tr><tr><td><span class="object-key str" style="color:white;background-color:red;">w</span></td><td><details open class="pyglove dict"><summary><div class="summary-title">dict(...)</div></summary><div class="complex-value dict"><table><tr><td><span class="object-key str" style="color:white;background-color:red;">d</span></td><td><details class="pyglove dict"><summary><div class="summary-title">dict(...)</div></summary><div class="complex-value dict"><table><tr><td><span class="object-key str" style="color:white;background-color:red;">qux</span></td><td><span class="simple-value int">5</span></td></tr></table></div></details></td></tr></table></div></details></td></tr></table></div></details></td></tr></table></div></details>
1078
+ """
1079
+ )
1080
+ self.assert_content(
1081
+ self._view.render(
1082
+ [Foo()],
1083
+ enable_summary_tooltip=False,
1084
+ enable_key_tooltip=False,
1085
+ collapse_level=None,
1086
+ ),
1087
+ """
1088
+ <details open class="pyglove list"><summary><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 open class="pyglove foo bar"><summary><div class="summary-title foo bar">Foo(...)</div></summary><div class="complex-value foo"><table><tr><td><span class="object-key str" style="color:white;background-color:blue;">x</span></td><td><details open class="pyglove dict"><summary><div class="summary-title">dict(...)</div></summary><div class="complex-value dict"><table><tr><td><span class="object-key str" style="color:white;background-color:blue;">a</span></td><td><details open class="pyglove dict"><summary><div class="summary-title">dict(...)</div></summary><div class="complex-value dict"><table><tr><td><span class="object-key str" style="color:white;background-color:blue;">foo</span></td><td><span class="simple-value int">2</span></td></tr></table></div></details></td></tr></table></div></details></td></tr><tr><td><span class="object-key str" style="color:white;background-color:red;">y</span></td><td><details open class="pyglove dict"><summary><div class="summary-title">dict(...)</div></summary><div class="complex-value dict"><table><tr><td><span class="object-key str" style="color:white;background-color:red;">b</span></td><td><details open class="pyglove dict"><summary><div class="summary-title">dict(...)</div></summary><div class="complex-value dict"><table><tr><td><span class="object-key str" style="color:white;background-color:red;">bar</span></td><td><span class="simple-value int">3</span></td></tr></table></div></details></td></tr><tr><td><span class="object-key str" style="color:white;background-color:red;">e</span></td><td><details open class="pyglove dict"><summary><div class="summary-title">dict(...)</div></summary><div class="complex-value dict"><table><tr><td><span class="object-key str" style="color:white;background-color:red;">srt</span></td><td><span class="simple-value int">6</span></td></tr></table></div></details></td></tr></table></div></details></td></tr><tr><td><span class="object-key str" style="color:white;background-color:red;">w</span></td><td><details open class="pyglove dict"><summary><div class="summary-title">dict(...)</div></summary><div class="complex-value dict"><table><tr><td><span class="object-key str" style="color:white;background-color:red;">d</span></td><td><details open class="pyglove dict"><summary><div class="summary-title">dict(...)</div></summary><div class="complex-value dict"><table><tr><td><span class="object-key str" style="color:white;background-color:red;">qux</span></td><td><span class="simple-value int">5</span></td></tr></table></div></details></td></tr></table></div></details></td></tr></table></div></details></td></tr></table></div></details>
1089
+ """
1090
+ )
1091
+
1092
+ def test_behavior_overrides(self):
1093
+
1094
+ class Foo(tree_view.HtmlTreeView.Extension):
1095
+
1096
+ def _html_tree_view_summary(
1097
+ self,
1098
+ *,
1099
+ view: tree_view.HtmlTreeView,
1100
+ **kwargs
1101
+ ):
1102
+ kwargs.pop('title', None)
1103
+ return view.summary(
1104
+ self,
1105
+ title='MyFoo',
1106
+ **kwargs
1107
+ )
1108
+
1109
+ def _html_tree_view_content(
1110
+ self,
1111
+ **kwargs
1112
+ ):
1113
+ del kwargs
1114
+ return 'Content of MyFoo'
1115
+
1116
+ self.assert_content(
1117
+ self._view.render(
1118
+ Foo(),
1119
+ enable_summary_tooltip=False,
1120
+ enable_key_tooltip=False,
1121
+ ),
1122
+ """
1123
+ <details open class="pyglove foo"><summary><div class="summary-title">MyFoo</div></summary>Content of MyFoo</details>
1124
+ """
1125
+ )
1126
+ self.assert_content(
1127
+ self._view.render(
1128
+ Foo(),
1129
+ enable_summary=False,
1130
+ ),
1131
+ """
1132
+ Content of MyFoo
1133
+ """
1134
+ )
1135
+
1136
+
1137
+ class OverrideKwargsTest(TestCase):
1138
+
1139
+ def test_override_collapse_level(self):
1140
+ def assert_collapse_level(
1141
+ call_kwargs, overriden_kwargs, expected_collapse_level
1142
+ ):
1143
+ self.assertEqual(
1144
+ tree_view.HtmlTreeView.get_kwargs(
1145
+ call_kwargs,
1146
+ overriden_kwargs,
1147
+ KeyPath(''),
1148
+ ).get('collapse_level', -1),
1149
+ expected_collapse_level,
1150
+ )
1151
+ assert_collapse_level(
1152
+ dict(),
1153
+ dict(),
1154
+ -1,
1155
+ )
1156
+ assert_collapse_level(
1157
+ dict(collapse_level=1),
1158
+ dict(),
1159
+ 1,
1160
+ )
1161
+ assert_collapse_level(
1162
+ dict(collapse_level=1),
1163
+ dict(collapse_level=None),
1164
+ None,
1165
+ )
1166
+ assert_collapse_level(
1167
+ dict(collapse_level=1),
1168
+ dict(collapse_level=2),
1169
+ 2,
1170
+ )
1171
+ assert_collapse_level(
1172
+ dict(collapse_level=2),
1173
+ dict(collapse_level=1),
1174
+ 2,
1175
+ )
1176
+
1177
+ def test_override_uncollapse(self):
1178
+ def assert_uncollapse(
1179
+ call_kwargs,
1180
+ overriden_kwargs,
1181
+ expected_uncollapse,
1182
+ root_path=KeyPath(),
1183
+ ):
1184
+ self.assertEqual(
1185
+ tree_view.HtmlTreeView.get_kwargs(
1186
+ call_kwargs,
1187
+ overriden_kwargs,
1188
+ root_path,
1189
+ ).get('uncollapse', None),
1190
+ expected_uncollapse
1191
+ )
1192
+ assert_uncollapse(
1193
+ dict(uncollapse=None),
1194
+ dict(),
1195
+ None,
1196
+ )
1197
+ assert_uncollapse(
1198
+ dict(),
1199
+ dict(uncollapse=None),
1200
+ KeyPathSet(),
1201
+ )
1202
+ assert_uncollapse(
1203
+ dict(uncollapse=KeyPathSet(['a.b'])),
1204
+ dict(),
1205
+ KeyPathSet(['a.b']),
1206
+ )
1207
+ assert_uncollapse(
1208
+ dict(uncollapse=KeyPathSet(['a.b.c'])),
1209
+ dict(uncollapse=KeyPathSet(['b'])),
1210
+ KeyPathSet(['a.b.c', 'a.b']),
1211
+ root_path=KeyPath('a'),
1212
+ )
1213
+ assert_uncollapse(
1214
+ dict(uncollapse=[]),
1215
+ dict(uncollapse=KeyPathSet(['b'])),
1216
+ KeyPathSet(['a.b']),
1217
+ root_path=KeyPath('a'),
1218
+ )
1219
+ assert_uncollapse(
1220
+ dict(uncollapse=KeyPathSet(['a.b.c'])),
1221
+ dict(uncollapse=KeyPathSet(['b'])),
1222
+ KeyPathSet(['a.b.c', 'a.b']),
1223
+ root_path=KeyPath('a'),
1224
+ )
1225
+
1226
+ def test_override_kwargs(self):
1227
+ def assert_child_config(
1228
+ call_kwargs,
1229
+ overriden_kwargs,
1230
+ expected_child_config,
1231
+ root_path=KeyPath(''),
1232
+ ):
1233
+ self.maxDiff = None
1234
+ actual = tree_view.HtmlTreeView.get_kwargs(
1235
+ call_kwargs,
1236
+ overriden_kwargs,
1237
+ root_path,
1238
+ )
1239
+ if actual != expected_child_config:
1240
+ print(actual)
1241
+ self.assertEqual(actual, expected_child_config)
1242
+
1243
+ assert_child_config(
1244
+ dict(
1245
+ child_config=dict(
1246
+ x=dict(
1247
+ enable_summary=True
1248
+ ),
1249
+ )
1250
+ ),
1251
+ dict(
1252
+ child_config=dict(
1253
+ x=dict(
1254
+ enable_summary_tooltip=False
1255
+ ),
1256
+ y=dict(
1257
+ collapse_level=None
1258
+ ),
1259
+ )
1260
+ ),
1261
+ dict(
1262
+ child_config=dict(
1263
+ x=dict(
1264
+ enable_summary=True,
1265
+ enable_summary_tooltip=False,
1266
+ ),
1267
+ y=dict(
1268
+ collapse_level=None
1269
+ ),
1270
+ ),
1271
+ ),
1272
+ )
1273
+
1274
+ def test_get_child_kwargs(self):
1275
+ def assert_child_kwargs(
1276
+ call_kwargs,
1277
+ child_key,
1278
+ root_path,
1279
+ expected_child_kwargs,
1280
+ ):
1281
+ self.maxDiff = None
1282
+ actual = tree_view.HtmlTreeView.get_child_kwargs(
1283
+ call_kwargs,
1284
+ call_kwargs.pop('child_config', {}),
1285
+ child_key,
1286
+ root_path,
1287
+ )
1288
+ if actual != expected_child_kwargs:
1289
+ print(actual)
1290
+ self.assertEqual(actual, expected_child_kwargs)
1291
+
1292
+ assert_child_kwargs(
1293
+ call_kwargs=dict(
1294
+ enable_summary=False,
1295
+ collapse_level=1,
1296
+ uncollapse=KeyPathSet(['a.y']),
1297
+ child_config=dict(
1298
+ x=dict(
1299
+ enable_summary=True,
1300
+ uncollapse=KeyPathSet(['b']),
1301
+ child_config=dict(
1302
+ y=dict(
1303
+ collapse_level=2
1304
+ ),
1305
+ ),
1306
+ ),
1307
+ )
1308
+ ),
1309
+ child_key='x',
1310
+ root_path=KeyPath(['a']),
1311
+ expected_child_kwargs=dict(
1312
+ enable_summary=True,
1313
+ collapse_level=1,
1314
+ uncollapse=KeyPathSet(['a.y', 'a.x.b']),
1315
+ child_config=dict(
1316
+ y=dict(
1317
+ collapse_level=2
1318
+ ),
1319
+ ),
1320
+ ),
1321
+ )
1322
+
1323
+
1324
+ class HelperTest(TestCase):
1325
+
1326
+ def test_get_collapse_level(self):
1327
+ self.assertIsNone(
1328
+ tree_view.HtmlTreeView.get_collapse_level(
1329
+ None,
1330
+ None
1331
+ ),
1332
+ )
1333
+ self.assertIsNone(
1334
+ tree_view.HtmlTreeView.get_collapse_level(
1335
+ 1,
1336
+ None
1337
+ ),
1338
+ )
1339
+ self.assertIsNone(
1340
+ tree_view.HtmlTreeView.get_collapse_level(
1341
+ None,
1342
+ 1
1343
+ ),
1344
+ )
1345
+ self.assertEqual(
1346
+ tree_view.HtmlTreeView.get_collapse_level(
1347
+ 2,
1348
+ 3
1349
+ ),
1350
+ 3
1351
+ )
1352
+ self.assertEqual(
1353
+ tree_view.HtmlTreeView.get_collapse_level(
1354
+ (3, -1),
1355
+ 0
1356
+ ),
1357
+ 2
1358
+ )
1359
+ self.assertEqual(
1360
+ tree_view.HtmlTreeView.get_collapse_level(
1361
+ 0,
1362
+ (2, 1)
1363
+ ),
1364
+ 3
1365
+ )
1366
+
1367
+ def test_get_color(self):
1368
+ self.assertEqual(
1369
+ tree_view.HtmlTreeView.get_color(None, KeyPath('a.b.c'), 1, None),
1370
+ (None, None)
1371
+ )
1372
+ self.assertEqual(
1373
+ tree_view.HtmlTreeView.get_color(
1374
+ ('blue', None), KeyPath('a.b.c'), 1, None
1375
+ ),
1376
+ ('blue', None)
1377
+ )
1378
+ self.assertEqual(
1379
+ tree_view.HtmlTreeView.get_color(
1380
+ lambda k, v, p: ('white', 'black'), KeyPath('a.b.c'), 1, None),
1381
+ ('white', 'black')
1382
+ )
1383
+
1384
+ def test_merge_uncollapse(self):
1385
+ self.assertEqual(
1386
+ tree_view.HtmlTreeView.merge_uncollapse(
1387
+ None,
1388
+ None,
1389
+ child_path=KeyPath.parse('a.b'),
1390
+ ),
1391
+ KeyPathSet()
1392
+ )
1393
+ self.assertEqual(
1394
+ tree_view.HtmlTreeView.merge_uncollapse(
1395
+ KeyPathSet(['a.b']),
1396
+ None,
1397
+ child_path=KeyPath.parse('x'),
1398
+ ),
1399
+ KeyPathSet(['a.b'])
1400
+ )
1401
+ path_fn = lambda k, v, p: True
1402
+ self.assertIs(
1403
+ tree_view.HtmlTreeView.merge_uncollapse(
1404
+ path_fn,
1405
+ None,
1406
+ child_path=KeyPath.parse('x'),
1407
+ ),
1408
+ path_fn
1409
+ )
1410
+ self.assertEqual(
1411
+ tree_view.HtmlTreeView.merge_uncollapse(
1412
+ KeyPathSet(),
1413
+ KeyPathSet(['d']),
1414
+ child_path=KeyPath.parse('a.b'),
1415
+ ),
1416
+ KeyPathSet(['a.b.d']),
1417
+ )
1418
+ self.assertEqual(
1419
+ tree_view.HtmlTreeView.merge_uncollapse(
1420
+ KeyPathSet(['a.b.c']),
1421
+ KeyPathSet(['d']),
1422
+ child_path=KeyPath.parse('a.b'),
1423
+ ),
1424
+ KeyPathSet(['a.b.c', 'a.b.d']),
1425
+ )
1426
+
1427
+ def test_get_passthrough_kwargs(self):
1428
+ key_filter = (lambda k, v, p: k == 'foo')
1429
+ self.assertEqual(
1430
+ tree_view.HtmlTreeView.get_passthrough_kwargs(
1431
+ name='foo',
1432
+ value=1,
1433
+ root_path=KeyPath(),
1434
+ enable_summary=False,
1435
+ include_keys=key_filter,
1436
+ ),
1437
+ {
1438
+ 'enable_summary': False,
1439
+ 'include_keys': key_filter,
1440
+ },
1441
+ )
1442
+
1443
+ self.assertEqual(
1444
+ tree_view.HtmlTreeView.get_passthrough_kwargs(
1445
+ name='foo',
1446
+ value=1,
1447
+ root_path=KeyPath(),
1448
+ enable_summary=False,
1449
+ include_keys=['a'],
1450
+ extra_flags=dict(x=1),
1451
+ exclude_keys='a',
1452
+ remove=['extra_flags']
1453
+ ),
1454
+ {
1455
+ 'enable_summary': False,
1456
+ },
1457
+ )
1458
+
1459
+
1460
+ if __name__ == '__main__':
1461
+ unittest.main()