pyglove 0.4.5.dev202412100720__py3-none-any.whl → 0.4.5.dev202501250807__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 (118) hide show
  1. pyglove/core/__init__.py +40 -21
  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 +312 -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 +53 -38
  14. pyglove/core/geno/base_test.py +2 -4
  15. pyglove/core/geno/categorical.py +36 -27
  16. pyglove/core/geno/custom.py +18 -15
  17. pyglove/core/geno/numerical.py +19 -16
  18. pyglove/core/geno/space.py +3 -4
  19. pyglove/core/hyper/base.py +6 -6
  20. pyglove/core/hyper/categorical.py +91 -52
  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 +3 -5
  25. pyglove/core/hyper/dynamic_evaluation.py +3 -4
  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/logging_test.py +0 -2
  31. pyglove/core/patching/object_factory.py +4 -4
  32. pyglove/core/patching/pattern_based.py +4 -4
  33. pyglove/core/patching/rule_based.py +4 -3
  34. pyglove/core/symbolic/__init__.py +4 -0
  35. pyglove/core/symbolic/base.py +200 -136
  36. pyglove/core/symbolic/base_test.py +17 -19
  37. pyglove/core/symbolic/boilerplate.py +4 -5
  38. pyglove/core/symbolic/class_wrapper.py +10 -14
  39. pyglove/core/symbolic/class_wrapper_test.py +2 -2
  40. pyglove/core/symbolic/compounding.py +2 -2
  41. pyglove/core/symbolic/compounding_test.py +2 -4
  42. pyglove/core/symbolic/contextual_object.py +288 -0
  43. pyglove/core/symbolic/contextual_object_test.py +327 -0
  44. pyglove/core/symbolic/dict.py +115 -87
  45. pyglove/core/symbolic/dict_test.py +188 -131
  46. pyglove/core/symbolic/diff.py +12 -12
  47. pyglove/core/symbolic/flags.py +1 -1
  48. pyglove/core/symbolic/functor.py +16 -15
  49. pyglove/core/symbolic/functor_test.py +2 -4
  50. pyglove/core/symbolic/inferred.py +2 -2
  51. pyglove/core/symbolic/list.py +70 -47
  52. pyglove/core/symbolic/list_test.py +117 -98
  53. pyglove/core/symbolic/object.py +59 -58
  54. pyglove/core/symbolic/object_test.py +143 -90
  55. pyglove/core/symbolic/origin.py +5 -7
  56. pyglove/core/symbolic/pure_symbolic.py +4 -3
  57. pyglove/core/symbolic/ref.py +33 -16
  58. pyglove/core/symbolic/ref_test.py +17 -0
  59. pyglove/core/tuning/local_backend.py +2 -2
  60. pyglove/core/tuning/protocols.py +3 -3
  61. pyglove/core/typing/annotation_conversion.py +8 -3
  62. pyglove/core/typing/annotation_conversion_test.py +8 -0
  63. pyglove/core/typing/callable_ext.py +11 -13
  64. pyglove/core/typing/callable_signature.py +22 -19
  65. pyglove/core/typing/callable_signature_test.py +3 -5
  66. pyglove/core/typing/class_schema.py +93 -54
  67. pyglove/core/typing/class_schema_test.py +4 -5
  68. pyglove/core/typing/custom_typing.py +5 -4
  69. pyglove/core/typing/key_specs.py +5 -7
  70. pyglove/core/typing/key_specs_test.py +4 -4
  71. pyglove/core/typing/type_conversion.py +4 -5
  72. pyglove/core/typing/type_conversion_test.py +12 -12
  73. pyglove/core/typing/typed_missing.py +6 -7
  74. pyglove/core/typing/typed_missing_test.py +7 -8
  75. pyglove/core/typing/value_specs.py +287 -144
  76. pyglove/core/typing/value_specs_test.py +148 -25
  77. pyglove/core/utils/__init__.py +172 -0
  78. pyglove/core/{object_utils → utils}/common_traits.py +2 -2
  79. pyglove/core/{object_utils → utils}/common_traits_test.py +1 -3
  80. pyglove/core/utils/contextual.py +147 -0
  81. pyglove/core/utils/contextual_test.py +88 -0
  82. pyglove/core/{object_utils → utils}/docstr_utils_test.py +1 -3
  83. pyglove/core/{object_utils → utils}/error_utils.py +3 -3
  84. pyglove/core/{object_utils → utils}/error_utils_test.py +1 -1
  85. pyglove/core/{object_utils → utils}/formatting.py +1 -1
  86. pyglove/core/{object_utils → utils}/formatting_test.py +1 -2
  87. pyglove/core/{object_utils → utils}/hierarchical.py +23 -25
  88. pyglove/core/{object_utils → utils}/hierarchical_test.py +3 -5
  89. pyglove/core/{object_utils → utils}/json_conversion.py +1 -1
  90. pyglove/core/{object_utils → utils}/json_conversion_test.py +1 -3
  91. pyglove/core/{object_utils → utils}/missing.py +2 -2
  92. pyglove/core/{object_utils → utils}/missing_test.py +2 -4
  93. pyglove/core/utils/text_color.py +128 -0
  94. pyglove/core/utils/text_color_test.py +94 -0
  95. pyglove/core/{object_utils → utils}/thread_local_test.py +1 -3
  96. pyglove/core/{object_utils → utils}/timing.py +21 -10
  97. pyglove/core/{object_utils → utils}/timing_test.py +14 -12
  98. pyglove/core/{object_utils → utils}/value_location.py +2 -2
  99. pyglove/core/{object_utils → utils}/value_location_test.py +2 -4
  100. pyglove/core/views/base.py +25 -29
  101. pyglove/core/views/html/base.py +14 -15
  102. pyglove/core/views/html/controls/base.py +5 -5
  103. pyglove/core/views/html/controls/label.py +1 -1
  104. pyglove/core/views/html/controls/label_test.py +6 -6
  105. pyglove/core/views/html/controls/progress_bar.py +3 -5
  106. pyglove/core/views/html/controls/progress_bar_test.py +2 -2
  107. pyglove/core/views/html/controls/tab.py +80 -2
  108. pyglove/core/views/html/controls/tab_test.py +34 -1
  109. pyglove/core/views/html/tree_view.py +39 -37
  110. {pyglove-0.4.5.dev202412100720.dist-info → pyglove-0.4.5.dev202501250807.dist-info}/METADATA +17 -3
  111. pyglove-0.4.5.dev202501250807.dist-info/RECORD +218 -0
  112. {pyglove-0.4.5.dev202412100720.dist-info → pyglove-0.4.5.dev202501250807.dist-info}/WHEEL +1 -1
  113. pyglove/core/object_utils/__init__.py +0 -164
  114. pyglove-0.4.5.dev202412100720.dist-info/RECORD +0 -203
  115. /pyglove/core/{object_utils → utils}/docstr_utils.py +0 -0
  116. /pyglove/core/{object_utils → utils}/thread_local.py +0 -0
  117. {pyglove-0.4.5.dev202412100720.dist-info → pyglove-0.4.5.dev202501250807.dist-info}/LICENSE +0 -0
  118. {pyglove-0.4.5.dev202412100720.dist-info → pyglove-0.4.5.dev202501250807.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,94 @@
1
+ # Copyright 2025 The PyGlove Authors
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ import inspect
15
+ import os
16
+ import unittest
17
+ from pyglove.core.utils import text_color
18
+
19
+
20
+ class TextColorTest(unittest.TestCase):
21
+
22
+ def setUp(self):
23
+ super().setUp()
24
+ os.environ.pop('ANSI_COLORS_DISABLED', None)
25
+ os.environ.pop('NO_COLOR', None)
26
+ os.environ['FORCE_COLOR'] = '1'
27
+
28
+ def test_colored_block_without_colors_and_styles(self):
29
+ self.assertEqual(text_color.colored_block('foo', '{{', '}}'), 'foo')
30
+
31
+ def test_colored_block(self):
32
+ original_text = inspect.cleandoc("""
33
+ Hi << foo >>
34
+ <# print x if x is present #>
35
+ <% if x %>
36
+ << x >>
37
+ <% endif %>
38
+ """)
39
+
40
+ colored_text = text_color.colored_block(
41
+ text_color.colored(original_text, color='blue'),
42
+ '<<', '>>',
43
+ color='white',
44
+ background='blue',
45
+ )
46
+ origin_color = '\x1b[34m'
47
+ reset = '\x1b[0m'
48
+ block_color = text_color.colored(
49
+ 'TEXT', color='white', background='blue'
50
+ ).split('TEXT')[0]
51
+ self.assertEqual(
52
+ colored_text,
53
+ f'{origin_color}Hi {block_color}<< foo >>{reset}{origin_color}\n'
54
+ '<# print x if x is present #>\n<% if x %>\n'
55
+ f'{block_color}<< x >>{reset}{origin_color}\n'
56
+ f'<% endif %>{reset}'
57
+ )
58
+ self.assertEqual(text_color.decolor(colored_text), original_text)
59
+
60
+ def test_colored_block_without_full_match(self):
61
+ self.assertEqual(
62
+ text_color.colored_block(
63
+ 'Hi {{ foo',
64
+ '{{', '}}',
65
+ color='white',
66
+ background='blue',
67
+ ),
68
+ 'Hi {{ foo'
69
+ )
70
+
71
+ def test_colored_block_without_termcolor(self):
72
+ termcolor = text_color.termcolor
73
+ text_color.termcolor = None
74
+ original_text = inspect.cleandoc("""
75
+ Hi {{ foo }}
76
+ {# print x if x is present #}
77
+ {% if x %}
78
+ {{ x }}
79
+ {% endif %}
80
+ """)
81
+
82
+ colored_text = text_color.colored_block(
83
+ text_color.colored(original_text, color='blue'),
84
+ '{{', '}}',
85
+ color='white',
86
+ background='blue',
87
+ )
88
+ self.assertEqual(colored_text, original_text)
89
+ self.assertEqual(text_color.decolor(colored_text), original_text)
90
+ text_color.termcolor = termcolor
91
+
92
+
93
+ if __name__ == '__main__':
94
+ unittest.main()
@@ -11,13 +11,11 @@
11
11
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
- """Tests for pyglove.object_utils.thread_local."""
15
-
16
14
  import threading
17
15
  import time
18
16
  import unittest
19
17
 
20
- from pyglove.core.object_utils import thread_local
18
+ from pyglove.core.utils import thread_local
21
19
 
22
20
 
23
21
  class ThreadLocalTest(unittest.TestCase):
@@ -18,9 +18,9 @@ import dataclasses
18
18
  import time
19
19
  from typing import Any, Dict, List, Optional
20
20
 
21
- from pyglove.core.object_utils import error_utils
22
- from pyglove.core.object_utils import json_conversion
23
- from pyglove.core.object_utils import thread_local
21
+ from pyglove.core.utils import error_utils
22
+ from pyglove.core.utils import json_conversion
23
+ from pyglove.core.utils import thread_local
24
24
 
25
25
 
26
26
  class TimeIt:
@@ -56,6 +56,16 @@ class TimeIt:
56
56
  **kwargs,
57
57
  )
