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,547 @@
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 and the base HtmlView."""
15
+
16
+ import abc
17
+ import functools
18
+ import html as html_lib
19
+ import inspect
20
+ import typing
21
+ from typing import Any, Callable, Dict, Iterable, List, Optional, Sequence, Union
22
+
23
+ from pyglove.core import typing as pg_typing
24
+ from pyglove.core import utils
25
+ from pyglove.core.views import base
26
+
27
+ NestableStr = Union[
28
+ str,
29
+ Sequence[Union[str, None, Sequence[Optional[str]]]],
30
+ None,
31
+ ]
32
+
33
+ NodeFilter = base.NodeFilter
34
+ NodeColor = Callable[
35
+ [
36
+ utils.KeyPath, # The path to the value.
37
+ Any, # Current value.
38
+ Any, # Parent value
39
+ ],
40
+ Optional[str], # The color of the node.
41
+ ]
42
+
43
+
44
+ class Html(base.Content):
45
+ """HTML with consolidated CSS and Scripts.
46
+
47
+ Example::
48
+
49
+ .. code-block:: python
50
+
51
+ def foo() -> pg.Html:
52
+ s = pg.Html()
53
+ s.add_style('div.foo { color: red; }')
54
+ s.add_script('function myFoo() { console.log("foo");}')
55
+ s.write('<div class="foo">Foo</div>')
56
+ return s
57
+
58
+ def bar() -> pg.Html:
59
+ s = pg.Html()
60
+ s.add_style('div.bar { color: green; }')
61
+ s.add_script('function myBar() { console.log("bar");}')
62
+ s.write('<div class="bar">')
63
+ s.write(foo())
64
+ s.write('</div>')
65
+ return s
66
+
67
+ html = bar.html_str()
68
+
69
+ This will output::
70
+
71
+ <html>
72
+ <head>
73
+ <style>
74
+ div.bar { color: green; }
75
+ div.foo { color: red; }
76
+ </style>
77
+ <script>
78
+ function myBar() { console.log("bar");}
79
+ function myFoo() { console.log("foo");}
80
+ </script>
81
+ </head>
82
+ <body><div class="bar"><div class="foo">Foo</div></div></body></html>
83
+
84
+ """
85
+
86
+ WritableTypes = Union[ # pylint: disable=invalid-name
87
+ str,
88
+ 'Html',
89
+ 'HtmlConvertible',
90
+ Callable[[], Union[str, 'Html', 'HtmlConvertible', None]],
91
+ None
92
+ ]
93
+
94
+ class Scripts(base.Content.SharedParts):
95
+ """Shared script definitions in the HEAD section."""
96
+
97
+ @functools.cached_property
98
+ def content(self) -> str:
99
+ if self.parts:
100
+ code = '\n'.join([inspect.cleandoc(v) for v in self.parts.keys()])
101
+ return f'<script>\n{code}\n</script>'
102
+ return ''
103
+
104
+ class ScriptFiles(base.Content.SharedParts):
105
+ """Shared script files to link to in the HEAD section."""
106
+
107
+ @functools.cached_property
108
+ def content(self) -> str:
109
+ return '\n'.join(
110
+ [f'<script src="{url}"></script>' for url in self.parts.keys()]
111
+ )
112
+
113
+ class Styles(base.Content.SharedParts):
114
+ """Shared style definitions in the HEAD section."""
115
+
116
+ @functools.cached_property
117
+ def content(self) -> str:
118
+ if self.parts:
119
+ styles = '\n'.join([inspect.cleandoc(v) for v in self.parts.keys()])
120
+ return f'<style>\n{styles}\n</style>'
121
+ return ''
122
+
123
+ class StyleFiles(base.Content.SharedParts):
124
+ """Shared style files to link to in the HEAD section."""
125
+
126
+ @functools.cached_property
127
+ def content(self) -> str:
128
+ return '\n'.join(
129
+ [
130
+ f'<link rel="stylesheet" href="{url}">'
131
+ for url in self.parts.keys()
132
+ ]
133
+ )
134
+
135
+ def __init__( # pylint: disable=useless-super-delegation
136
+ self,
137
+ *content: WritableTypes,
138
+ style_files: Optional[Iterable[str]] = None,
139
+ styles: Optional[Iterable[str]] = None,
140
+ script_files: Optional[Iterable[str]] = None,
141
+ scripts: Optional[Iterable[str]] = None,
142
+ ) -> None:
143
+ """Constructor.
144
+
145
+ Args:
146
+ *content: One or multiple body part (str, Html, lambda, None) of the HTML.
147
+ style_files: URLs for external styles to include.
148
+ styles: CSS styles to include.
149
+ script_files: URLs for external scripts to include.
150
+ scripts: JavaScript scripts to include.
151
+ """
152
+ super().__init__(
153
+ *content,
154
+ style_files=Html.StyleFiles(*(style_files or [])),
155
+ styles=Html.Styles(*(styles or [])),
156
+ script_files=Html.ScriptFiles(*(script_files or [])),
157
+ scripts=Html.Scripts(*(scripts or [])),
158
+ )
159
+
160
+ def _repr_html_(self) -> str:
161
+ return self.to_str()
162
+
163
+ @property
164
+ def styles(self) -> 'Html.Styles':
165
+ """Returns the styles to include in the HTML."""
166
+ return self._shared_parts['styles']
167
+
168
+ @property
169
+ def style_files(self) -> 'Html.StyleFiles':
170
+ """Returns the style files to link to."""
171
+ return self._shared_parts['style_files']
172
+
173
+ @property
174
+ def scripts(self) -> 'Html.Scripts':
175
+ """Returns the scripts to include in the HTML."""
176
+ return self._shared_parts['scripts']
177
+
178
+ @property
179
+ def script_files(self) -> 'Html.ScriptFiles':
180
+ """Returns the script files to link to."""
181
+ return self._shared_parts['script_files']
182
+
183
+ @property
184
+ def head_section(self) -> str:
185
+ """Returns the head section."""
186
+ return '\n'.join(
187
+ [
188
+ v for v in [
189
+ '<head>', self.style_section, self.script_section, '</head>']
190
+ if v
191
+ ]
192
+ )
193
+
194
+ @property
195
+ def style_section(self) -> str:
196
+ """Returns the style section."""
197
+ return '\n'.join(
198
+ [
199
+ v for v in [self.style_files.content, self.styles.content]
200
+ if v
201
+ ]
202
+ )
203
+
204
+ @property
205
+ def script_section(self) -> str:
206
+ """Returns the script section."""
207
+ return '\n'.join(
208
+ [
209
+ v for v in [self.script_files.content, self.scripts.content]
210
+ if v
211
+ ]
212
+ )
213
+
214
+ @property
215
+ def body_section(self) -> str:
216
+ """Returns the body section."""
217
+ return f'<body>\n{self.content}\n</body>'
218
+
219
+ #
220
+ # Methods for adding shared parts and writing HTML content.
221
+ #
222
+
223
+ def add_style(self, *css: str) -> 'Html':
224
+ """Adds CSS styles to the HTML."""
225
+ self.styles.add(*css)
226
+ return self
227
+
228
+ def add_script(self, *js: str) -> 'Html':
229
+ """Adds JavaScript scripts to the HTML."""
230
+ self.scripts.add(*js)
231
+ return self
232
+
233
+ def add_style_file(self, *url: str) -> 'Html':
234
+ """Adds a style file to the HTML."""
235
+ self.style_files.add(*url)
236
+ return self
237
+
238
+ def add_script_file(self, *url: str) -> 'Html':
239
+ """Adds a script file to the HTML."""
240
+ self.script_files.add(*url)
241
+ return self
242
+
243
+ def to_str(
244
+ self,
245
+ *,
246
+ content_only: bool = False,
247
+ **kwargs
248
+ ) -> str:
249
+ """Returns the HTML str.
250
+
251
+ Args:
252
+ content_only: If True, only the content will be returned.
253
+ **kwargs: Additional keyword arguments passed from the user that
254
+ will be ignored.
255
+
256
+ Returns:
257
+ The generated HTML str.
258
+ """
259
+ if content_only:
260
+ return self.content
261
+ return '\n'.join(
262
+ [
263
+ v for v in ['<html>', self.head_section,
264
+ self.body_section, '</html>'] if v
265
+ ]
266
+ )
267
+
268
+ @classmethod
269
+ def from_value(
270
+ cls,
271
+ value: WritableTypes,
272
+ copy: bool = False
273
+ ) -> Union['Html', None]:
274
+ return typing.cast(
275
+ Html, super().from_value(value, copy=copy)
276
+ )
277
+
278
+ @classmethod
279
+ def _to_content(
280
+ cls, value: WritableTypes
281
+ ) -> Union['Html', str, None]:
282
+ if callable(value):
283
+ value = value()
284
+ if value is None:
285
+ return None
286
+ elif isinstance(value, HtmlConvertible):
287
+ value = value.to_html()
288
+ elif not isinstance(value, (str, cls)):
289
+ raise TypeError(f'Not a writable value for `{cls.__name__}`: {value!r}')
290
+ return value
291
+
292
+ #
293
+ # Helper methods for creating templated Html objects.
294
+ #
295
+
296
+ @classmethod
297
+ def element(
298
+ cls,
299
+ tag: str,
300
+ inner_html: Optional[List[WritableTypes]] = None,
301
+ *,
302
+ options: Union[str, Iterable[str], None] = None,
303
+ css_classes: NestableStr = None,
304
+ styles: Union[str, Dict[str, Any], None] = None,
305
+ **properties
306
+ ) -> 'Html':
307
+ """Creates an HTML element.
308
+
309
+ Args:
310
+ tag: The HTML tag name.
311
+ inner_html: The inner HTML of the element.
312
+ options: Positional options that will be added to the element. E.g. 'open'
313
+ for `<details open>`.
314
+ css_classes: The CSS class name or a list of CSS class names.
315
+ styles: A single CSS style string or a dictionary of CSS properties.
316
+ **properties: Keyword arguments for HTML properties. For properties with
317
+ underscore in the name, the underscore will be replaced by dash in the
318
+ generated HTML. E.g. `background_color` will be converted to
319
+ `background-color`.
320
+
321
+ Returns:
322
+ The opening tag of an HTML element.
323
+ """
324
+ s = cls()
325
+
326
+ # Write the open tag.
327
+ css_classes = cls.concate(css_classes)
328
+ options = cls.concate(options)
329
+ styles = cls.style_str(styles)
330
+ s.write(
331
+ f'<{tag}',
332
+ f' {options}' if options else None,
333
+ f' class="{css_classes}"' if css_classes else None,
334
+ f' style="{styles}"' if styles else None,
335
+ )
336
+ for k, v in properties.items():
337
+ if v is not None:
338
+ s.write(f' {k.replace("_", "-")}="{v}"')
339
+ s.write('>')
340
+
341
+ # Write the inner HTML.
342
+ if inner_html:
343
+ for child in inner_html:
344
+ s.write(child)
345
+
346
+ # Write the closing tag.
347
+ s.write(f'</{tag}>')
348
+ return s
349
+
350
+ @classmethod
351
+ def escape(
352
+ cls,
353
+ s: WritableTypes,
354
+ javascript_str: bool = False
355
+ ) -> Any:
356
+ """Escapes an HTML writable object."""
357
+ if s is None:
358
+ return None
359
+
360
+ if callable(s):
361
+ s = s()
362
+ if isinstance(s, HtmlConvertible):
363
+ s = s.to_html()
364
+
365
+ def _escape(s: str) -> str:
366
+ if javascript_str:
367
+ return (
368
+ s.replace('\\', '\\\\')
369
+ .replace('"', '\\"')
370
+ .replace('\r', '\\r')
371
+ .replace('\n', '\\n')
372
+ .replace('\t', '\\t')
373
+ )
374
+ return html_lib.escape(s)
375
+
376
+ if isinstance(s, str):
377
+ return _escape(s)
378
+ else:
379
+ assert isinstance(s, Html), s
380
+ return Html(_escape(s.content)).write(
381
+ s, shared_parts_only=True
382
+ )
383
+
384
+ @classmethod
385
+ def concate(
386
+ cls, nestable_str: NestableStr, separator: str = ' ', dedup: bool = True
387
+ ) -> Optional[str]:
388
+ """Concates the string nodes in a nestable object."""
389
+ flattened = utils.flatten(nestable_str)
390
+ if isinstance(flattened, str):
391
+ return flattened
392
+ elif isinstance(flattened, dict):
393
+ str_items = [v for v in flattened.values() if isinstance(v, str)]
394
+ if dedup:
395
+ str_items = list(dict.fromkeys(str_items).keys())
396
+ if str_items:
397
+ return separator.join(str_items)
398
+ return None
399
+
400
+ @classmethod
401
+ def style_str(
402
+ cls, style: Union[str, Dict[str, Any], None],
403
+ ) -> Optional[str]:
404
+ """Gets a string representing an inline CSS style.
405
+
406
+ Args:
407
+ style: A single CSS style string, or a dictionary for CSS properties.
408
+ When dictionary form is used, underscore in the key name will be
409
+ replaced by dash in the generated CSS style string.
410
+ For example, `background_color` will be converted to `background-color`.
411
+
412
+ Returns:
413
+ A CSS style string or None if no CSS property is provided.
414
+ """
415
+ if not style:
416
+ return None
417
+ if isinstance(style, str):
418
+ return style
419
+ else:
420
+ assert isinstance(style, dict), style
421
+ return ''.join(
422
+ [
423
+ f'{k.replace("_", "-")}:{v};'
424
+ for k, v in style.items() if v is not None
425
+ ]
426
+ ) or None
427
+
428
+
429
+ # Allow automatic conversion from str to Html.
430
+ pg_typing.register_converter(str, Html, convert_fn=Html.from_value)
431
+
432
+
433
+ class HtmlConvertible:
434
+ """Base class for HTML convertible objects."""
435
+
436
+ def to_html(self, **kwargs) -> Html:
437
+ """Returns the HTML representation of the object."""
438
+ return to_html(self, **kwargs)
439
+
440
+ def to_html_str(self, *, content_only: bool = False, **kwargs) -> str:
441
+ """Returns the HTML str of the object."""
442
+ return self.to_html(**kwargs).to_str(content_only=content_only)
443
+
444
+ def _repr_html_(self) -> str:
445
+ return self.to_html_str()
446
+
447
+
448
+ class HtmlView(base.View):
449
+ """Base class for HTML views."""
450
+
451
+ class Extension(base.View.Extension, HtmlConvertible):
452
+ """Base class for HtmlView extensions."""
453
+
454
+ def render(
455
+ self,
456
+ value: Any,
457
+ *,
458
+ name: Optional[str] = None,
459
+ root_path: Optional[utils.KeyPath] = None,
460
+ **kwargs,
461
+ ) -> Html:
462
+ """Renders the input value into an HTML object."""
463
+ # For customized HtmlConvertible objects, call their `to_html()` method.
464
+ if (isinstance(value, HtmlConvertible)
465
+ and not isinstance(value, self.__class__.Extension)
466
+ and value.__class__.to_html is not HtmlConvertible.to_html):
467
+ return value.to_html(name=name, root_path=root_path, **kwargs)
468
+ return self._render(value, name=name, root_path=root_path, **kwargs)
469
+
470
+ @abc.abstractmethod
471
+ def _render(
472
+ self,
473
+ value: Any,
474
+ *,
475
+ name: Optional[str] = None,
476
+ root_path: Optional[utils.KeyPath] = None,
477
+ **kwargs,
478
+ ) -> Html:
479
+ """View's implementation of HTML rendering."""
480
+
481
+
482
+ def to_html(
483
+ value: Any,
484
+ *,
485
+ name: Optional[str] = None,
486
+ root_path: Optional[utils.KeyPath] = None,
487
+ view_id: str = 'html-tree-view',
488
+ **kwargs,
489
+ ) -> Html:
490
+ """Returns the HTML representation of a value.
491
+
492
+ Args:
493
+ value: The value to render.
494
+ name: The name of the value.
495
+ root_path: The root path of the value.
496
+ view_id: The ID of the view to render the value.
497
+ See `pg.views.HtmlView.dir()` for all available HTML view IDs.
498
+ **kwargs: Additional keyword arguments passed from `pg.to_html`, wich
499
+ will be passed to the `HtmlView.render_xxx()` (thus
500
+ `Extension._html_xxx()`) methods.
501
+
502
+ Returns:
503
+ The rendered HTML.
504
+ """
505
+ content = base.view(
506
+ value,
507
+ name=name,
508
+ root_path=root_path,
509
+ view_id=view_id,
510
+ **kwargs
511
+ )
512
+ assert isinstance(content, Html), content
513
+ return content
514
+
515
+
516
+ def to_html_str(
517
+ value: Any,
518
+ *,
519
+ name: Optional[str] = None,
520
+ root_path: Optional[utils.KeyPath] = None,
521
+ view_id: str = 'html-tree-view',
522
+ content_only: bool = False,
523
+ **kwargs,
524
+ ) -> str:
525
+ """Returns a HTML str for a value.
526
+
527
+ Args:
528
+ value: The value to render.
529
+ name: The name of the value.
530
+ root_path: The root path of the value.
531
+ view_id: The ID of the view to render the value.
532
+ See `pg.views.HtmlView.dir()` for all available HTML view IDs.
533
+ content_only: If True, only the content will be returned.
534
+ **kwargs: Additional keyword arguments passed from `pg.to_html`, wich
535
+ will be passed to the `HtmlView.render_xxx()` (thus
536
+ `Extension._html_xxx()`) methods.
537
+
538
+ Returns:
539
+ The rendered HTML str.
540
+ """
541
+ return to_html(
542
+ value,
543
+ name=name,
544
+ root_path=root_path,
545
+ view_id=view_id,
546
+ **kwargs
547
+ ).to_str(content_only=content_only)