pyglove 0.5.0.dev202510020810__py3-none-any.whl → 0.5.0.dev202512280810__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.
Potentially problematic release.
This version of pyglove might be problematic. Click here for more details.
- pyglove/core/geno/base.py +7 -3
- pyglove/core/io/file_system.py +452 -2
- pyglove/core/io/file_system_test.py +442 -0
- pyglove/core/monitoring.py +213 -90
- pyglove/core/monitoring_test.py +82 -29
- pyglove/core/symbolic/__init__.py +7 -0
- pyglove/core/symbolic/base.py +89 -35
- pyglove/core/symbolic/base_test.py +3 -3
- pyglove/core/symbolic/dict.py +31 -12
- pyglove/core/symbolic/dict_test.py +49 -0
- pyglove/core/symbolic/list.py +17 -3
- pyglove/core/symbolic/list_test.py +24 -2
- pyglove/core/symbolic/object.py +3 -1
- pyglove/core/symbolic/object_test.py +13 -10
- pyglove/core/symbolic/ref.py +19 -7
- pyglove/core/symbolic/ref_test.py +94 -7
- pyglove/core/symbolic/unknown_symbols.py +147 -0
- pyglove/core/symbolic/unknown_symbols_test.py +100 -0
- pyglove/core/typing/annotation_conversion.py +8 -1
- pyglove/core/typing/annotation_conversion_test.py +14 -19
- pyglove/core/typing/class_schema.py +24 -1
- pyglove/core/typing/json_schema.py +221 -8
- pyglove/core/typing/json_schema_test.py +508 -12
- pyglove/core/typing/type_conversion.py +17 -3
- pyglove/core/typing/type_conversion_test.py +7 -2
- pyglove/core/typing/value_specs.py +5 -1
- pyglove/core/typing/value_specs_test.py +5 -0
- pyglove/core/utils/__init__.py +1 -0
- pyglove/core/utils/contextual.py +9 -4
- pyglove/core/utils/contextual_test.py +10 -0
- pyglove/core/utils/json_conversion.py +360 -63
- pyglove/core/utils/json_conversion_test.py +146 -13
- pyglove/core/views/html/controls/tab.py +33 -0
- pyglove/core/views/html/controls/tab_test.py +37 -0
- pyglove/ext/evolution/base_test.py +1 -1
- {pyglove-0.5.0.dev202510020810.dist-info → pyglove-0.5.0.dev202512280810.dist-info}/METADATA +8 -1
- {pyglove-0.5.0.dev202510020810.dist-info → pyglove-0.5.0.dev202512280810.dist-info}/RECORD +40 -38
- {pyglove-0.5.0.dev202510020810.dist-info → pyglove-0.5.0.dev202512280810.dist-info}/WHEEL +0 -0
- {pyglove-0.5.0.dev202510020810.dist-info → pyglove-0.5.0.dev202512280810.dist-info}/licenses/LICENSE +0 -0
- {pyglove-0.5.0.dev202510020810.dist-info → pyglove-0.5.0.dev202512280810.dist-info}/top_level.txt +0 -0
pyglove/core/symbolic/base.py
CHANGED
|
@@ -503,10 +503,12 @@ class Symbolic(
|
|
|
503
503
|
return default
|
|
504
504
|
|
|
505
505
|
def _sym_inferred(self, key: Union[str, int], **kwargs) -> Any:
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
506
|
+
return self._infer_if_applicable(self.sym_getattr(key), **kwargs)
|
|
507
|
+
|
|
508
|
+
def _infer_if_applicable(self, value: Any, **kwargs) -> Any:
|
|
509
|
+
if isinstance(value, Inferential):
|
|
510
|
+
return value.infer(**kwargs)
|
|
511
|
+
return value
|
|
510
512
|
|
|
511
513
|
@abc.abstractmethod
|
|
512
514
|
def sym_keys(self) -> Iterator[Union[str, int]]:
|
|
@@ -944,7 +946,7 @@ class Symbolic(
|
|
|
944
946
|
|
|
945
947
|
def to_json(self, **kwargs) -> utils.JSONValueType:
|
|
946
948
|
"""Alias for `sym_jsonify`."""
|
|
947
|
-
return to_json(self, **kwargs)
|
|
949
|
+
return utils.to_json(self, **kwargs)
|
|
948
950
|
|
|
949
951
|
def to_json_str(self, json_indent: Optional[int] = None, **kwargs) -> str:
|
|
950
952
|
"""Serializes current object into a JSON string."""
|
|
@@ -1983,10 +1985,12 @@ def is_abstract(x: Any) -> bool:
|
|
|
1983
1985
|
def contains(
|
|
1984
1986
|
x: Any,
|
|
1985
1987
|
value: Any = None,
|
|
1986
|
-
type:
|
|
1988
|
+
type: Union[ # pylint: disable=redefined-builtin
|
|
1987
1989
|
Type[Any],
|
|
1988
|
-
Tuple[Type[Any]]
|
|
1989
|
-
|
|
1990
|
+
Tuple[Type[Any], ...],
|
|
1991
|
+
None,
|
|
1992
|
+
]=None,
|
|
1993
|
+
) -> bool:
|
|
1990
1994
|
"""Returns if a value contains values of specific type.
|
|
1991
1995
|
|
|
1992
1996
|
Example::
|
|
@@ -2035,10 +2039,12 @@ def contains(
|
|
|
2035
2039
|
def from_json(
|
|
2036
2040
|
json_value: Any,
|
|
2037
2041
|
*,
|
|
2042
|
+
context: Optional[utils.JSONConversionContext] = None,
|
|
2043
|
+
auto_symbolic: bool = True,
|
|
2044
|
+
auto_import: bool = True,
|
|
2045
|
+
convert_unknown: bool = False,
|
|
2038
2046
|
allow_partial: bool = False,
|
|
2039
2047
|
root_path: Optional[utils.KeyPath] = None,
|
|
2040
|
-
auto_import: bool = True,
|
|
2041
|
-
auto_dict: bool = False,
|
|
2042
2048
|
value_spec: Optional[pg_typing.ValueSpec] = None,
|
|
2043
2049
|
**kwargs,
|
|
2044
2050
|
) -> Any:
|
|
@@ -2059,14 +2065,23 @@ def from_json(
|
|
|
2059
2065
|
|
|
2060
2066
|
Args:
|
|
2061
2067
|
json_value: Input JSON value.
|
|
2062
|
-
|
|
2063
|
-
|
|
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.
|
|
2064
2072
|
auto_import: If True, when a '_type' is not registered, PyGlove will
|
|
2065
2073
|
identify its parent module and automatically import it. For example,
|
|
2066
2074
|
if the type is 'foo.bar.A', PyGlove will try to import 'foo.bar' and
|
|
2067
2075
|
find the class 'A' within the imported module.
|
|
2068
|
-
|
|
2069
|
-
|
|
2076
|
+
convert_unknown: If True, when a '_type' is not registered and cannot
|
|
2077
|
+
be imported, PyGlove will create objects of:
|
|
2078
|
+
- `pg.symbolic.UnknownType` for unknown types;
|
|
2079
|
+
- `pg.symbolic.UnknownTypedObject` for objects of unknown types;
|
|
2080
|
+
- `pg.symbolic.UnknownFunction` for unknown functions;
|
|
2081
|
+
- `pg.symbolic.UnknownMethod` for unknown methods.
|
|
2082
|
+
If False, TypeError will be raised.
|
|
2083
|
+
allow_partial: Whether to allow elements of the list to be partial.
|
|
2084
|
+
root_path: KeyPath of loaded object in its object tree.
|
|
2070
2085
|
value_spec: The value spec for the symbolic list or dict.
|
|
2071
2086
|
**kwargs: Allow passing through keyword arguments to from_json of specific
|
|
2072
2087
|
types.
|
|
@@ -2082,10 +2097,23 @@ def from_json(
|
|
|
2082
2097
|
if isinstance(json_value, Symbolic):
|
|
2083
2098
|
return json_value
|
|
2084
2099
|
|
|
2100
|
+
if context is None:
|
|
2101
|
+
if (isinstance(json_value, dict) and (
|
|
2102
|
+
context_node := json_value.get(utils.JSONConvertible.CONTEXT_KEY))):
|
|
2103
|
+
context = utils.JSONConversionContext.from_json(
|
|
2104
|
+
context_node,
|
|
2105
|
+
auto_import=auto_import,
|
|
2106
|
+
convert_unknown=convert_unknown,
|
|
2107
|
+
**kwargs
|
|
2108
|
+
)
|
|
2109
|
+
json_value = json_value[utils.JSONConvertible.ROOT_VALUE_KEY]
|
|
2110
|
+
else:
|
|
2111
|
+
context = utils.JSONConversionContext()
|
|
2112
|
+
|
|
2085
2113
|
typename_resolved = kwargs.pop('_typename_resolved', False)
|
|
2086
2114
|
if not typename_resolved:
|
|
2087
2115
|
json_value = utils.json_conversion.resolve_typenames(
|
|
2088
|
-
json_value, auto_import
|
|
2116
|
+
json_value, auto_import, convert_unknown
|
|
2089
2117
|
)
|
|
2090
2118
|
|
|
2091
2119
|
def _load_child(k, v):
|
|
@@ -2094,6 +2122,7 @@ def from_json(
|
|
|
2094
2122
|
root_path=utils.KeyPath(k, root_path),
|
|
2095
2123
|
_typename_resolved=True,
|
|
2096
2124
|
allow_partial=allow_partial,
|
|
2125
|
+
context=context,
|
|
2097
2126
|
**kwargs,
|
|
2098
2127
|
)
|
|
2099
2128
|
|
|
@@ -2109,24 +2138,42 @@ def from_json(
|
|
|
2109
2138
|
)
|
|
2110
2139
|
)
|
|
2111
2140
|
return tuple(_load_child(i, v) for i, v in enumerate(json_value[1:]))
|
|
2112
|
-
|
|
2141
|
+
if json_value and json_value[0] == utils.JSONConvertible.SYMBOLIC_MARKER:
|
|
2142
|
+
auto_symbolic = True
|
|
2143
|
+
if auto_symbolic:
|
|
2144
|
+
from_json_fn = Symbolic.ListType.from_json # pytype: disable=attribute-error
|
|
2145
|
+
else:
|
|
2146
|
+
from_json_fn = utils.from_json
|
|
2147
|
+
return from_json_fn(
|
|
2113
2148
|
json_value,
|
|
2149
|
+
context=context,
|
|
2114
2150
|
value_spec=value_spec,
|
|
2115
2151
|
root_path=root_path,
|
|
2116
2152
|
allow_partial=allow_partial,
|
|
2117
2153
|
**kwargs,
|
|
2118
2154
|
)
|
|
2119
2155
|
elif isinstance(json_value, dict):
|
|
2156
|
+
if utils.JSONConvertible.REF_KEY in json_value:
|
|
2157
|
+
x = context.get_shared(
|
|
2158
|
+
json_value[utils.JSONConvertible.REF_KEY]
|
|
2159
|
+
).value
|
|
2160
|
+
return x
|
|
2120
2161
|
if utils.JSONConvertible.TYPE_NAME_KEY not in json_value:
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
value_spec=value_spec,
|
|
2124
|
-
root_path=root_path,
|
|
2125
|
-
allow_partial=allow_partial,
|
|
2126
|
-
**kwargs,
|
|
2162
|
+
auto_symbolic = json_value.get(
|
|
2163
|
+
utils.JSONConvertible.SYMBOLIC_MARKER, auto_symbolic
|
|
2127
2164
|
)
|
|
2165
|
+
if auto_symbolic:
|
|
2166
|
+
return Symbolic.DictType.from_json( # pytype: disable=attribute-error
|
|
2167
|
+
json_value,
|
|
2168
|
+
context=context,
|
|
2169
|
+
value_spec=value_spec,
|
|
2170
|
+
root_path=root_path,
|
|
2171
|
+
allow_partial=allow_partial,
|
|
2172
|
+
**kwargs,
|
|
2173
|
+
)
|
|
2128
2174
|
return utils.from_json(
|
|
2129
2175
|
json_value,
|
|
2176
|
+
context=context,
|
|
2130
2177
|
_typename_resolved=True,
|
|
2131
2178
|
root_path=root_path,
|
|
2132
2179
|
allow_partial=allow_partial,
|
|
@@ -2138,10 +2185,12 @@ def from_json(
|
|
|
2138
2185
|
def from_json_str(
|
|
2139
2186
|
json_str: str,
|
|
2140
2187
|
*,
|
|
2188
|
+
context: Optional[utils.JSONConversionContext] = None,
|
|
2189
|
+
auto_import: bool = True,
|
|
2190
|
+
convert_unknown: bool = False,
|
|
2141
2191
|
allow_partial: bool = False,
|
|
2142
2192
|
root_path: Optional[utils.KeyPath] = None,
|
|
2143
|
-
|
|
2144
|
-
auto_dict: bool = False,
|
|
2193
|
+
value_spec: Optional[pg_typing.ValueSpec] = None,
|
|
2145
2194
|
**kwargs,
|
|
2146
2195
|
) -> Any:
|
|
2147
2196
|
"""Deserialize (maybe) symbolic object from JSON string.
|
|
@@ -2161,15 +2210,22 @@ def from_json_str(
|
|
|
2161
2210
|
|
|
2162
2211
|
Args:
|
|
2163
2212
|
json_str: JSON string.
|
|
2164
|
-
|
|
2165
|
-
Otherwise error will be raised on partial value.
|
|
2166
|
-
root_path: The symbolic path used for the deserialized root object.
|
|
2213
|
+
context: JSON conversion context.
|
|
2167
2214
|
auto_import: If True, when a '_type' is not registered, PyGlove will
|
|
2168
2215
|
identify its parent module and automatically import it. For example,
|
|
2169
2216
|
if the type is 'foo.bar.A', PyGlove will try to import 'foo.bar' and
|
|
2170
2217
|
find the class 'A' within the imported module.
|
|
2171
|
-
|
|
2172
|
-
|
|
2218
|
+
convert_unknown: If True, when a '_type' is not registered and cannot
|
|
2219
|
+
be imported, PyGlove will create objects of:
|
|
2220
|
+
- `pg.symbolic.UnknownType` for unknown types;
|
|
2221
|
+
- `pg.symbolic.UnknownTypedObject` for objects of unknown types;
|
|
2222
|
+
- `pg.symbolic.UnknownFunction` for unknown functions;
|
|
2223
|
+
- `pg.symbolic.UnknownMethod` for unknown methods.
|
|
2224
|
+
If False, TypeError will be raised.
|
|
2225
|
+
allow_partial: If True, allow a partial symbolic object to be created.
|
|
2226
|
+
Otherwise error will be raised on partial value.
|
|
2227
|
+
root_path: The symbolic path used for the deserialized root object.
|
|
2228
|
+
value_spec: The value spec for the symbolic list or dict.
|
|
2173
2229
|
**kwargs: Additional keyword arguments that will be passed to
|
|
2174
2230
|
``pg.from_json``.
|
|
2175
2231
|
|
|
@@ -2193,10 +2249,12 @@ def from_json_str(
|
|
|
2193
2249
|
|
|
2194
2250
|
return from_json(
|
|
2195
2251
|
_decode_int_keys(json.loads(json_str)),
|
|
2252
|
+
context=context,
|
|
2253
|
+
auto_import=auto_import,
|
|
2254
|
+
convert_unknown=convert_unknown,
|
|
2196
2255
|
allow_partial=allow_partial,
|
|
2197
2256
|
root_path=root_path,
|
|
2198
|
-
|
|
2199
|
-
auto_dict=auto_dict,
|
|
2257
|
+
value_spec=value_spec,
|
|
2200
2258
|
**kwargs
|
|
2201
2259
|
)
|
|
2202
2260
|
|
|
@@ -2232,10 +2290,6 @@ def to_json(value: Any, **kwargs) -> Any:
|
|
|
2232
2290
|
Returns:
|
|
2233
2291
|
JSON value.
|
|
2234
2292
|
"""
|
|
2235
|
-
# NOTE(daiyip): special handling `sym_jsonify` since symbolized
|
|
2236
|
-
# classes may have conflicting `to_json` method in their existing classes.
|
|
2237
|
-
if isinstance(value, Symbolic):
|
|
2238
|
-
return value.sym_jsonify(**kwargs)
|
|
2239
2293
|
return utils.to_json(value, **kwargs)
|
|
2240
2294
|
|
|
2241
2295
|
|
|
@@ -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(
|
|
@@ -236,7 +238,8 @@ class Dict(dict, base.Symbolic, pg_typing.CustomTyping):
|
|
|
236
238
|
accessor_writable=True,
|
|
237
239
|
# We delay seal operation until members are filled.
|
|
238
240
|
sealed=False,
|
|
239
|
-
root_path=root_path
|
|
241
|
+
root_path=root_path
|
|
242
|
+
)
|
|
240
243
|
|
|
241
244
|
dict.__init__(self)
|
|
242
245
|
self._value_spec = None
|
|
@@ -247,9 +250,10 @@ class Dict(dict, base.Symbolic, pg_typing.CustomTyping):
|
|
|
247
250
|
for k, v in kwargs.items():
|
|
248
251
|
dict_obj[k] = v
|
|
249
252
|
|
|
253
|
+
iter_items = getattr(dict_obj, 'sym_items', dict_obj.items)
|
|
250
254
|
if value_spec:
|
|
251
255
|
if pass_through:
|
|
252
|
-
for k, v in
|
|
256
|
+
for k, v in iter_items():
|
|
253
257
|
super().__setitem__(k, self._relocate_if_symbolic(k, v))
|
|
254
258
|
|
|
255
259
|
# NOTE(daiyip): when pass_through is on, we simply trust input
|
|
@@ -258,11 +262,11 @@ class Dict(dict, base.Symbolic, pg_typing.CustomTyping):
|
|
|
258
262
|
# repeated validation and transformation.
|
|
259
263
|
self._value_spec = value_spec
|
|
260
264
|
else:
|
|
261
|
-
for k, v in
|
|
265
|
+
for k, v in iter_items():
|
|
262
266
|
super().__setitem__(k, self._formalized_value(k, None, v))
|
|
263
267
|
self.use_value_spec(value_spec, allow_partial)
|
|
264
268
|
else:
|
|
265
|
-
for k, v in
|
|
269
|
+
for k, v in iter_items():
|
|
266
270
|
self._set_item_without_permission_check(k, v)
|
|
267
271
|
|
|
268
272
|
# NOTE(daiyip): We set onchange callback at the end of init to avoid
|
|
@@ -537,7 +541,7 @@ class Dict(dict, base.Symbolic, pg_typing.CustomTyping):
|
|
|
537
541
|
raise KeyError(self._error_message(
|
|
538
542
|
f'Key must be string or int type. Encountered {key!r}.'))
|
|
539
543
|
|
|
540
|
-
old_value = self.
|
|
544
|
+
old_value = self.sym_getattr(key, pg_typing.MISSING_VALUE)
|
|
541
545
|
if old_value is value:
|
|
542
546
|
return None
|
|
543
547
|
|
|
@@ -644,6 +648,13 @@ class Dict(dict, base.Symbolic, pg_typing.CustomTyping):
|
|
|
644
648
|
except AttributeError as e:
|
|
645
649
|
raise KeyError(key) from e
|
|
646
650
|
|
|
651
|
+
def get(self, key: Union[str, int], default: Any = None) -> Any:
|
|
652
|
+
"""Get item in this Dict."""
|
|
653
|
+
try:
|
|
654
|
+
return self.sym_inferred(key)
|
|
655
|
+
except AttributeError:
|
|
656
|
+
return default
|
|
657
|
+
|
|
647
658
|
def __setitem__(self, key: Union[str, int], value: Any) -> None:
|
|
648
659
|
"""Set item in this Dict.
|
|
649
660
|
|
|
@@ -751,11 +762,13 @@ class Dict(dict, base.Symbolic, pg_typing.CustomTyping):
|
|
|
751
762
|
|
|
752
763
|
def items(self) -> Iterator[Tuple[Union[str, int], Any]]: # pytype: disable=signature-mismatch
|
|
753
764
|
"""Returns an iterator of (key, value) items in current dict."""
|
|
754
|
-
|
|
765
|
+
for k, v in self.sym_items():
|
|
766
|
+
yield k, self._infer_if_applicable(v)
|
|
755
767
|
|
|
756
768
|
def values(self) -> Iterator[Any]: # pytype: disable=signature-mismatch
|
|
757
769
|
"""Returns an iterator of values in current dict.."""
|
|
758
|
-
|
|
770
|
+
for v in self.sym_values():
|
|
771
|
+
yield self._infer_if_applicable(v)
|
|
759
772
|
|
|
760
773
|
def copy(self) -> 'Dict':
|
|
761
774
|
"""Overridden copy using symbolic copy."""
|
|
@@ -824,12 +837,15 @@ class Dict(dict, base.Symbolic, pg_typing.CustomTyping):
|
|
|
824
837
|
hide_default_values: bool = False,
|
|
825
838
|
exclude_keys: Optional[Sequence[Union[str, int]]] = None,
|
|
826
839
|
use_inferred: bool = False,
|
|
840
|
+
omit_symbolic_marker: bool = True,
|
|
827
841
|
**kwargs,
|
|
828
842
|
) -> utils.JSONValueType:
|
|
829
843
|
"""Converts current object to a dict with plain Python objects."""
|
|
830
844
|
exclude_keys = set(exclude_keys or [])
|
|
845
|
+
json_repr = {}
|
|
846
|
+
if not omit_symbolic_marker:
|
|
847
|
+
json_repr[utils.JSONConvertible.SYMBOLIC_MARKER] = True
|
|
831
848
|
if self._value_spec and self._value_spec.schema:
|
|
832
|
-
json_repr = dict()
|
|
833
849
|
matched_keys, _ = self._value_spec.schema.resolve(self.keys()) # pytype: disable=attribute-error
|
|
834
850
|
for key_spec, keys in matched_keys.items():
|
|
835
851
|
# NOTE(daiyip): The key values of frozen field can safely be excluded
|
|
@@ -851,20 +867,23 @@ class Dict(dict, base.Symbolic, pg_typing.CustomTyping):
|
|
|
851
867
|
hide_frozen=hide_frozen,
|
|
852
868
|
hide_default_values=hide_default_values,
|
|
853
869
|
use_inferred=use_inferred,
|
|
854
|
-
|
|
855
|
-
|
|
870
|
+
omit_symbolic_marker=omit_symbolic_marker,
|
|
871
|
+
**kwargs
|
|
872
|
+
)
|
|
856
873
|
else:
|
|
857
|
-
|
|
874
|
+
json_repr.update({
|
|
858
875
|
k: base.to_json(
|
|
859
876
|
self.sym_inferred(k, default=v) if (
|
|
860
877
|
use_inferred and isinstance(v, base.Inferential)) else v,
|
|
861
878
|
hide_frozen=hide_frozen,
|
|
862
879
|
hide_default_values=hide_default_values,
|
|
863
880
|
use_inferred=use_inferred,
|
|
881
|
+
omit_symbolic_marker=omit_symbolic_marker,
|
|
864
882
|
**kwargs)
|
|
865
883
|
for k, v in self.sym_items()
|
|
866
884
|
if k not in exclude_keys
|
|
867
|
-
}
|
|
885
|
+
})
|
|
886
|
+
return json_repr
|
|
868
887
|
|
|
869
888
|
def custom_apply(
|
|
870
889
|
self,
|
|
@@ -415,6 +415,19 @@ class DictTest(unittest.TestCase):
|
|
|
415
415
|
with self.assertRaisesRegex(KeyError, 'Key \'y1\' is not allowed'):
|
|
416
416
|
sd.y1 = 4
|
|
417
417
|
|
|
418
|
+
def test_get(self):
|
|
419
|
+
sd = Dict(a=1)
|
|
420
|
+
self.assertEqual(sd.get('a'), 1)
|
|
421
|
+
self.assertIsNone(sd.get('x'))
|
|
422
|
+
self.assertEqual(sd.get('x', 2), 2)
|
|
423
|
+
|
|
424
|
+
# Test inferred values.
|
|
425
|
+
sd = Dict(x=inferred.ValueFromParentChain())
|
|
426
|
+
self.assertIsNone(sd.get('x'))
|
|
427
|
+
|
|
428
|
+
_ = Dict(sd=sd, x=1)
|
|
429
|
+
self.assertEqual(sd.get('x'), 1)
|
|
430
|
+
|
|
418
431
|
def test_getattr(self):
|
|
419
432
|
sd = Dict(a=1)
|
|
420
433
|
self.assertEqual(sd.a, 1)
|
|
@@ -713,6 +726,11 @@ class DictTest(unittest.TestCase):
|
|
|
713
726
|
]))
|
|
714
727
|
self.assertEqual(list(sd.values()), [2, 1, 3])
|
|
715
728
|
|
|
729
|
+
# Test values with inferred values.
|
|
730
|
+
sd = Dict(x=1, y=inferred.ValueFromParentChain())
|
|
731
|
+
_ = Dict(sd=sd, y=2)
|
|
732
|
+
self.assertEqual(list(sd.values()), [1, 2])
|
|
733
|
+
|
|
716
734
|
def test_items(self):
|
|
717
735
|
sd = Dict(b={'c': True, 'd': []}, a=0)
|
|
718
736
|
self.assertEqual(list(sd.items()), [('b', {'c': True, 'd': []}), ('a', 0)])
|
|
@@ -725,6 +743,11 @@ class DictTest(unittest.TestCase):
|
|
|
725
743
|
]))
|
|
726
744
|
self.assertEqual(list(sd.items()), [('b', 2), ('a', 1), ('c', 3)])
|
|
727
745
|
|
|
746
|
+
# Test items with inferred values.
|
|
747
|
+
sd = Dict(x=1, y=inferred.ValueFromParentChain())
|
|
748
|
+
_ = Dict(sd=sd, y=2)
|
|
749
|
+
self.assertEqual(list(sd.items()), [('x', 1), ('y', 2)])
|
|
750
|
+
|
|
728
751
|
def test_non_default(self):
|
|
729
752
|
sd = Dict(a=1)
|
|
730
753
|
self.assertEqual(len(sd.non_default_values()), 1)
|
|
@@ -923,6 +946,14 @@ class DictTest(unittest.TestCase):
|
|
|
923
946
|
sd.sym_jsonify(),
|
|
924
947
|
{'x': 1, 'y': inferred.ValueFromParentChain().to_json()},
|
|
925
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
|
+
)
|
|
926
957
|
|
|
927
958
|
def test_sym_rebind(self):
|
|
928
959
|
# Refer to RebindTest for more detailed tests.
|
|
@@ -1941,6 +1972,24 @@ class SerializationTest(unittest.TestCase):
|
|
|
1941
1972
|
self.assertEqual(sd.to_json_str(), '{"x": 1, "y": 2.0}')
|
|
1942
1973
|
self.assertEqual(base.from_json_str(sd.to_json_str(), value_spec=spec), sd)
|
|
1943
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
|
+
|
|
1944
1993
|
def test_hide_frozen(self):
|
|
1945
1994
|
|
|
1946
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,
|
|
@@ -506,8 +506,7 @@ class ListTest(unittest.TestCase):
|
|
|
506
506
|
def test_index(self):
|
|
507
507
|
sl = List([0, 1, 2, 1])
|
|
508
508
|
self.assertEqual(sl.index(1), 1)
|
|
509
|
-
with self.assertRaisesRegex(
|
|
510
|
-
ValueError, '3 is not in list'):
|
|
509
|
+
with self.assertRaisesRegex(ValueError, '.* not in list'):
|
|
511
510
|
_ = sl.index(3)
|
|
512
511
|
|
|
513
512
|
# Index of inferred value is based on its symbolic form.
|
|
@@ -799,6 +798,10 @@ class ListTest(unittest.TestCase):
|
|
|
799
798
|
self.assertEqual(
|
|
800
799
|
sl.sym_jsonify(), [0, inferred.ValueFromParentChain().to_json()]
|
|
801
800
|
)
|
|
801
|
+
self.assertEqual(
|
|
802
|
+
sl.sym_jsonify(omit_symbolic_marker=False),
|
|
803
|
+
['__symbolic__', 0, inferred.ValueFromParentChain().to_json()]
|
|
804
|
+
)
|
|
802
805
|
|
|
803
806
|
def test_sym_rebind(self):
|
|
804
807
|
# Refer to RebindTest for more detailed tests.
|
|
@@ -1633,6 +1636,25 @@ class SerializationTest(unittest.TestCase):
|
|
|
1633
1636
|
'Tuple should have at least one element besides \'__tuple__\'.'):
|
|
1634
1637
|
base.from_json_str('["__tuple__"]')
|
|
1635
1638
|
|
|
1639
|
+
def test_auto_symbolic(self):
|
|
1640
|
+
value = base.from_json([1, 2, 3], auto_symbolic=True)
|
|
1641
|
+
self.assertIsInstance(value, List)
|
|
1642
|
+
|
|
1643
|
+
value = base.from_json([1, 2, 3], auto_symbolic=False)
|
|
1644
|
+
self.assertNotIsInstance(value, List)
|
|
1645
|
+
|
|
1646
|
+
def test_omit_symbolic_marker(self):
|
|
1647
|
+
sl = List([0])
|
|
1648
|
+
self.assertEqual(sl.to_json(omit_symbolic_marker=True), [0])
|
|
1649
|
+
self.assertEqual(
|
|
1650
|
+
sl.to_json(omit_symbolic_marker=False),
|
|
1651
|
+
['__symbolic__', 0]
|
|
1652
|
+
)
|
|
1653
|
+
sl = base.from_json(['__symbolic__', 0], auto_symbolic=False)
|
|
1654
|
+
self.assertEqual(sl.to_json(omit_symbolic_marker=True), [0])
|
|
1655
|
+
self.assertIsInstance(sl, List)
|
|
1656
|
+
self.assertEqual(sl, [0])
|
|
1657
|
+
|
|
1636
1658
|
def test_hide_default_values(self):
|
|
1637
1659
|
sl = List.partial(
|
|
1638
1660
|
[dict(x=1)],
|
pyglove/core/symbolic/object.py
CHANGED
|
@@ -339,7 +339,8 @@ class Object(base.Symbolic, metaclass=ObjectMeta):
|
|
|
339
339
|
|
|
340
340
|
# Set `__serialization_key__` before JSONConvertible.__init_subclass__
|
|
341
341
|
# is called.
|
|
342
|
-
|
|
342
|
+
if '__serialization_key__' not in cls.__dict__:
|
|
343
|
+
setattr(cls, '__serialization_key__', cls.__type_name__)
|
|
343
344
|
|
|
344
345
|
super().__init_subclass__()
|
|
345
346
|
|
|
@@ -994,6 +995,7 @@ class Object(base.Symbolic, metaclass=ObjectMeta):
|
|
|
994
995
|
self.__class__.__serialization_key__
|
|
995
996
|
)
|
|
996
997
|
}
|
|
998
|
+
kwargs['omit_symbolic_marker'] = True
|
|
997
999
|
json_dict.update(self._sym_attributes.to_json(**kwargs))
|
|
998
1000
|
return json_dict
|
|
999
1001
|
|
|
@@ -38,6 +38,7 @@ from pyglove.core.symbolic.object import use_init_args as pg_use_init_args
|
|
|
38
38
|
from pyglove.core.symbolic.origin import Origin
|
|
39
39
|
from pyglove.core.symbolic.pure_symbolic import NonDeterministic
|
|
40
40
|
from pyglove.core.symbolic.pure_symbolic import PureSymbolic
|
|
41
|
+
from pyglove.core.symbolic.unknown_symbols import UnknownTypedObject
|
|
41
42
|
from pyglove.core.views.html import tree_view # pylint: disable=unused-import
|
|
42
43
|
|
|
43
44
|
|
|
@@ -3158,7 +3159,7 @@ class SerializationTest(unittest.TestCase):
|
|
|
3158
3159
|
Q.partial(P.partial()).to_json_str(), allow_partial=True),
|
|
3159
3160
|
Q.partial(P.partial()))
|
|
3160
3161
|
|
|
3161
|
-
def
|
|
3162
|
+
def test_serialization_with_convert_unknown(self):
|
|
3162
3163
|
|
|
3163
3164
|
class P(Object):
|
|
3164
3165
|
auto_register = False
|
|
@@ -3181,15 +3182,17 @@ class SerializationTest(unittest.TestCase):
|
|
|
3181
3182
|
}
|
|
3182
3183
|
)
|
|
3183
3184
|
self.assertEqual(
|
|
3184
|
-
base.from_json_str(
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3185
|
+
base.from_json_str(
|
|
3186
|
+
Q(P(1), y='foo').to_json_str(), convert_unknown=True
|
|
3187
|
+
),
|
|
3188
|
+
UnknownTypedObject(
|
|
3189
|
+
type_name=Q.__type_name__,
|
|
3190
|
+
p=UnknownTypedObject(
|
|
3191
|
+
type_name=P.__type_name__,
|
|
3192
|
+
x=1
|
|
3193
|
+
),
|
|
3194
|
+
y='foo'
|
|
3195
|
+
)
|
|
3193
3196
|
)
|
|
3194
3197
|
|
|
3195
3198
|
def test_serialization_with_converter(self):
|
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.')
|