58
58
 
59
+ def merge(self, other: 'TimeIt.Status') -> 'TimeIt.Status':
60
+ """Merges the status of two `pg.timeit`."""
61
+ assert other.name == self.name, (self.name, other.name)
62
+ return TimeIt.Status(
63
+ name=self.name,
64
+ elapse=self.elapse + other.elapse,
65
+ has_ended=self.has_ended and other.has_ended,
66
+ error=self.error or other.error,
67
+ )
68
+
59
69
  @dataclasses.dataclass
60
70
  class StatusSummary(json_conversion.JSONConvertible):
61
71
  """Aggregated summary for repeated calls for `pg.timeit`."""
@@ -125,7 +135,7 @@ class TimeIt:
125
135
  self._name: str = name
126
136
  self._start_time: Optional[float] = None
127
137
  self._end_time: Optional[float] = None
128
- self._child_contexts: Dict[str, TimeIt] = {}
138
+ self._child_contexts: List[TimeIt] = []
129
139
  self._error: Optional[error_utils.ErrorInfo] = None
130
140
  self._parent: Optional[TimeIt] = None
131
141
 
@@ -137,13 +147,11 @@ class TimeIt:
137
147
  @property
138
148
  def children(self) -> List['TimeIt']:
139
149
  """Returns child contexts."""
