pyglove 0.4.5.dev202410020809__py3-none-any.whl → 0.4.5.dev202410100808__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 (29) hide show
  1. pyglove/core/__init__.py +9 -0
  2. pyglove/core/object_utils/__init__.py +1 -0
  3. pyglove/core/object_utils/value_location.py +10 -0
  4. pyglove/core/object_utils/value_location_test.py +22 -0
  5. pyglove/core/symbolic/base.py +40 -2
  6. pyglove/core/symbolic/base_test.py +67 -0
  7. pyglove/core/symbolic/diff.py +190 -1
  8. pyglove/core/symbolic/diff_test.py +290 -0
  9. pyglove/core/symbolic/object_test.py +3 -8
  10. pyglove/core/symbolic/ref.py +29 -0
  11. pyglove/core/symbolic/ref_test.py +143 -0
  12. pyglove/core/typing/__init__.py +4 -0
  13. pyglove/core/typing/callable_ext.py +240 -1
  14. pyglove/core/typing/callable_ext_test.py +255 -0
  15. pyglove/core/typing/inspect.py +63 -0
  16. pyglove/core/typing/inspect_test.py +39 -0
  17. pyglove/core/views/__init__.py +30 -0
  18. pyglove/core/views/base.py +906 -0
  19. pyglove/core/views/base_test.py +615 -0
  20. pyglove/core/views/html/__init__.py +27 -0
  21. pyglove/core/views/html/base.py +529 -0
  22. pyglove/core/views/html/base_test.py +804 -0
  23. pyglove/core/views/html/tree_view.py +1052 -0
  24. pyglove/core/views/html/tree_view_test.py +748 -0
  25. {pyglove-0.4.5.dev202410020809.dist-info → pyglove-0.4.5.dev202410100808.dist-info}/METADATA +1 -1
  26. {pyglove-0.4.5.dev202410020809.dist-info → pyglove-0.4.5.dev202410100808.dist-info}/RECORD +29 -21
  27. {pyglove-0.4.5.dev202410020809.dist-info → pyglove-0.4.5.dev202410100808.dist-info}/LICENSE +0 -0
  28. {pyglove-0.4.5.dev202410020809.dist-info → pyglove-0.4.5.dev202410100808.dist-info}/WHEEL +0 -0
  29. {pyglove-0.4.5.dev202410020809.dist-info → pyglove-0.4.5.dev202410100808.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,1052 @@
1
+ # Copyright 2024 The Langfun 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
+ import re
18
+ from typing import Any, Dict, Iterable, Optional, Sequence, Set, Union
19
+
20
+ from pyglove.core import object_utils
21
+ from pyglove.core.views.html import base
22
+
23
+
24
+ KeyPath = object_utils.KeyPath
25
+ Html = base.Html
26
+ HtmlView = base.HtmlView
27
+
28
+
29
+ # pytype: disable=annotation-type-mismatch
30
+
31
+
32
+ class HtmlTreeView(HtmlView):
33
+ """HTML Tree View."""
34
+
35
+ VIEW_ID = 'html-tree-view'
36
+
37
+ class Extension(HtmlView.Extension):
38
+ """The base class for extensions for HtmlTreeView."""
39
+
40
+ def _html_tree_view_render(
41
+ self,
42
+ *,
43
+ view: 'HtmlView',
44
+ name: Optional[str],
45
+ parent: Any,
46
+ root_path: KeyPath,
47
+ title: Union[str, Html, None] = None,
48
+ special_keys: Optional[Sequence[Union[int, str]]] = None,
49
+ include_keys: Optional[Iterable[Union[int, str]]] = None,
50
+ exclude_keys: Optional[Iterable[Union[int, str]]] = None,
51
+ filter: Optional[base.NodeFilter] = HtmlView.PresetArgValue(None), # pylint: disable=redefined-builtin
52
+ highlight: Optional[base.NodeFilter] = HtmlView.PresetArgValue(None),
53
+ lowlight: Optional[base.NodeFilter] = HtmlView.PresetArgValue(None),
54
+ enable_summary: Optional[bool] = HtmlView.PresetArgValue(None),
55
+ max_summary_len_for_str: int = HtmlView.PresetArgValue(40),
56
+ enable_summary_tooltip: bool = HtmlView.PresetArgValue(True),
57
+ enable_key_tooltip: bool = HtmlView.PresetArgValue(True),
58
+ collapse_level: Optional[int] = HtmlView.PresetArgValue(1),
59
+ uncollapse: Union[
60
+ Iterable[Union[KeyPath, str]], base.NodeFilter, None
61
+ ] = HtmlView.PresetArgValue(None),
62
+ **kwargs,
63
+ ) -> Html:
64
+ """Returns the topmost HTML representation of the object.
65
+
66
+ Args:
67
+ view: The view to render the object.
68
+ name: The name of the object.
69
+ parent: The parent of the object.
70
+ root_path: The key path of the object relative to the root.
71
+ title: The title of the summary.
72
+ special_keys: The special keys to display (at the immediate child
73
+ level).
74
+ include_keys: The keys to include (at the immediate child level).
75
+ exclude_keys: The keys to exclude (at the immediate child level).
76
+ filter: A function with signature (path, value, parent) -> include
77
+ to determine whether to include a field (at all levels).
78
+ highlight: A function with signature (path, value, parent) -> bool
79
+ to determine whether to highlight a field.
80
+ lowlight: A function with signature (path, value, parent) -> bool
81
+ to determine whether to lowlight a field.
82
+ enable_summary: Whether to enable the summary. If None, summary will
83
+ be enabled for complex types or when string exceeds
84
+ `max_summary_len_for_str`.
85
+ max_summary_len_for_str: The maximum length of the string to display.
86
+ enable_summary_tooltip: Whether to enable the tooltip for the summary.
87
+ enable_key_tooltip: Whether to enable the tooltip for the key.
88
+ collapse_level: The level to collapse the tree
89
+ uncollapse: A set of key paths for the nodes to uncollapse. or a
90
+ function with signature (path, value, parent) -> bool to determine
91
+ whether to uncollapse a node.
92
+ **kwargs: Additional keyword arguments passed from `pg.to_html`.
93
+
94
+ Returns:
95
+ The rendered HTML.
96
+ """
97
+ return view.render(
98
+ self,
99
+ name=name,
100
+ parent=parent,
101
+ root_path=root_path,
102
+ title=title,
103
+ special_keys=special_keys,
104
+ include_keys=include_keys,
105
+ exclude_keys=exclude_keys,
106
+ filter=filter,
107
+ highlight=highlight,
108
+ lowlight=lowlight,
109
+ enable_summary=enable_summary,
110
+ max_summary_len_for_str=max_summary_len_for_str,
111
+ enable_summary_tooltip=enable_summary_tooltip,
112
+ enable_key_tooltip=enable_key_tooltip,
113
+ collapse_level=collapse_level,
114
+ uncollapse=uncollapse,
115
+ **kwargs
116
+ )
117
+
118
+ def _html_tree_view_summary(
119
+ self,
120
+ *,
121
+ view: 'HtmlTreeView',
122
+ name: Optional[str],
123
+ parent: Any,
124
+ root_path: KeyPath,
125
+ css_class: Optional[Sequence[str]] = None,
126
+ title: Union[str, Html, None] = None,
127
+ enable_summary: Optional[bool] = HtmlView.PresetArgValue(None),
128
+ max_summary_len_for_str: int = HtmlView.PresetArgValue(40),
129
+ enable_summary_tooltip: bool = HtmlView.PresetArgValue(True),
130
+ **kwargs,
131
+ ) -> Optional[Html]:
132
+ """Returns the HTML representation of the object.
133
+
134
+ Args:
135
+ view: The view to render the object.
136
+ name: The name of the object.
137
+ parent: The parent of the object.
138
+ root_path: The key path of the object relative to the root.
139
+ css_class: The CSS classes to add to the root element
140
+ title: The title of the summary.
141
+ enable_summary: Whether to enable the summary. If None, summary will
142
+ be enabled for complex types or when string exceeds
143
+ `max_summary_len_for_str`.
144
+ max_summary_len_for_str: The maximum length of the string to display.
145
+ enable_summary_tooltip: Whether to enable the tooltip for the summary.
146
+ **kwargs: Additional keyword arguments passed from `pg.to_html`. These
147
+ arguments may be handled by the user logic but not the general
148
+ HtmlTreeView.
149
+
150
+ Returns:
151
+ An optional HTML object representing the summary of the object. If None,
152
+ the summary will be hidden.
153
+ """
154
+ return view.summary(
155
+ self,
156
+ name=name,
157
+ parent=parent,
158
+ root_path=root_path,
159
+ css_class=css_class,
160
+ title=title,
161
+ enable_summary=enable_summary,
162
+ max_summary_len_for_str=max_summary_len_for_str,
163
+ enable_summary_tooltip=enable_summary_tooltip,
164
+ **kwargs,
165
+ )
166
+
167
+ def _html_tree_view_content(
168
+ self,
169
+ *,
170
+ view: 'HtmlTreeView',
171
+ name: Optional[str],
172
+ parent: Any,
173
+ root_path: KeyPath,
174
+ special_keys: Optional[Sequence[Union[int, str]]] = None,
175
+ include_keys: Optional[Iterable[Union[int, str]]] = None,
176
+ exclude_keys: Optional[Iterable[Union[int, str]]] = None,
177
+ filter: Optional[base.NodeFilter] = HtmlView.PresetArgValue(None), # pylint: disable=redefined-builtin
178
+ highlight: Optional[base.NodeFilter] = HtmlView.PresetArgValue(None),
179
+ lowlight: Optional[base.NodeFilter] = HtmlView.PresetArgValue(None),
180
+ max_summary_len_for_str: int = HtmlView.PresetArgValue(40),
181
+ enable_summary_tooltip: bool = HtmlView.PresetArgValue(True),
182
+ enable_key_tooltip: bool = HtmlView.PresetArgValue(True),
183
+ collapse_level: Optional[int] = HtmlView.PresetArgValue(1),
184
+ uncollapse: Union[
185
+ Iterable[Union[KeyPath, str]], base.NodeFilter, None
186
+ ] = HtmlView.PresetArgValue(None),
187
+ **kwargs,
188
+ ) -> Html:
189
+ """Returns the main content for the object.
190
+
191
+ Args:
192
+ view: The view to render the object.
193
+ name: The name of the object.
194
+ parent: The parent of the object.
195
+ root_path: The key path of the object relative to the root.
196
+ special_keys: The special keys to display (at the immediate child
197
+ level).
198
+ include_keys: The keys to include (at the immediate child level).
199
+ exclude_keys: The keys to exclude (at the immediate child level).
200
+ filter: A function with signature (path, value, parent) -> include
201
+ to determine whether to include a field (at all levels).
202
+ highlight: A function with signature (path, value, parent) -> bool
203
+ to determine whether to highlight a field (at all levels).
204
+ lowlight: A function with signature (path, value, parent) -> bool
205
+ to determine whether to lowlight a field (at all levels).
206
+ max_summary_len_for_str: The maximum length of the string to display.
207
+ enable_summary_tooltip: Whether to enable the tooltip for the summary.
208
+ enable_key_tooltip: Whether to enable the key tooltip.
209
+ collapse_level: The level to collapse the tree
210
+ uncollapse: The paths to uncollapse.
211
+ **kwargs: Additional keyword arguments passed from `pg.to_html`. These
212
+ arguments may be handled by the user logic but not the general
213
+ HtmlTreeView.
214
+
215
+ Returns:
216
+ The rendered HTML as the main content of the object.
217
+ """
218
+ return view.content(
219
+ self,
220
+ name=name,
221
+ parent=parent,
222
+ root_path=root_path,
223
+ special_keys=special_keys,
224
+ include_keys=include_keys,
225
+ exclude_keys=exclude_keys,
226
+ filter=filter,
227
+ highlight=highlight,
228
+ lowlight=lowlight,
229
+ max_summary_len_for_str=max_summary_len_for_str,
230
+ enable_summary_tooltip=enable_summary_tooltip,
231
+ enable_key_tooltip=enable_key_tooltip,
232
+ collapse_level=collapse_level,
233
+ uncollapse=uncollapse,
234
+ **kwargs
235
+ )
236
+
237
+ def _html_tree_view_tooltip(
238
+ self,
239
+ *,
240
+ view: 'HtmlTreeView',
241
+ name: Optional[str],
242
+ parent: Any,
243
+ root_path: KeyPath,
244
+ content: Union[str, Html, None] = None,
245
+ **kwargs,
246
+ ) -> Optional[Html]:
247
+ """Returns the tooltip for the object.
248
+
249
+ Args:
250
+ view: The view to render the object.
251
+ name: The referenced name of the object.
252
+ parent: The parent of the object.
253
+ root_path: The key path of the object relative to the root.
254
+ content: Custom content to display in the tooltip.
255
+ **kwargs: Additional keyword arguments passed from `pg.to_html`. These
256
+ arguments may be handled by the user logic but not the general
257
+ HtmlTreeView.
258
+
259
+ Returns:
260
+ An optional HTML object representing the tooltip of the object. If None,
261
+ the tooltip will be hidden.
262
+ """
263
+ return view.tooltip(
264
+ value=self, name=name, parent=parent, root_path=root_path,
265
+ content=content, **kwargs
266
+ )
267
+
268
+ @HtmlView.extension_method('_html_tree_view_render')
269
+ def render(
270
+ self,
271
+ value: Any,
272
+ *,
273
+ name: Optional[str] = None,
274
+ parent: Any = None,
275
+ root_path: Optional[KeyPath] = None,
276
+ css_class: Optional[Sequence[str]] = None,
277
+ title: Union[str, Html, None] = None,
278
+ special_keys: Optional[Sequence[Union[int, str]]] = None,
279
+ include_keys: Optional[Iterable[Union[int, str]]] = None,
280
+ exclude_keys: Optional[Iterable[Union[int, str]]] = None,
281
+ filter: Optional[base.NodeFilter] = HtmlView.PresetArgValue(None), # pylint: disable=redefined-builtin
282
+ highlight: Optional[base.NodeFilter] = HtmlView.PresetArgValue(None),
283
+ lowlight: Optional[base.NodeFilter] = HtmlView.PresetArgValue(None),
284
+ enable_summary: Optional[bool] = HtmlView.PresetArgValue(None),
285
+ max_summary_len_for_str: int = HtmlView.PresetArgValue(40),
286
+ enable_summary_tooltip: bool = HtmlView.PresetArgValue(True),
287
+ enable_key_tooltip: bool = HtmlView.PresetArgValue(True),
288
+ collapse_level: Optional[int] = HtmlView.PresetArgValue(1),
289
+ uncollapse: Union[
290
+ Iterable[Union[KeyPath, str]], base.NodeFilter, None
291
+ ] = HtmlView.PresetArgValue(None),
292
+ **kwargs
293
+ ) -> Html:
294
+ """Renders the entire HTML tree view for the value.
295
+
296
+ Args:
297
+ value: The value to render.
298
+ name: The name of the value.
299
+ parent: The parent of the value.
300
+ root_path: The root path of the value.
301
+ css_class: The CSS classes to add to the root element
302
+ title: The title of the summary.
303
+ special_keys: The special keys to display (at the immediate child level).
304
+ include_keys: The keys to include (at the immediate child level).
305
+ exclude_keys: The keys to exclude (at the immediate child level).
306
+ filter: A function with signature (path, value, parent) -> include
307
+ to determine whether to include a field (at all levels).
308
+ highlight: A function with signature (path, value, parent) -> bool
309
+ to determine whether to highlight a field.
310
+ lowlight: A function with signature (path, value, parent) -> bool
311
+ to determine whether to lowlight a field.
312
+ enable_summary: Whether to enable the summary. If None, summary will
313
+ be enabled for complex types or when string exceeds
314
+ `max_summary_len_for_str`.
315
+ max_summary_len_for_str: The maximum length of the string to display.
316
+ enable_summary_tooltip: Whether to enable the tooltip for the summary.
317
+ enable_key_tooltip: Whether to enable the key tooltip.
318
+ collapse_level: The level to collapse the tree.
319
+ uncollapse: The paths to uncollapse.
320
+ **kwargs: Additional keyword arguments passed from `pg.to_html`.
321
+
322
+ Returns:
323
+ The rendered HTML.
324
+ """
325
+ root_path = root_path or KeyPath()
326
+ uncollapse = self.normalize_uncollapse(uncollapse)
327
+ summary = self.summary(
328
+ value,
329
+ name=name,
330
+ parent=parent,
331
+ root_path=root_path,
332
+ title=title,
333
+ enable_summary=enable_summary,
334
+ enable_summary_tooltip=enable_summary_tooltip,
335
+ max_summary_len_for_str=max_summary_len_for_str,
336
+ **kwargs,
337
+ )
338
+ content = self.content(
339
+ value,
340
+ name=name,
341
+ parent=parent,
342
+ root_path=root_path,
343
+ css_class=css_class if summary is None else None,
344
+ filter=filter,
345
+ special_keys=special_keys,
346
+ include_keys=include_keys,
347
+ exclude_keys=exclude_keys,
348
+ max_summary_len_for_str=max_summary_len_for_str,
349
+ enable_summary_tooltip=enable_summary_tooltip,
350
+ enable_key_tooltip=enable_key_tooltip,
351
+ collapse_level=collapse_level,
352
+ uncollapse=uncollapse,
353
+ **kwargs,
354
+ )
355
+ if summary is None:
356
+ content = Html.from_value(content)
357
+ assert content is not None
358
+ return content
359
+
360
+ collapse_view = self.should_collapse(
361
+ value, parent=parent, root_path=root_path,
362
+ collapse_level=collapse_level, uncollapse=uncollapse,
363
+ )
364
+ return Html.element(
365
+ 'details',
366
+ [
367
+ summary,
368
+ content,
369
+ ],
370
+ options=[None if collapse_view else 'open'],
371
+ css_class=[
372
+ 'pyglove',
373
+ self.css_class_name(value),
374
+ ] + (css_class or []),
375
+ ).add_style(
376
+ """
377
+ /* Value details styles. */
378
+ details.pyglove {
379
+ border: 1px solid #aaa;
380
+ border-radius: 4px;
381
+ padding: 0.5em 0.5em 0;
382
+ margin: 0.1em 0;
383
+ }
384
+ details.pyglove.special_value {
385
+ margin-bottom: 0.75em;
386
+ }
387
+ details.pyglove[open] {
388
+ padding: 0.5em 0.5em 0.5em;
389
+ }
390
+ .highlight {
391
+ background-color: Mark;
392
+ }
393
+ .lowlight {
394
+ opacity: 0.2;
395
+ }
396
+ """
397
+ )
398
+
399
+ def normalize_uncollapse(
400
+ self,
401
+ uncollapse: Union[
402
+ Iterable[Union[KeyPath, str]], base.NodeFilter, None
403
+ ] = HtmlView.PresetArgValue(None),
404
+ ) -> Union[None, Set[KeyPath], base.NodeFilter]:
405
+ """Normalize the uncollapse argument."""
406
+ if uncollapse is None:
407
+ return None
408
+ elif isinstance(uncollapse, set) or callable(uncollapse):
409
+ return uncollapse
410
+ else:
411
+ expanded = set()
412
+ for path in uncollapse:
413
+ path = object_utils.KeyPath.from_value(path)
414
+ expanded.add(path)
415
+ while path:
416
+ expanded.add(path.parent)
417
+ path = path.parent
418
+ return expanded
419
+
420
+ def should_collapse(
421
+ self,
422
+ value: Any,
423
+ root_path: KeyPath,
424
+ parent: Any,
425
+ collapse_level: Optional[int] = 0,
426
+ uncollapse: Union[
427
+ Set[Union[KeyPath, str]], base.NodeFilter, None
428
+ ] = None,
429
+ ) -> bool:
430
+ """Returns whether the object should be collapsed."""
431
+ if collapse_level is None or root_path.depth < collapse_level:
432
+ return False
433
+ if uncollapse is None:
434
+ return True
435
+ elif callable(uncollapse):
436
+ return not uncollapse(root_path, value, parent)
437
+ else:
438
+ return root_path not in uncollapse
439
+
440
+ def needs_summary(
441
+ self,
442
+ value: Any,
443
+ *,
444
+ name: Optional[str] = None,
445
+ parent: Any = None,
446
+ title: Union[str, Html, None] = None,
447
+ enable_summary: Optional[bool] = HtmlView.PresetArgValue(True),
448
+ max_summary_len_for_str: int = HtmlView.PresetArgValue(40),
449
+ ) -> bool:
450
+ """Returns whether the object needs a summary."""
451
+ del parent
452
+ if enable_summary is None:
453
+ if name is not None or title is not None or not (
454
+ isinstance(value, (int, float, bool, type(None)))
455
+ or (
456
+ isinstance(value, str)
457
+ and len(value) <= max_summary_len_for_str
458
+ )
459
+ ):
460
+ return True
461
+ return enable_summary
462
+
463
+ @HtmlView.extension_method('_html_tree_view_summary')
464
+ def summary(
465
+ self,
466
+ value: Any,
467
+ *,
468
+ name: Optional[str] = None,
469
+ parent: Any = None,
470
+ root_path: Optional[KeyPath] = None,
471
+ css_class: Optional[Sequence[str]] = None,
472
+ title: Union[str, Html, None] = None,
473
+ enable_summary: Optional[bool] = HtmlView.PresetArgValue(None),
474
+ enable_summary_tooltip: bool = HtmlView.PresetArgValue(True),
475
+ max_summary_len_for_str: int = HtmlView.PresetArgValue(40),
476
+ **kwargs
477
+ ) -> Optional[Html]:
478
+ """Renders a summary for the value.
479
+
480
+ Args:
481
+ value: The value to render.
482
+ name: The name of the value.
483
+ parent: The parent of the value.
484
+ root_path: The root path of the value.
485
+ css_class: The CSS classes to add to the root element
486
+ title: The title of the summary.
487
+ enable_summary: Whether to enable the summary. If None, summary will
488
+ be enabled for complex types or when string exceeds
489
+ `max_summary_len_for_str`.
490
+ enable_summary_tooltip: Whether to enable the tooltip for the summary.
491
+ max_summary_len_for_str: The maximum length of the string to display.
492
+ **kwargs: Additional keyword arguments passed from `pg.to_html`.
493
+
494
+ Returns:
495
+ An optional HTML object representing the summary of the value. If None,
496
+ the summary will be hidden.
497
+ """
498
+ if not self.needs_summary(
499
+ value,
500
+ name=name,
501
+ parent=parent,
502
+ title=title,
503
+ max_summary_len_for_str=max_summary_len_for_str,
504
+ enable_summary=enable_summary,
505
+ ):
506
+ return None
507
+
508
+ def make_title(value: Any):
509
+ if isinstance(value, str):
510
+ if len(value) > max_summary_len_for_str:
511
+ value = value[:max_summary_len_for_str] + '...'
512
+ return Html.escape(repr(value))
513
+ return f'{type(value).__name__}(...)'
514
+
515
+ return Html.element(
516
+ 'summary',
517
+ [
518
+ # Summary name.
519
+ lambda: Html.element( # pylint: disable=g-long-ternary
520
+ 'div',
521
+ [
522
+ name,
523
+ ],
524
+ css_class=['summary_name']
525
+ ) if name else None,
526
+
527
+ # Summary title
528
+ Html.element(
529
+ 'div',
530
+ [
531
+ title or make_title(value),
532
+ ],
533
+ css_class=['summary_title']
534
+ ),
535
+
536
+ # Tooltip.
537
+ lambda: self.tooltip( # pylint: disable=g-long-ternary
538
+ value,
539
+ name=name,
540
+ parent=parent,
541
+ root_path=root_path,
542
+ **kwargs,
543
+ ) if enable_summary_tooltip else None,
544
+ ],
545
+ css_class=css_class,
546
+ ).add_style(
547
+ """
548
+ /* Summary styles. */
549
+ details.pyglove summary {
550
+ font-weight: bold;
551
+ margin: -0.5em -0.5em 0;
552
+ padding: 0.5em;
553
+ }
554
+ .summary_name {
555
+ display: inline;
556
+ padding: 0 5px;
557
+ }
558
+ .summary_title {
559
+ display: inline;
560
+ }
561
+ .summary_name + div.summary_title {
562
+ display: inline;
563
+ color: #aaa;
564
+ }
565
+ .summary_title:hover + span.tooltip {
566
+ visibility: visible;
567
+ }
568
+ /* Type-specific styles. */
569
+ .pyglove.str .summary_title {
570
+ color: darkred;
571
+ font-style: italic;
572
+ }
573
+ """
574
+ )
575
+
576
+ # NOTE(daiyip)" `object_key`` does not have a corresponding extension
577
+ # method in `HtmlTreeView.Extension`, because the rendering of the key is not
578
+ # delegated to `HtmlTreeView.Extension`.
579
+ def object_key(
580
+ self,
581
+ key: Union[str, int],
582
+ *,
583
+ name: Optional[str] = None,
584
+ parent: Any,
585
+ root_path: Optional[KeyPath] = None,
586
+ css_class: Optional[Sequence[str]] = None,
587
+ key_color: Optional[str] = None,
588
+ enable_tooltip: bool = HtmlView.PresetArgValue(True),
589
+ **kwargs
590
+ ) -> Html:
591
+ """Renders the key for the value.
592
+
593
+ Args:
594
+ key: The key of the value.
595
+ name: The name of the value.
596
+ parent: The parent value of the key.
597
+ root_path: The root path of the value.
598
+ css_class: The CSS classes to add to the root element.
599
+ key_color: The color of the key.
600
+ enable_tooltip: Whether to enable the tooltip.
601
+ **kwargs: Additional keyword arguments passed from `pg.to_html`.
602
+
603
+ Returns:
604
+ The rendered HTML as the key of the value.
605
+ """
606
+ return (
607
+ # Key span.
608
+ Html.element(
609
+ 'span',
610
+ [
611
+ str(key),
612
+ ],
613
+ css_class=['object_key'] + (css_class or []),
614
+ style=dict(
615
+ color=key_color,
616
+ )
617
+ ) + (
618
+ # Tooltip if enabled.
619
+ lambda: self.tooltip( # pylint: disable=g-long-ternary
620
+ value=root_path,
621
+ root_path=root_path,
622
+ name=name,
623
+ parent=parent,
624
+ **kwargs
625
+ ) if enable_tooltip else None
626
+ )
627
+ ).add_style(
628
+ """
629
+ /* Object key styles. */
630
+ .object_key {
631
+ margin-right: 0.25em;
632
+ }
633
+ .object_key:hover + .tooltip {
634
+ visibility: visible;
635
+ background-color: darkblue;
636
+ }
637
+ .complex_value .object_key{
638
+ color: gray;
639
+ border: 1px solid lightgray;
640
+ background-color: ButtonFace;
641
+ border-radius: 0.2em;
642
+ padding: 0.3em;
643
+ }
644
+ .complex_value.list .object_key{
645
+ border: 0;
646
+ color: lightgray;
647
+ background-color: transparent;
648
+ border-radius: 0;
649
+ padding: 0;
650
+ }
651
+ .complex_value.list .object_key::before{
652
+ content: '[';
653
+ }
654
+ .complex_value.list .object_key::after{
655
+ content: ']';
656
+ }
657
+ """
658
+ )
659
+
660
+ @HtmlView.extension_method('_html_tree_view_content')
661
+ def content(
662
+ self,
663
+ value: Any,
664
+ *,
665
+ name: Optional[str] = None,
666
+ parent: Any = None,
667
+ root_path: Optional[KeyPath] = None,
668
+ css_class: Optional[Sequence[str]] = None,
669
+ special_keys: Optional[Sequence[Union[int, str]]] = None,
670
+ include_keys: Optional[Iterable[Union[int, str]]] = None,
671
+ exclude_keys: Optional[Iterable[Union[int, str]]] = None,
672
+ filter: Optional[base.NodeFilter] = HtmlView.PresetArgValue(None), # pylint: disable=redefined-builtin
673
+ highlight: Optional[base.NodeFilter] = HtmlView.PresetArgValue(None),
674
+ lowlight: Optional[base.NodeFilter] = HtmlView.PresetArgValue(None),
675
+ max_summary_len_for_str: int = HtmlView.PresetArgValue(40),
676
+ enable_summary_tooltip: bool = HtmlView.PresetArgValue(True),
677
+ enable_key_tooltip: bool = HtmlView.PresetArgValue(True),
678
+ collapse_level: Optional[int] = HtmlView.PresetArgValue(1),
679
+ uncollapse: Union[
680
+ Iterable[Union[KeyPath, str]], base.NodeFilter, None
681
+ ] = HtmlView.PresetArgValue(None),
682
+ **kwargs
683
+ ) -> Html:
684
+ """Renders the main content for the value.
685
+
686
+ Args:
687
+ value: The value to render.
688
+ name: The name of the value.
689
+ parent: The parent of the value.
690
+ root_path: The root path of the value.
691
+ css_class: Additional CSS classes for root element.
692
+ special_keys: The special keys to display (at the immediate child level).
693
+ include_keys: The keys to include (at the immediate child level).
694
+ exclude_keys: The keys to exclude (at the immediate child level).
695
+ filter: A function with signature (path, value, parent) -> include
696
+ to determine whether to include a field (at all levels).
697
+ highlight: A function with signature (path, value, parent) -> bool
698
+ to determine whether to highlight a field (at all levels).
699
+ lowlight: A function with signature (path, value, parent) -> bool
700
+ to determine whether to lowlight a field (at all levels).
701
+ max_summary_len_for_str: The maximum length of the string to display.
702
+ enable_summary_tooltip: Whether to enable the summary tooltip.
703
+ enable_key_tooltip: Whether to enable the key tooltip.
704
+ collapse_level: The level of the tree to collapse.
705
+ uncollapse: The keys to uncollapse.
706
+ **kwargs: Additional keyword arguments passed from `pg.to_html`.
707
+
708
+ Returns:
709
+ The rendered HTML as the main content of the value.
710
+ """
711
+ if isinstance(value, (tuple, list)):
712
+ items = {i: v for i, v in enumerate(value)}
713
+ elif isinstance(value, dict):
714
+ items = value
715
+ else:
716
+ return self.simple_value(
717
+ value, name=name, parent=parent, root_path=root_path,
718
+ css_class=css_class, max_summary_len_for_str=max_summary_len_for_str
719
+ )
720
+ return self.complex_value(
721
+ items,
722
+ name=name,
723
+ parent=value,
724
+ root_path=root_path or KeyPath(),
725
+ css_class=css_class,
726
+ special_keys=special_keys,
727
+ include_keys=include_keys,
728
+ exclude_keys=exclude_keys,
729
+ filter=filter,
730
+ highlight=highlight,
731
+ lowlight=lowlight,
732
+ max_summary_len_for_str=max_summary_len_for_str,
733
+ enable_summary_tooltip=enable_summary_tooltip,
734
+ enable_key_tooltip=enable_key_tooltip,
735
+ collapse_level=collapse_level,
736
+ uncollapse=uncollapse,
737
+ **kwargs,
738
+ )
739
+
740
+ def simple_value(
741
+ self,
742
+ value: Any,
743
+ *,
744
+ name: Optional[str] = None,
745
+ parent: Any = None,
746
+ root_path: Optional[KeyPath] = None,
747
+ css_class: Optional[Sequence[str]] = None,
748
+ max_summary_len_for_str: int = HtmlView.PresetArgValue(40),
749
+ ) -> Html:
750
+ """Renders a simple value.
751
+
752
+ Args:
753
+ value: The value to render.
754
+ name: The name of the value.
755
+ parent: The parent of the value.
756
+ root_path: The root path of the value.
757
+ css_class: Additional CSS classes for root span.
758
+ max_summary_len_for_str: The maximum length of the string to display.
759
+
760
+ Returns:
761
+ The rendered HTML as the simple value.
762
+ """
763
+ del name, parent, root_path
764
+ def value_repr() -> str:
765
+ if isinstance(value, str):
766
+ if len(value) < max_summary_len_for_str:
767
+ return repr(value)
768
+ else:
769
+ return value
770
+ return object_utils.format(
771
+ value,
772
+ compact=False, verbose=False, hide_default_values=True,
773
+ python_format=True, use_inferred=True,
774
+ max_bytes_len=64,
775
+ )
776
+ return Html.element(
777
+ 'span',
778
+ [
779
+ Html.escape(value_repr),
780
+ ],
781
+ css_class=['simple_value', self.css_class_name(value)] + (
782
+ css_class or []
783
+ ),
784
+ ).add_style(
785
+ """
786
+ /* Simple value styles. */
787
+ .simple_value {
788
+ color: blue;
789
+ display: inline-block;
790
+ white-space: pre-wrap;
791
+ padding: 0.2em;
792
+ margin-top: 0.15em;
793
+ }
794
+ .simple_value.str {
795
+ color: darkred;
796
+ font-style: italic;
797
+ }
798
+ .simple_value.int, .simple_value.float {
799
+ color: darkblue;
800
+ }
801
+ """
802
+ )
803
+
804
+ def complex_value(
805
+ self,
806
+ kv: Dict[Union[int, str], Any],
807
+ *,
808
+ name: Optional[str] = None,
809
+ parent: Any = None,
810
+ root_path: Optional[KeyPath] = None,
811
+ css_class: Optional[Sequence[str]] = None,
812
+ special_keys: Optional[Sequence[Union[int, str]]] = None,
813
+ include_keys: Optional[Iterable[Union[int, str]]] = None,
814
+ exclude_keys: Optional[Iterable[Union[int, str]]] = None,
815
+ filter: Optional[base.NodeFilter] = HtmlView.PresetArgValue(None), # pylint: disable=redefined-builtin
816
+ highlight: Optional[base.NodeFilter] = HtmlView.PresetArgValue(None),
817
+ lowlight: Optional[base.NodeFilter] = HtmlView.PresetArgValue(None),
818
+ max_summary_len_for_str: int = HtmlView.PresetArgValue(40),
819
+ enable_summary_tooltip: bool = HtmlView.PresetArgValue(True),
820
+ enable_key_tooltip: bool = HtmlView.PresetArgValue(True),
821
+ collapse_level: Optional[int] = HtmlView.PresetArgValue(1),
822
+ uncollapse: Union[
823
+ Iterable[Union[KeyPath, str]], base.NodeFilter, None
824
+ ] = HtmlView.PresetArgValue(None),
825
+ **kwargs,
826
+ ) -> Html:
827
+ """Renders a list of key-value pairs.
828
+
829
+ Args:
830
+ kv: The key-value pairs to render.
831
+ name: The name of the value.
832
+ parent: The parent value of the key-value pairs.
833
+ root_path: The root path of the value.
834
+ css_class: Additional CSS classes for root div.
835
+ special_keys: The special keys to display (at the immediate child level).
836
+ include_keys: The keys to include (at the immediate child level).
837
+ exclude_keys: The keys to exclude (at the immediate child level).
838
+ filter: A function with signature (path, value, parent) -> include
839
+ to determine whether to include.
840
+ highlight: A function with signature (path, value, parent) -> bool
841
+ to determine whether to highlight.
842
+ lowlight: A function with signature (path, value, parent) -> bool
843
+ to determine whether to lowlight.
844
+ max_summary_len_for_str: The maximum length of the string to display.
845
+ enable_summary_tooltip: Whether to enable the summary tooltip.
846
+ enable_key_tooltip: Whether to enable the key tooltip.
847
+ collapse_level: The level of the tree to collapse.
848
+ uncollapse: The keys to uncollapse.
849
+ **kwargs: Additional keyword arguments passed from `pg.to_html`.
850
+
851
+ Returns:
852
+ The rendered HTML as the key-value pairs.
853
+ """
854
+ del name
855
+ special_keys = special_keys or []
856
+ include_keys = set(include_keys or [])
857
+ exclude_keys = set(exclude_keys or [])
858
+ uncollapse = self.normalize_uncollapse(uncollapse)
859
+
860
+ s = Html()
861
+ if kv:
862
+ include_keys = include_keys or set(kv.keys())
863
+ if filter is not None:
864
+ include_keys -= set(
865
+ k for k, v in kv.items()
866
+ if not filter(root_path + k, v, parent)
867
+ )
868
+ if exclude_keys:
869
+ include_keys -= exclude_keys
870
+
871
+ if special_keys:
872
+ for k in special_keys:
873
+ if k in include_keys and k in kv:
874
+ child_path = root_path + k
875
+ v = kv[k]
876
+
877
+ child_css_class = [
878
+ 'special_value',
879
+ (
880
+ 'highlight' if highlight
881
+ and highlight(child_path, v, parent) else None
882
+ ),
883
+ (
884
+ 'lowlight' if lowlight
885
+ and lowlight(child_path, v, parent) else None
886
+ )
887
+ ]
888
+ s.write(
889
+ self.render(
890
+ value=v,
891
+ name=k,
892
+ parent=v,
893
+ root_path=child_path,
894
+ css_class=child_css_class,
895
+ filter=filter,
896
+ special_keys=None,
897
+ include_keys=None,
898
+ exclude_keys=None,
899
+ highlight=highlight,
900
+ lowlight=lowlight,
901
+ max_summary_len_for_str=max_summary_len_for_str,
902
+ enable_summary_tooltip=enable_summary_tooltip,
903
+ enable_key_tooltip=enable_key_tooltip,
904
+ collapse_level=collapse_level,
905
+ uncollapse=uncollapse,
906
+ **kwargs
907
+ )
908
+ )
909
+ include_keys.remove(k)
910
+
911
+ if include_keys:
912
+ s.write('<table>')
913
+ for k, v in kv.items():
914
+ if k not in include_keys:
915
+ continue
916
+ child_path = root_path + k
917
+ key = self.object_key(
918
+ key=k, parent=v, root_path=child_path,
919
+ enable_tooltip=enable_key_tooltip,
920
+ )
921
+ child_css_class = [
922
+ (
923
+ 'highlight' if highlight and highlight(child_path, v, parent)
924
+ else None
925
+ ),
926
+ (
927
+ 'lowlight' if lowlight and lowlight(child_path, v, parent)
928
+ else None
929
+ )
930
+ ]
931
+ value = self.render(
932
+ value=v,
933
+ name=None,
934
+ parent=v,
935
+ root_path=child_path,
936
+ css_class=child_css_class,
937
+ special_keys=None,
938
+ include_keys=None,
939
+ exclude_keys=None,
940
+ filter=filter,
941
+ highlight=highlight,
942
+ lowlight=lowlight,
943
+ max_summary_len_for_str=max_summary_len_for_str,
944
+ enable_summary_tooltip=enable_summary_tooltip,
945
+ enable_key_tooltip=enable_key_tooltip,
946
+ collapse_level=collapse_level,
947
+ uncollapse=uncollapse,
948
+ **kwargs,
949
+ )
950
+ s.write(
951
+ Html.element(
952
+ 'tr',
953
+ [
954
+ '<td>', key, '</td>',
955
+ '<td>', value, '</td>',
956
+ ],
957
+ )
958
+ )
959
+ s.write('</table>')
960
+ else:
961
+ s.write(Html.element('span', css_class=['empty_container']))
962
+ return Html.element(
963
+ 'div',
964
+ [s],
965
+ css_class=['complex_value', self.css_class_name(parent)] + (
966
+ css_class or []
967
+ ),
968
+ ).add_style(
969
+ """
970
+ /* Complex value styles. */
971
+ span.empty_container::before {
972
+ content: '(empty)';
973
+ font-style: italic;
974
+ margin-left: 0.5em;
975
+ color: #aaa;
976
+ }
977
+ """
978
+ )
979
+
980
+ @HtmlView.extension_method('_html_tree_view_tooltip')
981
+ def tooltip(
982
+ self,
983
+ value: Any,
984
+ *,
985
+ name: Optional[str] = None,
986
+ parent: Any = None,
987
+ root_path: Optional[KeyPath] = None,
988
+ css_class: Optional[Sequence[str]] = None,
989
+ content: Union[str, Html, None] = HtmlView.PresetArgValue(None),
990
+ **kwargs
991
+ ) -> Html:
992
+ """Renders a tooltip for the value.
993
+
994
+ Args:
995
+ value: The value to render.
996
+ name: The name of the value.
997
+ parent: The parent value of the key-value pairs.
998
+ root_path: The root path of the value.
999
+ css_class: Additional CSS classes for the tooltip span.
1000
+ content: The content of the tooltip. If None, the formatted value will be
1001
+ used as the content.
1002
+ **kwargs: Additional keyword arguments passed from `pg.to_html`.
1003
+
1004
+ Returns:
1005
+ The rendered HTML as the tooltip of the value.
1006
+ """
1007
+ del name, parent
1008
+ if content is None:
1009
+ content = Html.escape(
1010
+ object_utils.format(
1011
+ value,
1012
+ root_path=root_path,
1013
+ compact=False,
1014
+ verbose=False,
1015
+ python_format=True,
1016
+ max_bytes_len=64,
1017
+ max_str_len=256,
1018
+ **kwargs
1019
+ )
1020
+ )
1021
+ return Html.element(
1022
+ 'span',
1023
+ [content],
1024
+ css_class=['tooltip'] + (css_class or []),
1025
+ ).add_style(
1026
+ """
1027
+ /* Tooltip styles. */
1028
+ span.tooltip {
1029
+ visibility: hidden;
1030
+ white-space: pre-wrap;
1031
+ font-weight: normal;
1032
+ background-color: #484848;
1033
+ color: #fff;
1034
+ padding: 10px;
1035
+ border-radius: 6px;
1036
+ position: absolute;
1037
+ z-index: 1;
1038
+ }
1039
+ """
1040
+ )
1041
+
1042
+ @staticmethod
1043
+ def css_class_name(value: Any) -> str:
1044
+ """Returns the CSS class name for the value."""
1045
+ value = value if inspect.isclass(value) else type(value)
1046
+ class_name = value.__name__
1047
+ return _REGEX_CAMEL_TO_SNAKE.sub('-', class_name).lower()
1048
+
1049
+
1050
+ _REGEX_CAMEL_TO_SNAKE = re.compile(r'(?<!^)(?=[A-Z])')
1051
+
1052
+ # pytype: enable=annotation-type-mismatch