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,1517 @@
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
+ """HTML Tree View (The default view for PyGlove objects)."""
15
+
16
+ import inspect
17
+ from typing import Any, Callable, Dict, Iterable, Literal, Optional, Sequence, Tuple, Union
18
+
19
+ from pyglove.core import utils
20
+ from pyglove.core.symbolic import base as pg_symbolic
21
+ from pyglove.core.views.html import base
22
+
23
+
24
+ KeyPath = utils.KeyPath
25
+ KeyPathSet = utils.KeyPathSet
26
+ Html = base.Html
27
+ HtmlView = base.HtmlView
28
+
29
+
30
+ # pytype: disable=annotation-type-mismatch
31
+
32
+
33
+ class HtmlTreeView(HtmlView):
34
+ """HTML Tree View."""
35
+
36
+ VIEW_ID = 'html-tree-view'
37
+
38
+ class Extension(HtmlView.Extension):
39
+ """The base class for extensions for HtmlTreeView."""
40
+
41
+ def _html_tree_view_render(
42
+ self,
43
+ *,
44
+ view: 'HtmlTreeView',
45
+ name: Optional[str] = None,
46
+ parent: Any = None,
47
+ root_path: Optional[KeyPath] = None,
48
+ **kwargs,
49
+ ) -> Html:
50
+ """The entrypoint of rendering the subtree represented by this extension.
51
+
52
+ Args:
53
+ view: The view to render the object.
54
+ name: The name of the object.
55
+ parent: The parent of the object.
56
+ root_path: The key path of the object relative to the root.
57
+ **kwargs: kwargs to pass to `view.render()` on this extension.
58
+
59
+ Returns:
60
+ The rendered HTML.
61
+ """
62
+ return self._html_tree_view(
63
+ view=view,
64
+ name=name,
65
+ parent=parent,
66
+ root_path=root_path,
67
+ **view.get_kwargs(
68
+ kwargs, self._html_tree_view_config(), root_path or KeyPath()
69
+ )
70
+ ).add_style(
71
+ *self._html_tree_view_css_styles()
72
+ )
73
+
74
+ #
75
+ # Users could override this methods to customize the styles and
76
+ # rendering arguments for the subtree.
77
+ #
78
+
79
+ @classmethod
80
+ def _html_tree_view_css_styles(cls) -> list[str]:
81
+ """Returns the CSS styles for the subtree."""
82
+ del cls
83
+ return []
84
+
85
+ @classmethod
86
+ def _html_tree_view_config(cls) -> Dict[str, Any]:
87
+ """Returns the config (rendering arguments) of current extension.
88
+
89
+ Returns:
90
+ A dictionary of rendering arguments for the subtree. These arguments
91
+ will override the arguments passed to `view.render()`. See the
92
+ `render()` method for the full list of arguments.
93
+ """
94
+ return {}
95
+
96
+ #
97
+ # Users could override the methods below to customize rendering
98
+ # logics.
99
+ #
100
+
101
+ def _html_tree_view(
102
+ self,
103
+ *,
104
+ view: 'HtmlTreeView',
105
+ name: Optional[str] = None,
106
+ parent: Any = None,
107
+ root_path: Optional[KeyPath] = None,
108
+ **kwargs,
109
+ ) -> Html:
110
+ """Returns the topmost HTML representation of this extension.
111
+
112
+ Args:
113
+ view: The view to render the object.
114
+ name: The name of the object.
115
+ parent: The parent of the object.
116
+ root_path: The key path of the object relative to the root.
117
+ **kwargs: kwargs to pass to the view. See `_html_tree_view_config` for
118
+ the builtin arguments.
119
+
120
+ Returns:
121
+ The rendered HTML.
122
+ """
123
+ return view.render(
124
+ self,
125
+ name=name,
126
+ parent=parent,
127
+ root_path=root_path,
128
+ **kwargs,
129
+ )
130
+
131
+ def _html_tree_view_summary(
132
+ self,
133
+ *,
134
+ view: 'HtmlTreeView',
135
+ name: Optional[str] = None,
136
+ parent: Any = None,
137
+ root_path: Optional[KeyPath] = None,
138
+ **kwargs,
139
+ ) -> Optional[Html]:
140
+ """Returns the HTML summary for the object.
141
+
142
+ Args:
143
+ view: The view to render the object.
144
+ name: The name of the object.
145
+ parent: The parent of the object.
146
+ root_path: The key path of the object relative to the root.
147
+ **kwargs: kwargs to pass to the view. See `_html_tree_view_config` for
148
+ the builtin arguments.
149
+
150
+ Returns:
151
+ An optional HTML object representing the summary of the object. If None,
152
+ the content will be returned directly instead of having a <details>
153
+ container.
154
+ """
155
+ return view.summary(
156
+ self,
157
+ name=name,
158
+ parent=parent,
159
+ root_path=root_path,
160
+ **kwargs,
161
+ )
162
+
163
+ def _html_tree_view_content(
164
+ self,
165
+ *,
166
+ view: 'HtmlTreeView',
167
+ name: Optional[str] = None,
168
+ parent: Any = None,
169
+ root_path: Optional[KeyPath] = None,
170
+ **kwargs,
171
+ ) -> Html:
172
+ """Returns the main content for the object.
173
+
174
+ Args:
175
+ view: The view to render the object.
176
+ name: The name of the object.
177
+ parent: The parent of the object.
178
+ root_path: The key path of the object relative to the root.
179
+ **kwargs: kwargs to pass to the view. See `_html_tree_view_config` for
180
+ the builtin arguments.
181
+
182
+ Returns:
183
+ The rendered HTML as the main content of the object.
184
+ """
185
+ return view.content(
186
+ self,
187
+ name=name,
188
+ parent=parent,
189
+ root_path=root_path,
190
+ **kwargs,
191
+ )
192
+
193
+ # NOTE(daiyip): update `get_kwargs()` and `get_passthrough_kwargs()` when new
194
+ # arguments are added.
195
+ @HtmlView.extension_method('_html_tree_view_render')
196
+ def _render(
197
+ self,
198
+ value: Any,
199
+ *,
200
+ name: Optional[str] = None,
201
+ parent: Any = None,
202
+ root_path: Optional[KeyPath] = None,
203
+ css_classes: Optional[Sequence[str]] = None,
204
+ # Summary settings.
205
+ title: Union[str, Html, None] = None,
206
+ enable_summary: Optional[bool] = None,
207
+ enable_summary_for_str: bool = True,
208
+ max_summary_len_for_str: int = 80,
209
+ enable_summary_tooltip: bool = True,
210
+ summary_color: Union[
211
+ Tuple[Optional[str], Optional[str]],
212
+ Callable[[KeyPath, Any, Any], Tuple[Optional[str], Optional[str]]]
213
+ ] = None,
214
+ # Content settings.
215
+ key_style: Union[
216
+ Literal['label', 'summary'],
217
+ Callable[[KeyPath, Any, Any], Literal['label', 'summary']]
218
+ ] = 'summary',
219
+ key_color: Union[
220
+ Tuple[Optional[str], Optional[str]],
221
+ Callable[[KeyPath, Any, Any], Tuple[Optional[str], Optional[str]]]
222
+ ] = None,
223
+ include_keys: Union[
224
+ Iterable[Union[int, str]],
225
+ Callable[[KeyPath, Any, Any], Iterable[Union[int, str]]],
226
+ None
227
+ ] = None,
228
+ exclude_keys: Union[
229
+ Iterable[Union[int, str]],
230
+ Callable[[KeyPath, Any, Any], Iterable[Union[int, str]]],
231
+ None
232
+ ] = None,
233
+ enable_key_tooltip: bool = True,
234
+ # Collapse settings.
235
+ collapse_level: Optional[int] = 1,
236
+ uncollapse: Union[KeyPathSet, base.NodeFilter, None] = None,
237
+ # Extension settings.
238
+ child_config: Optional[Dict[str, Any]] = None,
239
+ extra_flags: Optional[Dict[str, Any]] = None,
240
+ # Tree operations.
241
+ highlight: Optional[base.NodeFilter] = None,
242
+ lowlight: Optional[base.NodeFilter] = None,
243
+ debug: bool = False,
244
+ ) -> Html:
245
+ """Renders the entire HTML tree view for the value.
246
+
247
+ Args:
248
+ value: The value to render.
249
+ name: The name of the value.
250
+ parent: The parent of the value.
251
+ root_path: The root path of the value.
252
+ css_classes: CSS classes to add to the top-most element.
253
+ title: The title of the summary. If None, the default title will be used,
254
+ which is the type name of the value.
255
+ enable_summary: Whether to enable the summary. If None, summary will
256
+ be enabled for complex types or when string exceeds
257
+ `max_summary_len_for_str`.
258
+ enable_summary_for_str: Whether to enable the summary for strings.
259
+ max_summary_len_for_str: The maximum length of the string to display.
260
+ enable_summary_tooltip: Whether to enable the tooltip for the summary.
261
+ summary_color: The color used for the summary for displaying the referred
262
+ field name of the object. It can be a tuple of (color, background-color)
263
+ or a function that takes (root_path, value, parent) and returns the
264
+ color tuple.
265
+ key_style: The style of the key. If 'label', the key will be rendered as a
266
+ label. If 'summary', it will be rendered as a summary in the <details>
267
+ tag. If a function, it will be called with (root_path, value, parent)
268
+ and return the style.
269
+ key_color: The color for label-style keys under this extension. It can be
270
+ a tuple of (color, background-color) or a function that takes
271
+ (root_path, value, parent) and returns the color tuple.
272
+ include_keys: A list of keys to include when displaying the sub-nodes of
273
+ the object. If None, all keys will be displayed. If a function, it will
274
+ be called with (root_path, value, parent) and return whether the key
275
+ should be included.
276
+ exclude_keys: A set of keys to exclude when displaying the sub-nodes of
277
+ the object. If None, all keys will be displayed. If a function, it will
278
+ be called with (root_path, value, parent) and return whether the key
279
+ should be excluded.
280
+ enable_key_tooltip: Whether to enable the tooltip for the object name.
281
+ collapse_level: The level of collapsing. If 0, the object will be
282
+ collapsed (without showing its sub-nodes). If 1, the immediate sub-nodes
283
+ will be shown in collapsed form. If None, all sub-tree will be shown.
284
+ uncollapse: Indivdual nodes to uncollapse. It can be a KeyPathSet or a
285
+ function that takes (root_path, value, parent) and returns a KeyPathSet.
286
+ child_config: The configs for the immediate child nodes of the object
287
+ being rendered. It's a dictionary of (key, child-config) where the key
288
+ is the name of the child node and the child-config is a dictionary of
289
+ (key, value) to override the default configs for the child node.
290
+ extra_flags: A dictionary of user-defined flags to control the rendering
291
+ behavior.
292
+ highlight: A function that takes (root_path, value, parent) and returns
293
+ whether the node should be highlighted.
294
+ lowlight: A function that takes (root_path, value, parent) and returns
295
+ whether the node should be lowlighted.
296
+ debug: Whether to show debug information for this rendering.
297
+
298
+ Returns:
299
+ The rendered HTML.
300
+ """
301
+ root_path = root_path or KeyPath()
302
+ child_config = child_config or {}
303
+ extra_flags = extra_flags or {}
304
+ uncollapse = self.init_uncollapse(uncollapse)
305
+
306
+ summary = self.summary(
307
+ value,
308
+ name=f'[{name}]' if isinstance(name, int) else name,
309
+ parent=parent,
310
+ root_path=root_path,
311
+ css_classes=css_classes,
312
+ title=title,
313
+ summary_color=summary_color,
314
+ enable_summary=enable_summary,
315
+ enable_summary_for_str=enable_summary_for_str,
316
+ enable_summary_tooltip=enable_summary_tooltip,
317
+ enable_key_tooltip=enable_key_tooltip,
318
+ max_summary_len_for_str=max_summary_len_for_str,
319
+ extra_flags=extra_flags,
320
+ )
321
+
322
+ if debug:
323
+ debug_info = Html.element(
324
+ 'div',
325
+ [
326
+ Html.element(
327
+ 'span', ['DEBUG'], css_classes=['debug-info-trigger']
328
+ ),
329
+ self.tooltip(
330
+ dict(
331
+ # Most error-prone settings.
332
+ css_classes=css_classes,
333
+ collapse_level=collapse_level,
334
+ uncollapse=uncollapse,
335
+ extra_flags=extra_flags,
336
+ child_config=child_config,
337
+ # Relative obvious settings.
338
+ key_style=key_style,
339
+ key_color=key_color,
340
+ include_keys=include_keys,
341
+ exclude_keys=exclude_keys,
342
+ # More obvious settings.
343
+ summary_color=summary_color,
344
+ enable_summary=enable_summary,
345
+ enable_summary_for_str=enable_summary_for_str,
346
+ max_summary_len_for_str=max_summary_len_for_str,
347
+ enable_summary_tooltip=enable_summary_tooltip,
348
+ enable_key_tooltip=enable_key_tooltip,
349
+ ),
350
+ name='debug_info',
351
+ parent=parent,
352
+ root_path=root_path,
353
+ css_classes=['debug-info'],
354
+ ),
355
+ ],
356
+ ).add_style(
357
+ """
358
+ .debug-info-trigger {
359
+ display: inline-flex;
360
+ cursor: pointer;
361
+ font-size: 0.6em;
362
+ background-color: red;
363
+ color: white;
364
+ padding: 5px;
365
+ border-radius: 3px;
366
+ margin: 5px 0 5px 0;
367
+ }
368
+ .debug-info-trigger:hover + span.tooltip {
369
+ visibility: visible;
370
+ }
371
+ """
372
+ )
373
+ else:
374
+ debug_info = None
375
+
376
+ content = self.content(
377
+ value,
378
+ name=name,
379
+ parent=parent,
380
+ root_path=root_path,
381
+ css_classes=css_classes if summary is None else None,
382
+ # Summary settings (child nodes).
383
+ enable_summary=enable_summary,
384
+ enable_summary_for_str=enable_summary_for_str,
385
+ max_summary_len_for_str=max_summary_len_for_str,
386
+ enable_summary_tooltip=enable_summary_tooltip,
387
+ # Content settings.
388
+ key_style=key_style,
389
+ key_color=key_color,
390
+ enable_key_tooltip=enable_key_tooltip,
391
+ include_keys=include_keys,
392
+ exclude_keys=exclude_keys,
393
+ collapse_level=collapse_level,
394
+ uncollapse=uncollapse,
395
+ highlight=highlight,
396
+ lowlight=lowlight,
397
+ child_config=child_config,
398
+ extra_flags=extra_flags,
399
+ debug=debug,
400
+ )
401
+
402
+ if summary is None:
403
+ content = Html.from_value(content)
404
+ assert content is not None
405
+ return debug_info + content
406
+
407
+ collapse_details = self.should_collapse(
408
+ value, name=name, parent=parent, root_path=root_path,
409
+ collapse_level=collapse_level, uncollapse=uncollapse,
410
+ )
411
+ return Html.element(
412
+ 'details',
413
+ [
414
+ summary,
415
+ debug_info,
416
+ content,
417
+ ],
418
+ options=[None if collapse_details else 'open'],
419
+ css_classes=[
420
+ 'pyglove',
421
+ self.css_class_name(value),
422
+ css_classes,
423
+ ],
424
+ ).add_style(
425
+ """
426
+ /* Value details styles. */
427
+ details.pyglove {
428
+ border: 1px solid #aaa;
429
+ border-radius: 4px;
430
+ padding: 0.5em 0.5em 0;
431
+ margin: 0.25em 0;
432
+ }
433
+ details.pyglove[open] {
434
+ padding: 0.5em 0.5em 0.5em;
435
+ }
436
+ .highlight {
437
+ background-color: Mark;
438
+ }
439
+ .lowlight {
440
+ opacity: 0.2;
441
+ }
442
+ """,
443
+ )
444
+
445
+ def should_collapse(
446
+ self,
447
+ value: Any,
448
+ name: Optional[str],
449
+ root_path: KeyPath,
450
+ parent: Any,
451
+ collapse_level: Optional[int] = 1,
452
+ uncollapse: Union[KeyPathSet, base.NodeFilter] = None,
453
+ ) -> bool:
454
+ """Returns True if the object should be collapsed.
455
+
456
+ Args:
457
+ value: The value to render.
458
+ name: The referred field name of the value.
459
+ root_path: The root path of the value.
460
+ parent: The parent of the value.
461
+ collapse_level: The level of collapsing. If 0, the object will be
462
+ collapsed (without showing its sub-nodes). If 1, the immediate sub-nodes
463
+ will be shown in collapsed form. If None, all sub-tree will be shown.
464
+ uncollapse: Indivdual nodes to uncollapse. It can be a KeyPathSet or a
465
+ function that takes (root_path, value, parent) and returns a KeyPathSet.
466
+
467
+ Returns:
468
+ True if the object should be collapsed.
469
+ """
470
+ if collapse_level is None or collapse_level > 0:
471
+ return False
472
+ if callable(uncollapse):
473
+ return not uncollapse(root_path, value, parent)
474
+ if root_path in uncollapse:
475
+ return False
476
+ # Always uncollapse simple types.
477
+ if (name is not None
478
+ and isinstance(value, (bool, int, float, str, type(None)))):
479
+ return False
480
+ return True
481
+
482
+ def needs_summary(
483
+ self,
484
+ value: Any,
485
+ *,
486
+ name: Optional[str] = None,
487
+ parent: Any = None,
488
+ title: Union[str, Html, None] = None,
489
+ enable_summary: Optional[bool] = None,
490
+ enable_summary_for_str: bool = True,
491
+ max_summary_len_for_str: int = 80,
492
+ ) -> bool:
493
+ """Returns True if the object needs a summary.
494
+
495
+ Args:
496
+ value: The value to render.
497
+ name: The referred field name of the value.
498
+ parent: The parent of the value.
499
+ title: The title of the summary.
500
+ enable_summary: Whether to enable the summary. If None, summary will
501
+ be enabled for complex types or when string exceeds
502
+ `max_summary_len_for_str`.
503
+ enable_summary_for_str: Whether to enable the summary for strings.
504
+ max_summary_len_for_str: The maximum length of the string to display.
505
+
506
+ Returns:
507
+ True if the object needs a summary.
508
+ """
509
+ del parent
510
+ if isinstance(enable_summary, bool):
511
+ return enable_summary
512
+ assert enable_summary is None
513
+ if not enable_summary_for_str and isinstance(value, str):
514
+ return False
515
+ if name is None and title is None and (
516
+ isinstance(value, (int, float, bool, type(None)))
517
+ or (isinstance(value, str) and len(value) <= max_summary_len_for_str)
518
+ ):
519
+ return False
520
+ return True
521
+
522
+ @HtmlView.extension_method('_html_tree_view_summary')
523
+ def summary(
524
+ self,
525
+ value: Any,
526
+ *,
527
+ name: Optional[str] = None,
528
+ parent: Any = None,
529
+ root_path: Optional[KeyPath] = None,
530
+ css_classes: Optional[Sequence[str]] = None,
531
+ title: Union[str, Html, None] = None,
532
+ enable_summary: Optional[bool] = None,
533
+ enable_summary_tooltip: bool = True,
534
+ summary_color: Union[
535
+ Tuple[Optional[str], Optional[str]],
536
+ Callable[[KeyPath, Any, Any], Tuple[Optional[str], Optional[str]]]
537
+ ] = None,
538
+ max_summary_len_for_str: int = 80,
539
+ enable_summary_for_str: bool = True,
540
+ enable_key_tooltip: bool = True,
541
+ summary_tooltip_fn: Optional[Callable[..., Html]] = None,
542
+ key_tooltip_fn: Optional[Callable[..., Html]] = None,
543
+ extra_flags: Optional[Dict[str, Any]] = None,
544
+ ) -> Optional[Html]:
545
+ """Renders the summary for an input value.
546
+
547
+ Args:
548
+ value: The value to render.
549
+ name: The referred field name of the value.
550
+ parent: The parent of the value.
551
+ root_path: The root path of the value.
552
+ css_classes: The CSS classes to add to the HTML element.
553
+ title: The title of the summary.
554
+ enable_summary: Whether to enable the summary. If None, summary will
555
+ be enabled for complex types or when string exceeds
556
+ `max_summary_len_for_str`.
557
+ enable_summary_tooltip: Whether to enable the summary tooltip.
558
+ summary_color: The color of the summary. If None, the summary will be
559
+ rendered without a color. If a tuple, the first element is the text
560
+ color and the second element is the background color. If a function,
561
+ the function takes (root_path, value, parent) and returns a tuple of
562
+ (text_color, background_color).
563
+ max_summary_len_for_str: The maximum length of the string to display.
564
+ enable_summary_for_str: Whether to enable the summary for strings.
565
+ enable_key_tooltip: Whether to enable the key tooltip.
566
+ summary_tooltip_fn: The function to render the summary tooltip.
567
+ key_tooltip_fn: The function to render the key tooltip.
568
+ extra_flags: The extra flags to pass to the summary.
569
+
570
+ Returns:
571
+ An optional HTML object representing the summary of the value. If None,
572
+ the summary will not be rendered.
573
+ """
574
+ del extra_flags
575
+ root_path = root_path or KeyPath()
576
+ if not self.needs_summary(
577
+ value,
578
+ name=name,
579
+ parent=parent,
580
+ title=title,
581
+ max_summary_len_for_str=max_summary_len_for_str,
582
+ enable_summary=enable_summary,
583
+ enable_summary_for_str=enable_summary_for_str,
584
+ ):
585
+ return None
586
+
587
+ key_tooltip_fn = key_tooltip_fn or self.tooltip
588
+ summary_tooltip_fn = summary_tooltip_fn or self.tooltip
589
+
590
+ def make_title(value: Any):
591
+ if inspect.isclass(value):
592
+ return 'type'
593
+ elif isinstance(value, (int, float, bool, str)):
594
+ return type(value).__name__
595
+ return f'{type(value).__name__}(...)'
596
+
597
+ if name is not None:
598
+ summary_color = self.get_color(
599
+ summary_color, root_path + name, value, parent
600
+ )
601
+ else:
602
+ summary_color = (None, None)
603
+
604
+ return Html.element(
605
+ 'summary',
606
+ [
607
+ # Summary name.
608
+ lambda: Html.element( # pylint: disable=g-long-ternary
609
+ 'div',
610
+ [
611
+ name,
612
+ key_tooltip_fn( # pylint: disable=g-long-ternary
613
+ root_path,
614
+ name=name,
615
+ parent=parent,
616
+ root_path=root_path,
617
+ css_classes=css_classes,
618
+ ) if enable_key_tooltip else None,
619
+ ],
620
+ css_classes=['summary-name', css_classes],
621
+ styles=dict(
622
+ color=summary_color[0],
623
+ background_color=summary_color[1],
624
+ ),
625
+ ) if name is not None else None,
626
+
627
+ # Summary title
628
+ Html.element(
629
+ 'div',
630
+ [
631
+ title or make_title(value),
632
+ ],
633
+ css_classes=['summary-title', css_classes],
634
+ ),
635
+
636
+ # Summary tooltip.
637
+ lambda: summary_tooltip_fn( # pylint: disable=g-long-ternary
638
+ value,
639
+ parent=parent,
640
+ root_path=root_path,
641
+ css_classes=css_classes,
642
+ ) if enable_summary_tooltip else None,
643
+ ],
644
+ ).add_style(
645
+ """
646
+ /* Summary styles. */
647
+ details.pyglove summary {
648
+ font-weight: bold;
649
+ margin: -0.5em -0.5em 0;
650
+ padding: 0.5em;
651
+ }
652
+ .summary-name {
653
+ display: inline;
654
+ padding: 3px 5px 3px 5px;
655
+ margin: 0 5px;
656
+ border-radius: 3px;
657
+ }
658
+ .summary-title {
659
+ display: inline;
660
+ }
661
+ .summary-name + div.summary-title {
662
+ display: inline;
663
+ color: #aaa;
664
+ }
665
+ .summary-title:hover + span.tooltip {
666
+ visibility: visible;
667
+ }
668
+ .summary-name:hover > span.tooltip {
669
+ visibility: visible;
670
+ background-color: darkblue;
671
+ }
672
+ """
673
+ )
674
+
675
+ # NOTE(daiyip)" `object_key`` does not have a corresponding extension
676
+ # method in `HtmlTreeView.Extension`, because the rendering of the key is not
677
+ # delegated to `HtmlTreeView.Extension`.
678
+ def object_key(
679
+ self,
680
+ root_path: KeyPath,
681
+ *,
682
+ value: Any,
683
+ parent: Any,
684
+ css_classes: Optional[Sequence[str]] = None,
685
+ key_color: Union[
686
+ Tuple[Optional[str], Optional[str]],
687
+ Callable[[KeyPath, Any, Any], Tuple[Optional[str], Optional[str]]]
688
+ ] = None,
689
+ enable_key_tooltip: bool = True,
690
+ key_tooltip_fn: Optional[Callable[..., Html]] = None,
691
+ **kwargs,
692
+ ) -> Html:
693
+ """Renders a label-style key for the value.
694
+
695
+ Args:
696
+ root_path: The root path of the value.
697
+ value: The value to render.
698
+ parent: The parent of the value.
699
+ css_classes: The CSS classes to add to the HTML element.
700
+ key_color: The color of the key. If None, the key will be rendered
701
+ without a color. If a tuple, the first element is the text color and
702
+ the second element is the background color. If a function, the function
703
+ takes (root_path, value, parent) and returns a tuple of (text_color,
704
+ background_color).
705
+ enable_key_tooltip: Whether to enable the tooltip.
706
+ key_tooltip_fn: The function to render the key tooltip.
707
+ **kwargs: Additional arguments passed by the user that will be ignored.
708
+
709
+ Returns:
710
+ The rendered HTML as the key of the value.
711
+ """
712
+ del kwargs
713
+ key_tooltip_fn = key_tooltip_fn or self.tooltip
714
+ key_color = self.get_color(key_color, root_path, value, parent)
715
+ return (
716
+ # Key span.
717
+ Html.element(
718
+ 'span',
719
+ [
720
+ str(root_path.key),
721
+ ],
722
+ css_classes=[
723
+ 'object-key',
724
+ type(root_path.key).__name__,
725
+ css_classes,
726
+ ],
727
+ styles=dict(
728
+ color=key_color[0],
729
+ background_color=key_color[1],
730
+ )
731
+ ) + (
732
+ # Tooltip if enabled.
733
+ lambda: key_tooltip_fn( # pylint: disable=g-long-ternary
734
+ value=root_path,
735
+ root_path=root_path,
736
+ parent=parent,
737
+ ) if enable_key_tooltip else None
738
+ )
739
+ ).add_style(
740
+ """
741
+ /* Object key styles. */
742
+ .object-key {
743
+ margin: 0.15em 0.3em 0.15em 0;
744
+ display: block;
745
+ }
746
+ .object-key:hover + .tooltip {
747
+ visibility: visible;
748
+ background-color: darkblue;
749
+ }
750
+ .object-key.str {
751
+ color: gray;
752
+ border: 1px solid lightgray;
753
+ background-color: ButtonFace;
754
+ border-radius: 0.2em;
755
+ padding: 0.3em;
756
+ }
757
+ .object-key.int::before{
758
+ content: '[';
759
+ }
760
+ .object-key.int::after{
761
+ content: ']';
762
+ }
763
+ .object-key.int{
764
+ border: 0;
765
+ color: lightgray;
766
+ background-color: transparent;
767
+ border-radius: 0;
768
+ padding: 0;
769
+ }
770
+ """
771
+ )
772
+
773
+ @HtmlView.extension_method('_html_tree_view_content')
774
+ def content(
775
+ self,
776
+ value: Any,
777
+ *,
778
+ name: Optional[str] = None,
779
+ parent: Any = None,
780
+ root_path: Optional[KeyPath] = None,
781
+ css_classes: Optional[Sequence[str]] = None,
782
+ # Summary settings (for child nodes).
783
+ enable_summary: Optional[bool] = None,
784
+ enable_summary_for_str: bool = True,
785
+ max_summary_len_for_str: int = 80,
786
+ enable_summary_tooltip: bool = True,
787
+ # Content settings.
788
+ key_style: Union[
789
+ Literal['label', 'summary'],
790
+ Callable[[KeyPath, Any, Any], Literal['label', 'summary']]
791
+ ] = 'summary',
792
+ key_color: Union[
793
+ Tuple[Optional[str], Optional[str]],
794
+ Callable[[KeyPath, Any, Any], Tuple[Optional[str], Optional[str]]]
795
+ ] = None,
796
+ include_keys: Union[
797
+ Iterable[Union[int, str]],
798
+ Callable[[KeyPath, Any, Any], Iterable[Union[int, str]]],
799
+ None
800
+ ] = None,
801
+ exclude_keys: Union[
802
+ Iterable[Union[int, str]],
803
+ Callable[[KeyPath, Any, Any], Iterable[Union[int, str]]],
804
+ None
805
+ ] = None,
806
+ enable_key_tooltip: bool = True,
807
+ # Collapse settings.
808
+ collapse_level: Optional[int] = 1,
809
+ uncollapse: Union[KeyPathSet, base.NodeFilter, None] = None,
810
+ # Other settings.
811
+ highlight: Optional[base.NodeFilter] = None,
812
+ lowlight: Optional[base.NodeFilter] = None,
813
+ child_config: Optional[Dict[str, Any]] = None,
814
+ extra_flags: Optional[Dict[str, Any]] = None,
815
+ debug: bool = False,
816
+ ) -> Html:
817
+ """Renders the main content for the value.
818
+
819
+ Args:
820
+ value: The value to render.
821
+ name: The name of the value.
822
+ parent: The parent of the value.
823
+ root_path: The root path of the value.
824
+ css_classes: CSS classes to add to the HTML element.
825
+ enable_summary: Whether to enable the summary.
826
+ enable_summary_for_str: Whether to enable the summary for string.
827
+ max_summary_len_for_str: The maximum length of the string to display.
828
+ enable_summary_tooltip: Whether to enable the summary tooltip.
829
+ key_style: The style of the key. It can be either 'label' or 'summary'.
830
+ If it is a function, the function takes (root_path, value, parent) and
831
+ returns either 'label' or 'summary'.
832
+ key_color: The color of the key. If it is a tuple, the first element is
833
+ the text color and the second element is the background color. If it is
834
+ a function, the function takes (root_path, value, parent) and returns
835
+ a tuple of (text_color, background_color).
836
+ include_keys: The keys to include (at the immediate child level). If it is
837
+ a function, the function takes (root_path, value, parent) and returns
838
+ an iterable of keys to include.
839
+ exclude_keys: The keys to exclude (at the immediate child level). If it is
840
+ a function, the function takes (root_path, value, parent) and returns
841
+ an iterable of keys to exclude.
842
+ enable_key_tooltip: Whether to enable the key tooltip.
843
+ collapse_level: The level to collapse the tree.
844
+ uncollapse: A key path set (relative to root_path) for the nodes to
845
+ uncollapse. or a function with signature (path, value, parent) -> bool
846
+ to filter nodes to uncollapse.
847
+ highlight: A function with signature (path, value, parent) -> bool
848
+ to determine whether to highlight.
849
+ lowlight: A function with signature (path, value, parent) -> bool
850
+ to determine whether to lowlight.
851
+ child_config: The configuration for rendering the child nodes.
852
+ extra_flags: Extra flags to pass to the child render.
853
+ debug: Whether to enable debug mode.
854
+
855
+ Returns:
856
+ The rendered HTML as the main content of the value.
857
+ """
858
+ root_path = root_path or KeyPath()
859
+ if isinstance(value, pg_symbolic.Symbolic):
860
+ extra_flags = extra_flags or {}
861
+ hide_frozen = extra_flags.get('hide_frozen', True)
862
+ hide_default_values = extra_flags.get('hide_default_values', False)
863
+ use_inferred = extra_flags.get('use_inferred', False)
864
+ items = {}
865
+ for k, v in value.sym_items():
866
+ # Apply frozen filter.
867
+ field = value.sym_attr_field(k)
868
+ if hide_frozen and field and field.frozen:
869
+ continue
870
+
871
+ # Apply inferred value.
872
+ if use_inferred and isinstance(v, pg_symbolic.Inferential):
873
+ v = value.sym_inferred(k, default=v)
874
+
875
+ # Apply default value filter.
876
+ if field and hide_default_values and v == field.default_value:
877
+ continue
878
+ items[k] = v
879
+ elif isinstance(value, (tuple, list)):
880
+ items = {i: v for i, v in enumerate(value)}
881
+ elif isinstance(value, dict):
882
+ items = value
883
+ else:
884
+ return self.simple_value(
885
+ value, name=name, parent=parent, root_path=root_path,
886
+ css_classes=css_classes,
887
+ max_summary_len_for_str=max_summary_len_for_str
888
+ )
889
+ return self.complex_value(
890
+ items,
891
+ name=name,
892
+ parent=value,
893
+ root_path=root_path,
894
+ css_classes=css_classes,
895
+ enable_summary=enable_summary,
896
+ enable_summary_for_str=enable_summary_for_str,
897
+ max_summary_len_for_str=max_summary_len_for_str,
898
+ enable_summary_tooltip=enable_summary_tooltip,
899
+ key_style=key_style,
900
+ key_color=key_color,
901
+ enable_key_tooltip=enable_key_tooltip,
902
+ include_keys=include_keys,
903
+ exclude_keys=exclude_keys,
904
+ collapse_level=collapse_level,
905
+ uncollapse=uncollapse,
906
+ child_config=child_config,
907
+ highlight=highlight,
908
+ lowlight=lowlight,
909
+ extra_flags=extra_flags,
910
+ debug=debug,
911
+ )
912
+
913
+ def simple_value(
914
+ self,
915
+ value: Any,
916
+ *,
917
+ name: Optional[str] = None,
918
+ parent: Any = None,
919
+ root_path: Optional[KeyPath] = None,
920
+ css_classes: Optional[Sequence[str]] = None,
921
+ max_summary_len_for_str: int = 80,
922
+ ) -> Html:
923
+ """Renders a simple value.
924
+
925
+ Args:
926
+ value: The value to render.
927
+ name: The name of the value.
928
+ parent: The parent of the value.
929
+ root_path: The root path of the value.
930
+ css_classes: CSS classes to add to the HTML element.
931
+ max_summary_len_for_str: The maximum length of the string to display.
932
+
933
+ Returns:
934
+ The rendered HTML as the simple value.
935
+ """
936
+ del name, parent, root_path
937
+ def value_repr() -> str:
938
+ if isinstance(value, str):
939
+ if len(value) < max_summary_len_for_str:
940
+ return repr(value)
941
+ else:
942
+ return value
943
+ return utils.format(
944
+ value,
945
+ compact=False,
946
+ verbose=False,
947
+ hide_default_values=True,
948
+ python_format=True,
949
+ use_inferred=True,
950
+ max_bytes_len=64,
951
+ )
952
+ return Html.element(
953
+ 'span',
954
+ [
955
+ Html.escape(value_repr),
956
+ ],
957
+ css_classes=[
958
+ 'simple-value',
959
+ self.css_class_name(value),
960
+ css_classes,
961
+ ],
962
+ ).add_style(
963
+ """
964
+ /* Simple value styles. */
965
+ .simple-value {
966
+ color: blue;
967
+ display: inline-block;
968
+ white-space: pre-wrap;
969
+ padding: 0.2em;
970
+ margin-top: 0.15em;
971
+ }
972
+ .simple-value.str {
973
+ color: darkred;
974
+ font-style: italic;
975
+ }
976
+ .simple-value.int, .simple-value.float {
977
+ color: darkblue;
978
+ }
979
+ """
980
+ )
981
+
982
+ def complex_value(
983
+ self,
984
+ kv: Dict[Union[int, str], Any],
985
+ *,
986
+ parent: Any,
987
+ root_path: KeyPath,
988
+ name: Optional[str] = None,
989
+ css_classes: Optional[Sequence[str]] = None,
990
+ # Summary settings (for child nodes).
991
+ enable_summary: Optional[bool] = None,
992
+ enable_summary_for_str: bool = True,
993
+ max_summary_len_for_str: int = 80,
994
+ enable_summary_tooltip: bool = True,
995
+ # Content settings.
996
+ key_style: Union[
997
+ Literal['label', 'summary'],
998
+ Callable[..., Literal['label', 'summary']]
999
+ ] = 'summary',
1000
+ key_color: Union[
1001
+ Tuple[Optional[str], Optional[str]],
1002
+ Callable[[KeyPath, Any, Any], Tuple[Optional[str], Optional[str]]]
1003
+ ] = None,
1004
+ include_keys: Union[
1005
+ Iterable[Union[int, str]],
1006
+ Callable[[KeyPath, Any, Any], Iterable[Union[int, str]]],
1007
+ None
1008
+ ] = None,
1009
+ exclude_keys: Union[
1010
+ Iterable[Union[int, str]],
1011
+ Callable[[KeyPath, Any, Any], Iterable[Union[int, str]]],
1012
+ None
1013
+ ] = None,
1014
+ enable_key_tooltip: bool = True,
1015
+ # Collapse settings.
1016
+ collapse_level: Optional[int] = 1,
1017
+ uncollapse: Union[KeyPathSet, base.NodeFilter, None] = None,
1018
+ # Other settings.
1019
+ child_config: Optional[Dict[str, Any]] = None,
1020
+ highlight: Optional[base.NodeFilter] = None,
1021
+ lowlight: Optional[base.NodeFilter] = None,
1022
+ # Custom render functions.
1023
+ render_key_fn: Optional[Callable[..., Html]] = None,
1024
+ render_value_fn: Optional[Callable[..., Html]] = None,
1025
+ extra_flags: Optional[Dict[str, Any]] = None,
1026
+ debug: bool = False,
1027
+ ) -> Html:
1028
+ """Renders a list of key-value pairs.
1029
+
1030
+ Args:
1031
+ kv: The key-value pairs to render.
1032
+ parent: The parent of the value.
1033
+ root_path: The root path of the value.
1034
+ name: The name of the value.
1035
+ css_classes: CSS classes to add to the HTML element.
1036
+ enable_summary: Whether to enable the summary. If None, the default is
1037
+ to enable the summary for non-string and disable the summary for
1038
+ string.
1039
+ enable_summary_for_str: Whether to enable the summary for string.
1040
+ max_summary_len_for_str: The maximum length of the string to display.
1041
+ enable_summary_tooltip: Whether to enable the summary tooltip.
1042
+ key_style: The style of the key. It can be either 'label' or 'summary'.
1043
+ If it is a function, the function takes (root_path, value, parent) and
1044
+ returns either 'label' or 'summary'.
1045
+ key_color: The color of the key. If it is a tuple, the first element is
1046
+ the text color and the second element is the background color. If it is
1047
+ a function, the function takes (root_path, value, parent) and returns
1048
+ a tuple of (text_color, background_color).
1049
+ include_keys: The keys to include (at the immediate child level). If it is
1050
+ a function, the function takes (root_path, value, parent) and returns
1051
+ an iterable of keys to include.
1052
+ exclude_keys: The keys to exclude (at the immediate child level). If it is
1053
+ a function, the function takes (root_path, value, parent) and returns
1054
+ an iterable of keys to exclude.
1055
+ enable_key_tooltip: Whether to enable the key tooltip.
1056
+ collapse_level: The level to collapse the tree.
1057
+ uncollapse: A key path set (relative to root_path) for the nodes to
1058
+ uncollapse. or a function with signature (path, value, parent) -> bool
1059
+ to filter nodes to uncollapse.
1060
+ child_config: The configuration for rendering the child nodes.
1061
+ highlight: A function with signature (path, value, parent) -> bool
1062
+ to determine whether to highlight.
1063
+ lowlight: A function with signature (path, value, parent) -> bool
1064
+ to determine whether to lowlight.
1065
+ render_key_fn: A custom function to render the label-style key.
1066
+ render_value_fn: A custom function to render the child value.
1067
+ extra_flags: Extra flags to pass to the child render.
1068
+ debug: Whether to enable debug mode.
1069
+
1070
+ Returns:
1071
+ The rendered HTML as the key-value pairs.
1072
+ """
1073
+ del name
1074
+ root_path = root_path or KeyPath()
1075
+ uncollapse = self.init_uncollapse(uncollapse)
1076
+ extra_flags = extra_flags or {}
1077
+
1078
+ inherited_kwargs = dict(
1079
+ # For child summary.
1080
+ enable_summary=enable_summary,
1081
+ enable_summary_for_str=enable_summary_for_str,
1082
+ max_summary_len_for_str=max_summary_len_for_str,
1083
+ enable_summary_tooltip=enable_summary_tooltip,
1084
+ # For child content.
1085
+ enable_key_tooltip=enable_key_tooltip,
1086
+ key_style=key_style,
1087
+ key_color=key_color,
1088
+ include_keys=include_keys if callable(include_keys) else None,
1089
+ exclude_keys=exclude_keys if callable(exclude_keys) else None,
1090
+ collapse_level=None if collapse_level is None else (collapse_level - 1),
1091
+ uncollapse=uncollapse,
1092
+ highlight=highlight,
1093
+ lowlight=lowlight,
1094
+ extra_flags=extra_flags,
1095
+ debug=debug,
1096
+ )
1097
+
1098
+ render_key_fn = render_key_fn or HtmlTreeView.object_key
1099
+ render_value_fn = render_value_fn or HtmlTreeView.render
1100
+
1101
+ def render_child_key(child_path, value, parent, child_kwargs):
1102
+ render_child_key_fn = child_kwargs['extra_flags'].get(
1103
+ 'render_key_fn', render_key_fn
1104
+ )
1105
+ return render_child_key_fn(
1106
+ self,
1107
+ child_path,
1108
+ value=value,
1109
+ parent=parent,
1110
+ **child_kwargs
1111
+ )
1112
+
1113
+ def render_child_value(name, value, child_path, child_kwargs):
1114
+ render_child_value_fn = child_kwargs['extra_flags'].get(
1115
+ 'render_value_fn', render_value_fn
1116
+ )
1117
+ child_html = render_child_value_fn(
1118
+ self,
1119
+ value=value, name=child_kwargs.pop('name', name),
1120
+ parent=parent,
1121
+ root_path=child_path,
1122
+ **child_kwargs
1123
+ )
1124
+ should_highlight = highlight and highlight(child_path, value, parent)
1125
+ should_lowlight = lowlight and lowlight(child_path, value, parent)
1126
+ if should_highlight or should_lowlight:
1127
+ return Html.element(
1128
+ 'div', [child_html],
1129
+ css_classes=[
1130
+ 'highlight' if should_highlight else None,
1131
+ 'lowlight' if should_lowlight else None,
1132
+ ],
1133
+ )
1134
+ else:
1135
+ return child_html
1136
+
1137
+ has_child = False
1138
+ s = Html()
1139
+ if kv:
1140
+ # Compute included keys.
1141
+ if callable(include_keys):
1142
+ include_keys = [
1143
+ k for k, v in kv.items() if include_keys(root_path + k, v, parent)
1144
+ ]
1145
+ elif include_keys is not None:
1146
+ include_keys = list(k for k in include_keys if k in kv)
1147
+ else:
1148
+ include_keys = list(kv.keys())
1149
+
1150
+ # Filter with excluded keys.
1151
+ if callable(exclude_keys):
1152
+ include_keys = [
1153
+ k for k in include_keys if not exclude_keys(
1154
+ root_path + k, kv[k], parent
1155
+ )
1156
+ ]
1157
+ elif exclude_keys is not None:
1158
+ exclude_keys = set(exclude_keys)
1159
+ include_keys = [k for k in include_keys if k not in exclude_keys]
1160
+
1161
+ # Figure out keys of different styles.
1162
+ label_keys = []
1163
+ summary_keys = []
1164
+ if isinstance(parent, (tuple, list)) or key_style == 'label':
1165
+ label_keys = include_keys
1166
+ elif key_style == 'summary':
1167
+ summary_keys = include_keys
1168
+ else:
1169
+ assert callable(key_style), key_style
1170
+ for k in include_keys:
1171
+ ks = key_style(root_path + k, kv[k], parent)
1172
+ if ks == 'summary':
1173
+ summary_keys.append(k)
1174
+ elif ks == 'label':
1175
+ label_keys.append(k)
1176
+
1177
+ # Render child nodes with summary keys.
1178
+ if summary_keys:
1179
+ for k in summary_keys:
1180
+ child_path = root_path + k
1181
+ child_kwargs = self.get_child_kwargs(
1182
+ inherited_kwargs, child_config, k, root_path
1183
+ )
1184
+ s.write(render_child_value(k, kv[k], child_path, child_kwargs))
1185
+ has_child = True
1186
+
1187
+ # Render child nodes with label keys.
1188
+ if label_keys:
1189
+ s.write('<table>')
1190
+ for k in label_keys:
1191
+ v = kv[k]
1192
+ child_path = root_path + k
1193
+ child_kwargs = self.get_child_kwargs(
1194
+ inherited_kwargs, child_config, k, root_path
1195
+ )
1196
+ key_cell = render_child_key(child_path, v, parent, child_kwargs)
1197
+ value_cell = render_child_value(None, v, child_path, child_kwargs)
1198
+ if value_cell is not None:
1199
+ s.write(
1200
+ Html.element(
1201
+ 'tr',
1202
+ [
1203
+ '<td>', key_cell, '</td>',
1204
+ '<td>', value_cell, '</td>',
1205
+ ],
1206
+ )
1207
+ )
1208
+ has_child = True
1209
+ s.write('</table>')
1210
+
1211
+ if not has_child:
1212
+ s.write(Html.element('span', css_classes=['empty-container']))
1213
+
1214
+ return Html.element(
1215
+ 'div',
1216
+ [s],
1217
+ css_classes=[
1218
+ 'complex-value',
1219
+ self.css_class_name(parent),
1220
+ css_classes,
1221
+ ]
1222
+ ).add_style(
1223
+ """
1224
+ /* Complex value styles. */
1225
+ span.empty-container::before {
1226
+ content: '(empty)';
1227
+ font-style: italic;
1228
+ margin-left: 0.5em;
1229
+ color: #aaa;
1230
+ }
1231
+ """
1232
+ )
1233
+
1234
+ def tooltip(
1235
+ self,
1236
+ value: Any,
1237
+ *,
1238
+ parent: Any = None,
1239
+ root_path: Optional[KeyPath] = None,
1240
+ css_classes: Optional[Sequence[str]] = None,
1241
+ id: Optional[str] = None, # pylint: disable=redefined-builtin
1242
+ content: Union[str, Html, None] = None,
1243
+ **kwargs,
1244
+ ) -> Html:
1245
+ """Renders a tooltip for the value.
1246
+
1247
+ Args:
1248
+ value: The value to render.
1249
+ parent: The parent of the value.
1250
+ root_path: The root path of the value.
1251
+ css_classes: CSS classes to add to the HTML element.
1252
+ id: The ID of the tooltip span element. If None, no ID will be added.
1253
+ content: The content to render. If None, the value will be rendered.
1254
+ **kwargs: Additional keyword arguments passed from the user that
1255
+ will be ignored.
1256
+
1257
+ Returns:
1258
+ The rendered HTML as the tooltip of the value.
1259
+ """
1260
+ del parent, kwargs
1261
+ if content is None:
1262
+ content = Html.escape(
1263
+ utils.format(
1264
+ value,
1265
+ root_path=root_path,
1266
+ compact=False,
1267
+ verbose=False,
1268
+ python_format=True,
1269
+ max_bytes_len=64,
1270
+ max_str_len=256,
1271
+ )
1272
+ )
1273
+ return Html.element(
1274
+ 'span',
1275
+ [content],
1276
+ id=id,
1277
+ css_classes=[
1278
+ 'tooltip',
1279
+ css_classes,
1280
+ ],
1281
+ ).add_style(
1282
+ """
1283
+ /* Tooltip styles. */
1284
+ span.tooltip {
1285
+ visibility: hidden;
1286
+ white-space: pre-wrap;
1287
+ font-weight: normal;
1288
+ background-color: #484848;
1289
+ color: #fff;
1290
+ padding: 10px;
1291
+ border-radius: 6px;
1292
+ position: absolute;
1293
+ z-index: 1;
1294
+ }
1295
+ span.tooltip:hover {
1296
+ visibility: visible;
1297
+ }
1298
+ """
1299
+ )
1300
+
1301
+ @staticmethod
1302
+ def css_class_name(value: Any) -> Optional[str]:
1303
+ """Returns the CSS class name for the value."""
1304
+ if inspect.isclass(value):
1305
+ class_name = f'{value.__name__}-class'
1306
+ else:
1307
+ class_name = type(value).__name__
1308
+ return utils.camel_to_snake(class_name, '-')
1309
+
1310
+ @staticmethod
1311
+ def init_uncollapse(
1312
+ uncollapse: Union[Iterable[Union[KeyPath, str]], base.NodeFilter, None],
1313
+ ) -> Union[KeyPathSet, base.NodeFilter]:
1314
+ """Initializes the uncollapse argument."""
1315
+ if uncollapse is None:
1316
+ return KeyPathSet()
1317
+ elif callable(uncollapse):
1318
+ return uncollapse
1319
+ else:
1320
+ return KeyPathSet.from_value(uncollapse, include_intermediate=True)
1321
+
1322
+ @staticmethod
1323
+ def get_child_kwargs(
1324
+ call_kwargs: Dict[str, Any],
1325
+ child_config: Dict[str, Any],
1326
+ child_key: Optional[str],
1327
+ root_path: KeyPath,
1328
+ ) -> Dict[str, Any]:
1329
+ """Enter the child config for a child key."""
1330
+ if not child_config:
1331
+ return call_kwargs
1332
+
1333
+ child_kwargs = child_config.get(
1334
+ child_key, child_config.get('__default__', None)
1335
+ )
1336
+ if not child_kwargs:
1337
+ return call_kwargs
1338
+
1339
+ return HtmlTreeView.get_kwargs(
1340
+ call_kwargs, child_kwargs, root_path + child_key,
1341
+ )
1342
+
1343
+ # pytype: disable=annotation-type-mismatch
1344
+ @staticmethod
1345
+ def get_passthrough_kwargs(
1346
+ *,
1347
+ enable_summary: Optional[bool] = utils.MISSING_VALUE,
1348
+ enable_summary_for_str: bool = utils.MISSING_VALUE,
1349
+ max_summary_len_for_str: int = utils.MISSING_VALUE,
1350
+ enable_summary_tooltip: bool = utils.MISSING_VALUE,
1351
+ key_style: Union[
1352
+ Literal['label', 'summary'],
1353
+ Callable[[KeyPath, Any, Any], Literal['label', 'summary']],
1354
+ ] = utils.MISSING_VALUE,
1355
+ key_color: Union[
1356
+ Tuple[Optional[str], Optional[str]],
1357
+ Callable[[KeyPath, Any, Any], Tuple[Optional[str], Optional[str]]],
1358
+ ] = utils.MISSING_VALUE,
1359
+ include_keys: Union[
1360
+ Iterable[Union[int, str]],
1361
+ Callable[[KeyPath, Any, Any], Iterable[Union[int, str]]],
1362
+ None,
1363
+ ] = utils.MISSING_VALUE,
1364
+ exclude_keys: Union[
1365
+ Iterable[Union[int, str]],
1366
+ Callable[[KeyPath, Any, Any], Iterable[Union[int, str]]],
1367
+ None,
1368
+ ] = utils.MISSING_VALUE,
1369
+ enable_key_tooltip: bool = utils.MISSING_VALUE,
1370
+ uncollapse: Union[
1371
+ KeyPathSet, base.NodeFilter, None
1372
+ ] = utils.MISSING_VALUE,
1373
+ extra_flags: Optional[Dict[str, Any]] = utils.MISSING_VALUE,
1374
+ highlight: Optional[base.NodeFilter] = utils.MISSING_VALUE,
1375
+ lowlight: Optional[base.NodeFilter] = utils.MISSING_VALUE,
1376
+ debug: bool = utils.MISSING_VALUE,
1377
+ remove: Optional[Iterable[str]] = None,
1378
+ **kwargs,
1379
+ ):
1380
+ # pytype: enable=annotation-type-mismatch
1381
+ """Gets the rendering arguments to pass through to the child nodes."""
1382
+ del kwargs
1383
+ passthrough_kwargs = dict(
1384
+ enable_summary=enable_summary,
1385
+ enable_summary_for_str=enable_summary_for_str,
1386
+ max_summary_len_for_str=max_summary_len_for_str,
1387
+ enable_summary_tooltip=enable_summary_tooltip,
1388
+ enable_key_tooltip=enable_key_tooltip,
1389
+ key_style=key_style,
1390
+ key_color=key_color,
1391
+ include_keys=(
1392
+ include_keys if callable(include_keys) else utils.MISSING_VALUE
1393
+ ),
1394
+ exclude_keys=(
1395
+ exclude_keys if callable(exclude_keys) else utils.MISSING_VALUE
1396
+ ),
1397
+ uncollapse=uncollapse,
1398
+ highlight=highlight,
1399
+ lowlight=lowlight,
1400
+ extra_flags=extra_flags,
1401
+ debug=debug,
1402
+ )
1403
+ # Filter out missing values.
1404
+ passthrough_kwargs = {
1405
+ k: v
1406
+ for k, v in passthrough_kwargs.items()
1407
+ if v is not utils.MISSING_VALUE
1408
+ }
1409
+ if remove:
1410
+ return {
1411
+ k: v for k, v in passthrough_kwargs.items()
1412
+ if k not in remove
1413
+ }
1414
+ return passthrough_kwargs
1415
+
1416
+ @staticmethod
1417
+ def get_collapse_level(
1418
+ original_level: Union[None, int, Tuple[Optional[int], int]],
1419
+ overriden_level: Union[None, int, Tuple[Optional[int], int]],
1420
+ ) -> Optional[int]:
1421
+ """Gets the collapse level for a child node."""
1422
+ original_offset, overriden_offset = 0, 0
1423
+ if isinstance(original_level, tuple):
1424
+ original_level, original_offset = original_level
1425
+ if isinstance(overriden_level, tuple):
1426
+ overriden_level, overriden_offset = overriden_level
1427
+
1428
+ if original_level is None:
1429
+ return original_level
1430
+ elif overriden_level is None:
1431
+ return overriden_level
1432
+ else:
1433
+ return max(
1434
+ original_level + original_offset,
1435
+ overriden_level + overriden_offset
1436
+ )
1437
+
1438
+ @staticmethod
1439
+ def get_kwargs(
1440
+ call_kwargs: Dict[str, Any],
1441
+ overriden_kwargs: Dict[str, Any],
1442
+ root_path: Optional[KeyPath] = None,
1443
+ ) -> Dict[str, Any]:
1444
+ """Override render arguments."""
1445
+ # Select child config to override.
1446
+ if not overriden_kwargs:
1447
+ return call_kwargs
1448
+
1449
+ call_kwargs = call_kwargs.copy()
1450
+ overriden_kwargs = overriden_kwargs.copy()
1451
+
1452
+ # Override collapse_level.
1453
+ if 'collapse_level' in call_kwargs or 'collapse_level' in overriden_kwargs:
1454
+ call_kwargs['collapse_level'] = HtmlTreeView.get_collapse_level(
1455
+ call_kwargs.pop('collapse_level', 1),
1456
+ overriden_kwargs.pop('collapse_level', 0)
1457
+ )
1458
+
1459
+ # Override uncollapse.
1460
+ if 'uncollapse' in call_kwargs or 'uncollapse' in overriden_kwargs:
1461
+ uncollapse = KeyPathSet.from_value(
1462
+ call_kwargs.pop('uncollapse', None) or []
1463
+ )
1464
+ child_uncollapse = KeyPathSet.from_value(
1465
+ overriden_kwargs.pop('uncollapse', None) or []
1466
+ )
1467
+ call_kwargs['uncollapse'] = HtmlTreeView.merge_uncollapse(
1468
+ uncollapse, child_uncollapse, root_path
1469
+ )
1470
+
1471
+ # Deep hierarchy merge.
1472
+ return utils.merge_tree(call_kwargs, overriden_kwargs)
1473
+
1474
+ @staticmethod
1475
+ def merge_uncollapse(
1476
+ uncollapse: Union[KeyPathSet, base.NodeFilter, None],
1477
+ child_uncollapse: Optional[KeyPathSet],
1478
+ child_path: Optional[KeyPath] = None,
1479
+ ) -> Union[KeyPathSet, base.NodeFilter]:
1480
+ """Merge uncollapse paths."""
1481
+ if not uncollapse and not child_uncollapse:
1482
+ return KeyPathSet()
1483
+
1484
+ if callable(uncollapse) or not child_uncollapse:
1485
+ assert uncollapse is not None
1486
+ return uncollapse
1487
+
1488
+ assert isinstance(uncollapse, KeyPathSet), uncollapse
1489
+ assert isinstance(child_uncollapse, KeyPathSet), child_uncollapse
1490
+ if child_path:
1491
+ child_uncollapse = child_uncollapse.copy()
1492
+ child_uncollapse.rebase(child_path)
1493
+ uncollapse.update(child_uncollapse)
1494
+ return uncollapse
1495
+
1496
+ @staticmethod
1497
+ def get_color(
1498
+ color: Union[
1499
+ Tuple[str, str],
1500
+ Callable[
1501
+ [KeyPath, Any, Any],
1502
+ Tuple[Optional[str], Optional[str]]
1503
+ ],
1504
+ None
1505
+ ],
1506
+ root_path,
1507
+ value,
1508
+ parent
1509
+ ) -> Tuple[Optional[str], Optional[str]]:
1510
+ if callable(color):
1511
+ return color(root_path, value, parent)
1512
+ if color is None:
1513
+ return (None, None)
1514
+ assert isinstance(color, tuple) and len(color) == 2, color
1515
+ return color
1516
+
1517
+ # pytype: enable=annotation-type-mismatch