140
- return list(self._child_contexts.values())
150
+ return self._child_contexts
141
151
 
142
152
  def add(self, context: 'TimeIt'):
143
153
  """Adds a child context."""
144
- if context.name in self._child_contexts:
145
- raise ValueError(f'`timeit` with name {context.name!r} already exists.')
146
- self._child_contexts[context.name] = context
154
+ self._child_contexts.append(context)
147
155
 
148
156
  def start(self):
149
157
  """Starts timing."""
@@ -206,11 +214,14 @@ class TimeIt:
206
214
  has_ended=self.has_ended, error=self._error,
207
215
  )
208
216
  }
209
- for child in self._child_contexts.values():
217
+ for child in self._child_contexts:
210
218
  child_result = child.status()
211
219
  for k, v in child_result.items():
212
220
  key = f'{self.name}.{k}' if self.name else k
213
- result[key] = v
221
+ if key in result:
222
+ result[key] = result[key].merge(v)
223
+ else:
224
+ result[key] = v
214
225
  return result
215
226
 
216
227
  def __enter__(self):
@@ -11,12 +11,11 @@
11
11
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
-
15
14
  import time
16
15
  import unittest
17
16
 
18
- from pyglove.core.object_utils import json_conversion
19
- from pyglove.core.object_utils import timing
17
+ from pyglove.core.utils import json_conversion
18
+ from pyglove.core.utils import timing
20
19
 
