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
@@ -25,27 +25,30 @@ import typing
25
25
  from typing import Any, Callable, Dict, Iterator, List, Literal, Optional, Tuple, Type, Union
26
26
 
27
27
  from pyglove.core import io as pg_io
28
- from pyglove.core import object_utils
29
28
  from pyglove.core import typing as pg_typing
29
+ from pyglove.core import utils
30
30
  from pyglove.core.symbolic import flags
31
31
  from pyglove.core.symbolic.origin import Origin
32
32
  from pyglove.core.symbolic.pure_symbolic import NonDeterministic
33
33
  from pyglove.core.symbolic.pure_symbolic import PureSymbolic
34
+ from pyglove.core.views.html.base import HtmlConvertible
34
35
 
35
36
 
36
37
  class WritePermissionError(Exception):
37
38
  """Exception raisen when write access to object fields is not allowed."""
38
39
 
39
40
 
40
- class FieldUpdate(object_utils.Formattable):
41
+ class FieldUpdate(utils.Formattable):
41
42
  """Class that describes an update to a field in an object tree."""
42
43
 
43
- def __init__(self,
44
- path: object_utils.KeyPath,
45
- target: 'Symbolic',
46
- field: Optional[pg_typing.Field],
47
- old_value: Any,
48
- new_value: Any):
44
+ def __init__(
45
+ self,
46
+ path: utils.KeyPath,
47
+ target: 'Symbolic',
48
+ field: Optional[pg_typing.Field],
49
+ old_value: Any,
50
+ new_value: Any,
51
+ ):
49
52
  """Constructor.
50
53
 
51
54
  Args:
@@ -66,32 +69,21 @@ class FieldUpdate(object_utils.Formattable):
66
69
  compact: bool = False,
67
70
  verbose: bool = True,
68
71
  root_indent: int = 0,
69
- *,
70
- python_format: bool = False,
71
- markdown: bool = False,
72
- hide_default_values: bool = False,
73
- hide_missing_values: bool = False,
74
72
  **kwargs,
75
73
  ) -> str:
76
74
  """Formats this object."""
77
- kwargs.update({
78
- 'python_format': python_format,
79
- 'hide_default_values': hide_default_values,
80
- 'hide_missing_values': hide_missing_values,
81
- })
82
- details = object_utils.kvlist_str([
83
- ('parent_path', self.target.sym_path, None),
84
- ('path', self.path.path, None),
85
- ('old_value', object_utils.format(
86
- self.old_value, compact, verbose, root_indent + 1, **kwargs),
87
- object_utils.MISSING_VALUE),
88
- ('new_value',
89
- object_utils.format(
90
- self.new_value, compact, verbose, root_indent + 1, **kwargs),
91
- object_utils.MISSING_VALUE),
92
- ])
93
- return object_utils.maybe_markdown_quote(
94
- f'{self.__class__.__name__}({details})', markdown
75
+ return utils.kvlist_str(
76
+ [
77
+ ('parent_path', self.target.sym_path, None),
78
+ ('path', self.path, None),
79
+ ('old_value', self.old_value, utils.MISSING_VALUE),
80
+ ('new_value', self.new_value, utils.MISSING_VALUE),
81
+ ],
82
+ label=self.__class__.__name__,
83
+ compact=compact,
84
+ verbose=verbose,
85
+ root_indent=root_indent,
86
+ **kwargs,
95
87
  )
96
88
 
97
89
  def __eq__(self, other: Any) -> bool:
@@ -134,11 +126,11 @@ class TopologyAware(metaclass=abc.ABCMeta):
134
126
 
135
127
  @property
136
128
  @abc.abstractmethod
137
- def sym_path(self) -> object_utils.KeyPath:
129
+ def sym_path(self) -> utils.KeyPath:
138
130
  """Returns the path of this object under its topology."""
139
131
 
140
132
  @abc.abstractmethod
141
- def sym_setpath(self, path: object_utils.KeyPath) -> None:
133
+ def sym_setpath(self, path: utils.KeyPath) -> None:
142
134
  """Sets the path of this object under its topology."""
143
135
 
144
136
 
@@ -182,9 +174,10 @@ RAISE_IF_NOT_FOUND = (pg_typing.MISSING_VALUE,)
182
174
 
183
175
  class Symbolic(
184
176
  TopologyAware,
185
- object_utils.JSONConvertible,
186
- object_utils.MaybePartial,
187
- object_utils.Formattable,
177
+ utils.Formattable,
178
+ utils.JSONConvertible,
179
+ utils.MaybePartial,
180
+ HtmlConvertible,
188
181
  ):
189
182
  """Base for all symbolic types.
190
183
 
