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.
- pyglove/core/__init__.py +54 -20
- pyglove/core/coding/__init__.py +42 -0
- pyglove/core/coding/errors.py +111 -0
- pyglove/core/coding/errors_test.py +98 -0
- pyglove/core/coding/execution.py +309 -0
- pyglove/core/coding/execution_test.py +333 -0
- pyglove/core/{object_utils/codegen.py → coding/function_generation.py} +10 -4
- pyglove/core/{object_utils/codegen_test.py → coding/function_generation_test.py} +5 -7
- pyglove/core/coding/parsing.py +153 -0
- pyglove/core/coding/parsing_test.py +150 -0
- pyglove/core/coding/permissions.py +100 -0
- pyglove/core/coding/permissions_test.py +93 -0
- pyglove/core/geno/base.py +54 -41
- pyglove/core/geno/base_test.py +2 -4
- pyglove/core/geno/categorical.py +37 -28
- pyglove/core/geno/custom.py +19 -16
- pyglove/core/geno/numerical.py +20 -17
- pyglove/core/geno/space.py +4 -5
- pyglove/core/hyper/base.py +6 -6
- pyglove/core/hyper/categorical.py +94 -55
- pyglove/core/hyper/custom.py +7 -7
- pyglove/core/hyper/custom_test.py +9 -10
- pyglove/core/hyper/derived.py +30 -22
- pyglove/core/hyper/derived_test.py +2 -4
- pyglove/core/hyper/dynamic_evaluation.py +5 -6
- pyglove/core/hyper/evolvable.py +57 -46
- pyglove/core/hyper/numerical.py +48 -24
- pyglove/core/hyper/numerical_test.py +9 -9
- pyglove/core/hyper/object_template.py +58 -46
- pyglove/core/io/__init__.py +1 -0
- pyglove/core/io/file_system.py +17 -7
- pyglove/core/io/file_system_test.py +2 -0
- pyglove/core/io/sequence.py +299 -0
- pyglove/core/io/sequence_test.py +124 -0
- pyglove/core/logging_test.py +0 -2
- pyglove/core/patching/object_factory.py +4 -4
- pyglove/core/patching/pattern_based.py +4 -4
- pyglove/core/patching/rule_based.py +17 -5
- pyglove/core/patching/rule_based_test.py +27 -4
- pyglove/core/symbolic/__init__.py +2 -7
- pyglove/core/symbolic/base.py +320 -183
- pyglove/core/symbolic/base_test.py +123 -19
- pyglove/core/symbolic/boilerplate.py +7 -13
- pyglove/core/symbolic/boilerplate_test.py +25 -23
- pyglove/core/symbolic/class_wrapper.py +48 -45
- pyglove/core/symbolic/class_wrapper_test.py +2 -2
- pyglove/core/symbolic/compounding.py +9 -15
- pyglove/core/symbolic/compounding_test.py +2 -4
- pyglove/core/symbolic/dict.py +154 -110
- pyglove/core/symbolic/dict_test.py +238 -130
- pyglove/core/symbolic/diff.py +199 -10
- pyglove/core/symbolic/diff_test.py +226 -0
- pyglove/core/symbolic/flags.py +1 -1
- pyglove/core/symbolic/functor.py +29 -26
- pyglove/core/symbolic/functor_test.py +102 -50
- pyglove/core/symbolic/inferred.py +2 -2
- pyglove/core/symbolic/list.py +81 -50
- pyglove/core/symbolic/list_test.py +119 -97
- pyglove/core/symbolic/object.py +225 -113
- pyglove/core/symbolic/object_test.py +320 -108
- pyglove/core/symbolic/origin.py +17 -14
- pyglove/core/symbolic/origin_test.py +4 -2
- pyglove/core/symbolic/pure_symbolic.py +4 -3
- pyglove/core/symbolic/ref.py +108 -21
- pyglove/core/symbolic/ref_test.py +93 -0
- pyglove/core/symbolic/symbolize_test.py +10 -2
- pyglove/core/tuning/local_backend.py +2 -2
- pyglove/core/tuning/protocols.py +3 -3
- pyglove/core/tuning/sample_test.py +3 -3
- pyglove/core/typing/__init__.py +14 -5
- pyglove/core/typing/annotation_conversion.py +43 -27
- pyglove/core/typing/annotation_conversion_test.py +23 -0
- pyglove/core/typing/callable_ext.py +241 -3
- pyglove/core/typing/callable_ext_test.py +255 -0
- pyglove/core/typing/callable_signature.py +510 -66
- pyglove/core/typing/callable_signature_test.py +619 -99
- pyglove/core/typing/class_schema.py +229 -154
- pyglove/core/typing/class_schema_test.py +149 -95
- pyglove/core/typing/custom_typing.py +5 -4
- pyglove/core/typing/inspect.py +63 -0
- pyglove/core/typing/inspect_test.py +39 -0
- pyglove/core/typing/key_specs.py +10 -11
- pyglove/core/typing/key_specs_test.py +7 -4
- pyglove/core/typing/type_conversion.py +4 -5
- pyglove/core/typing/type_conversion_test.py +12 -12
- pyglove/core/typing/typed_missing.py +6 -7
- pyglove/core/typing/typed_missing_test.py +7 -8
- pyglove/core/typing/value_specs.py +604 -362
- pyglove/core/typing/value_specs_test.py +328 -90
- pyglove/core/utils/__init__.py +164 -0
- pyglove/core/{object_utils → utils}/common_traits.py +3 -67
- pyglove/core/utils/common_traits_test.py +36 -0
- pyglove/core/{object_utils → utils}/docstr_utils.py +23 -0
- pyglove/core/{object_utils → utils}/docstr_utils_test.py +36 -4
- pyglove/core/{object_utils → utils}/error_utils.py +78 -9
- pyglove/core/{object_utils → utils}/error_utils_test.py +61 -5
- pyglove/core/utils/formatting.py +464 -0
- pyglove/core/utils/formatting_test.py +453 -0
- pyglove/core/{object_utils → utils}/hierarchical.py +23 -25
- pyglove/core/{object_utils → utils}/hierarchical_test.py +3 -5
- pyglove/core/{object_utils → utils}/json_conversion.py +177 -52
- pyglove/core/{object_utils → utils}/json_conversion_test.py +97 -16
- pyglove/core/{object_utils → utils}/missing.py +3 -3
- pyglove/core/{object_utils → utils}/missing_test.py +2 -4
- pyglove/core/utils/text_color.py +128 -0
- pyglove/core/utils/text_color_test.py +94 -0
- pyglove/core/{object_utils → utils}/thread_local_test.py +1 -3
- pyglove/core/utils/timing.py +236 -0
- pyglove/core/utils/timing_test.py +154 -0
- pyglove/core/{object_utils → utils}/value_location.py +275 -6
- pyglove/core/utils/value_location_test.py +707 -0
- pyglove/core/views/__init__.py +32 -0
- pyglove/core/views/base.py +804 -0
- pyglove/core/views/base_test.py +580 -0
- pyglove/core/views/html/__init__.py +27 -0
- pyglove/core/views/html/base.py +547 -0
- pyglove/core/views/html/base_test.py +830 -0
- pyglove/core/views/html/controls/__init__.py +35 -0
- pyglove/core/views/html/controls/base.py +275 -0
- pyglove/core/views/html/controls/label.py +207 -0
- pyglove/core/views/html/controls/label_test.py +157 -0
- pyglove/core/views/html/controls/progress_bar.py +183 -0
- pyglove/core/views/html/controls/progress_bar_test.py +97 -0
- pyglove/core/views/html/controls/tab.py +320 -0
- pyglove/core/views/html/controls/tab_test.py +87 -0
- pyglove/core/views/html/controls/tooltip.py +99 -0
- pyglove/core/views/html/controls/tooltip_test.py +99 -0
- pyglove/core/views/html/tree_view.py +1517 -0
- pyglove/core/views/html/tree_view_test.py +1461 -0
- {pyglove-0.4.5.dev20240319.dist-info → pyglove-0.4.5.dev202501132210.dist-info}/METADATA +18 -4
- pyglove-0.4.5.dev202501132210.dist-info/RECORD +214 -0
- {pyglove-0.4.5.dev20240319.dist-info → pyglove-0.4.5.dev202501132210.dist-info}/WHEEL +1 -1
- pyglove/core/object_utils/__init__.py +0 -154
- pyglove/core/object_utils/common_traits_test.py +0 -82
- pyglove/core/object_utils/formatting.py +0 -234
- pyglove/core/object_utils/formatting_test.py +0 -223
- pyglove/core/object_utils/value_location_test.py +0 -385
- pyglove/core/symbolic/schema_utils.py +0 -327
- pyglove/core/symbolic/schema_utils_test.py +0 -57
- pyglove/core/typing/class_schema_utils.py +0 -202
- pyglove/core/typing/class_schema_utils_test.py +0 -194
- pyglove-0.4.5.dev20240319.dist-info/RECORD +0 -185
- /pyglove/core/{object_utils → utils}/thread_local.py +0 -0
- {pyglove-0.4.5.dev20240319.dist-info → pyglove-0.4.5.dev202501132210.dist-info}/LICENSE +0 -0
- {pyglove-0.4.5.dev20240319.dist-info → pyglove-0.4.5.dev202501132210.dist-info}/top_level.txt +0 -0
pyglove/core/symbolic/base.py
CHANGED
@@ -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(
|
41
|
+
class FieldUpdate(utils.Formattable):
|
41
42
|
"""Class that describes an update to a field in an object tree."""
|
42
43
|
|
43
|
-
def __init__(
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
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
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
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) ->
|
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:
|
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
|
-
|
186
|
-
|
187
|
-
|
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__(
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
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
|
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 =
|
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 =
|
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[
|
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
|
421
|
+
return utils.KeyPath.from_value(path).exists(self)
|
427
422
|
|
428
423
|
def sym_get(
|
429
424
|
self,
|
430
|
-
path: Union[
|
425
|
+
path: Union[utils.KeyPath, str, int],
|
431
426
|
default: Any = RAISE_IF_NOT_FOUND,
|
432
|
-
use_inferred: bool = False
|
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 =
|
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) ->
|
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[
|
560
|
-
Dict[
|
561
|
-
|
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 = {
|
588
|
-
|
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(
|
614
|
-
|
615
|
-
|
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[
|
762
|
-
Dict[
|
763
|
-
|
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
|
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) ->
|
945
|
+
def to_json(self, **kwargs) -> utils.JSONValueType:
|
954
946
|
"""Alias for `sym_jsonify`."""
|
955
|
-
return self
|
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
|
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[
|
977
|
-
|
978
|
-
|
979
|
-
|
980
|
-
|
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
|
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
|
-
|
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[
|
1087
|
-
|
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
|
-
|
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[
|
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 =
|
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(
|
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:
|
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,
|
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[
|
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
|
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:
|
1285
|
-
|
1286
|
-
|
1287
|
-
|
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.
|
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(
|
1340
|
-
|
1341
|
-
|
1342
|
-
|
1343
|
-
|
1344
|
-
|
1345
|
-
|
1346
|
-
|
1347
|
-
|
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
|
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(
|
1398
|
-
|
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(
|
1404
|
-
|
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(
|
1410
|
-
|
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[
|
1424
|
-
|
1438
|
+
where: Optional[
|
1439
|
+
Union[Callable[[Any], bool], Callable[[Any, Any], bool]]
|
1440
|
+
] = None,
|
1425
1441
|
enter_selected: bool = False,
|
1426
|
-
custom_selector: Optional[
|
1427
|
-
|
1428
|
-
|
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.
|
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.
|
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(
|
1530
|
-
|
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,
|
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
|
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(
|
2005
|
-
|
2006
|
-
|
2007
|
-
|
2008
|
-
|
2009
|
-
|
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
|
-
|
2030
|
-
|
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
|
-
|
2046
|
-
|
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
|
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
|
-
|
2058
|
-
|
2059
|
-
f
|
2060
|
-
f'Encountered: {json_value}',
|
2061
|
-
|
2062
|
-
|
2063
|
-
|
2064
|
-
|
2065
|
-
|
2066
|
-
|
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
|
2069
|
-
return Symbolic.DictType.from_json(
|
2070
|
-
|
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(
|
2075
|
-
|
2076
|
-
|
2077
|
-
|
2078
|
-
|
2079
|
-
|
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
|
-
|
2101
|
-
|
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
|
-
|
2113
|
-
|
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
|
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
|
-
|
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 =
|
2281
|
-
value
|
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
|