21
20
 
22
21
  class TimeItTest(unittest.TestCase):
@@ -63,9 +62,9 @@ class TimeItTest(unittest.TestCase):
63
62
  self.assertFalse(r['node.child'].has_ended)
64
63
  self.assertTrue(r['node.child.grandchild'].has_ended)
65
64
 
66
- with self.assertRaisesRegex(ValueError, '.* already exists'):
67
- with timing.timeit('grandchild'):
68
- pass
65
+ with timing.timeit('grandchild') as t3:
66
+ time.sleep(0.5)
67
+ self.assertEqual(t1.children, [t2, t3])
69
68
 
70
69
  elapse2 = t.elapse
71
70
  self.assertTrue(t.has_ended)
@@ -73,17 +72,20 @@ class TimeItTest(unittest.TestCase):
73
72
  time.sleep(0.5)
74
73
  self.assertEqual(elapse2, t.elapse)
75
74
 
76
- statuss = t.status()
75
+ status = t.status()
77
76
  self.assertEqual(
78
- list(statuss.keys()),
77
+ list(status.keys()),
79
78
  ['node', 'node.child', 'node.child.grandchild']
80
79
  )
81
80
  self.assertEqual(
82
- sorted([v.elapse for v in statuss.values()], reverse=True),
83
- [v.elapse for v in statuss.values()],
81
+ status['node.child.grandchild'].elapse, t2.elapse + t3.elapse
82
+ )
83
+ self.assertEqual(
84
+ sorted([v.elapse for v in status.values()], reverse=True),
85
+ [v.elapse for v in status.values()],
84
86
  )
85
- self.assertTrue(all(v.has_ended for v in statuss.values()))
86
- self.assertFalse(any(v.has_error for v in statuss.values()))
87
+ self.assertTrue(all(v.has_ended for v in status.values()))
88
+ self.assertFalse(any(v.has_error for v in status.values()))
87
89
  status = t.status()
88
90
  json_dict = json_conversion.to_json(status)
89
91
  status2 = json_conversion.from_json(json_dict)
@@ -17,7 +17,7 @@ import abc
17
17
  import copy as copy_lib
18
18
  import operator
19
19
  from typing import Any, Callable, Iterable, Iterator, List, Optional, Union
20
- from pyglove.core.object_utils import formatting
20
+ from pyglove.core.utils import formatting
21
21
 
22
22
 
23
23
  class KeyPath(formatting.Formattable):
@@ -822,7 +822,7 @@ class StrKey(metaclass=abc.ABCMeta):
822
822
 
823
823
  Example::
824
824
 
825
- class MyKey(pg.object_utils.StrKey):
825
+ class MyKey(pg.utils.StrKey):
826
826
 
827
827
  def __init__(self, name):
828
828
  self.name = name
@@ -11,11 +11,9 @@
11
11
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
- """Tests for pyglove.object_utils.value_location."""
15
-
16
14
  import unittest
