pyglove 0.4.5.dev202504220810__py3-none-any.whl → 0.4.5.dev202504230810__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.
@@ -161,6 +161,7 @@ def kvlist_str(
161
161
  label: Optional[str] = None,
162
162
  bracket_type: BracketType = BracketType.ROUND,
163
163
  custom_format: Optional[CustomFormatFn] = None,
164
+ memo: Optional[Set[int]] = None,
164
165
  **kwargs,
165
166
  ) -> str:
166
167
  """Formats a list key/value pairs into a comma delimited string.
@@ -178,6 +179,8 @@ def kvlist_str(
178
179
  custom_format: An optional custom format function, which will be applied to
179
180
  each value (and child values) in kvlist. If the function returns None, it
180
181
  will fall back to the default `pg.format`.
182
+ memo: A set of object ids that have been formatted. Used to avoid
183
+ infinite recursion in the formatting process.
181
184
  **kwargs: Keyword arguments that will be passed through unto child
182
185
  ``Formattable`` objects.
183
186
  Returns:
@@ -208,6 +211,7 @@ def kvlist_str(
208
211
  verbose=verbose,
209
212
  root_indent=child_indent,
210
213
  custom_format=custom_format,
214
+ memo=memo,
211
215
  **kwargs
212
216
  )
213
217
  if not compact:
@@ -271,6 +275,7 @@ def format( # pylint: disable=redefined-builtin
271
275
  max_bytes_len: Optional[int] = None,
272
276
  *,
273
277
  custom_format: Optional[CustomFormatFn] = None,
278
+ memo: Optional[Set[int]] = None,
274
279
  **kwargs,
275
280
  ) -> str:
276
281
  """Formats a (maybe) hierarchical value with flags.
@@ -297,6 +302,8 @@ def format( # pylint: disable=redefined-builtin
297
302
  custom_format: An optional custom format function, which will be applied to
298
303
  each value (and child values) in kvlist. If the function returns None, it
299
304
  will fall back to the default `pg.format`.
305
+ memo: A set of object ids that have been formatted. Used to avoid
306
+ infinite recursion in the formatting process.
300
307
  **kwargs: Keyword arguments that will be passed through unto child
301
308
  ``Formattable`` objects.
302
309
 
@@ -309,6 +316,14 @@ def format( # pylint: disable=redefined-builtin
309
316
  if result is not None:
310
317
  return maybe_markdown_quote(result, markdown)
311
318
 
319
+ if memo is None:
320
+ memo = set()
321
+
322
+ id_ = id(value)
323
+ if id_ in memo:
324
+ return f'<recursive {value.__class__.__name__} at 0x{id_:x}>' # pylint: disable=bad-whitespace
325
+ memo.add(id_)
326
+
312
327
  exclude_keys = exclude_keys or set()
313
328
 
314
329
  def _should_include_key(key: str) -> bool:
@@ -327,6 +342,7 @@ def format( # pylint: disable=redefined-builtin
327
342
  max_str_len=max_str_len,
328
343
  max_bytes_len=max_bytes_len,
329
344
  custom_format=custom_format,
345
+ memo=memo,
330
346
  **kwargs
331
347
  )
332
348
 
@@ -345,6 +361,7 @@ def format( # pylint: disable=redefined-builtin
345
361
  max_str_len=max_str_len,
346
362
  max_bytes_len=max_bytes_len,
347
363
  custom_format=custom_format,
364
+ memo=memo,
348
365
  **kwargs
349
366
  )
350
367
  elif isinstance(value, (list, tuple)):
@@ -393,6 +410,7 @@ def format( # pylint: disable=redefined-builtin
393
410
  if compact else str_ext(value, custom_format, root_indent)]
394
411
  if strip_object_id and 'object at 0x' in s[-1]:
395
412
  s = [f'{value.__class__.__name__}(...)']
413
+ memo.remove(id_)
396
414
  return maybe_markdown_quote(''.join(s), markdown)
397
415
 
398
416
 
@@ -454,10 +472,10 @@ def camel_to_snake(text: str, separator: str = '_') -> str:
454
472
  return (separator.join(c for c in chunks if c)).lower()
455
473
 
456
474
 
457
- def printv(v: Any, **kwargs):
475
+ def printv(*args, **kwargs):
458
476
  """Prints formatted value."""
459
477
  fs = kwargs.pop('file', sys.stdout)
460
- print(format(v, **kwargs), file=fs)
478
+ print(*[format(v, **kwargs) for v in args], file=fs)
461
479
 
462
480
 
463
481
  def _indent(text: str, indent: int) -> str:
@@ -12,6 +12,7 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
  import inspect
15
+ import io
15
16
  import unittest
16
17
 
17
18
  from pyglove.core.utils import formatting
@@ -408,6 +409,26 @@ class FormatTest(unittest.TestCase):
408
409
  '{\'x\': <a/>}'
409
410
  )
410
411
 
412
+ def test_recursion(self):
413
+ # Recursive dict.
414
+ x = dict(x=1)
415
+ x['y'] = x
416
+ self.assertEqual(
417
+ formatting.format(x, compact=True),
418
+ f'{{\'x\': 1, \'y\': <recursive dict at 0x{id(x):x}>}}'
419
+ )
420
+ self.assertEqual(
421
+ formatting.format(x, compact=False),
422
+ f'{{\n \'x\': 1,\n \'y\': <recursive dict at 0x{id(x):x}>\n}}'
423
+ )
424
+ # Non-recursive dict.
425
+ y = dict(x=1)
426
+ a = dict(x=y, y=y)
427
+ self.assertEqual(
428
+ formatting.format(a, compact=True),
429
+ '{\'x\': {\'x\': 1}, \'y\': {\'x\': 1}}'
430
+ )
431
+
411
432
  def test_markdown(self):
412
433
 
413
434
  class A(formatting.Formattable):
@@ -448,6 +469,11 @@ class FormatTest(unittest.TestCase):
448
469
  formatting.format(b'bar', max_bytes_len=2), 'b\'ba...\''
449
470
  )
