pyglove 0.4.5.dev20240318__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.dev20240318.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.dev20240318.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.dev20240318.dist-info/RECORD +0 -185
  143. /pyglove/core/{object_utils → utils}/thread_local.py +0 -0
  144. {pyglove-0.4.5.dev20240318.dist-info → pyglove-0.4.5.dev202501132210.dist-info}/LICENSE +0 -0
  145. {pyglove-0.4.5.dev20240318.dist-info → pyglove-0.4.5.dev202501132210.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,453 @@
1
+ # Copyright 2022 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 unittest
16
+
17
+ from pyglove.core.utils import formatting
18
+
19
+
20
+ class Foo(formatting.Formattable):
21
+
22
+ def format(
23
+ self, compact: bool = False, verbose: bool = True, **kwargs):
24
+ return f'{self.__class__.__name__}(compact={compact}, verbose={verbose})'
25
+
26
+
27
+ class Bar(formatting.Formattable):
28
+
29
+ def __init__(self, foo: Foo):
30
+ self._foo = foo
31
+
32
+ def format(
33
+ self, compact: bool = False, verbose: bool = True,
34
+ root_indent: int = 0, **kwargs):
35
+ foo_str = self._foo.format(
36
+ compact=compact, verbose=verbose, root_indent=root_indent + 1)
37
+ return f'{self.__class__.__name__}(foo={foo_str})'
38
+
39
+
40
+ class FormattableTest(unittest.TestCase):
41
+
42
+ def test_formattable(self):
43
+ foo = Foo()
44
+ self.assertEqual(repr(foo), 'Foo(compact=True, verbose=True)')
45
+ self.assertEqual(str(foo), 'Foo(compact=False, verbose=True)')
46
+
47
+ def test_formattable_with_custom_format(self):
48
+ class Baz(Foo):
49
+ __str_format_kwargs__ = {'compact': False, 'verbose': False}
50
+ __repr_format_kwargs__ = {'compact': True, 'verbose': False}
51
+
52
+ bar = Baz()
53
+ self.assertEqual(repr(bar), 'Baz(compact=True, verbose=False)')
54
+ self.assertEqual(str(bar), 'Baz(compact=False, verbose=False)')
55
+
56
+ def test_formattable_with_context_managers(self):
57
+ foo = Foo()
58
+ with formatting.str_format(verbose=False):
59
+ with formatting.repr_format(compact=False):
60
+ self.assertEqual(repr(foo), 'Foo(compact=False, verbose=True)')
61
+ self.assertEqual(str(foo), 'Foo(compact=False, verbose=False)')
62
+
63
+
64
+ class StringHelperTest(unittest.TestCase):
65
+ """Tests for string helper methods in formatting."""
66
+
67
+ def test_raw_text(self):
68
+ raw = formatting.RawText('abc')
69
+ self.assertEqual(formatting.format(raw), 'abc')
70
+ self.assertEqual(formatting.format(raw, compact=True), 'abc')
71
+ self.assertEqual(raw, formatting.RawText('abc'))
72
+ self.assertEqual(raw, 'abc')
73
+ self.assertNotEqual(raw, formatting.RawText('abcd'))
74
+ self.assertNotEqual(raw, 'abcd')
75
+
76
+ def test_camel_to_snake(self):
77
+ self.assertEqual(formatting.camel_to_snake('foo'), 'foo')
78
+ self.assertEqual(formatting.camel_to_snake('Foo'), 'foo')
79
+ self.assertEqual(formatting.camel_to_snake('FooBar'), 'foo_bar')
80
+ self.assertEqual(formatting.camel_to_snake('AI'), 'ai')
81
+ self.assertEqual(formatting.camel_to_snake('AIMessage'), 'ai_message')
82
+ self.assertEqual(formatting.camel_to_snake('ABCMeta'), 'abc_meta')
83
+ self.assertEqual(formatting.camel_to_snake('ABC123Meta'), 'abc123_meta')
84
+
85
+ def test_special_format_support(self):
86
+
87
+ class NewLine:
88
+ def _repr_html_(self):
89
+ return '<hr>'
90
+
91
+ def __str__(self):
92
+ return 'NewLine()'
93
+
94
+ def __repr__(self):
95
+ return 'NewLine()'
96
+
97
+ v = NewLine()
98
+ self.assertEqual(formatting.str_ext(v), 'NewLine()')
99
+ def _method(attr_name: str):
100
+ def fn(v, root_indent):
101
+ del root_indent
102
+ f = getattr(v, attr_name, None)
103
+ return f() if f is not None else None
104
+ return fn
105
+
106
+ self.assertEqual(
107
+ formatting.str_ext(v, custom_format=_method('_repr_html_')),
108
+ '<hr>'
109
+ )
110
+ self.assertEqual(
111
+ formatting.str_ext(v, custom_format=_method('_repr_xml_')),
112
+ 'NewLine()'
113
+ )
114
+ self.assertEqual(formatting.repr_ext(v), 'NewLine()')
115
+ self.assertEqual(
116
+ formatting.repr_ext(v, custom_format=_method('_repr_html_')),
117
+ '<hr>'
118
+ )
119
+ self.assertEqual(
120
+ formatting.repr_ext(v, custom_format=_method('_repr_xml_')),
121
+ 'NewLine()'
122
+ )
123
+
124
+ def test_kvlist_str(self):
125
+ self.assertEqual(
126
+ formatting.kvlist_str([
127
+ ('', 'foo', None),
128
+ ('a', 1, None),
129
+ ('b', 'str', (None, 'str')),
130
+ ('c', [1, 2, 3], False),
131
+ ], label='Foo'),
132
+ 'Foo(\'foo\', a=1, c=[1, 2, 3])'
133
+ )
134
+
135
+ self.assertEqual(
136
+ formatting.kvlist_str([
137
+ ('', 'foo', None),
138
+ ('a', 1, None),
139
+ ('b', 'str', (None, 'str')),
140
+ ('c', True, False),
141
+ ]),
142
+ '\'foo\', a=1, c=True'
143
+ )
144
+
145
+ self.assertEqual(
146
+ formatting.kvlist_str([
147
+ ('', 'foo', None),
148
+ ('a', 1, None),
149
+ ('b', 'str', (None, 'str')),
150
+ ('c', True, False),
151
+ ], label='Foo', compact=False),
152
+ 'Foo(\n \'foo\',\n a=1,\n c=True\n)'
153
+ )
154
+
155
+ self.assertEqual(
156
+ formatting.kvlist_str([
157
+ ('', 'foo', None),
158
+ ('a', 1, None),
159
+ ('b', 'str', (None, 'str')),
160
+ ('c', dict(x=1), False),
161
+ ], compact=False),
162
+ '\'foo\',\na=1,\nc={\n \'x\': 1\n}'
163
+ )
164
+
165
+ self.assertEqual(
166
+ formatting.kvlist_str([
167
+ ('', 'foo', 'foo')
168
+ ], label='Foo', compact=False),
169
+ 'Foo()'
170
+ )
171
+
172
+ class Foo: # pylint: disable=redefined-outer-name
173
+ def _repr_xml_(self):
174
+ return '<foo/>'
175
+
176
+ def __str__(self):
177
+ return 'Foo()'
178
+
179
+ self.assertEqual(
180
+ formatting.kvlist_str([
181
+ ('', Foo(), None)
182
+ ], compact=False),
183
+ 'Foo()'
184
+ )
185
+ def _custom_format(v, root_indent):
186
+ del root_indent
187
+ f = getattr(v, '_repr_xml_', None)
188
+ return f() if f is not None else None
189
+
190
+ self.assertEqual(
191
+ formatting.kvlist_str([
192
+ ('', Foo(), None)
193
+ ], compact=False, custom_format=_custom_format),
194
+ '<foo/>'
195
+ )
196
+ self.assertEqual(
197
+ formatting.kvlist_str([
198
+ ('', (Foo(), 1), None)
199
+ ], compact=True, custom_format=_custom_format),
200
+ '(<foo/>, 1)'
201
+ )
202
+
203
+ def test_quote_if_str(self):
204
+ self.assertEqual(formatting.quote_if_str(1), 1)
205
+ self.assertEqual(formatting.quote_if_str('foo'), '\'foo\'')
206
+ self.assertEqual(formatting.quote_if_str('foo\'s\na'), '"foo\'s\\na"')
207
+
208
+ def test_comma_delimited_str(self):
209
+ self.assertEqual(
210
+ formatting.comma_delimited_str([1, 2, 'abc']), '1, 2, \'abc\'')
211
+
212
+ def test_auto_plural(self):
213
+ self.assertEqual(formatting.auto_plural(2, 'number'), 'numbers')
214
+ self.assertEqual(formatting.auto_plural(2, 'was', 'were'), 'were')
215
+
216
+
217
+ class FormatTest(unittest.TestCase):
218
+ """Tests for formatting.format."""
219
+
220
+ def test_formattable(self):
221
+
222
+ class A(formatting.Formattable):
223
+
224
+ def format(self, compact=True, **kwargs):
225
+ if compact:
226
+ return 'A()'
227
+ else:
228
+ return 'A(...)'
229
+
230
+ self.assertEqual(str(A()), 'A(...)')
231
+ self.assertEqual(repr(A()), 'A()')
232
+
233
+ def test_simple_types(self):
234
+ self.assertEqual(formatting.format(True, compact=True), 'True')
235
+ self.assertEqual(formatting.format(1, compact=True), '1')
236
+ self.assertEqual(formatting.format(1.0, compact=True), '1.0')
237
+ self.assertEqual(formatting.format('foo', compact=True), '\'foo\'')
238
+ self.assertEqual(
239
+ formatting.format('foo\'s\na', compact=True), '"foo\'s\\na"')
240
+
241
+ # Compact=False has no impact on simple types.
242
+ self.assertEqual(formatting.format(True, compact=False), 'True')
243
+ self.assertEqual(formatting.format(1, compact=False), '1')
244
+ self.assertEqual(formatting.format(1.0, compact=False), '1.0')
245
+ self.assertEqual(formatting.format('foo', compact=False), '\'foo\'')
246
+ self.assertEqual(
247
+ formatting.format('foo\'s\na', compact=False), '"foo\'s\\na"')
248
+
249
+ # Verbose has no impact on simple types.
250
+ self.assertEqual(formatting.format(True, verbose=True), 'True')
251
+ self.assertEqual(formatting.format(1, verbose=True), '1')
252
+ self.assertEqual(formatting.format(1.0, verbose=True), '1.0')
253
+ self.assertEqual(formatting.format('foo', verbose=True), '\'foo\'')
254
+ self.assertEqual(
255
+ formatting.format('foo\'s\na', verbose=True), '"foo\'s\\na"')
256
+
257
+ # Root indent has no impact on simple types.
258
+ self.assertEqual(formatting.format(True, root_indent=4), 'True')
259
+ self.assertEqual(formatting.format(1, root_indent=4), '1')
260
+ self.assertEqual(formatting.format(1.0, root_indent=4), '1.0')
261
+ self.assertEqual(formatting.format('foo', root_indent=4), '\'foo\'')
262
+ self.assertEqual(
263
+ formatting.format('foo\'s\na', root_indent=4), '"foo\'s\\na"')
264
+
265
+ def test_complex_types(self):
266
+
267
+ class CustomFormattable(formatting.Formattable):
268
+ """Custom formattable."""
269
+
270
+ def format(self, custom_param=None, **kwargs):
271
+ return f'CustomFormattable({custom_param})'
272
+
273
+ class A:
274
+ pass
275
+
276
+ self.assertEqual(
277
+ formatting.format(
278
+ {
279
+ 'a': CustomFormattable(),
280
+ 'b': {
281
+ 'c': [1, 2, 3],
282
+ 'd': ['foo', 'bar\na', 3, 4, 5]
283
+ }
284
+ },
285
+ compact=True,
286
+ custom_param='foo'),
287
+ "{'a': CustomFormattable(foo), 'b': {'c': [1, 2, 3], "
288
+ "'d': ['foo', 'bar\\na', 3, 4, 5]}}")
289
+
290
+ self.assertEqual(
291
+ formatting.format(
292
+ {
293
+ 'a': A(),
294
+ 'b': {
295
+ 'c': [1, 2, 3],
296
+ 'd': ['foo', 'bar\na', 3, 4, 5]
297
+ }
298
+ },
299
+ compact=False,
300
+ list_wrap_threshold=15,
301
+ strip_object_id=True),
302
+ inspect.cleandoc("""{
303
+ 'a': A(...),
304
+ 'b': {
305
+ 'c': [1, 2, 3],
306
+ 'd': [
307
+ 'foo',
308
+ 'bar\\na',
309
+ 3,
310
+ 4,
311
+ 5
312
+ ]
313
+ }
314
+ }"""))
315
+
316
+ def test_include_exclude_keys(self):
317
+ """Test format with excluded keys."""
318
+
319
+ class A:
320
+ pass
321
+
322
+ class B(formatting.Formattable):
323
+ """Custom formattable."""
324
+
325
+ def format(
326
+ self, custom_param=None,
327
+ include_keys=None, exclude_keys=None, **kwargs):
328
+ exclude_keys = exclude_keys or set()
329
+ kv = dict(a=1, b=2, c=3)
330
+ def _should_include(k):
331
+ if include_keys:
332
+ return k in include_keys
333
+ return k not in exclude_keys
334
+ kv_pairs = [(k, v, None) for k, v in kv.items() if _should_include(k)]
335
+ return f'B({formatting.kvlist_str(kv_pairs, compact=True)})'
336
+
337
+ self.assertEqual(
338
+ formatting.format(B(), compact=False, include_keys=set(['a', 'c'])),
339
+ 'B(a=1, c=3)')
340
+ self.assertEqual(
341
+ formatting.format(B(), compact=False, exclude_keys=set(['a', 'c'])),
342
+ 'B(b=2)')
343
+ self.assertEqual(
344
+ formatting.format(
345
+ {
346
+ 'a': A(),
347
+ 'b': B(),
348
+ 'c': {
349
+ 'd': [1, 2, 3],
350
+ }
351
+ },
352
+ compact=False,
353
+ list_wrap_threshold=15,
354
+ strip_object_id=True,
355
+ # 'a' should be removed, but 'b.a', 'c.d' should be kept as they are
356
+ # not at the top level.
357
+ exclude_keys=set(['a', 'd'])),
358
+ inspect.cleandoc("""{
359
+ 'b': B(a=1, b=2, c=3),
360
+ 'c': {
361
+ 'd': [1, 2, 3]
362
+ }
363
+ }"""))
364
+
365
+ def test_custom_format(self):
366
+
367
+ class A:
368
+
369
+ def _repr_xml_(self):
370
+ return '<a/>'
371
+
372
+ def __repr__(self):
373
+ return 'A()'
374
+
375
+ def __str__(self):
376
+ return 'AA()'
377
+
378
+ self.assertEqual(formatting.format(A), str(A))
379
+ self.assertEqual(formatting.format(A()), 'AA()')
380
+ def _custom_format(v, root_indent):
381
+ del root_indent
382
+ f = getattr(v, '_repr_xml_', None)
383
+ return f() if f else None
384
+
385
+ self.assertEqual(
386
+ formatting.format(A(), custom_format=_custom_format), '<a/>'
387
+ )
388
+ self.assertEqual(
389
+ formatting.format(A(), compact=True),
390
+ 'A()'
391
+ )
392
+ self.assertEqual(
393
+ formatting.format(A(), compact=True, custom_format=_custom_format),
394
+ '<a/>'
395
+ )
396
+ self.assertEqual(
397
+ formatting.format([A()], compact=True, custom_format=_custom_format),
398
+ '[<a/>]'
399
+ )
400
+ self.assertEqual(
401
+ formatting.format((A(), 1), compact=True, custom_format=_custom_format),
402
+ '(<a/>, 1)'
403
+ )
404
+ self.assertEqual(
405
+ formatting.format(
406
+ dict(x=A()), compact=True, custom_format=_custom_format
407
+ ),
408
+ '{\'x\': <a/>}'
409
+ )
410
+
411
+ def test_markdown(self):
412
+
413
+ class A(formatting.Formattable):
414
+ def __init__(self, x):
415
+ self.x = x
416
+
417
+ def format(self, *args, **kwargs):
418
+ del args, kwargs
419
+ return 'A(' + formatting.format(self.x) + ')'
420
+
421
+ with formatting.str_format(markdown=True):
422
+ self.assertEqual(str(A(1)), '`A(1)`')
423
+ self.assertEqual(str(A([A(1)])), '`A([A(1)])`')
424
+ self.assertEqual(
425
+ formatting.format([1], compact=True, markdown=True), '`[1]`'
426
+ )
427
+
428
+ self.assertEqual(
429
+ formatting.format(
430
+ [1, 2, 3], list_wrap_threshold=5, compact=False, markdown=True
431
+ ),
432
+ inspect.cleandoc("""
433
+ ```
434
+ [
435
+ 1,
436
+ 2,
437
+ 3
438
+ ]
439
+ ```
440
+ """),
441
+ )
442
+
443
+ def test_max_len(self):
444
+ self.assertEqual(
445
+ formatting.format('foo', max_str_len=2), '\'fo...\''
446
+ )
447
+ self.assertEqual(
448
+ formatting.format(b'bar', max_bytes_len=2), 'b\'ba...\''
449
+ )
450
+
451
+
452
+ if __name__ == '__main__':
453
+ unittest.main()
@@ -14,9 +14,9 @@
14
14
  """Operating hierarchical object."""
15
15
 
16
16
  from typing import Any, Callable, Dict, List, Optional, Tuple, Union
17
- from pyglove.core.object_utils import common_traits
18
- from pyglove.core.object_utils.missing import MISSING_VALUE
19
- from pyglove.core.object_utils.value_location import KeyPath
17
+ from pyglove.core.utils import common_traits
18
+ from pyglove.core.utils.missing import MISSING_VALUE
19
+ from pyglove.core.utils.value_location import KeyPath
20
20
 
21
21
 
22
22
  def traverse(value: Any,
@@ -33,7 +33,7 @@ def traverse(value: Any,
33
33
  print(path)
34
34
 
35
35
  tree = {'a': [{'c': [1, 2]}, {'d': {'g': (3, 4)}}], 'b': 'foo'}
36
- pg.object_utils.traverse(tree, preorder_visit)
36
+ pg.utils.traverse(tree, preorder_visit)
37
37
 
38
38
  # Should print:
39
39
  # 'a'
@@ -48,10 +48,10 @@ def traverse(value: Any,
48
48
 
49
49
  Args:
50
50
  value: A maybe hierarchical value to traverse.
51
- preorder_visitor_fn: Preorder visitor function.
52
- Function signature is (path, value) -> should_continue.
53
- postorder_visitor_fn: Postorder visitor function.
54
- Function signature is (path, value) -> should_continue.
51
+ preorder_visitor_fn: Preorder visitor function. Function signature is (path,
52
+ value) -> should_continue.
53
+ postorder_visitor_fn: Postorder visitor function. Function signature is
54
+ (path, value) -> should_continue.
55
55
  root_path: The key path of the root value.
56
56
 
57
57
  Returns:
@@ -111,7 +111,7 @@ def transform(value: Any,
111
111
  'e': 'bar',
112
112
  'f': 4
113
113
  }
114
- output = pg.object_utils.transform(inputs, _remove_int)
114
+ output = pg.utils.transform(inputs, _remove_int)
115
115
  assert output == {
116
116
  'a': {
117
117
  'c': ['bar'],
@@ -123,11 +123,11 @@ def transform(value: Any,
123
123
  Args:
124
124
  value: Any python value type. If value is a list of dict, transformation
125
125
  will occur recursively.
126
- transform_fn: Transform function in signature
127
- (path, value) -> new value
128
- If new value is MISSING_VALUE, key will be deleted.
126
+ transform_fn: Transform function in signature (path, value) -> new value If
127
+ new value is MISSING_VALUE, key will be deleted.
129
128
  root_path: KeyPath of the root.
130
129
  inplace: If True, perform transformation in place.
130
+
131
131
  Returns:
132
132
  Transformed value.
133
133
  """
@@ -186,7 +186,7 @@ def flatten(src: Any, flatten_complex_keys: bool = True) -> Any:
186
186
  'b': 'hi',
187
187
  'c': None
188
188
  }
189
- output = pg.object_utils.flatten(inputs)
189
+ output = pg.utils.flatten(inputs)
190
190
  assert output == {
191
191
  'a.e': 1,
192
192
  'a.f[0].g': 2,
@@ -200,9 +200,9 @@ def flatten(src: Any, flatten_complex_keys: bool = True) -> Any:
200
200
  Args:
201
201
  src: source value to flatten.
202
202
  flatten_complex_keys: if True, complex keys such as 'x.y' will be flattened
203
- as 'x'.'y'. For example:
204
- {'a': {'b.c': 1}} will be flattened into {'a.b.c': 1} if this flag is on,
205
- otherwise it will be flattened as {'a[b.c]': 1}.
203
+ as 'x'.'y'. For example: {'a': {'b.c': 1}} will be flattened into
204
+ {'a.b.c': 1} if this flag is on, otherwise it will be flattened as
205
+ {'a[b.c]': 1}.
206
206
 
207
207
  Returns:
208
208
  For primitive value types, `src` itself will be returned.
@@ -464,7 +464,7 @@ def merge(value_list: List[Any],
464
464
  'f': 10
465
465
  }
466
466
  }
467
- output = pg.object_utils.merge([original, patch])
467
+ output = pg.utils.merge([original, patch])
468
468
  assert output == {
469
469
  'a': 1,
470
470
  # b is updated.
@@ -486,14 +486,12 @@ def merge(value_list: List[Any],
486
486
  value. The merge process will keep input values intact.
487
487
  merge_fn: A function to handle value merge that will be called for updated
488
488
  or added keys. If a branch is added/updated, the root of branch will be
489
- passed to merge_fn.
490
- the signature of function is:
491
- `(path, left_value, right_value) -> final_value`
492
- If a key is only present in src dict, old_value is MISSING_VALUE;
493
- If a key is only present in dest dict, new_value is MISSING_VALUE;
494
- otherwise both new_value and old_value are filled.
495
- If final_value is MISSING_VALUE for a path, it will be removed from its
496
- parent collection.
489
+ passed to merge_fn. the signature of function is: `(path, left_value,
490
+ right_value) -> final_value` If a key is only present in src dict,
491
+ old_value is MISSING_VALUE; If a key is only present in dest dict,
492
+ new_value is MISSING_VALUE; otherwise both new_value and old_value are
493
+ filled. If final_value is MISSING_VALUE for a path, it will be removed
494
+ from its parent collection.
497
495
 
498
496
  Returns:
499
497
  A merged value.
@@ -11,12 +11,10 @@
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.hierarchical."""
15
-
16
14
  import unittest
17
- from pyglove.core.object_utils import common_traits
18
- from pyglove.core.object_utils import hierarchical
19
- from pyglove.core.object_utils import value_location
15
+ from pyglove.core.utils import common_traits
16
+ from pyglove.core.utils import hierarchical
17
+ from pyglove.core.utils import value_location
20
18
 
21
19
 
22
20
  class TraverseTest(unittest.TestCase):