17
- from pyglove.core.object_utils import formatting
18
- from pyglove.core.object_utils import value_location
15
+ from pyglove.core.utils import formatting
16
+ from pyglove.core.utils import value_location
19
17
 
20
18
 
21
19
  KeyPath = value_location.KeyPath
@@ -136,18 +136,18 @@ import types
136
136
  from typing import Any, Callable, Dict, Iterator, Optional, Sequence, Set, Type, Union
137
137
 
138
138
  from pyglove.core import io as pg_io
139
- from pyglove.core import object_utils
140
139
  from pyglove.core import typing as pg_typing
140
+ from pyglove.core import utils
141
141
 
142
142
 
143
143
  # Type definition for the value filter function.
144
144
  NodeFilter = Callable[
145
145
  [
146
- object_utils.KeyPath, # The path to the value.
147
- Any, # Current value.
148
- Any, # Parent value
146
+ utils.KeyPath, # The path to the value.
147
+ Any, # Current value.
148
+ Any, # Parent value
149
149
  ],
150
- bool # Whether to include the value.
150
+ bool, # Whether to include the value.
151
151
  ]
152
152
 
153
153
 
@@ -157,7 +157,7 @@ _TLS_KEY_OPERAND_STACK_BY_METHOD = '__view_operand_stack__'
157
157
  _TLS_KEY_VIEW_OPTIONS = '__view_options__'
158
158
 
159
159
 
160
- class Content(object_utils.Formattable, metaclass=abc.ABCMeta):
160
+ class Content(utils.Formattable, metaclass=abc.ABCMeta):
161
161
  """Content: A type of media to be displayed in a view.
162
162
 
163
163
  For example, `pg.Html` is a `Content` type that represents HTML to be
@@ -171,7 +171,7 @@ class Content(object_utils.Formattable, metaclass=abc.ABCMeta):
171
171
  None
172
172
  ]
173
173
 
174
- class SharedParts(object_utils.Formattable):
174
+ class SharedParts(utils.Formattable):
175
175
  """A part of the content that should appear just once.
176
176
 
177
177
  For example, `pg.Html.Styles` is a `SharedParts` type that represents
@@ -244,7 +244,7 @@ class Content(object_utils.Formattable, metaclass=abc.ABCMeta):
244
244
  **kwargs
245
245
  ) -> str:
246
246
  if compact:
247
- return object_utils.kvlist_str(
247
+ return utils.kvlist_str(
248
248
  [
249
249
  ('parts', self._parts, {}),
250
250
  ],
@@ -252,7 +252,7 @@ class Content(object_utils.Formattable, metaclass=abc.ABCMeta):
252
252
  compact=compact,
253
253
  verbose=verbose,
254
254
  root_indent=root_indent,
255
- bracket_type=object_utils.BracketType.ROUND,
255
+ bracket_type=utils.BracketType.ROUND,
256
256
  )
257
257
  return self.content
258
258
 
@@ -363,17 +363,16 @@ class Content(object_utils.Formattable, metaclass=abc.ABCMeta):
363
363
  """Formats the Content object."""
364
364
  del kwargs
365
365
  if compact:
366
- return object_utils.kvlist_str(
366
+ return utils.kvlist_str(
367
367
  [
368
368
  ('content', self.content, ''),
369
- ] + [
370
- (k, v, None) for k, v in self._shared_parts.items()
371
- ],
369
+ ]
370
+ + [(k, v, None) for k, v in self._shared_parts.items()],
372
371
  label=self.__class__.__name__,
373
372
  compact=compact,
374
373
  verbose=verbose,
375
374
  root_indent=root_indent,
376
- bracket_type=object_utils.BracketType.ROUND,
375
+ bracket_type=utils.BracketType.ROUND,
377
376
  )
378
377
  return self.to_str(content_only=content_only)
379
378
 
@@ -427,9 +426,9 @@ def view(
427
426
  value: Any,
428
427
  *,
429
428
  name: Optional[str] = None,
430
- root_path: Optional[object_utils.KeyPath] = None,
429
+ root_path: Optional[utils.KeyPath] = None,
431
430
  view_id: str = 'html-tree-view',
432
- **kwargs
431
+ **kwargs,
433
432
  ) -> Content:
434
433
  """Views an object through generating content based on a specific view.
435
434
 
@@ -451,8 +450,7 @@ def view(
451
450
  with view_options(**kwargs) as options:
452
451
  view_object = View.create(view_id)
453
452
  return view_object.render(
454
- value, name=name, root_path=root_path or object_utils.KeyPath(),
455
- **options
453
+ value, name=name, root_path=root_path or utils.KeyPath(), **options
456
454
  )
457
455
 
458
456
 
@@ -471,14 +469,14 @@ def view_options(**kwargs) -> Iterator[Dict[str, Any]]:
471
469
  Yields:
472
470
  The merged keyword arguments.
473
471
  """
474
- parent_options = object_utils.thread_local_peek(_TLS_KEY_VIEW_OPTIONS, {})
472
+ parent_options = utils.thread_local_peek(_TLS_KEY_VIEW_OPTIONS, {})
475
473
  # Deep merge the two dict.
476
- options = object_utils.merge([parent_options, kwargs])
477
- object_utils.thread_local_push(_TLS_KEY_VIEW_OPTIONS, options)
474
+ options = utils.merge([parent_options, kwargs])
475
+ utils.thread_local_push(_TLS_KEY_VIEW_OPTIONS, options)
478
476
  try:
479
477
  yield options
480
478
  finally:
481
- object_utils.thread_local_pop(_TLS_KEY_VIEW_OPTIONS)
479
+ utils.thread_local_pop(_TLS_KEY_VIEW_OPTIONS)
482
480
 
483
481
 
484
482
  class View(metaclass=abc.ABCMeta):
@@ -697,8 +695,8 @@ class View(metaclass=abc.ABCMeta):
697
695
  value: Any,
698
696
  *,
699
697
  name: Optional[str] = None,
700
- root_path: Optional[object_utils.KeyPath] = None,
701
- **kwargs
698
+ root_path: Optional[utils.KeyPath] = None,
699
+ **kwargs,
702
700
  ) -> Content:
