pyglove 0.5.0.dev202510210811__py3-none-any.whl → 0.5.0.dev202510230131__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/geno/base.py +7 -3
- pyglove/core/symbolic/base.py +64 -27
- pyglove/core/symbolic/base_test.py +3 -3
- pyglove/core/symbolic/dict.py +13 -5
- pyglove/core/symbolic/dict_test.py +26 -0
- pyglove/core/symbolic/list.py +17 -3
- pyglove/core/symbolic/list_test.py +23 -0
- pyglove/core/symbolic/object.py +1 -0
- pyglove/core/symbolic/ref.py +19 -7
- pyglove/core/symbolic/ref_test.py +94 -7
- pyglove/core/typing/annotation_conversion.py +5 -1
- pyglove/core/typing/annotation_conversion_test.py +3 -0
- pyglove/core/utils/__init__.py +1 -0
- pyglove/core/utils/json_conversion.py +254 -15
- pyglove/core/utils/json_conversion_test.py +61 -3
- {pyglove-0.5.0.dev202510210811.dist-info → pyglove-0.5.0.dev202510230131.dist-info}/METADATA +1 -1
- {pyglove-0.5.0.dev202510210811.dist-info → pyglove-0.5.0.dev202510230131.dist-info}/RECORD +20 -20
- {pyglove-0.5.0.dev202510210811.dist-info → pyglove-0.5.0.dev202510230131.dist-info}/WHEEL +0 -0
- {pyglove-0.5.0.dev202510210811.dist-info → pyglove-0.5.0.dev202510230131.dist-info}/licenses/LICENSE +0 -0
- {pyglove-0.5.0.dev202510210811.dist-info → pyglove-0.5.0.dev202510230131.dist-info}/top_level.txt +0 -0
pyglove/core/geno/base.py
CHANGED
|
@@ -1447,6 +1447,7 @@ class DNA(symbolic.Object):
|
|
|
1447
1447
|
*,
|
|
1448
1448
|
allow_partial: bool = False,
|
|
1449
1449
|
root_path: Optional[utils.KeyPath] = None,
|
|
1450
|
+
**kwargs,
|
|
1450
1451
|
) -> 'DNA':
|
|
1451
1452
|
"""Class method that load a DNA from a JSON value.
|
|
1452
1453
|
|
|
@@ -1454,6 +1455,7 @@ class DNA(symbolic.Object):
|
|
|
1454
1455
|
json_value: Input JSON value, only JSON dict is acceptable.
|
|
1455
1456
|
allow_partial: Whether to allow elements of the list to be partial.
|
|
1456
1457
|
root_path: KeyPath of loaded object in its object tree.
|
|
1458
|
+
**kwargs: Keyword arguments that will be passed to symbolic.from_json.
|
|
1457
1459
|
|
|
1458
1460
|
Returns:
|
|
1459
1461
|
A DNA object.
|
|
@@ -1463,16 +1465,18 @@ class DNA(symbolic.Object):
|
|
|
1463
1465
|
# NOTE(daiyip): DNA.parse will validate the input. Therefore, we can
|
|
1464
1466
|
# disable runtime type check during constructing the DNA objects.
|
|
1465
1467
|
with symbolic.enable_type_check(False):
|
|
1466
|
-
dna = DNA.parse(symbolic.from_json(json_value.get('value')))
|
|
1468
|
+
dna = DNA.parse(symbolic.from_json(json_value.get('value'), **kwargs))
|
|
1467
1469
|
if 'metadata' in json_value:
|
|
1468
1470
|
dna.rebind(
|
|
1469
|
-
metadata=symbolic.from_json(json_value.get('metadata')),
|
|
1471
|
+
metadata=symbolic.from_json(json_value.get('metadata'), **kwargs),
|
|
1470
1472
|
raise_on_no_change=False, skip_notification=True)
|
|
1471
1473
|
else:
|
|
1472
1474
|
dna = super(DNA, cls).from_json(
|
|
1473
1475
|
json_value,
|
|
1474
1476
|
allow_partial=allow_partial,
|
|
1475
|
-
root_path=root_path
|
|
1477
|
+
root_path=root_path,
|
|
1478
|
+
**kwargs,
|
|
1479
|
+
) # pytype: disable=bad-return-type
|
|
1476
1480
|
assert isinstance(dna, DNA)
|
|
1477
1481
|
if cloneable_metadata_keys:
|
|
1478
1482
|
dna._cloneable_metadata_keys = set(cloneable_metadata_keys) # pylint: disable=protected-access
|
pyglove/core/symbolic/base.py
CHANGED
|
@@ -946,7 +946,7 @@ class Symbolic(
|
|
|
946
946
|
|
|
947
947
|
def to_json(self, **kwargs) -> utils.JSONValueType:
|
|
948
948
|
"""Alias for `sym_jsonify`."""
|
|
949
|
-
return to_json(self, **kwargs)
|
|
949
|
+
return utils.to_json(self, **kwargs)
|
|
950
950
|
|
|
951
951
|
def to_json_str(self, json_indent: Optional[int] = None, **kwargs) -> str:
|
|
952
952
|
"""Serializes current object into a JSON string."""
|
|
@@ -1985,10 +1985,12 @@ def is_abstract(x: Any) -> bool:
|
|
|
1985
1985
|
def contains(
|
|
1986
1986
|
x: Any,
|
|
1987
1987
|
value: Any = None,
|
|
1988
|
-
type:
|
|
1988
|
+
type: Union[ # pylint: disable=redefined-builtin
|
|
1989
1989
|
Type[Any],
|
|
1990
|
-
Tuple[Type[Any]]
|
|
1991
|
-
|
|
1990
|
+
Tuple[Type[Any], ...],
|
|
1991
|
+
None,
|
|
1992
|
+
]=None,
|
|
1993
|
+
) -> bool:
|
|
1992
1994
|
"""Returns if a value contains values of specific type.
|
|
1993
1995
|
|
|
1994
1996
|
Example::
|
|
@@ -2037,10 +2039,12 @@ def contains(
|
|
|
2037
2039
|
def from_json(
|
|
2038
2040
|
json_value: Any,
|
|
2039
2041
|
*,
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
+
context: Optional[utils.JSONConversionContext] = None,
|
|
2043
|
+
auto_symbolic: bool = True,
|
|
2042
2044
|
auto_import: bool = True,
|
|
2043
2045
|
auto_dict: bool = False,
|
|
2046
|
+
allow_partial: bool = False,
|
|
2047
|
+
root_path: Optional[utils.KeyPath] = None,
|
|
2044
2048
|
value_spec: Optional[pg_typing.ValueSpec] = None,
|
|
2045
2049
|
**kwargs,
|
|
2046
2050
|
) -> Any:
|
|
@@ -2061,14 +2065,18 @@ def from_json(
|
|
|
2061
2065
|
|
|
2062
2066
|
Args:
|
|
2063
2067
|
json_value: Input JSON value.
|
|
2064
|
-
|
|
2065
|
-
|
|
2068
|
+
context: JSON conversion context.
|
|
2069
|
+
auto_symbolic: If True, list and dict will be automatically converted to
|
|
2070
|
+
`pg.List` and `pg.Dict`. Otherwise, they will be plain lists
|
|
2071
|
+
and dicts.
|
|
2066
2072
|
auto_import: If True, when a '_type' is not registered, PyGlove will
|
|
2067
2073
|
identify its parent module and automatically import it. For example,
|
|
2068
2074
|
if the type is 'foo.bar.A', PyGlove will try to import 'foo.bar' and
|
|
2069
2075
|
find the class 'A' within the imported module.
|
|
2070
2076
|
auto_dict: If True, dict with '_type' that cannot be loaded will remain
|
|
2071
2077
|
as dict, with '_type' renamed to 'type_name'.
|
|
2078
|
+
allow_partial: Whether to allow elements of the list to be partial.
|
|
2079
|
+
root_path: KeyPath of loaded object in its object tree.
|
|
2072
2080
|
value_spec: The value spec for the symbolic list or dict.
|
|
2073
2081
|
**kwargs: Allow passing through keyword arguments to from_json of specific
|
|
2074
2082
|
types.
|
|
@@ -2084,10 +2092,18 @@ def from_json(
|
|
|
2084
2092
|
if isinstance(json_value, Symbolic):
|
|
2085
2093
|
return json_value
|
|
2086
2094
|
|
|
2095
|
+
if context is None:
|
|
2096
|
+
if (isinstance(json_value, dict) and (
|
|
2097
|
+
context_node := json_value.get(utils.JSONConvertible.CONTEXT_KEY))):
|
|
2098
|
+
context = utils.JSONConversionContext.from_json(context_node, **kwargs)
|
|
2099
|
+
json_value = json_value[utils.JSONConvertible.ROOT_VALUE_KEY]
|
|
2100
|
+
else:
|
|
2101
|
+
context = utils.JSONConversionContext()
|
|
2102
|
+
|
|
2087
2103
|
typename_resolved = kwargs.pop('_typename_resolved', False)
|
|
2088
2104
|
if not typename_resolved:
|
|
2089
2105
|
json_value = utils.json_conversion.resolve_typenames(
|
|
2090
|
-
json_value, auto_import
|
|
2106
|
+
json_value, auto_import, auto_dict
|
|
2091
2107
|
)
|
|
2092
2108
|
|
|
2093
2109
|
def _load_child(k, v):
|
|
@@ -2096,6 +2112,7 @@ def from_json(
|
|
|
2096
2112
|
root_path=utils.KeyPath(k, root_path),
|
|
2097
2113
|
_typename_resolved=True,
|
|
2098
2114
|
allow_partial=allow_partial,
|
|
2115
|
+
context=context,
|
|
2099
2116
|
**kwargs,
|
|
2100
2117
|
)
|
|
2101
2118
|
|
|
@@ -2111,24 +2128,42 @@ def from_json(
|
|
|
2111
2128
|
)
|
|
2112
2129
|
)
|
|
2113
2130
|
return tuple(_load_child(i, v) for i, v in enumerate(json_value[1:]))
|
|
2114
|
-
|
|
2131
|
+
if json_value and json_value[0] == utils.JSONConvertible.SYMBOLIC_MARKER:
|
|
2132
|
+
auto_symbolic = True
|
|
2133
|
+
if auto_symbolic:
|
|
2134
|
+
from_json_fn = Symbolic.ListType.from_json # pytype: disable=attribute-error
|
|
2135
|
+
else:
|
|
2136
|
+
from_json_fn = utils.from_json
|
|
2137
|
+
return from_json_fn(
|
|
2115
2138
|
json_value,
|
|
2139
|
+
context=context,
|
|
2116
2140
|
value_spec=value_spec,
|
|
2117
2141
|
root_path=root_path,
|
|
2118
2142
|
allow_partial=allow_partial,
|
|
2119
2143
|
**kwargs,
|
|
2120
2144
|
)
|
|
2121
2145
|
elif isinstance(json_value, dict):
|
|
2146
|
+
if utils.JSONConvertible.REF_KEY in json_value:
|
|
2147
|
+
x = context.get_shared(
|
|
2148
|
+
json_value[utils.JSONConvertible.REF_KEY]
|
|
2149
|
+
).value
|
|
2150
|
+
return x
|
|
2122
2151
|
if utils.JSONConvertible.TYPE_NAME_KEY not in json_value:
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
value_spec=value_spec,
|
|
2126
|
-
root_path=root_path,
|
|
2127
|
-
allow_partial=allow_partial,
|
|
2128
|
-
**kwargs,
|
|
2152
|
+
auto_symbolic = json_value.get(
|
|
2153
|
+
utils.JSONConvertible.SYMBOLIC_MARKER, auto_symbolic
|
|
2129
2154
|
)
|
|
2155
|
+
if auto_symbolic:
|
|
2156
|
+
return Symbolic.DictType.from_json( # pytype: disable=attribute-error
|
|
2157
|
+
json_value,
|
|
2158
|
+
context=context,
|
|
2159
|
+
value_spec=value_spec,
|
|
2160
|
+
root_path=root_path,
|
|
2161
|
+
allow_partial=allow_partial,
|
|
2162
|
+
**kwargs,
|
|
2163
|
+
)
|
|
2130
2164
|
return utils.from_json(
|
|
2131
2165
|
json_value,
|
|
2166
|
+
context=context,
|
|
2132
2167
|
_typename_resolved=True,
|
|
2133
2168
|
root_path=root_path,
|
|
2134
2169
|
allow_partial=allow_partial,
|
|
@@ -2140,10 +2175,12 @@ def from_json(
|
|
|
2140
2175
|
def from_json_str(
|
|
2141
2176
|
json_str: str,
|
|
2142
2177
|
*,
|
|
2143
|
-
|
|
2144
|
-
root_path: Optional[utils.KeyPath] = None,
|
|
2178
|
+
context: Optional[utils.JSONConversionContext] = None,
|
|
2145
2179
|
auto_import: bool = True,
|
|
2146
2180
|
auto_dict: bool = False,
|
|
2181
|
+
allow_partial: bool = False,
|
|
2182
|
+
root_path: Optional[utils.KeyPath] = None,
|
|
2183
|
+
value_spec: Optional[pg_typing.ValueSpec] = None,
|
|
2147
2184
|
**kwargs,
|
|
2148
2185
|
) -> Any:
|
|
2149
2186
|
"""Deserialize (maybe) symbolic object from JSON string.
|
|
@@ -2163,15 +2200,17 @@ def from_json_str(
|
|
|
2163
2200
|
|
|
2164
2201
|
Args:
|
|
2165
2202
|
json_str: JSON string.
|
|
2166
|
-
|
|
2167
|
-
Otherwise error will be raised on partial value.
|
|
2168
|
-
root_path: The symbolic path used for the deserialized root object.
|
|
2203
|
+
context: JSON conversion context.
|
|
2169
2204
|
auto_import: If True, when a '_type' is not registered, PyGlove will
|
|
2170
2205
|
identify its parent module and automatically import it. For example,
|
|
2171
2206
|
if the type is 'foo.bar.A', PyGlove will try to import 'foo.bar' and
|
|
2172
2207
|
find the class 'A' within the imported module.
|
|
2173
2208
|
auto_dict: If True, dict with '_type' that cannot be loaded will remain
|
|
2174
2209
|
as dict, with '_type' renamed to 'type_name'.
|
|
2210
|
+
allow_partial: If True, allow a partial symbolic object to be created.
|
|
2211
|
+
Otherwise error will be raised on partial value.
|
|
2212
|
+
root_path: The symbolic path used for the deserialized root object.
|
|
2213
|
+
value_spec: The value spec for the symbolic list or dict.
|
|
2175
2214
|
**kwargs: Additional keyword arguments that will be passed to
|
|
2176
2215
|
``pg.from_json``.
|
|
2177
2216
|
|
|
@@ -2195,10 +2234,12 @@ def from_json_str(
|
|
|
2195
2234
|
|
|
2196
2235
|
return from_json(
|
|
2197
2236
|
_decode_int_keys(json.loads(json_str)),
|
|
2198
|
-
|
|
2199
|
-
root_path=root_path,
|
|
2237
|
+
context=context,
|
|
2200
2238
|
auto_import=auto_import,
|
|
2201
2239
|
auto_dict=auto_dict,
|
|
2240
|
+
allow_partial=allow_partial,
|
|
2241
|
+
root_path=root_path,
|
|
2242
|
+
value_spec=value_spec,
|
|
2202
2243
|
**kwargs
|
|
2203
2244
|
)
|
|
2204
2245
|
|
|
@@ -2234,10 +2275,6 @@ def to_json(value: Any, **kwargs) -> Any:
|
|
|
2234
2275
|
Returns:
|
|
2235
2276
|
JSON value.
|
|
2236
2277
|
"""
|
|
2237
|
-
# NOTE(daiyip): special handling `sym_jsonify` since symbolized
|
|
2238
|
-
# classes may have conflicting `to_json` method in their existing classes.
|
|
2239
|
-
if isinstance(value, Symbolic):
|
|
2240
|
-
return value.sym_jsonify(**kwargs)
|
|
2241
2278
|
return utils.to_json(value, **kwargs)
|
|
2242
2279
|
|
|
2243
2280
|
|
|
@@ -20,9 +20,9 @@ from pyglove.core import typing as pg_typing
|
|
|
20
20
|
from pyglove.core import utils
|
|
21
21
|
from pyglove.core import views
|
|
22
22
|
from pyglove.core.symbolic import base
|
|
23
|
-
from pyglove.core.symbolic.dict import Dict
|
|
24
|
-
from pyglove.core.symbolic.inferred import ValueFromParentChain
|
|
25
|
-
from pyglove.core.symbolic.object import Object
|
|
23
|
+
from pyglove.core.symbolic.dict import Dict # pylint: disable=g-importing-member
|
|
24
|
+
from pyglove.core.symbolic.inferred import ValueFromParentChain # pylint: disable=g-importing-member
|
|
25
|
+
from pyglove.core.symbolic.object import Object # pylint: disable=g-importing-member
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
class FieldUpdateTest(unittest.TestCase):
|
pyglove/core/symbolic/dict.py
CHANGED
|
@@ -156,6 +156,8 @@ class Dict(dict, base.Symbolic, pg_typing.CustomTyping):
|
|
|
156
156
|
# Not okay:
|
|
157
157
|
d.a.f2.abc = 1
|
|
158
158
|
"""
|
|
159
|
+
# Remove symbolic marker if present.
|
|
160
|
+
json_value.pop(utils.JSONConvertible.SYMBOLIC_MARKER, None)
|
|
159
161
|
return cls(
|
|
160
162
|
{
|
|
161
163
|
k: base.from_json(
|
|
@@ -835,12 +837,15 @@ class Dict(dict, base.Symbolic, pg_typing.CustomTyping):
|
|
|
835
837
|
hide_default_values: bool = False,
|
|
836
838
|
exclude_keys: Optional[Sequence[Union[str, int]]] = None,
|
|
837
839
|
use_inferred: bool = False,
|
|
840
|
+
omit_symbolic_marker: bool = True,
|
|
838
841
|
**kwargs,
|
|
839
842
|
) -> utils.JSONValueType:
|
|
840
843
|
"""Converts current object to a dict with plain Python objects."""
|
|
841
844
|
exclude_keys = set(exclude_keys or [])
|
|
845
|
+
json_repr = {}
|
|
846
|
+
if not omit_symbolic_marker:
|
|
847
|
+
json_repr[utils.JSONConvertible.SYMBOLIC_MARKER] = True
|
|
842
848
|
if self._value_spec and self._value_spec.schema:
|
|
843
|
-
json_repr = dict()
|
|
844
849
|
matched_keys, _ = self._value_spec.schema.resolve(self.keys()) # pytype: disable=attribute-error
|
|
845
850
|
for key_spec, keys in matched_keys.items():
|
|
846
851
|
# NOTE(daiyip): The key values of frozen field can safely be excluded
|
|
@@ -862,20 +867,23 @@ class Dict(dict, base.Symbolic, pg_typing.CustomTyping):
|
|
|
862
867
|
hide_frozen=hide_frozen,
|
|
863
868
|
hide_default_values=hide_default_values,
|
|
864
869
|
use_inferred=use_inferred,
|
|
865
|
-
|
|
866
|
-
|
|
870
|
+
omit_symbolic_marker=omit_symbolic_marker,
|
|
871
|
+
**kwargs
|
|
872
|
+
)
|
|
867
873
|
else:
|
|
868
|
-
|
|
874
|
+
json_repr.update({
|
|
869
875
|
k: base.to_json(
|
|
870
876
|
self.sym_inferred(k, default=v) if (
|
|
871
877
|
use_inferred and isinstance(v, base.Inferential)) else v,
|
|
872
878
|
hide_frozen=hide_frozen,
|
|
873
879
|
hide_default_values=hide_default_values,
|
|
874
880
|
use_inferred=use_inferred,
|
|
881
|
+
omit_symbolic_marker=omit_symbolic_marker,
|
|
875
882
|
**kwargs)
|
|
876
883
|
for k, v in self.sym_items()
|
|
877
884
|
if k not in exclude_keys
|
|
878
|
-
}
|
|
885
|
+
})
|
|
886
|
+
return json_repr
|
|
879
887
|
|
|
880
888
|
def custom_apply(
|
|
881
889
|
self,
|
|
@@ -946,6 +946,14 @@ class DictTest(unittest.TestCase):
|
|
|
946
946
|
sd.sym_jsonify(),
|
|
947
947
|
{'x': 1, 'y': inferred.ValueFromParentChain().to_json()},
|
|
948
948
|
)
|
|
949
|
+
self.assertEqual(
|
|
950
|
+
sd.sym_jsonify(omit_symbolic_marker=False),
|
|
951
|
+
{
|
|
952
|
+
'__symbolic__': True,
|
|
953
|
+
'x': 1,
|
|
954
|
+
'y': inferred.ValueFromParentChain().to_json()
|
|
955
|
+
},
|
|
956
|
+
)
|
|
949
957
|
|
|
950
958
|
def test_sym_rebind(self):
|
|
951
959
|
# Refer to RebindTest for more detailed tests.
|
|
@@ -1964,6 +1972,24 @@ class SerializationTest(unittest.TestCase):
|
|
|
1964
1972
|
self.assertEqual(sd.to_json_str(), '{"x": 1, "y": 2.0}')
|
|
1965
1973
|
self.assertEqual(base.from_json_str(sd.to_json_str(), value_spec=spec), sd)
|
|
1966
1974
|
|
|
1975
|
+
def test_auto_symbolic(self):
|
|
1976
|
+
value = base.from_json({'x': 1}, auto_symbolic=True)
|
|
1977
|
+
self.assertIsInstance(value, Dict)
|
|
1978
|
+
|
|
1979
|
+
value = base.from_json({'x': 1}, auto_symbolic=False)
|
|
1980
|
+
self.assertNotIsInstance(value, Dict)
|
|
1981
|
+
|
|
1982
|
+
def test_omit_symbolic_marker(self):
|
|
1983
|
+
sd = Dict(x=1)
|
|
1984
|
+
self.assertEqual(sd.to_json(omit_symbolic_marker=True), {'x': 1})
|
|
1985
|
+
self.assertEqual(
|
|
1986
|
+
sd.to_json(omit_symbolic_marker=False),
|
|
1987
|
+
{'__symbolic__': True, 'x': 1}
|
|
1988
|
+
)
|
|
1989
|
+
sd = base.from_json({'__symbolic__': True, 'x': 1}, auto_symbolic=False)
|
|
1990
|
+
self.assertIsInstance(sd, Dict)
|
|
1991
|
+
self.assertEqual(sd, {'x': 1})
|
|
1992
|
+
|
|
1967
1993
|
def test_hide_frozen(self):
|
|
1968
1994
|
|
|
1969
1995
|
class A(pg_object.Object):
|
pyglove/core/symbolic/list.py
CHANGED
|
@@ -137,6 +137,9 @@ class List(list, base.Symbolic, pg_typing.CustomTyping):
|
|
|
137
137
|
Returns:
|
|
138
138
|
A schema-less symbolic list, but its items maybe symbolic.
|
|
139
139
|
"""
|
|
140
|
+
# Remove symbolic marker if present.
|
|
141
|
+
if json_value and json_value[0] == utils.JSONConvertible.SYMBOLIC_MARKER:
|
|
142
|
+
json_value.pop(0)
|
|
140
143
|
return cls(
|
|
141
144
|
[
|
|
142
145
|
base.from_json(
|
|
@@ -770,15 +773,26 @@ class List(list, base.Symbolic, pg_typing.CustomTyping):
|
|
|
770
773
|
return (proceed_with_standard_apply, self)
|
|
771
774
|
|
|
772
775
|
def sym_jsonify(
|
|
773
|
-
self,
|
|
776
|
+
self,
|
|
777
|
+
use_inferred: bool = False,
|
|
778
|
+
omit_symbolic_marker: bool = True,
|
|
779
|
+
**kwargs
|
|
774
780
|
) -> utils.JSONValueType:
|
|
775
781
|
"""Converts current list to a list of plain Python objects."""
|
|
776
782
|
def json_item(idx):
|
|
777
783
|
v = self.sym_getattr(idx)
|
|
778
784
|
if use_inferred and isinstance(v, base.Inferential):
|
|
779
785
|
v = self.sym_inferred(idx, default=v)
|
|
780
|
-
return base.to_json(
|
|
781
|
-
|
|
786
|
+
return base.to_json(
|
|
787
|
+
v,
|
|
788
|
+
use_inferred=use_inferred,
|
|
789
|
+
omit_symbolic_marker=omit_symbolic_marker,
|
|
790
|
+
**kwargs
|
|
791
|
+
)
|
|
792
|
+
json_value = [json_item(i) for i in range(len(self))]
|
|
793
|
+
if not omit_symbolic_marker:
|
|
794
|
+
json_value.insert(0, utils.JSONConvertible.SYMBOLIC_MARKER)
|
|
795
|
+
return json_value
|
|
782
796
|
|
|
783
797
|
def format(
|
|
784
798
|
self,
|
|
@@ -799,6 +799,10 @@ class ListTest(unittest.TestCase):
|
|
|
799
799
|
self.assertEqual(
|
|
800
800
|
sl.sym_jsonify(), [0, inferred.ValueFromParentChain().to_json()]
|
|
801
801
|
)
|
|
802
|
+
self.assertEqual(
|
|
803
|
+
sl.sym_jsonify(omit_symbolic_marker=False),
|
|
804
|
+
['__symbolic__', 0, inferred.ValueFromParentChain().to_json()]
|
|
805
|
+
)
|
|
802
806
|
|
|
803
807
|
def test_sym_rebind(self):
|
|
804
808
|
# Refer to RebindTest for more detailed tests.
|
|
@@ -1633,6 +1637,25 @@ class SerializationTest(unittest.TestCase):
|
|
|
1633
1637
|
'Tuple should have at least one element besides \'__tuple__\'.'):
|
|
1634
1638
|
base.from_json_str('["__tuple__"]')
|
|
1635
1639
|
|
|
1640
|
+
def test_auto_symbolic(self):
|
|
1641
|
+
value = base.from_json([1, 2, 3], auto_symbolic=True)
|
|
1642
|
+
self.assertIsInstance(value, List)
|
|
1643
|
+
|
|
1644
|
+
value = base.from_json([1, 2, 3], auto_symbolic=False)
|
|
1645
|
+
self.assertNotIsInstance(value, List)
|
|
1646
|
+
|
|
1647
|
+
def test_omit_symbolic_marker(self):
|
|
1648
|
+
sl = List([0])
|
|
1649
|
+
self.assertEqual(sl.to_json(omit_symbolic_marker=True), [0])
|
|
1650
|
+
self.assertEqual(
|
|
1651
|
+
sl.to_json(omit_symbolic_marker=False),
|
|
1652
|
+
['__symbolic__', 0]
|
|
1653
|
+
)
|
|
1654
|
+
sl = base.from_json(['__symbolic__', 0], auto_symbolic=False)
|
|
1655
|
+
self.assertEqual(sl.to_json(omit_symbolic_marker=True), [0])
|
|
1656
|
+
self.assertIsInstance(sl, List)
|
|
1657
|
+
self.assertEqual(sl, [0])
|
|
1658
|
+
|
|
1636
1659
|
def test_hide_default_values(self):
|
|
1637
1660
|
sl = List.partial(
|
|
1638
1661
|
[dict(x=1)],
|
pyglove/core/symbolic/object.py
CHANGED
pyglove/core/symbolic/ref.py
CHANGED
|
@@ -138,9 +138,7 @@ class Ref(
|
|
|
138
138
|
del child_transform
|
|
139
139
|
# Check if the field being assigned could accept the referenced value.
|
|
140
140
|
# We do not do any transformation, thus not passing the child transform.
|
|
141
|
-
value_spec.apply(
|
|
142
|
-
self._value,
|
|
143
|
-
allow_partial=allow_partial)
|
|
141
|
+
value_spec.apply(self._value, allow_partial=allow_partial)
|
|
144
142
|
return (False, self)
|
|
145
143
|
|
|
146
144
|
def _sym_clone(self, deep: bool, memo: Any = None) -> 'Ref':
|
|
@@ -152,10 +150,24 @@ class Ref(
|
|
|
152
150
|
def sym_eq(self, other: Any) -> bool:
|
|
153
151
|
return isinstance(other, Ref) and self.value is other.value
|
|
154
152
|
|
|
155
|
-
def sym_jsonify(
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
153
|
+
def sym_jsonify(
|
|
154
|
+
self,
|
|
155
|
+
*,
|
|
156
|
+
context: utils.JSONConversionContext,
|
|
157
|
+
**kwargs: Any
|
|
158
|
+
) -> Any:
|
|
159
|
+
# Disable auto_symbolic for Ref value. This allows Ref to create a sub-tree
|
|
160
|
+
# for reference sharing.
|
|
161
|
+
kwargs['omit_symbolic_marker'] = False
|
|
162
|
+
return {
|
|
163
|
+
utils.JSONConvertible.TYPE_NAME_KEY: self.__class__.__type_name__,
|
|
164
|
+
'value': context.serialize_maybe_shared(self._value, **kwargs)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
@classmethod
|
|
168
|
+
def from_json(cls, json: Any, **kwargs):
|
|
169
|
+
kwargs['auto_symbolic'] = False
|
|
170
|
+
return super().from_json(json, **kwargs)
|
|
159
171
|
|
|
160
172
|
def __getstate__(self):
|
|
161
173
|
raise TypeError(f'{self!r} cannot be pickled at the moment.')
|
|
@@ -21,8 +21,11 @@ from typing import Any
|
|
|
21
21
|
import unittest
|
|
22
22
|
|
|
23
23
|
from pyglove.core import typing as pg_typing
|
|
24
|
+
from pyglove.core.symbolic import list as pg_list # pylint: disable=unused-import
|
|
24
25
|
from pyglove.core.symbolic import ref
|
|
25
26
|
from pyglove.core.symbolic.base import contains
|
|
27
|
+
from pyglove.core.symbolic.base import from_json
|
|
28
|
+
from pyglove.core.symbolic.base import to_json
|
|
26
29
|
from pyglove.core.symbolic.dict import Dict
|
|
27
30
|
from pyglove.core.symbolic.object import Object
|
|
28
31
|
|
|
@@ -33,6 +36,10 @@ class A(Object):
|
|
|
33
36
|
|
|
34
37
|
class RefTest(unittest.TestCase):
|
|
35
38
|
|
|
39
|
+
def setUp(self):
|
|
40
|
+
super().setUp()
|
|
41
|
+
self.maxDiff = None
|
|
42
|
+
|
|
36
43
|
def test_basics(self):
|
|
37
44
|
|
|
38
45
|
a = A(1)
|
|
@@ -109,14 +116,94 @@ class RefTest(unittest.TestCase):
|
|
|
109
116
|
"""))
|
|
110
117
|
|
|
111
118
|
def test_to_json(self):
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
ref.Ref(A(1)).to_json()
|
|
119
|
+
class B(Object):
|
|
120
|
+
y: Any
|
|
115
121
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
)
|
|
122
|
+
a = A(1)
|
|
123
|
+
r1 = ref.Ref(a)
|
|
124
|
+
r2 = ref.Ref({'z': a})
|
|
125
|
+
r3 = ref.Ref(Dict(t=r1, p=r2))
|
|
126
|
+
v = Dict(a=r1, b=[B(r2), [r3], r1])
|
|
127
|
+
self.assertIs(v.a, v.b[0].y['z'])
|
|
128
|
+
self.assertIs(v.a, v.b[1][0].t)
|
|
129
|
+
self.assertIs(v.b[0].y, v.b[1][0].p)
|
|
130
|
+
self.assertIs(v.a, v.b[2])
|
|
131
|
+
self.assertIsInstance(v, dict)
|
|
132
|
+
self.assertIsInstance(v.b[0].y, dict)
|
|
133
|
+
self.assertNotIsInstance(v.b[0].y, Dict)
|
|
134
|
+
self.assertIsInstance(v.b[1][0], Dict)
|
|
135
|
+
|
|
136
|
+
json = to_json(v)
|
|
137
|
+
expected = {
|
|
138
|
+
'__context__': {
|
|
139
|
+
'shared_objects': [
|
|
140
|
+
{
|
|
141
|
+
'_type': A.__type_name__,
|
|
142
|
+
'x': 1
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
'z': {
|
|
146
|
+
'__ref__': 0
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
]
|
|
150
|
+
},
|
|
151
|
+
'__root__': {
|
|
152
|
+
'a': {
|
|
153
|
+
'_type': ref.Ref.__type_name__,
|
|
154
|
+
'value': {
|
|
155
|
+
'__ref__': 0
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
'b': [
|
|
159
|
+
{
|
|
160
|
+
'_type': B.__type_name__,
|
|
161
|
+
'y': {
|
|
162
|
+
'_type': ref.Ref.__type_name__,
|
|
163
|
+
'value': {
|
|
164
|
+
'__ref__': 1
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
[
|
|
169
|
+
{
|
|
170
|
+
'_type': ref.Ref.__type_name__,
|
|
171
|
+
'value': {
|
|
172
|
+
'__symbolic__': True,
|
|
173
|
+
't': {
|
|
174
|
+
'_type': ref.Ref.__type_name__,
|
|
175
|
+
'value': {
|
|
176
|
+
'__ref__': 0
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
'p': {
|
|
180
|
+
'_type': ref.Ref.__type_name__,
|
|
181
|
+
'value': {
|
|
182
|
+
'__ref__': 1
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
],
|
|
188
|
+
{
|
|
189
|
+
'_type': ref.Ref.__type_name__,
|
|
190
|
+
'value': {
|
|
191
|
+
'__ref__': 0
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
]
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
self.assertEqual(json, expected)
|
|
198
|
+
v = from_json(json)
|
|
199
|
+
self.assertIs(v.a, v.b[0].y['z'])
|
|
200
|
+
self.assertIs(v.a, v.b[1][0].t)
|
|
201
|
+
self.assertIs(v.b[0].y, v.b[1][0].p)
|
|
202
|
+
self.assertIs(v.a, v.b[2])
|
|
203
|
+
self.assertIsInstance(v, dict)
|
|
204
|
+
self.assertIsInstance(v.b[0].y, dict)
|
|
205
|
+
self.assertNotIsInstance(v.b[0].y, Dict)
|
|
206
|
+
self.assertIsInstance(v.b[1][0], Dict)
|
|
120
207
|
|
|
121
208
|
def test_pickle(self):
|
|
122
209
|
with self.assertRaisesRegex(
|
|
@@ -318,6 +318,9 @@ def _value_spec_from_type_annotation(
|
|
|
318
318
|
parent_module: typing.Optional[types.ModuleType] = None
|
|
319
319
|
) -> class_schema.ValueSpec:
|
|
320
320
|
"""Creates a value spec from type annotation."""
|
|
321
|
+
if isinstance(annotation, class_schema.ValueSpec):
|
|
322
|
+
return annotation
|
|
323
|
+
|
|
321
324
|
if isinstance(annotation, str) and not accept_value_as_annotation:
|
|
322
325
|
annotation = annotation_from_str(annotation, parent_module)
|
|
323
326
|
|
|
@@ -454,7 +457,8 @@ def _value_spec_from_annotation(
|
|
|
454
457
|
"""Creates a value spec from annotation."""
|
|
455
458
|
if isinstance(annotation, class_schema.ValueSpec):
|
|
456
459
|
return annotation
|
|
457
|
-
|
|
460
|
+
|
|
461
|
+
if annotation == inspect.Parameter.empty:
|
|
458
462
|
return vs.Any()
|
|
459
463
|
|
|
460
464
|
if annotation is None:
|
|
@@ -396,6 +396,9 @@ class ValueSpecFromAnnotationTest(unittest.TestCase):
|
|
|
396
396
|
self.assertEqual(
|
|
397
397
|
ValueSpec.from_annotation(typing.Dict[str, int], True),
|
|
398
398
|
vs.Dict([(ks.StrKey(), vs.Int())]))
|
|
399
|
+
self.assertEqual(
|
|
400
|
+
ValueSpec.from_annotation(typing.Dict[str, vs.Int()], True),
|
|
401
|
+
vs.Dict([(ks.StrKey(), vs.Int())]))
|
|
399
402
|
self.assertEqual(
|
|
400
403
|
ValueSpec.from_annotation(typing.Mapping[str, int], True),
|
|
401
404
|
vs.Dict([(ks.StrKey(), vs.Int())]))
|
pyglove/core/utils/__init__.py
CHANGED
|
@@ -74,6 +74,7 @@ modules with the following features:
|
|
|
74
74
|
from pyglove.core.utils.json_conversion import Nestable
|
|
75
75
|
from pyglove.core.utils.json_conversion import JSONValueType
|
|
76
76
|
|
|
77
|
+
from pyglove.core.utils.json_conversion import JSONConversionContext
|
|
77
78
|
from pyglove.core.utils.json_conversion import JSONConvertible
|
|
78
79
|
from pyglove.core.utils.json_conversion import from_json
|
|
79
80
|
from pyglove.core.utils.json_conversion import to_json
|
|
@@ -17,6 +17,7 @@ import abc
|
|
|
17
17
|
import base64
|
|
18
18
|
import collections
|
|
19
19
|
import contextlib
|
|
20
|
+
import dataclasses
|
|
20
21
|
import importlib
|
|
21
22
|
import inspect
|
|
22
23
|
import marshal
|
|
@@ -25,6 +26,7 @@ import types
|
|
|
25
26
|
import typing
|
|
26
27
|
from typing import Any, Callable, ContextManager, Dict, Iterable, Iterator, List, Optional, Sequence, Set, Tuple, Type, TypeVar, Union
|
|
27
28
|
|
|
29
|
+
|
|
28
30
|
# Nestable[T] is a (maybe) nested structure of T, which could be T, a Dict
|
|
29
31
|
# a List or a Tuple of Nestable[T]. We use a Union to fool PyType checker to
|
|
30
32
|
# make Nestable[T] a valid type annotation without type check.
|
|
@@ -186,6 +188,19 @@ class JSONConvertible(metaclass=abc.ABCMeta):
|
|
|
186
188
|
# Marker (as the first element of a list) for serializing tuples.
|
|
187
189
|
TUPLE_MARKER = '__tuple__'
|
|
188
190
|
|
|
191
|
+
# Marker (as the first element of a list or key of a dict) for symbolic
|
|
192
|
+
# lists and dicts.
|
|
193
|
+
SYMBOLIC_MARKER = '__symbolic__'
|
|
194
|
+
|
|
195
|
+
# Marker for references to shared objects.
|
|
196
|
+
REF_KEY = '__ref__'
|
|
197
|
+
|
|
198
|
+
# Marker for root value when JSONConversionContext is used.
|
|
199
|
+
ROOT_VALUE_KEY = '__root__'
|
|
200
|
+
|
|
201
|
+
# Marker for JSONConversionContext.
|
|
202
|
+
CONTEXT_KEY = '__context__'
|
|
203
|
+
|
|
189
204
|
# Type converter that converts a complex type to basic JSON value type.
|
|
190
205
|
# When this field is set by users, the converter will be invoked when a
|
|
191
206
|
# complex value cannot be serialized by existing methods.
|
|
@@ -215,7 +230,12 @@ class JSONConvertible(metaclass=abc.ABCMeta):
|
|
|
215
230
|
return cls(**init_args)
|
|
216
231
|
|
|
217
232
|
@abc.abstractmethod
|
|
218
|
-
def to_json(
|
|
233
|
+
def to_json(
|
|
234
|
+
self,
|
|
235
|
+
*,
|
|
236
|
+
context: Optional['JSONConversionContext'] = None,
|
|
237
|
+
**kwargs
|
|
238
|
+
) -> JSONValueType:
|
|
219
239
|
"""Returns a plain Python value as a representation for this object.
|
|
220
240
|
|
|
221
241
|
A plain Python value are basic python types that can be serialized into
|
|
@@ -224,6 +244,7 @@ class JSONConvertible(metaclass=abc.ABCMeta):
|
|
|
224
244
|
Python values as their values.
|
|
225
245
|
|
|
226
246
|
Args:
|
|
247
|
+
context: JSON conversion context.
|
|
227
248
|
**kwargs: Keyword arguments as flags to control JSON conversion.
|
|
228
249
|
|
|
229
250
|
Returns:
|
|
@@ -389,8 +410,14 @@ class _OpaqueObject(JSONConvertible):
|
|
|
389
410
|
}, **kwargs)
|
|
390
411
|
|
|
391
412
|
@classmethod
|
|
392
|
-
def from_json(
|
|
393
|
-
|
|
413
|
+
def from_json(
|
|
414
|
+
cls,
|
|
415
|
+
json_value: JSONValueType,
|
|
416
|
+
*args,
|
|
417
|
+
context: Optional['JSONConversionContext'] = None,
|
|
418
|
+
**kwargs
|
|
419
|
+
) -> Any:
|
|
420
|
+
del args, context, kwargs
|
|
394
421
|
assert isinstance(json_value, dict) and 'value' in json_value, json_value
|
|
395
422
|
encoder = cls(json_value['value'], encoded=True)
|
|
396
423
|
return encoder.value
|
|
@@ -401,7 +428,169 @@ def registered_types() -> Iterable[Tuple[str, Type[JSONConvertible]]]:
|
|
|
401
428
|
return JSONConvertible.registered_types()
|
|
402
429
|
|
|
403
430
|
|
|
404
|
-
|
|
431
|
+
class JSONConversionContext(JSONConvertible):
|
|
432
|
+
"""JSON conversion context.
|
|
433
|
+
|
|
434
|
+
JSONConversionContext is introduced to handle serialization scenarios where
|
|
435
|
+
operations cannot be performed in a single pass. For example: Serialization
|
|
436
|
+
and deserialization of shared objects across different locations.
|
|
437
|
+
|
|
438
|
+
# Shared object serialization/deserialization.
|
|
439
|
+
|
|
440
|
+
In PyGlove, only values referenced by `pg.Ref` and non-PyGlove managed objects
|
|
441
|
+
are sharable. This ensures that multiple references to the same object are
|
|
442
|
+
serialized only once. During deserialization, the object is created just once
|
|
443
|
+
and shared among all references.
|
|
444
|
+
"""
|
|
445
|
+
|
|
446
|
+
@dataclasses.dataclass
|
|
447
|
+
class ObjectEntry:
|
|
448
|
+
value: Any
|
|
449
|
+
serialized: Optional[JSONValueType]
|
|
450
|
+
ref_index: int
|
|
451
|
+
ref_count: int
|
|
452
|
+
|
|
453
|
+
def __init__(self,) -> None:
|
|
454
|
+
self._shared_objects: list[JSONConversionContext.ObjectEntry] = []
|
|
455
|
+
self._id_to_shared_object = {}
|
|
456
|
+
|
|
457
|
+
def get_shared(self, ref_index: int) -> ObjectEntry:
|
|
458
|
+
"""Gets the shared object of a ref index."""
|
|
459
|
+
return self._shared_objects[ref_index]
|
|
460
|
+
|
|
461
|
+
def add_shared(self, shared: ObjectEntry) -> None:
|
|
462
|
+
self._shared_objects.append(shared)
|
|
463
|
+
self._id_to_shared_object[id(shared.value)] = shared
|
|
464
|
+
|
|
465
|
+
def next_shared_index(self) -> int:
|
|
466
|
+
"""Returns the next shared index."""
|
|
467
|
+
return len(self._shared_objects)
|
|
468
|
+
|
|
469
|
+
def serialize_maybe_shared(
|
|
470
|
+
self,
|
|
471
|
+
value: Any,
|
|
472
|
+
json_fn: Optional[Callable[..., JSONValueType]] = None,
|
|
473
|
+
**kwargs
|
|
474
|
+
) -> JSONValueType:
|
|
475
|
+
"""Track maybe shared objects and returns their JSON representation."""
|
|
476
|
+
if json_fn is None:
|
|
477
|
+
json_fn = lambda **kwargs: to_json(value, **kwargs)
|
|
478
|
+
kwargs.pop('context', None)
|
|
479
|
+
value_id = id(value)
|
|
480
|
+
shared_object = self._id_to_shared_object.get(value_id)
|
|
481
|
+
if shared_object is None:
|
|
482
|
+
serialized = json_fn(context=self, **kwargs)
|
|
483
|
+
|
|
484
|
+
# It's possible that maybe_shared_json is called recursively on the same
|
|
485
|
+
# object, thus we need to check for self-references explicitly.
|
|
486
|
+
if (isinstance(serialized, dict)
|
|
487
|
+
and JSONConvertible.REF_KEY in serialized
|
|
488
|
+
and len(serialized) == 1):
|
|
489
|
+
return serialized
|
|
490
|
+
|
|
491
|
+
shared_object = self.ObjectEntry(
|
|
492
|
+
value=value,
|
|
493
|
+
serialized=serialized,
|
|
494
|
+
ref_index=self.next_shared_index(),
|
|
495
|
+
ref_count=0,
|
|
496
|
+
)
|
|
497
|
+
self._shared_objects.append(shared_object)
|
|
498
|
+
self._id_to_shared_object[value_id] = shared_object
|
|
499
|
+
shared_object.ref_count += 1
|
|
500
|
+
return {
|
|
501
|
+
JSONConvertible.REF_KEY: shared_object.ref_index
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
def _maybe_deref(self, serialized: Any, ref_index_map: dict[int, int]) -> Any:
|
|
505
|
+
"""In-place dereference ref-1 shared objects in an object tree.
|
|
506
|
+
|
|
507
|
+
Args:
|
|
508
|
+
serialized: The object tree to dereference.
|
|
509
|
+
ref_index_map: A map from the original index of shared objects to their
|
|
510
|
+
indices after the ref-1 shared objects are trimmed.
|
|
511
|
+
|
|
512
|
+
Returns:
|
|
513
|
+
The (maybe) dereferenced object tree.
|
|
514
|
+
"""
|
|
515
|
+
if isinstance(serialized, dict):
|
|
516
|
+
ref_index = serialized.get(JSONConvertible.REF_KEY)
|
|
517
|
+
if ref_index is None:
|
|
518
|
+
for k, x in serialized.items():
|
|
519
|
+
serialized[k] = self._maybe_deref(x, ref_index_map)
|
|
520
|
+
else:
|
|
521
|
+
shared = self.get_shared(ref_index)
|
|
522
|
+
if shared.ref_count == 1:
|
|
523
|
+
ref_serialized = self._maybe_deref(shared.serialized, ref_index_map)
|
|
524
|
+
if isinstance(ref_serialized, dict):
|
|
525
|
+
serialized.pop(JSONConvertible.REF_KEY)
|
|
526
|
+
serialized.update(ref_serialized)
|
|
527
|
+
return serialized
|
|
528
|
+
return ref_serialized
|
|
529
|
+
else:
|
|
530
|
+
serialized[JSONConvertible.REF_KEY] = ref_index_map[shared.ref_index]
|
|
531
|
+
elif isinstance(serialized, list):
|
|
532
|
+
for i, x in enumerate(serialized):
|
|
533
|
+
serialized[i] = self._maybe_deref(x, ref_index_map)
|
|
534
|
+
return serialized
|
|
535
|
+
|
|
536
|
+
def to_json(self, *, root: Any, **kwargs) -> JSONValueType:
|
|
537
|
+
"""Serializes a root node with the context to JSON value."""
|
|
538
|
+
# `ref_index_map` stores the original index of shared objects to their
|
|
539
|
+
# indices after the ref-1 shared objects are trimmed.
|
|
540
|
+
ref_index_map = {}
|
|
541
|
+
|
|
542
|
+
shared_objects = []
|
|
543
|
+
for i, v in enumerate(self._shared_objects):
|
|
544
|
+
ref_index_map[i] = len(shared_objects)
|
|
545
|
+
if v.ref_count != 1:
|
|
546
|
+
shared_objects.append(v.value)
|
|
547
|
+
|
|
548
|
+
root = self._maybe_deref(root, ref_index_map)
|
|
549
|
+
|
|
550
|
+
serialized_shared_objects = [
|
|
551
|
+
v.serialized for v in self._shared_objects if v.ref_count != 1
|
|
552
|
+
]
|
|
553
|
+
if not shared_objects:
|
|
554
|
+
return root
|
|
555
|
+
serialized = {}
|
|
556
|
+
if shared_objects:
|
|
557
|
+
serialized[JSONConvertible.CONTEXT_KEY] = {
|
|
558
|
+
'shared_objects': [
|
|
559
|
+
self._maybe_deref(x, ref_index_map)
|
|
560
|
+
for x in serialized_shared_objects
|
|
561
|
+
],
|
|
562
|
+
}
|
|
563
|
+
serialized[JSONConvertible.ROOT_VALUE_KEY] = root
|
|
564
|
+
return serialized
|
|
565
|
+
|
|
566
|
+
@classmethod
|
|
567
|
+
def from_json(
|
|
568
|
+
cls, json_value: JSONValueType, **kwargs
|
|
569
|
+
) -> 'JSONConversionContext':
|
|
570
|
+
"""Deserializes a JSONConvertible value from JSON value."""
|
|
571
|
+
context = cls()
|
|
572
|
+
if isinstance(json_value, dict):
|
|
573
|
+
# Shared objects are serialized in a bottom-up order, thus dependent
|
|
574
|
+
# shared objects must be deserialized first.
|
|
575
|
+
if shared_objects_json := json_value.get('shared_objects'):
|
|
576
|
+
for v in shared_objects_json:
|
|
577
|
+
context.add_shared(
|
|
578
|
+
cls.ObjectEntry(
|
|
579
|
+
value=from_json(v, context=context, **kwargs),
|
|
580
|
+
serialized=v,
|
|
581
|
+
ref_index=context.next_shared_index(),
|
|
582
|
+
ref_count=0,
|
|
583
|
+
)
|
|
584
|
+
)
|
|
585
|
+
return context
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
def to_json(
|
|
589
|
+
value: Any,
|
|
590
|
+
*,
|
|
591
|
+
context: Optional[JSONConversionContext] = None,
|
|
592
|
+
**kwargs
|
|
593
|
+
) -> Any:
|
|
405
594
|
"""Serializes a (maybe) JSONConvertible value into a plain Python object.
|
|
406
595
|
|
|
407
596
|
Args:
|
|
@@ -413,22 +602,50 @@ def to_json(value: Any, **kwargs) -> Any:
|
|
|
413
602
|
* Tuple types;
|
|
414
603
|
* Dict types.
|
|
415
604
|
|
|
605
|
+
context: JSON conversion context.
|
|
416
606
|
**kwargs: Keyword arguments to pass to value.to_json if value is
|
|
417
607
|
JSONConvertible.
|
|
418
608
|
|
|
419
609
|
Returns:
|
|
420
610
|
JSON value.
|
|
421
611
|
"""
|
|
612
|
+
if context is None:
|
|
613
|
+
is_root = True
|
|
614
|
+
context = JSONConversionContext()
|
|
615
|
+
else:
|
|
616
|
+
is_root = False
|
|
617
|
+
|
|
422
618
|
if isinstance(value, (type(None), bool, int, float, str)):
|
|
619
|
+
# Primitive types serialize by values.
|
|
423
620
|
v = value
|
|
424
621
|
elif isinstance(value, JSONConvertible):
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
622
|
+
# Non-symbolic objects serialize by references.
|
|
623
|
+
v = context.serialize_maybe_shared(
|
|
624
|
+
value,
|
|
625
|
+
json_fn=getattr(value, 'sym_jsonify', value.to_json),
|
|
626
|
+
**kwargs
|
|
627
|
+
)
|
|
428
628
|
elif isinstance(value, list):
|
|
429
|
-
|
|
629
|
+
# Standard lists serialize by references.
|
|
630
|
+
v = context.serialize_maybe_shared(
|
|
631
|
+
value,
|
|
632
|
+
json_fn=lambda **kwargs: [to_json(x, **kwargs) for x in value],
|
|
633
|
+
**kwargs
|
|
634
|
+
)
|
|
430
635
|
elif isinstance(value, dict):
|
|
431
|
-
|
|
636
|
+
# Standard dicts serialize by references.
|
|
637
|
+
v = context.serialize_maybe_shared(
|
|
638
|
+
value,
|
|
639
|
+
json_fn=lambda **kwargs: {
|
|
640
|
+
k: to_json(v, **kwargs) for k, v in value.items() # pytype: disable=attribute-error
|
|
641
|
+
},
|
|
642
|
+
**kwargs
|
|
643
|
+
)
|
|
644
|
+
elif isinstance(value, tuple):
|
|
645
|
+
# Tuples serialize by values.
|
|
646
|
+
v = [JSONConvertible.TUPLE_MARKER] + [
|
|
647
|
+
to_json(item, context=context, **kwargs) for item in value
|
|
648
|
+
]
|
|
432
649
|
elif isinstance(value, (type, typing.GenericAlias)): # pytype: disable=module-attr
|
|
433
650
|
v = _type_to_json(value)
|
|
434
651
|
elif inspect.isbuiltin(value):
|
|
@@ -448,23 +665,34 @@ def to_json(value: Any, **kwargs) -> Any:
|
|
|
448
665
|
if JSONConvertible.TYPE_CONVERTER is not None:
|
|
449
666
|
converter = JSONConvertible.TYPE_CONVERTER(type(value)) # pylint: disable=not-callable
|
|
450
667
|
if converter:
|
|
451
|
-
v = to_json(converter(value))
|
|
668
|
+
v = to_json(converter(value), context=context, **kwargs)
|
|
452
669
|
converted = True
|
|
453
670
|
if not converted:
|
|
454
|
-
|
|
671
|
+
# Opaque objects serialize by references.
|
|
672
|
+
v = context.serialize_maybe_shared(
|
|
673
|
+
value,
|
|
674
|
+
json_fn=lambda **kwargs: _OpaqueObject(value).to_json(**kwargs),
|
|
675
|
+
**kwargs
|
|
676
|
+
)
|
|
677
|
+
|
|
678
|
+
if is_root:
|
|
679
|
+
return context.to_json(root=v, **kwargs)
|
|
455
680
|
return v
|
|
456
681
|
|
|
457
682
|
|
|
458
683
|
def from_json(
|
|
459
684
|
json_value: JSONValueType,
|
|
460
685
|
*,
|
|
686
|
+
context: Optional[JSONConversionContext] = None,
|
|
461
687
|
auto_import: bool = True,
|
|
462
688
|
auto_dict: bool = False,
|
|
463
|
-
**kwargs
|
|
689
|
+
**kwargs
|
|
690
|
+
) -> Any:
|
|
464
691
|
"""Deserializes a (maybe) JSONConvertible value from JSON value.
|
|
465
692
|
|
|
466
693
|
Args:
|
|
467
694
|
json_value: Input JSON value.
|
|
695
|
+
context: Serialization context.
|
|
468
696
|
auto_import: If True, when a '_type' is not registered, PyGlove will
|
|
469
697
|
identify its parent module and automatically import it. For example,
|
|
470
698
|
if the type is 'foo.bar.A', PyGlove will try to import 'foo.bar' and
|
|
@@ -476,12 +704,20 @@ def from_json(
|
|
|
476
704
|
Returns:
|
|
477
705
|
Deserialized value.
|
|
478
706
|
"""
|
|
707
|
+
if context is None:
|
|
708
|
+
if (isinstance(json_value, dict)
|
|
709
|
+
and (context_node := json_value.get(JSONConvertible.CONTEXT_KEY))):
|
|
710
|
+
context = JSONConversionContext.from_json(context_node, **kwargs)
|
|
711
|
+
json_value = json_value[JSONConvertible.ROOT_VALUE_KEY]
|
|
712
|
+
else:
|
|
713
|
+
context = JSONConversionContext()
|
|
714
|
+
|
|
479
715
|
typename_resolved = kwargs.pop('_typename_resolved', False)
|
|
480
716
|
if not typename_resolved:
|
|
481
717
|
json_value = resolve_typenames(json_value, auto_import, auto_dict)
|
|
482
718
|
|
|
483
719
|
def child_from(v):
|
|
484
|
-
return from_json(v, _typename_resolved=True, **kwargs)
|
|
720
|
+
return from_json(v, context=context, _typename_resolved=True, **kwargs)
|
|
485
721
|
|
|
486
722
|
if isinstance(json_value, list):
|
|
487
723
|
if json_value and json_value[0] == JSONConvertible.TUPLE_MARKER:
|
|
@@ -493,18 +729,21 @@ def from_json(
|
|
|
493
729
|
return tuple([child_from(v) for v in json_value[1:]])
|
|
494
730
|
return [child_from(v) for v in json_value]
|
|
495
731
|
elif isinstance(json_value, dict):
|
|
732
|
+
if JSONConvertible.REF_KEY in json_value:
|
|
733
|
+
v = context.get_shared(json_value[JSONConvertible.REF_KEY]).value
|
|
734
|
+
return v
|
|
496
735
|
if JSONConvertible.TYPE_NAME_KEY not in json_value:
|
|
497
736
|
return {k: child_from(v) for k, v in json_value.items()}
|
|
498
737
|
factory_fn = json_value.pop(JSONConvertible.TYPE_NAME_KEY)
|
|
499
738
|
assert factory_fn is not None
|
|
500
|
-
return factory_fn(json_value, **kwargs)
|
|
739
|
+
return factory_fn(json_value, context=context, **kwargs)
|
|
501
740
|
return json_value
|
|
502
741
|
|
|
503
742
|
|
|
504
743
|
def resolve_typenames(
|
|
505
744
|
json_value: JSONValueType,
|
|
506
745
|
auto_import: bool = True,
|
|
507
|
-
auto_dict: bool = False
|
|
746
|
+
auto_dict: bool = False,
|
|
508
747
|
) -> JSONValueType:
|
|
509
748
|
"""Inplace resolves the "_type" keys with their factories in a JSON tree."""
|
|
510
749
|
|
|
@@ -156,7 +156,7 @@ class JSONConvertibleTest(unittest.TestCase):
|
|
|
156
156
|
def __init__(self, x=None):
|
|
157
157
|
self.x = x
|
|
158
158
|
|
|
159
|
-
def to_json(self):
|
|
159
|
+
def to_json(self, **kwargs):
|
|
160
160
|
return T.to_json_dict(dict(x=(self.x, None)), exclude_default=True)
|
|
161
161
|
|
|
162
162
|
def __eq__(self, other):
|
|
@@ -202,7 +202,7 @@ class JSONConvertibleTest(unittest.TestCase):
|
|
|
202
202
|
def __init__(self, x=None):
|
|
203
203
|
self.x = x
|
|
204
204
|
|
|
205
|
-
def to_json(self):
|
|
205
|
+
def to_json(self, **kwargs):
|
|
206
206
|
return self.to_json_dict(
|
|
207
207
|
dict(x=(self.x, None)), exclude_default=True
|
|
208
208
|
)
|
|
@@ -215,7 +215,6 @@ class JSONConvertibleTest(unittest.TestCase):
|
|
|
215
215
|
|
|
216
216
|
def test_json_conversion_with_auto_import(self):
|
|
217
217
|
json_dict = json_conversion.to_json(self.CustomJsonConvertible(1))
|
|
218
|
-
|
|
219
218
|
with self.assertRaisesRegex(
|
|
220
219
|
TypeError, 'Type name .* is not registered'):
|
|
221
220
|
json_conversion.from_json(json_dict, auto_import=False)
|
|
@@ -393,6 +392,65 @@ class JSONConvertibleTest(unittest.TestCase):
|
|
|
393
392
|
TypeError, '.* is not a `pg.JSONConvertible` subclass'):
|
|
394
393
|
json_conversion.from_json({'_type': '__main__.A'})
|
|
395
394
|
|
|
395
|
+
def test_json_conversion_with_sharing(self):
|
|
396
|
+
|
|
397
|
+
class T(json_conversion.JSONConvertible):
|
|
398
|
+
|
|
399
|
+
def __init__(self, x=None):
|
|
400
|
+
self.x = x
|
|
401
|
+
|
|
402
|
+
def to_json(self, **kwargs):
|
|
403
|
+
return T.to_json_dict(dict(x=(self.x, None)), exclude_default=True)
|
|
404
|
+
|
|
405
|
+
t = T(1)
|
|
406
|
+
x = X(1)
|
|
407
|
+
u = {'x': x}
|
|
408
|
+
v = [u, t]
|
|
409
|
+
y = dict(t=t, x=x, u=u, v=v)
|
|
410
|
+
y_json = json_conversion.to_json(y)
|
|
411
|
+
x_serialized = json_conversion._OpaqueObject(x).to_json()
|
|
412
|
+
self.assertEqual(
|
|
413
|
+
y_json,
|
|
414
|
+
{
|
|
415
|
+
'__context__': {
|
|
416
|
+
'shared_objects': [
|
|
417
|
+
{
|
|
418
|
+
'_type': json_conversion._type_name(T),
|
|
419
|
+
'x': 1
|
|
420
|
+
},
|
|
421
|
+
x_serialized,
|
|
422
|
+
{
|
|
423
|
+
'x': {
|
|
424
|
+
'__ref__': 1
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
]
|
|
428
|
+
},
|
|
429
|
+
'__root__': {
|
|
430
|
+
't': {
|
|
431
|
+
'__ref__': 0
|
|
432
|
+
},
|
|
433
|
+
'x': {
|
|
434
|
+
'__ref__': 1
|
|
435
|
+
},
|
|
436
|
+
'u': {
|
|
437
|
+
'__ref__': 2
|
|
438
|
+
},
|
|
439
|
+
'v': [
|
|
440
|
+
{
|
|
441
|
+
'__ref__': 2
|
|
442
|
+
},
|
|
443
|
+
{
|
|
444
|
+
'__ref__': 0
|
|
445
|
+
}
|
|
446
|
+
]
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
)
|
|
450
|
+
y_prime = json_conversion.from_json(y_json)
|
|
451
|
+
self.assertIs(y_prime['t'], y_prime['v'][1])
|
|
452
|
+
self.assertIs(y_prime['u'], y_prime['v'][0])
|
|
453
|
+
|
|
396
454
|
|
|
397
455
|
if __name__ == '__main__':
|
|
398
456
|
unittest.main()
|
|
@@ -19,7 +19,7 @@ pyglove/core/detouring/__init__.py,sha256=ck_n2VSuU31HNVYQkbG4Zvnx90mNYtSVc2StN3
|
|
|
19
19
|
pyglove/core/detouring/class_detour.py,sha256=ejuUr7UfRU3l9PrDxD0dpKmt2iqdDU6liHdebA1jEfQ,13312
|
|
20
20
|
pyglove/core/detouring/class_detour_test.py,sha256=9aAK6qPiT0_HJe5oUpqMVTpoHv0wr_h6c4gWYKMTJoM,5507
|
|
21
21
|
pyglove/core/geno/__init__.py,sha256=cWA583n7JpsGUkGtoTlMMX83tNS7geRBKFsoPEvFZ4A,4767
|
|
22
|
-
pyglove/core/geno/base.py,sha256=
|
|
22
|
+
pyglove/core/geno/base.py,sha256=fZaTmmCJaiIm2ACU8EgEUHQLr9iF0f6yBNdYWIwYKWg,64639
|
|
23
23
|
pyglove/core/geno/base_test.py,sha256=f0pMpfXsjAqFOBtlOxof3jRX0jLvXpzowk9szcr6bgY,38512
|
|
24
24
|
pyglove/core/geno/categorical.py,sha256=t1bS129D5iF4CZ0yVHaFDXy2T6ujv5F5HHDc_0X36z8,28015
|
|
25
25
|
pyglove/core/geno/categorical_test.py,sha256=23UkOPhDSvonxxM_1YoBYp723Xim-BmBzywl612CRyg,18444
|
|
@@ -68,8 +68,8 @@ pyglove/core/patching/pattern_based_test.py,sha256=PW1EcVfsFPB6wtgwg3s4dzvigWn3b
|
|
|
68
68
|
pyglove/core/patching/rule_based.py,sha256=JAQp8mWeIOxwIdqusA3GmXia-fxQhQsxbUTmE329wF8,17038
|
|
69
69
|
pyglove/core/patching/rule_based_test.py,sha256=qfy0ILmczV_LMHWEnwo2y079OrJsGYO0nKxSZdmIUcI,18782
|
|
70
70
|
pyglove/core/symbolic/__init__.py,sha256=VMQj8oW2hGaJwqgchBJbu5qXoJKaNizxv__vsYL4b7U,6057
|
|
71
|
-
pyglove/core/symbolic/base.py,sha256=
|
|
72
|
-
pyglove/core/symbolic/base_test.py,sha256=
|
|
71
|
+
pyglove/core/symbolic/base.py,sha256=aRo-v1w1kGHPhJNddaGA-SZMeLSQs4dNyRPEjHH4Phg,79102
|
|
72
|
+
pyglove/core/symbolic/base_test.py,sha256=OHEexXI7uE2bixT-trfR3j-dfiAJsbY7pFyAA6XEPqA,7338
|
|
73
73
|
pyglove/core/symbolic/boilerplate.py,sha256=sQ3G25r5bo_UmIdjreL4jkAuQCXIHVlvUfGjjkNod6Y,5955
|
|
74
74
|
pyglove/core/symbolic/boilerplate_test.py,sha256=1CZ1W6kq3l-3tpaknhGFa04V18bO7vPzis5qzWnxHEs,5252
|
|
75
75
|
pyglove/core/symbolic/class_wrapper.py,sha256=xQiMh5vFlOQ76tbqsF5UWEghvU4d9UmvbNqIyBccNHg,22869
|
|
@@ -78,8 +78,8 @@ pyglove/core/symbolic/compounding.py,sha256=gvOodZ2gWHA0jNdwt8yvnRsPkHQDXDb5s88U
|
|
|
78
78
|
pyglove/core/symbolic/compounding_test.py,sha256=hOzrIROvajUTtPm0SUbEsEV4C1bXanhAoHinHrjZoXw,8320
|
|
79
79
|
pyglove/core/symbolic/contextual_object.py,sha256=ar9Q_0P0HHbDf8kLHpS8GFZEMCRuCCB6DP18iGItiiw,9146
|
|
80
80
|
pyglove/core/symbolic/contextual_object_test.py,sha256=wA5xfIhEHOC9qE3bbiA59CAPxWs9AVPaNiKEoprkWpQ,10209
|
|
81
|
-
pyglove/core/symbolic/dict.py,sha256=
|
|
82
|
-
pyglove/core/symbolic/dict_test.py,sha256=
|
|
81
|
+
pyglove/core/symbolic/dict.py,sha256=ELM-5XSxChNZNTaDxRDm_rCpMkuJ2nBASMOMxVjB07g,37657
|
|
82
|
+
pyglove/core/symbolic/dict_test.py,sha256=XPOe5DV_opYMkzSJZEl7rTvBBN-H9NvKe8zC7tQf7EE,74429
|
|
83
83
|
pyglove/core/symbolic/diff.py,sha256=zHfED0Bbq8G_HWNPj3vrOCWzt_062rFhx3BMlpCb9oo,16282
|
|
84
84
|
pyglove/core/symbolic/diff_test.py,sha256=EDiGHqqKhi-NeMxr-bgjBEqlquee_4l_0IM6hgAb9Mg,29400
|
|
85
85
|
pyglove/core/symbolic/error_info.py,sha256=rqRwfmnEibMixaS2G-P0VhKjkZl79qjO6EUItnATHlQ,3675
|
|
@@ -90,15 +90,15 @@ pyglove/core/symbolic/functor.py,sha256=AwE9GX2cO3QNihac1_ZN0sdG-TrWAJ5lXO2ZQ5Gs
|
|
|
90
90
|
pyglove/core/symbolic/functor_test.py,sha256=9c5_7OBKNVNbYC7IaVQB6c5ks2v00qQ36oivyWiBbKA,31798
|
|
91
91
|
pyglove/core/symbolic/inferred.py,sha256=E4zgphg6NNZad9Fl3jdHQOMZeqEp9XHq5OUYqXEmwZQ,3178
|
|
92
92
|
pyglove/core/symbolic/inferred_test.py,sha256=G6uPykONcChvs6vZujXHSWaYfjewLTVBscMqzzKNty0,1270
|
|
93
|
-
pyglove/core/symbolic/list.py,sha256=
|
|
94
|
-
pyglove/core/symbolic/list_test.py,sha256=
|
|
95
|
-
pyglove/core/symbolic/object.py,sha256=
|
|
93
|
+
pyglove/core/symbolic/list.py,sha256=CRDoBxYJsmNly1MxhY5vO0wp6EUTnxze6-2O9vXHna4,30717
|
|
94
|
+
pyglove/core/symbolic/list_test.py,sha256=Q3kqQ6aaQ40Nw6osSxrGk4N_E1lGZyvwdvxZgLRwMS0,61954
|
|
95
|
+
pyglove/core/symbolic/object.py,sha256=UgDgKdcYDYX3J51EMQamdwaXHkG4SHPunAGkSuHuTa0,42768
|
|
96
96
|
pyglove/core/symbolic/object_test.py,sha256=rDP7lcQZTFdQCqeaNYs7ZfwvGanzHz7CHM33NCmRIFk,94391
|
|
97
97
|
pyglove/core/symbolic/origin.py,sha256=OSWMKjvPcISOXrzuX3lCQC8m_qaGl-9INsIB81erUnU,6124
|
|
98
98
|
pyglove/core/symbolic/origin_test.py,sha256=dU_ZGrGDetM_lYVMn3wQO0d367_t_t8eESe3NrKPBNE,3159
|
|
99
99
|
pyglove/core/symbolic/pure_symbolic.py,sha256=pvo15gn35_KLiGW_XrTjlx5ddmHbwpLr93VgbQ59uQ8,3231
|
|
100
|
-
pyglove/core/symbolic/ref.py,sha256=
|
|
101
|
-
pyglove/core/symbolic/ref_test.py,sha256
|
|
100
|
+
pyglove/core/symbolic/ref.py,sha256=gIu02b8BfKspH1XejXhEFh_Iil3jvfGHdpaCRq6qor0,8817
|
|
101
|
+
pyglove/core/symbolic/ref_test.py,sha256=-rCA1AaLZnyuKOh0cJzS5UaQ_9Kp4p7xexZ_e3IwpOg,8974
|
|
102
102
|
pyglove/core/symbolic/symbolize.py,sha256=ohID9-V8QiFe7OMpPlRomiqUnKBVMpypd8ZuMuHaa4s,6582
|
|
103
103
|
pyglove/core/symbolic/symbolize_test.py,sha256=o7bRfMhGc6uw2FIH8arE99-bPb3i0YixcHYyiP-QqeQ,6487
|
|
104
104
|
pyglove/core/tuning/__init__.py,sha256=JtXpjsBto01fLf55hZ1dSx-CEZUyVQeyRP9AMH_hw8c,2229
|
|
@@ -113,8 +113,8 @@ pyglove/core/tuning/sample_test.py,sha256=JqwDPy3EPC_VjU9dipk90jj1kovZB3Zb9hAjAl
|
|
|
113
113
|
pyglove/core/typing/__init__.py,sha256=u2YSrSi8diTkQn8_1J2hEpk5o7zDhx2tU_oRuS-k1XU,14580
|
|
114
114
|
pyglove/core/typing/annotated.py,sha256=llaajIDj9GK-4kUGJoO4JsHU6ESPOra2SZ-jG6xmsOQ,3203
|
|
115
115
|
pyglove/core/typing/annotated_test.py,sha256=p1qid3R-jeiOTTxOVq6hXW8XFvn-h1cUzJWISPst2l8,2484
|
|
116
|
-
pyglove/core/typing/annotation_conversion.py,sha256=
|
|
117
|
-
pyglove/core/typing/annotation_conversion_test.py,sha256=
|
|
116
|
+
pyglove/core/typing/annotation_conversion.py,sha256=hi4LcRXwYzspHAKZ0rtXlq2dQyAmo9GXrm_HCJAGl34,15657
|
|
117
|
+
pyglove/core/typing/annotation_conversion_test.py,sha256=g66iyt3ti_pskIXndk4DiKgO8j8aMYOe2nPKXszlXmw,17675
|
|
118
118
|
pyglove/core/typing/annotation_future_test.py,sha256=tAVuzWNfW8R4e4l7fx88Q4nJDM2LPUogNKNAIIPAEWQ,3959
|
|
119
119
|
pyglove/core/typing/callable_ext.py,sha256=PiBQWPeUAH7Lgmf2xKCZqgK7N0OSrTdbnEkV8Ph31OA,9127
|
|
120
120
|
pyglove/core/typing/callable_ext_test.py,sha256=TnWKU4_ZjvpbHZFtFHgFvCMDiCos8VmLlODcM_7Xg8M,10156
|
|
@@ -136,7 +136,7 @@ pyglove/core/typing/typed_missing.py,sha256=-l1omAu0jBZv5BnsFYXBqfvQwVBnmPh_X1wc
|
|
|
136
136
|
pyglove/core/typing/typed_missing_test.py,sha256=TCNsb1SRpFaVdxYn2mB_yaLuja8w5Qn5NP7uGiZVBWs,2301
|
|
137
137
|
pyglove/core/typing/value_specs.py,sha256=8E83QDZMb3lMXhgzfVNt9u6Bg3NPkvpjLXetjkps8UU,103263
|
|
138
138
|
pyglove/core/typing/value_specs_test.py,sha256=eGXVxdduIM-oEaapJS9Kh7WSQHRUFegLIJ1GEzQkKHA,131017
|
|
139
|
-
pyglove/core/utils/__init__.py,sha256=
|
|
139
|
+
pyglove/core/utils/__init__.py,sha256=6P2VcGkjDsOFG640Jqu-jd1K3pfAK5NkcK3NBPLI6RY,8726
|
|
140
140
|
pyglove/core/utils/common_traits.py,sha256=PWxOgPhG5H60ZwfO8xNAEGRjFUqqDZQBWQYomOfvdy8,3640
|
|
141
141
|
pyglove/core/utils/common_traits_test.py,sha256=DIuZB_1xfmeTVfWnGOguDQcDAM_iGgBOe8C-5CsIqBc,1122
|
|
142
142
|
pyglove/core/utils/contextual.py,sha256=_EO_ubCcmI81QyYyyucm3QcH1asQWSeXvT2Xa4KONs8,5355
|
|
@@ -149,8 +149,8 @@ pyglove/core/utils/formatting.py,sha256=Wn4d933LQLhuMIfjdRJgpxOThCxBxQrkRBa6Z1-h
|
|
|
149
149
|
pyglove/core/utils/formatting_test.py,sha256=hhg-nL6DyE5A2QA92ALHK5QtfAYKfPpTbBARF-IT1j0,14241
|
|
150
150
|
pyglove/core/utils/hierarchical.py,sha256=jwB-0FhqOspAymAkvJphRhPTQEsoShmKupCZpU3Vip4,19690
|
|
151
151
|
pyglove/core/utils/hierarchical_test.py,sha256=f382DMJPa_bavJGGQDjuw-hWcafUg5bkQCPX-nbzeiI,21077
|
|
152
|
-
pyglove/core/utils/json_conversion.py,sha256=
|
|
153
|
-
pyglove/core/utils/json_conversion_test.py,sha256=
|
|
152
|
+
pyglove/core/utils/json_conversion.py,sha256=V7wUviGKhEPWi1gtCGSDXXvBzV6cO7CvmEiGDZ2jWFY,34643
|
|
153
|
+
pyglove/core/utils/json_conversion_test.py,sha256=6afuPJxBXIO-OxFzJBkv1MZqNX-44HIBD6YJjwxXSbs,13412
|
|
154
154
|
pyglove/core/utils/missing.py,sha256=9gslt1lXd1qSEIuAFxUWu30oD-YdYcnm13eau1S9uqY,1445
|
|
155
155
|
pyglove/core/utils/missing_test.py,sha256=D6-FuVEwCyJemUiPLcwLmwyptqI5Bx0Pfipc2juhKSE,1335
|
|
156
156
|
pyglove/core/utils/text_color.py,sha256=xcCTCxY2qFNZs_jismMGus8scEXKBpYGAhpAgnz-MHk,4112
|
|
@@ -218,8 +218,8 @@ pyglove/ext/scalars/randoms.py,sha256=LkMIIx7lOq_lvJvVS3BrgWGuWl7Pi91-lA-O8x_gZs
|
|
|
218
218
|
pyglove/ext/scalars/randoms_test.py,sha256=nEhiqarg8l_5EOucp59CYrpO2uKxS1pe0hmBdZUzRNM,2000
|
|
219
219
|
pyglove/ext/scalars/step_wise.py,sha256=IDw3tuTpv0KVh7AN44W43zqm1-E0HWPUlytWOQC9w3Y,3789
|
|
220
220
|
pyglove/ext/scalars/step_wise_test.py,sha256=TL1vJ19xVx2t5HKuyIzGoogF7N3Rm8YhLE6JF7i0iy8,2540
|
|
221
|
-
pyglove-0.5.0.
|
|
222
|
-
pyglove-0.5.0.
|
|
223
|
-
pyglove-0.5.0.
|
|
224
|
-
pyglove-0.5.0.
|
|
225
|
-
pyglove-0.5.0.
|
|
221
|
+
pyglove-0.5.0.dev202510230131.dist-info/licenses/LICENSE,sha256=WNHhf_5RCaeuKWyq_K39vmp9F28LxKsB4SpomwSZ2L0,11357
|
|
222
|
+
pyglove-0.5.0.dev202510230131.dist-info/METADATA,sha256=ym-feRv6WNcWJcHwyu0hlpledVeQZznTxZpGyI62kog,7089
|
|
223
|
+
pyglove-0.5.0.dev202510230131.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
224
|
+
pyglove-0.5.0.dev202510230131.dist-info/top_level.txt,sha256=wITzJSKcj8GZUkbq-MvUQnFadkiuAv_qv5qQMw0fIow,8
|
|
225
|
+
pyglove-0.5.0.dev202510230131.dist-info/RECORD,,
|
|
File without changes
|
{pyglove-0.5.0.dev202510210811.dist-info → pyglove-0.5.0.dev202510230131.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|
{pyglove-0.5.0.dev202510210811.dist-info → pyglove-0.5.0.dev202510230131.dist-info}/top_level.txt
RENAMED
|
File without changes
|