@@ -212,13 +205,15 @@ class Symbolic(
212
205
 
213
206
  # pylint: enable=invalid-name
214
207
 
215
- def __init__(self,
216
- *,
217
- allow_partial: bool,
218
- accessor_writable: bool,
219
- sealed: bool,
220
- root_path: Optional[object_utils.KeyPath],
221
- init_super: bool = True):
208
+ def __init__(
209
+ self,
210
+ *,
211
+ allow_partial: bool,
212
+ accessor_writable: bool,
213
+ sealed: bool,
214
+ root_path: Optional[utils.KeyPath],
215
+ init_super: bool = True,
216
+ ):
222
217
  """Constructor.
223
218
 
224
219
  Args:
@@ -246,7 +241,7 @@ class Symbolic(
246
241
  # NOTE(daiyip): parent is used for rebind call to notify their ancestors
247
242
  # for updates, not for external usage.
248
243
  self._set_raw_attr('_sym_parent', None)
249
- self._set_raw_attr('_sym_path', root_path or object_utils.KeyPath())
244
+ self._set_raw_attr('_sym_path', root_path or utils.KeyPath())
250
245
  self._set_raw_attr('_sym_puresymbolic', None)
251
246
  self._set_raw_attr('_sym_missing_values', None)
252
247
  self._set_raw_attr('_sym_nondefault_values', None)
@@ -319,14 +314,14 @@ class Symbolic(
319
314
  """Seals or unseals current object from further modification."""
320
315
  return self._set_raw_attr('_sealed', is_seal)
321
316
 
322
- def sym_missing(self, flatten: bool = True) -> Dict[str, Any]:
317
+ def sym_missing(self, flatten: bool = True) -> Dict[Union[str, int], Any]:
323
318
  """Returns missing values."""
324
319
  missing = getattr(self, '_sym_missing_values')
325
320
  if missing is None:
326
321
  missing = self._sym_missing()
327
322
  self._set_raw_attr('_sym_missing_values', missing)
328
323
  if flatten:
329
- missing = object_utils.flatten(missing)
324
+ missing = utils.flatten(missing)
330
325
  return missing
331
326
 
332
327
  def sym_nondefault(self, flatten: bool = True) -> Dict[Union[int, str], Any]:
@@ -336,7 +331,7 @@ class Symbolic(
336
331
  nondefault = self._sym_nondefault()
337
332
  self._set_raw_attr('_sym_nondefault_values', nondefault)
338
333
  if flatten:
339
- nondefault = object_utils.flatten(nondefault)
334
+ nondefault = utils.flatten(nondefault)
340
335
  return nondefault
341
336
 
342
337
  @property
@@ -414,7 +409,7 @@ class Symbolic(
414
409
  def sym_attr_field(self, key: Union[str, int]) -> Optional[pg_typing.Field]:
415
410
  """Returns the field definition for a symbolic attribute."""
416
411
 
417
- def sym_has(self, path: Union[object_utils.KeyPath, str, int]) -> bool:
412
+ def sym_has(self, path: Union[utils.KeyPath, str, int]) -> bool:
418
413
  """Returns True if a path exists in the sub-tree.
419
414
 
420
415
  Args:
@@ -423,13 +418,14 @@ class Symbolic(
423
418
  Returns:
424
419
  True if the path exists in current sub-tree, otherwise False.
425
420
  """
426
- return object_utils.KeyPath.from_value(path).exists(self)
421
+ return utils.KeyPath.from_value(path).exists(self)
427
422
 
428
423
  def sym_get(
429
424
  self,
430
- path: Union[object_utils.KeyPath, str, int],
425
+ path: Union[utils.KeyPath, str, int],
431
426
  default: Any = RAISE_IF_NOT_FOUND,
432
- use_inferred: bool = False) -> Any:
427
+ use_inferred: bool = False,
428
+ ) -> Any:
433
429
  """Returns a sub-node by path.
434
430
 
435
431
  NOTE: there is no `sym_set`, use `sym_rebind`.
@@ -448,7 +444,7 @@ class Symbolic(
448
444
  Raises:
449
445
  KeyError if `path` does not exist and `default` is not specified.
450
446
  """
451
- path = object_utils.KeyPath.from_value(path)
447
+ path = utils.KeyPath.from_value(path)
452
448
  if default is RAISE_IF_NOT_FOUND:
453
449
  return path.query(self, use_inferred=use_inferred)
454
450
  else:
@@ -536,18 +532,17 @@ class Symbolic(
536
532
  def sym_contains(
537
533
  self,
538
534
  value: Any = None,
539
- type: Union[None, Type[Any], Tuple[Type[Any]]] = None # pylint: disable=redefined-builtin
535
+ type: Union[None, Type[Any], Tuple[Type[Any], ...]] = None # pylint: disable=redefined-builtin
540
536
  ) -> bool:
541
537
  """Returns True if the object contains sub-nodes of given value or type."""
542
538
  return contains(self, value, type)
543
539
 
544
540
  @property
545
- def sym_path(self) -> object_utils.KeyPath:
541
+ def sym_path(self) -> utils.KeyPath:
546
542
  """Returns the path of current object from the root of its symbolic tree."""
547
543
  return getattr(self, '_sym_path')
548
544
 
549
- def sym_setpath(
550
- self, path: Optional[Union[str, object_utils.KeyPath]]) -> None:
545
+ def sym_setpath(self, path: Optional[Union[str, utils.KeyPath]]) -> None:
551
546
  """Sets the path of current node in its symbolic tree."""
552
547
  if self.sym_path != path:
553
548
  old_path = self.sym_path
@@ -556,11 +551,9 @@ class Symbolic(
556
551
 
557
552
  def sym_rebind(
558
553
  self,
559
- path_value_pairs: Optional[Union[
560
- Dict[
561
- Union[object_utils.KeyPath, str, int],
562
- Any],
563
- Callable]] = None, # pylint: disable=g-bare-generic
554
+ path_value_pairs: Optional[
555
+ Union[Dict[Union[utils.KeyPath, str, int], Any], Callable[..., Any]]
556
+ ] = None, # pylint: disable=g-bare-generic
564
557
  *,
565
558
  raise_on_no_change: bool = True,
566
559
  notify_parents: bool = True,
@@ -584,8 +577,9 @@ class Symbolic(
584
577
  f'Argument \'path_value_pairs\' should be a dict. '
585
578
  f'Encountered {path_value_pairs}'))
586
579
  path_value_pairs.update(kwargs)
587
- path_value_pairs = {object_utils.KeyPath.from_value(k): v
588
- for k, v in path_value_pairs.items()}
580
+ path_value_pairs = {
581
+ utils.KeyPath.from_value(k): v for k, v in path_value_pairs.items()
582
+ }
589
583
 
590
584
  if not path_value_pairs and raise_on_no_change:
591
585
  raise ValueError(self._error_message('There are no values to rebind.'))
@@ -610,10 +604,9 @@ class Symbolic(
610
604
  return new_value
611
605
 
612
606
  @abc.abstractmethod
613
- def sym_jsonify(self,
614
- *,
615
- hide_default_values: bool = False,
616
- **kwargs) -> object_utils.JSONValueType:
607
+ def sym_jsonify(
608
+ self, *, hide_default_values: bool = False, **kwargs
609
+ ) -> utils.JSONValueType:
617
610
  """Converts representation of current object to a plain Python object."""
618
611
 
619
612
  def sym_ne(self, other: Any) -> bool:
@@ -738,7 +731,7 @@ class Symbolic(
738
731
  """Returns if current object is deterministic."""
739
732
  return is_deterministic(self)
740
733
 
741
- def missing_values(self, flatten: bool = True) -> Dict[str, Any]:
734
+ def missing_values(self, flatten: bool = True) -> Dict[Union[str, int], Any]:
742
735
  """Alias for `sym_missing`."""
743
736
  return self.sym_missing(flatten)
744
737
 
@@ -758,16 +751,15 @@ class Symbolic(
758
751
 
759
752
  def rebind(
760
753
  self,
761
- path_value_pairs: Optional[Union[
762
- Dict[
763
- Union[object_utils.KeyPath, str, int],
764
- Any],
765
- Callable]] = None, # pylint: disable=g-bare-generic
754
+ path_value_pairs: Optional[
755
+ Union[Dict[Union[utils.KeyPath, str, int], Any], Callable[..., Any]]
756
+ ] = None, # pylint: disable=g-bare-generic
766
757
  *,
767
758
  raise_on_no_change: bool = True,
768
759
  notify_parents: bool = True,
769
760
  skip_notification: Optional[bool] = None,
770
- **kwargs) -> 'Symbolic':
761
+ **kwargs,
762
+ ) -> 'Symbolic':
771
763
  """Alias for `sym_rebind`.
772
764
 
773
765
  Alias for `sym_rebind`. `rebind` is the recommended way for mutating
@@ -950,13 +942,13 @@ class Symbolic(
950
942
  """
951
943
  return self.sym_clone(deep, memo, override)
952
944
 
953
- def to_json(self, **kwargs) -> object_utils.JSONValueType:
945
+ def to_json(self, **kwargs) -> utils.JSONValueType:
954
946
  """Alias for `sym_jsonify`."""
955
- return self.sym_jsonify(**kwargs)
947
+ return to_json(self, **kwargs)
956
948
 
957
949
  def to_json_str(self, json_indent: Optional[int] = None, **kwargs) -> str:
958
950
  """Serializes current object into a JSON string."""
959
- return json.dumps(self.sym_jsonify(**kwargs), indent=json_indent)
951
+ return to_json_str(self, json_indent=json_indent, **kwargs)
960
952
 
961
953
  @classmethod
962
954
  def load(cls, *args, **kwargs) -> Any:
@@ -973,13 +965,18 @@ class Symbolic(
973
965
  def inspect(
974
966
  self,
975
967
  path_regex: Optional[str] = None,
976
- where: Optional[Union[Callable[[Any], bool],
977
- Callable[[Any, Any], bool]]] = None,
978
- custom_selector: Optional[Union[
979
- Callable[[object_utils.KeyPath, Any], bool],
980
- Callable[[object_utils.KeyPath, Any, Any], bool]]] = None,
968
+ where: Optional[
969
+ Union[Callable[[Any], bool], Callable[[Any, Any], bool]]
970
+ ] = None,
971
+ custom_selector: Optional[
972
+ Union[
973
+ Callable[[utils.KeyPath, Any], bool],
974
+ Callable[[utils.KeyPath, Any, Any], bool],
975
+ ]
976
+ ] = None,
981
977
  file=sys.stdout, # pylint: disable=redefined-builtin
982
- **kwargs) -> None:
978
+ **kwargs,
979
+ ) -> None:
983
980
  """Inspects current object by printing out selected values.
984
981
 
985
982
  Example::
@@ -1067,7 +1064,7 @@ class Symbolic(
1067
1064
  v = self
1068
1065
  else:
1069
1066
  v = query(self, path_regex, where, False, custom_selector)
1070
- object_utils.print(v, file=file, **kwargs)
1067
+ utils.print(v, file=file, **kwargs)
1071
1068
 
1072
1069
  def __copy__(self) -> 'Symbolic':
1073
1070
  """Overridden shallow copy."""
@@ -1083,8 +1080,8 @@ class Symbolic(
1083
1080
 
1084
1081
  @abc.abstractmethod
1085
1082
  def _sym_rebind(
1086
- self, path_value_pairs: Dict[object_utils.KeyPath, Any]
1087
- ) -> List[FieldUpdate]:
1083
+ self, path_value_pairs: Dict[utils.KeyPath, Any]
1084
+ ) -> List[FieldUpdate]:
1088
1085
  """Subclass specific rebind implementation.
1089
1086
 
1090
1087
  Args:
@@ -1103,7 +1100,7 @@ class Symbolic(
1103
1100
  """
1104
1101
 
1105
1102
  @abc.abstractmethod
1106
- def _sym_missing(self) -> Dict[str, Any]:
1103
+ def _sym_missing(self) -> Dict[Union[str, int], Any]:
1107
1104
  """Returns missing values."""
1108
1105
 
1109
1106
  @abc.abstractmethod
@@ -1120,9 +1117,8 @@ class Symbolic(
1120
1117
 
1121
1118
  @abc.abstractmethod
1122
1119
  def _update_children_paths(
1123
- self,
1124
- old_path: object_utils.KeyPath,
1125
- new_path: object_utils.KeyPath) -> None:
1120
+ self, old_path: utils.KeyPath, new_path: utils.KeyPath
1121
+ ) -> None:
1126
1122
  """Update children paths according to root_path of current node."""
1127
1123
 
1128
1124
  @abc.abstractmethod
@@ -1131,7 +1127,7 @@ class Symbolic(
1131
1127
  """Child should implement: set an item without permission check."""
1132
1128
 
1133
1129
  @abc.abstractmethod
1134
- def _on_change(self, field_updates: Dict[object_utils.KeyPath, FieldUpdate]):
1130
+ def _on_change(self, field_updates: Dict[utils.KeyPath, FieldUpdate]):
1135
1131
  """Event that is triggered when field values in the subtree are updated.
1136
1132
 
1137
1133
  This event will be called
@@ -1184,14 +1180,14 @@ class Symbolic(
1184
1180
  # NOTE(daiyip): make a copy of symbolic object if it belongs to another
1185
1181
  # object tree, this prevents it from having multiple parents. See
1186
1182
  # List._formalized_value for similar logic.
1187
- root_path = object_utils.KeyPath(key, self.sym_path)
1183
+ root_path = utils.KeyPath(key, self.sym_path)
1188
1184
  if (value.sym_parent is not None and
1189
1185
  (value.sym_parent is not self
1190
1186
  or root_path != value.sym_path)):
1191
1187
  value = value.clone()
1192
1188
 
1193
1189
  if isinstance(value, TopologyAware):
1194
- value.sym_setpath(object_utils.KeyPath(key, self.sym_path))
1190
+ value.sym_setpath(utils.KeyPath(key, self.sym_path))
1195
1191
  value.sym_setparent(self._sym_parent_for_children())
1196
1192
  return value
1197
1193
 
@@ -1200,9 +1196,10 @@ class Symbolic(
1200
1196
  return self
1201
1197
 
1202
1198
  def _set_item_of_current_tree(
1203
- self, path: object_utils.KeyPath, value: Any) -> Optional[FieldUpdate]:
1199
+ self, path: utils.KeyPath, value: Any
1200
+ ) -> Optional[FieldUpdate]:
1204
1201
  """Set a field of current tree by key path and return its parent."""
1205
- assert isinstance(path, object_utils.KeyPath), path
1202
+ assert isinstance(path, utils.KeyPath), path
1206
1203
  if not path:
1207
1204
  raise KeyError(
1208
1205
  self._error_message(
@@ -1231,8 +1228,8 @@ class Symbolic(
1231
1228
  per_target_updates = dict()
1232
1229
 
1233
1230
  def _get_target_updates(
1234
- target: 'Symbolic'
1235
- ) -> Dict[object_utils.KeyPath, FieldUpdate]:
1231
+ target: 'Symbolic',
1232
+ ) -> Dict[utils.KeyPath, FieldUpdate]:
1236
1233
  target_id = id(target)
1237
1234
  if target_id not in per_target_updates:
1238
1235
  per_target_updates[target_id] = (target, dict())
@@ -1265,7 +1262,7 @@ class Symbolic(
1265
1262
 
1266
1263
  def _error_message(self, message: str) -> str:
1267
1264
  """Create error message to include path information."""
1268
- return object_utils.message_on_path(message, self.sym_path)
1265
+ return utils.message_on_path(message, self.sym_path)
1269
1266
 
1270
1267
 
1271
1268
  #
@@ -1280,18 +1277,19 @@ def get_rebind_dict(
1280
1277
  """Generate rebind dict using rebinder on target value.
1281
1278
 
1282
1279
  Args:
1283
- rebinder: A callable object with signature:
1284
- (key_path: object_utils.KeyPath, value: Any) -> Any or
1285
- (key_path: object_utils.KeyPath, value: Any, parent: Any) -> Any. If
1286
- rebinder returns the same value from input, the value is considered
1287
- unchanged. Otherwise it will be put into the returning rebind dict. See
1288
- `Symbolic.rebind` for more details.
1280
+ rebinder: A callable object with signature: (key_path: utils.KeyPath, value:
1281
+ Any) -> Any or (key_path: utils.KeyPath, value: Any, parent: Any) -> Any.
1282
+ If rebinder returns the same value from input, the value is considered
1283
+ unchanged. Otherwise it will be put into the returning rebind dict. See
1284
+ `Symbolic.rebind` for more details.
1289
1285
  target: Upon which value the rebind dict is computed.
1290
1286
 
1291
1287
  Returns:
1292
1288
  An ordered dict of key path string to updated value.
1293
1289
  """
1294
- signature = pg_typing.get_signature(rebinder)
1290
+ signature = pg_typing.signature(
1291
+ rebinder, auto_typing=False, auto_doc=False
1292
+ )
1295
1293
  if len(signature.args) == 2:
1296
1294
  select_fn = lambda k, v, p: rebinder(k, v)
1297
1295
  elif len(signature.args) == 3:
@@ -1336,15 +1334,17 @@ class TraverseAction(enum.Enum):
1336
1334
  CONTINUE = 2
1337
1335
 
1338
1336
 
1339
- def traverse(x: Any,
1340
- preorder_visitor_fn: Optional[
1341
- Callable[[object_utils.KeyPath, Any, Any],
1342
- Optional[TraverseAction]]] = None,
1343
- postorder_visitor_fn: Optional[
1344
- Callable[[object_utils.KeyPath, Any, Any],
1345
- Optional[TraverseAction]]] = None,
1346
- root_path: Optional[object_utils.KeyPath] = None,
1347
- parent: Optional[Any] = None) -> bool:
1337
+ def traverse(
1338
+ x: Any,
1339
+ preorder_visitor_fn: Optional[
1340
+ Callable[[utils.KeyPath, Any, Any], Optional[TraverseAction]]
1341
+ ] = None,
1342
+ postorder_visitor_fn: Optional[
1343
+ Callable[[utils.KeyPath, Any, Any], Optional[TraverseAction]]
1344
+ ] = None,
1345
+ root_path: Optional[utils.KeyPath] = None,
1346
+ parent: Optional[Any] = None,
1347
+ ) -> bool:
1348
1348
  """Traverse a (maybe) symbolic value using visitor functions.
1349
1349
 
1350
1350
  Example::
@@ -1379,7 +1379,7 @@ def traverse(x: Any,
1379
1379
  either `TraverseAction.ENTER` or `TraverseAction.CONTINUE` for all nodes.
1380
1380
  Otherwise False.
1381
1381
  """
1382
- root_path = root_path or object_utils.KeyPath()
1382
+ root_path = root_path or utils.KeyPath()
1383
1383
 
1384
1384
  def no_op_visitor(path, value, parent):
1385
1385
  del path, value, parent
@@ -1394,20 +1394,35 @@ def traverse(x: Any,
1394
1394
  if preorder_action is None or preorder_action == TraverseAction.ENTER:
1395
1395
  if isinstance(x, dict):
1396
1396
  for k, v in x.items():
1397
- if not traverse(v, preorder_visitor_fn, postorder_visitor_fn,
1398
- object_utils.KeyPath(k, root_path), x):
1397
+ if not traverse(
1398
+ v,
1399
+ preorder_visitor_fn,
1400
+ postorder_visitor_fn,
1401
+ utils.KeyPath(k, root_path),
1402
+ x,
1403
+ ):
1399
1404
  preorder_action = TraverseAction.STOP
1400
1405
  break
1401
1406
  elif isinstance(x, list):
1402
1407
  for i, v in enumerate(x):
1403
- if not traverse(v, preorder_visitor_fn, postorder_visitor_fn,
1404
- object_utils.KeyPath(i, root_path), x):
1408
+ if not traverse(
1409
+ v,
1410
+ preorder_visitor_fn,
1411
+ postorder_visitor_fn,
1412
+ utils.KeyPath(i, root_path),
1413
+ x,
1414
+ ):
1405
1415
  preorder_action = TraverseAction.STOP
1406
1416
  break
1407
1417
  elif isinstance(x, Symbolic.ObjectType): # pytype: disable=wrong-arg-types
1408
1418
  for k, v in x.sym_items():
1409
- if not traverse(v, preorder_visitor_fn, postorder_visitor_fn,
1410
- object_utils.KeyPath(k, root_path), x):
1419
+ if not traverse(
1420
+ v,
1421
+ preorder_visitor_fn,
1422
+ postorder_visitor_fn,
1423
+ utils.KeyPath(k, root_path),
1424
+ x,
1425
+ ):
1411
1426
  preorder_action = TraverseAction.STOP
1412
1427
  break
1413
1428
  postorder_action = postorder_visitor_fn(root_path, x, parent)
@@ -1420,12 +1435,16 @@ def traverse(x: Any,
1420
1435
  def query(
1421
1436
  x: Any,
1422
1437
  path_regex: Optional[str] = None,
1423
- where: Optional[Union[Callable[[Any], bool],
1424
- Callable[[Any, Any], bool]]] = None,
1438
+ where: Optional[
1439
+ Union[Callable[[Any], bool], Callable[[Any, Any], bool]]
1440
+ ] = None,
1425
1441
  enter_selected: bool = False,
1426
- custom_selector: Optional[Union[
1427
- Callable[[object_utils.KeyPath, Any], bool],
1428
- Callable[[object_utils.KeyPath, Any, Any], bool]]] = None
1442
+ custom_selector: Optional[
1443
+ Union[
1444
+ Callable[[utils.KeyPath, Any], bool],
1445
+ Callable[[utils.KeyPath, Any, Any], bool],
1446
+ ]
1447
+ ] = None,
1429
1448
  ) -> Dict[str, Any]:
1430
1449
  """Queries a (maybe) symbolic value.
1431
1450
 
@@ -1496,7 +1515,9 @@ def query(
1496
1515
  if path_regex is not None or where is not None:
1497
1516
  raise ValueError('\'path_regex\' and \'where\' must be None when '
1498
1517
  '\'custom_selector\' is provided.')
1499
- signature = pg_typing.get_signature(custom_selector)
1518
+ signature = pg_typing.signature(
1519
+ custom_selector, auto_typing=False, auto_doc=False
1520
+ )
1500
1521
  if len(signature.args) == 2:
1501
1522
  select_fn = lambda k, v, p: custom_selector(k, v) # pytype: disable=wrong-arg-count
1502
1523
  elif len(signature.args) == 3:
@@ -1507,7 +1528,7 @@ def query(
1507
1528
  f'(key_path, value, [parent]). Encountered: {signature.args}')
1508
1529
  else:
1509
1530
  if where is not None:
1510
- signature = pg_typing.get_signature(where)
1531
+ signature = pg_typing.signature(where)
1511
1532
  if len(signature.args) == 1:
1512
1533
  where_fn = lambda v, p: where(v) # pytype: disable=wrong-arg-count
1513
1534
  elif len(signature.args) == 2:
@@ -1526,8 +1547,9 @@ def query(
1526
1547
 
1527
1548
  results = {}
1528
1549
 
1529
- def _preorder_visitor(path: object_utils.KeyPath, v: Any,
1530
- parent: Any) -> TraverseAction:
1550
+ def _preorder_visitor(
1551
+ path: utils.KeyPath, v: Any, parent: Any
1552
+ ) -> TraverseAction:
1531
1553
  if select_fn(path, v, parent): # pytype: disable=wrong-arg-count
1532
1554
  results[str(path)] = v
1533
1555
  return TraverseAction.ENTER if enter_selected else TraverseAction.CONTINUE
@@ -1757,7 +1779,7 @@ def gt(left: Any, right: Any) -> bool:
1757
1779
 
1758
1780
  def _type_order(value: Any) -> str:
1759
1781
  """Returns the ordering string of value's type."""
1760
- if isinstance(value, object_utils.MissingValue):
1782
+ if isinstance(value, utils.MissingValue):
1761
1783
  type_order = 0
1762
1784
  elif value is None:
1763
1785
  type_order = 1
@@ -1855,8 +1877,17 @@ def clone(
1855
1877
  """
1856
1878
  if isinstance(x, Symbolic):
1857
1879
  return x.sym_clone(deep, memo, override)
1880
+ elif isinstance(x, list):
1881
+ assert not override, override
1882
+ return [clone(v, deep, memo) for v in x]
1883
+ elif isinstance(x, tuple):
1884
+ assert not override, override
1885
+ return tuple([clone(v, deep, memo) for v in x])
1886
+ elif isinstance(x, dict):
1887
+ assert not override, override
1888
+ return {k: clone(v, deep, memo) for k, v in x.items()}
1858
1889
  else:
1859
- assert not override
1890
+ assert not override, override
1860
1891
  return copy.deepcopy(x, memo) if deep else copy.copy(x)
1861
1892
 
1862
1893
 
@@ -1946,7 +1977,7 @@ def is_abstract(x: Any) -> bool:
1946
1977
  True if value itself is partial/PureSymbolic or its child and nested
1947
1978
  child fields contain partial/PureSymbolic values.
1948
1979
  """
1949
- return object_utils.is_partial(x) or is_pure_symbolic(x)
1980
+ return utils.is_partial(x) or is_pure_symbolic(x)
1950
1981
 
1951
1982
 
1952
1983
  def contains(
@@ -2001,12 +2032,16 @@ def contains(
2001
2032
  return not traverse(x, _contains)
2002
2033
 
2003
2034
 
2004
- def from_json(json_value: Any,
2005
- *,
2006
- allow_partial: bool = False,
2007
- root_path: Optional[object_utils.KeyPath] = None,
2008
- force_dict: bool = False,
2009
- **kwargs) -> Any:
2035
+ def from_json(
2036
+ json_value: Any,
2037
+ *,
2038
+ allow_partial: bool = False,
2039
+ root_path: Optional[utils.KeyPath] = None,
2040
+ auto_import: bool = True,
2041
+ auto_dict: bool = False,
2042
+ value_spec: Optional[pg_typing.ValueSpec] = None,
2043
+ **kwargs,
2044
+ ) -> Any:
2010
2045
  """Deserializes a (maybe) symbolic value from JSON value.
2011
2046
 
2012
2047
  Example::
@@ -2026,8 +2061,13 @@ def from_json(json_value: Any,
2026
2061
  json_value: Input JSON value.
2027
2062
  allow_partial: Whether to allow elements of the list to be partial.
2028
2063
  root_path: KeyPath of loaded object in its object tree.
2029
- force_dict: If True, "_type" keys will be stripped before loading. As a
2030
- result, JSONConvertible objects will be returned as dict.
2064
+ auto_import: If True, when a '_type' is not registered, PyGlove will
2065
+ identify its parent module and automatically import it. For example,
2066
+ if the type is 'foo.bar.A', PyGlove will try to import 'foo.bar' and
2067
+ find the class 'A' within the imported module.
2068
+ auto_dict: If True, dict with '_type' that cannot be loaded will remain
2069
+ as dict, with '_type' renamed to 'type_name'.
2070
+ value_spec: The value spec for the symbolic list or dict.
2031
2071
  **kwargs: Allow passing through keyword arguments to from_json of specific
2032
2072
  types.
2033
2073
 
@@ -2042,41 +2082,68 @@ def from_json(json_value: Any,
2042
2082
  if isinstance(json_value, Symbolic):
2043
2083
  return json_value
2044
2084
 
2045
- if force_dict:
2046
- json_value = object_utils.json_conversion.strip_types(json_value)
2085
+ typename_resolved = kwargs.pop('_typename_resolved', False)
2086
+ if not typename_resolved:
2087
+ json_value = utils.json_conversion.resolve_typenames(
2088
+ json_value, auto_import=auto_import, auto_dict=auto_dict
2089
+ )
2090
+
2091
+ def _load_child(k, v):
2092
+ return from_json(
2093
+ v,
2094
+ root_path=utils.KeyPath(k, root_path),
2095
+ _typename_resolved=True,
2096
+ allow_partial=allow_partial,
2097
+ **kwargs,
2098
+ )
2047
2099
 
2048
- kwargs.update({
2049
- 'allow_partial': allow_partial,
2050
- 'root_path': root_path,
2051
- })
2052
2100
  if isinstance(json_value, list):
2053
- if (json_value
2054
- and json_value[0] == object_utils.JSONConvertible.TUPLE_MARKER):
2101
+ if json_value and json_value[0] == utils.JSONConvertible.TUPLE_MARKER:
2055
2102
  if len(json_value) < 2:
2056
2103
  raise ValueError(
2057
- object_utils.message_on_path(
2058
- f'Tuple should have at least one element '
2059
- f'besides \'{object_utils.JSONConvertible.TUPLE_MARKER}\'. '
2060
- f'Encountered: {json_value}', root_path))
2061
- return tuple([
2062
- from_json(v, allow_partial=allow_partial,
2063
- root_path=object_utils.KeyPath(i, root_path))
2064
- for i, v in enumerate(json_value[1:])
2065
- ])
2066
- return Symbolic.ListType(json_value, **kwargs) # pytype: disable=not-callable # pylint: disable=not-callable
2104
+ utils.message_on_path(
2105
+ 'Tuple should have at least one element '
2106
+ f"besides '{utils.JSONConvertible.TUPLE_MARKER}'. "
2107
+ f'Encountered: {json_value}',
2108
+ root_path,
2109
+ )
2110
+ )
2111
+ return tuple(_load_child(i, v) for i, v in enumerate(json_value[1:]))
2112
+ return Symbolic.ListType.from_json( # pytype: disable=attribute-error
2113
+ json_value,
2114
+ value_spec=value_spec,
2115
+ root_path=root_path,
2116
+ allow_partial=allow_partial,
2117
+ **kwargs,
2118
+ )
2067
2119
  elif isinstance(json_value, dict):
2068
- if object_utils.JSONConvertible.TYPE_NAME_KEY not in json_value:
2069
- return Symbolic.DictType.from_json(json_value, **kwargs)
2070
- return object_utils.from_json(json_value, **kwargs)
2120
+ if utils.JSONConvertible.TYPE_NAME_KEY not in json_value:
2121
+ return Symbolic.DictType.from_json( # pytype: disable=attribute-error
2122
+ json_value,
2123
+ value_spec=value_spec,
2124
+ root_path=root_path,
2125
+ allow_partial=allow_partial,
2126
+ **kwargs,
2127
+ )
2128
+ return utils.from_json(
2129
+ json_value,
2130
+ _typename_resolved=True,
2131
+ root_path=root_path,
2132
+ allow_partial=allow_partial,
2133
+ **kwargs,
2134
+ )
2071
2135
  return json_value
2072
2136
 
2073
2137
 
2074
- def from_json_str(json_str: str,
2075
- *,
2076
- allow_partial: bool = False,
2077
- root_path: Optional[object_utils.KeyPath] = None,
2078
- force_dict: bool = False,
2079
- **kwargs) -> Any:
2138
+ def from_json_str(
2139
+ json_str: str,
2140
+ *,
2141
+ allow_partial: bool = False,
2142
+ root_path: Optional[utils.KeyPath] = None,
2143
+ auto_import: bool = True,
2144
+ auto_dict: bool = False,
2145
+ **kwargs,
2146
+ ) -> Any:
2080
2147
  """Deserialize (maybe) symbolic object from JSON string.
2081
2148
 
2082
2149
  Example::
@@ -2097,20 +2164,41 @@ def from_json_str(json_str: str,
2097
2164
  allow_partial: If True, allow a partial symbolic object to be created.
2098
2165
  Otherwise error will be raised on partial value.
2099
2166
  root_path: The symbolic path used for the deserialized root object.
2100
- force_dict: If True, "_type" keys will be stripped before loading. As a
2101
- result, JSONConvertible objects will be returned as dict.
2167
+ auto_import: If True, when a '_type' is not registered, PyGlove will
2168
+ identify its parent module and automatically import it. For example,
2169
+ if the type is 'foo.bar.A', PyGlove will try to import 'foo.bar' and
2170
+ find the class 'A' within the imported module.
2171
+ auto_dict: If True, dict with '_type' that cannot be loaded will remain
2172
+ as dict, with '_type' renamed to 'type_name'.
2102
2173
  **kwargs: Additional keyword arguments that will be passed to
2103
2174
  ``pg.from_json``.
2104
2175
 
2105
2176
  Returns:
2106
2177
  A deserialized value.
2107
2178
  """
2179
+ def _get_key(k: str) -> Union[str, int]:
2180
+ if k.startswith('n_:'):
2181
+ return int(k[3:])
2182
+ return k
2183
+
2184
+ def _decode_int_keys(v):
2185
+ if isinstance(v, dict):
2186
+ return {
2187
+ _get_key(k): _decode_int_keys(v)
2188
+ for k, v in v.items()
2189
+ }
2190
+ elif isinstance(v, list):
2191
+ return [_decode_int_keys(v) for v in v]
2192
+ return v
2193
+
2108
2194
  return from_json(
2109
- json.loads(json_str),
2195
+ _decode_int_keys(json.loads(json_str)),
2110
2196
  allow_partial=allow_partial,
2111
2197
  root_path=root_path,
2112
- force_dict=force_dict,
2113
- **kwargs)
2198
+ auto_import=auto_import,
2199
+ auto_dict=auto_dict,
2200
+ **kwargs
2201
+ )
2114
2202
 
2115
2203
 
2116
2204
  def to_json(value: Any, **kwargs) -> Any:
@@ -2148,7 +2236,7 @@ def to_json(value: Any, **kwargs) -> Any:
2148
2236
  # classes may have conflicting `to_json` method in their existing classes.
2149
2237
  if isinstance(value, Symbolic):
2150
2238
  return value.sym_jsonify(**kwargs)
2151
- return object_utils.to_json(value, **kwargs)
2239
+ return utils.to_json(value, **kwargs)
2152
2240
 
2153
2241
 
2154
2242
  def to_json_str(value: Any,
@@ -2178,7 +2266,20 @@ def to_json_str(value: Any,
2178
2266
  Returns:
2179
2267
  A JSON string.
2180
2268
  """
2181
- return json.dumps(to_json(value, **kwargs), indent=json_indent)
2269
+ def _encode_int_keys(v):
2270
+ if isinstance(v, dict):
2271
+ return {
2272
+ f'n_:{k}' if isinstance(k, int) else k: _encode_int_keys(v)
2273
+ for k, v in v.items()
2274
+ }
2275
+ elif isinstance(v, list):
2276
+ return [
2277
+ _encode_int_keys(v) for v in v
2278
+ ]
2279
+ return v
2280
+ return json.dumps(
2281
+ _encode_int_keys(to_json(value, **kwargs)), indent=json_indent
2282
+ )
2182
2283
 
2183
2284
 
2184
2285
  def load(path: str, *args, **kwargs) -> Any:
@@ -2250,15 +2351,50 @@ def save(value: Any, path: str, *args, **kwargs) -> Any:
2250
2351
  return save_handler(value, path, *args, **kwargs)
2251
2352
 
2252
2353
 
2354
+ def open_jsonl(
2355
+ path: str,
2356
+ mode: str = 'r',
2357
+ **kwargs
2358
+ ) -> pg_io.Sequence:
2359
+ """Open a JSONL file for reading or writing.
2360
+
2361
+ Example::
2362
+
2363
+ with pg.open_jsonl('my_file.jsonl', 'w') as f:
2364
+ f.add(1)
2365
+ f.add('foo')
2366
+ f.add(dict(x=1))
2367
+
2368
+ with pg.open_jsonl('my_file.jsonl', 'r') as f:
2369
+ for value in f:
2370
+ print(value)
2371
+
2372
+ Args:
2373
+ path: The path to the file.
2374
+ mode: The mode of the file.
2375
+ **kwargs: Additional keyword arguments that will be passed to
2376
+ ``pg_io.open_sequence``.
2377
+
2378
+ Returns:
2379
+ A sequence for PyGlove objects.
2380
+ """
2381
+ return pg_io.open_sequence(
2382
+ path,
2383
+ mode,
2384
+ serializer=to_json_str,
2385
+ deserializer=from_json_str,
2386
+ **kwargs
2387
+ )
2388
+
2389
+
2253
2390
  def default_load_handler(
2254
2391
  path: str,
2255
2392
  file_format: Literal['json', 'txt'] = 'json',
2256
2393
  **kwargs) -> Any:
2257
2394
  """Default load handler from file."""
2258
- del kwargs
2259
2395
  content = pg_io.readfile(path)
2260
2396
  if file_format == 'json':
2261
- return from_json_str(content, allow_partial=True)
2397
+ return from_json_str(content, allow_partial=True, **kwargs)
2262
2398
  elif file_format == 'txt':
2263
2399
  return content
2264
2400
  else:
@@ -2273,12 +2409,14 @@ def default_save_handler(
2273
2409
  file_format: Literal['json', 'txt'] = 'json',
2274
2410
  **kwargs) -> None:
2275
2411
  """Default save handler to file."""
2276
- del kwargs
2277
2412
  if file_format == 'json':
2278
- content = to_json_str(value, json_indent=indent)
2413
+ content = to_json_str(value, json_indent=indent, **kwargs)
2279
2414
  elif file_format == 'txt':
2280
- content = value if isinstance(value, str) else object_utils.format(
2281
- value, compact=False, verbose=True)
2415
+ content = (
2416
+ value
2417
+ if isinstance(value, str)
2418
+ else utils.format(value, compact=False, verbose=True)
2419
+ )
2282
2420
  else:
2283
2421
  raise ValueError(f'Unsupported `file_format`: {file_format!r}.')
2284
2422
 
@@ -2314,8 +2452,7 @@ def treats_as_sealed(value: Symbolic) -> bool:
2314
2452
  def symbolic_transform_fn(allow_partial: bool):
2315
2453
  """Symbolic object transform function builder."""
2316
2454
 
2317
- def _fn(
2318
- path: object_utils.KeyPath, field: pg_typing.Field, value: Any) -> Any:
2455
+ def _fn(path: utils.KeyPath, field: pg_typing.Field, value: Any) -> Any:
2319
2456
  """Transform schema-less List and Dict to symbolic."""
2320
2457
  if isinstance(value, Symbolic):
2321
2458
  return value