703
701
  """Renders the input value.
704
702
 
@@ -789,20 +787,18 @@ class View(metaclass=abc.ABCMeta):
789
787
  ) -> Iterator[Any]:
790
788
  """Context manager for tracking the value being rendered."""
791
789
  del self
792
- rendering_stack = object_utils.thread_local_get(
790
+ rendering_stack = utils.thread_local_get(
793
791
  _TLS_KEY_OPERAND_STACK_BY_METHOD, {}
794
792
  )
795
793
  callsite_value = rendering_stack.get(view_method, None)
796
794
  rendering_stack[view_method] = value
797
- object_utils.thread_local_set(
798
- _TLS_KEY_OPERAND_STACK_BY_METHOD, rendering_stack
799
- )
795
+ utils.thread_local_set(_TLS_KEY_OPERAND_STACK_BY_METHOD, rendering_stack)
800
796
  try:
801
797
  yield callsite_value
802
798
  finally:
803
799
  if callsite_value is None:
804
800
  rendering_stack.pop(view_method)
805
801
  if not rendering_stack:
806
- object_utils.thread_local_del(_TLS_KEY_OPERAND_STACK_BY_METHOD)
802
+ utils.thread_local_del(_TLS_KEY_OPERAND_STACK_BY_METHOD)
807
803
  else:
808
804
  rendering_stack[view_method] = callsite_value
@@ -20,8 +20,8 @@ import inspect
20
20
  import typing
21
21
  from typing import Any, Callable, Dict, Iterable, List, Optional, Sequence, Union
22
22
 
23
- from pyglove.core import object_utils
24
23
  from pyglove.core import typing as pg_typing
24
+ from pyglove.core import utils
25
25
  from pyglove.core.views import base
26
26
 
27
27
  NestableStr = Union[
@@ -33,11 +33,11 @@ NestableStr = Union[
33
33
  NodeFilter = base.NodeFilter
34
34
  NodeColor = Callable[
35
35
  [
36
- object_utils.KeyPath, # The path to the value.
37
- Any, # Current value.
38
- Any, # Parent value
36
+ utils.KeyPath, # The path to the value.
37
+ Any, # Current value.
38
+ Any, # Parent value
39
39
  ],
40
- Optional[str] # The color of the node.
40
+ Optional[str], # The color of the node.
41
41
  ]
42
42
 
43
43
 
@@ -386,7 +386,7 @@ class Html(base.Content):
386
386
  cls, nestable_str: NestableStr, separator: str = ' ', dedup: bool = True
387
387
  ) -> Optional[str]:
388
388
  """Concates the string nodes in a nestable object."""
389
- flattened = object_utils.flatten(nestable_str)
389
+ flattened = utils.flatten(nestable_str)
390
390
  if isinstance(flattened, str):
391
391
  return flattened
392
392
  elif isinstance(flattened, dict):
@@ -456,8 +456,8 @@ class HtmlView(base.View):
456
456
  value: Any,
457
457
  *,
458
458
  name: Optional[str] = None,
459
- root_path: Optional[object_utils.KeyPath] = None,
460
- **kwargs
459
+ root_path: Optional[utils.KeyPath] = None,
460
+ **kwargs,
461
461
  ) -> Html:
462
462
  """Renders the input value into an HTML object."""
463
463
  # For customized HtmlConvertible objects, call their `to_html()` method.
@@ -473,8 +473,8 @@ class HtmlView(base.View):
473
473
  value: Any,
474
474
  *,
475
475
  name: Optional[str] = None,
476
- root_path: Optional[object_utils.KeyPath] = None,
477
- **kwargs
476
+ root_path: Optional[utils.KeyPath] = None,
477
+ **kwargs,
478
478
  ) -> Html:
479
479
  """View's implementation of HTML rendering."""