450
471
 
472
+ def test_printv(self):
473
+ with io.StringIO() as f:
474
+ formatting.printv(1, 2, 3, file=f)
475
+ self.assertEqual(f.getvalue(), '1 2 3\n')
476
+
451
477
 
452
478
  if __name__ == '__main__':
453
479
  unittest.main()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyglove
3
- Version: 0.4.5.dev202504220810
3
+ Version: 0.4.5.dev202504230810
4
4
  Summary: PyGlove: A library for manipulating Python objects.
5
5
  Home-page: https://github.com/google/pyglove
6
6
  Author: PyGlove Authors
@@ -139,8 +139,8 @@ pyglove/core/utils/docstr_utils.py,sha256=5BY40kXozPKVGOB0eN8jy1P5_GHIzqFJ9FXAu_
139
139
  pyglove/core/utils/docstr_utils_test.py,sha256=i33VT6zHXEuIIJ4PPg1bVSfaYSnUsC8yST_NfpT-Uds,4228
140
140
  pyglove/core/utils/error_utils.py,sha256=ACIqtq4hsWrV00Gxd1OyeHCI-obGBR6J3H9VI1Y9clM,5780
141
141
  pyglove/core/utils/error_utils_test.py,sha256=zwTzmyJupIW8GeF3Z-gnpVNdhdTYwzmO0ClODqbR4bU,4164
142
- pyglove/core/utils/formatting.py,sha256=qa5XEGRwKEEqEKtyEYZNdsKBxIQFGBTofFW29aQT8hU,14987
143
- pyglove/core/utils/formatting_test.py,sha256=OQboyCwhgvhw4EcCj1q-O53XG404c0XMK51rCl2_q2M,13533
142
+ pyglove/core/utils/formatting.py,sha256=Wn4d933LQLhuMIfjdRJgpxOThCxBxQrkRBa6Z1-hL_I,15591
143
+ pyglove/core/utils/formatting_test.py,sha256=hhg-nL6DyE5A2QA92ALHK5QtfAYKfPpTbBARF-IT1j0,14241
144
144
  pyglove/core/utils/hierarchical.py,sha256=jwB-0FhqOspAymAkvJphRhPTQEsoShmKupCZpU3Vip4,19690
145
145
  pyglove/core/utils/hierarchical_test.py,sha256=f382DMJPa_bavJGGQDjuw-hWcafUg5bkQCPX-nbzeiI,21077
146
146
  pyglove/core/utils/json_conversion.py,sha256=I0mWn87aAEdaAok9nDvT0ZrmplU40eNmEDUAaNIzZXk,26590
@@ -212,8 +212,8 @@ pyglove/ext/scalars/randoms.py,sha256=LkMIIx7lOq_lvJvVS3BrgWGuWl7Pi91-lA-O8x_gZs
212
212
  pyglove/ext/scalars/randoms_test.py,sha256=nEhiqarg8l_5EOucp59CYrpO2uKxS1pe0hmBdZUzRNM,2000
213
213
  pyglove/ext/scalars/step_wise.py,sha256=IDw3tuTpv0KVh7AN44W43zqm1-E0HWPUlytWOQC9w3Y,3789
214
214
  pyglove/ext/scalars/step_wise_test.py,sha256=TL1vJ19xVx2t5HKuyIzGoogF7N3Rm8YhLE6JF7i0iy8,2540
215
- pyglove-0.4.5.dev202504220810.dist-info/licenses/LICENSE,sha256=WNHhf_5RCaeuKWyq_K39vmp9F28LxKsB4SpomwSZ2L0,11357
216
- pyglove-0.4.5.dev202504220810.dist-info/METADATA,sha256=-Sj2NcVurblr6oRtg8MuQGrWR4BZUY3dx_hYoojtNuA,7089
217
- pyglove-0.4.5.dev202504220810.dist-info/WHEEL,sha256=pxyMxgL8-pra_rKaQ4drOZAegBVuX-G_4nRHjjgWbmo,91
218
- pyglove-0.4.5.dev202504220810.dist-info/top_level.txt,sha256=wITzJSKcj8GZUkbq-MvUQnFadkiuAv_qv5qQMw0fIow,8
219
- pyglove-0.4.5.dev202504220810.dist-info/RECORD,,
215
+ pyglove-0.4.5.dev202504230810.dist-info/licenses/LICENSE,sha256=WNHhf_5RCaeuKWyq_K39vmp9F28LxKsB4SpomwSZ2L0,11357
216
+ pyglove-0.4.5.dev202504230810.dist-info/METADATA,sha256=R5ML7dzXaFureIQjy76mwR2AHzaVU_ZskS2X4O_uXTM,7089
217
+ pyglove-0.4.5.dev202504230810.dist-info/WHEEL,sha256=pxyMxgL8-pra_rKaQ4drOZAegBVuX-G_4nRHjjgWbmo,91
218
+ pyglove-0.4.5.dev202504230810.dist-info/top_level.txt,sha256=wITzJSKcj8GZUkbq-MvUQnFadkiuAv_qv5qQMw0fIow,8
219
+ pyglove-0.4.5.dev202504230810.dist-info/RECORD,,