480
480
 
@@ -483,9 +483,9 @@ def to_html(
483
483
  value: Any,
484
484
  *,
485
485
  name: Optional[str] = None,
486
- root_path: Optional[object_utils.KeyPath] = None,
486
+ root_path: Optional[utils.KeyPath] = None,
487
487
  view_id: str = 'html-tree-view',
488
- **kwargs
488
+ **kwargs,
489
489
  ) -> Html:
490
490
  """Returns the HTML representation of a value.
491
491
 
@@ -517,10 +517,10 @@ def to_html_str(
517
517
  value: Any,
518
518
  *,
519
519
  name: Optional[str] = None,
520
- root_path: Optional[object_utils.KeyPath] = None,
520
+ root_path: Optional[utils.KeyPath] = None,
521
521
  view_id: str = 'html-tree-view',
522
522
  content_only: bool = False,
523
- **kwargs
523
+ **kwargs,
524
524
  ) -> str:
525
525
  """Returns a HTML str for a value.
526
526
 
@@ -545,4 +545,3 @@ def to_html_str(
545
545
  view_id=view_id,
546
546
  **kwargs
547
547
  ).to_str(content_only=content_only)
548
-
@@ -19,7 +19,7 @@ import inspect
19
19
  import sys
20
20
  from typing import Annotated, Any, Dict, Iterator, List, Optional, Union
21
21
 
22
- from pyglove.core import object_utils
22
+ from pyglove.core import utils
23
23
  from pyglove.core.symbolic import object as pg_object
24
24
  from pyglove.core.views.html import base
25
25
 
@@ -89,16 +89,16 @@ class HtmlControl(pg_object.Object):
89
89
  @contextlib.contextmanager
90
90
  def track_scripts(cls) -> Iterator[List[str]]:
91
91
  del cls
92
- all_tracked = object_utils.thread_local_get(_TLS_TRACKED_SCRIPTS, [])
92
+ all_tracked = utils.thread_local_get(_TLS_TRACKED_SCRIPTS, [])
93
93
  current = []
94
94
  all_tracked.append(current)
95
- object_utils.thread_local_set(_TLS_TRACKED_SCRIPTS, all_tracked)
95
+ utils.thread_local_set(_TLS_TRACKED_SCRIPTS, all_tracked)
96
96
  try:
97
97
  yield current
98
98
  finally:
99
99
  all_tracked.pop(-1)
100
100
  if not all_tracked:
101
- object_utils.thread_local_del(_TLS_TRACKED_SCRIPTS)
101
+ utils.thread_local_del(_TLS_TRACKED_SCRIPTS)
102
102
 
103
103
  def _sync_members(self, **fields) -> None:
104
104
  """Synchronizes displayed values to members."""
@@ -121,7 +121,7 @@ class HtmlControl(pg_object.Object):
121
121
  _notebook.display(_notebook.Javascript(code))
122
122
 
123
123
  # Track script execution.
124
- all_tracked = object_utils.thread_local_get(_TLS_TRACKED_SCRIPTS, [])
124
+ all_tracked = utils.thread_local_get(_TLS_TRACKED_SCRIPTS, [])
125
125
  for tracked in all_tracked:
126
126
  tracked.append(code)
127
127
 
@@ -74,7 +74,7 @@ class Label(HtmlControl):
74
74
 
75
75
  def _to_html(self, **kwargs) -> Html:
76
76
  text_elem = Html.element(
77
- 'a',
77
+ 'a' if self.link is not None else 'span',
78
78
  [self.text],
79
79
  id=self.element_id(),
80
80
  href=self.link,
@@ -34,7 +34,7 @@ class LabelTest(TestCase):
34
34
  label = label_lib.Label('foo')
35
35
  self.assertIsNone(label.tooltip)
36
36
  self.assertIsNone(label.link)
37
- self.assert_html_content(label, '<a class="label">foo</a>')
37
+ self.assert_html_content(label, '<span class="label">foo</span>')
38
38
  with self.assertRaisesRegex(ValueError, 'Non-interactive .*'):
39
39
  label.update('bar')
40
40
 
@@ -54,7 +54,7 @@ class LabelTest(TestCase):
54
54
  self.assert_html_content(
55
55
  label,
56
56
  (
57
- '<div class="label-container"><a class="label">foo</a>'
57
+ '<div class="label-container"><span class="label">foo</span>'
58
58
  '<span class="tooltip">bar</span></div>'
59
59
  )
60
60
  )
@@ -130,7 +130,7 @@ class LabelTest(TestCase):
130
130
 
131
131
  def test_badge(self):
132
132
  badge = label_lib.Badge('foo')
133
- self.assert_html_content(badge, '<a class="label badge">foo</a>')
133
+ self.assert_html_content(badge, '<span class="label badge">foo</span>')
134
134
 
135
135
 
136
136
  class LabelGroupTest(TestCase):
@@ -140,9 +140,9 @@ class LabelGroupTest(TestCase):
140
140
  self.assert_html_content(
141
141
  group,
142
142
  (
143
- '<div class="label-group"><a class="label group-name">baz</a>'
144
- '<a class="label group-value">foo</a>'
145
- '<a class="label group-value">bar</a></div>'
143
+ '<div class="label-group"><span class="label group-name">baz</span>'
144
+ '<span class="label group-value">foo</span>'
145
+ '<span class="label group-value">bar</span></div>'
146
146
  )
147
147
  )
148
148
 
@@ -16,7 +16,7 @@
16
16
  import functools
17
17
  from typing import Annotated, List, Optional, Union
18
18
 
19
- from pyglove.core import object_utils
19
+ from pyglove.core import utils
20
20
  from pyglove.core.symbolic import object as pg_object
21
21
  # pylint: disable=g-importing-member
22
22
  from pyglove.core.views.html.base import Html
@@ -73,10 +73,8 @@ class SubProgress(HtmlControl):
73
73
  [],
74
74
  id=self.element_id(),
75
75
  styles=styles,
76
- css_classes=[
77
- 'sub-progress',
78
- object_utils.camel_to_snake(self.name, '-')
79
- ] + self.css_classes,
76
+ css_classes=['sub-progress', utils.camel_to_snake(self.name, '-')]
77
+ + self.css_classes,
80
78
  )
81
79
 
82
80
  def increment(self, delta: int = 1):
@@ -42,8 +42,8 @@ class ProgressBarTest(unittest.TestCase):
42
42
  f'<div class="sub-progress foo" id="{bar["foo"].element_id()}">'
43
43
  '</div><div class="sub-progress bar" '
44
44
  f'id="{bar["bar"].element_id()}"></div></div>'
45
- '<div class="label-container"><a class="label progress-label"'
46
- f' id="{bar._progress_label.element_id()}">n/a</a><span class='
45
+ '<div class="label-container"><span class="label progress-label"'
46
+ f' id="{bar._progress_label.element_id()}">n/a</span><span class='
47
47
  f'"tooltip" id="{bar._progress_label.tooltip.element_id()}">'
48
48
  'Not started</span></div></div>'
49
49
  )