pyglove 0.5.0.dev202510200810__py3-none-any.whl → 0.5.0.dev202511240812__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 +295 -2
- pyglove/core/io/file_system_test.py +291 -0
- 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 +4 -1
- 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/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.dev202510200810.dist-info → pyglove-0.5.0.dev202511240812.dist-info}/METADATA +8 -1
- {pyglove-0.5.0.dev202510200810.dist-info → pyglove-0.5.0.dev202511240812.dist-info}/RECORD +34 -32
- {pyglove-0.5.0.dev202510200810.dist-info → pyglove-0.5.0.dev202511240812.dist-info}/WHEEL +0 -0
- {pyglove-0.5.0.dev202510200810.dist-info → pyglove-0.5.0.dev202511240812.dist-info}/licenses/LICENSE +0 -0
- {pyglove-0.5.0.dev202510200810.dist-info → pyglove-0.5.0.dev202511240812.dist-info}/top_level.txt +0 -0
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.')
|
|
@@ -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(
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# Copyright 2021 The PyGlove Authors
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
"""Symbolic types for reprenting unknown types and objects."""
|
|
15
|
+
|
|
16
|
+
from typing import Annotated, Any, ClassVar, Literal
|
|
17
|
+
from pyglove.core import typing as pg_typing
|
|
18
|
+
from pyglove.core import utils
|
|
19
|
+
from pyglove.core.symbolic import list as pg_list # pylint: disable=unused-import
|
|
20
|
+
from pyglove.core.symbolic import object as pg_object
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class UnknownSymbol(pg_object.Object, pg_typing.CustomTyping):
|
|
24
|
+
"""Interface for symbolic representation of unknown symbols."""
|
|
25
|
+
auto_register = False
|
|
26
|
+
|
|
27
|
+
def custom_apply(self, *args, **kwargs) -> tuple[bool, Any]:
|
|
28
|
+
"""Bypass PyGlove type check."""
|
|
29
|
+
return (False, self)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class UnknownType(UnknownSymbol):
|
|
33
|
+
"""Symbolic object for representing unknown types."""
|
|
34
|
+
|
|
35
|
+
auto_register = True
|
|
36
|
+
__serialization_key__ = 'unknown_type'
|
|
37
|
+
|
|
38
|
+
# TODO(daiyip): Revisit the design on how `pg.typing.Object()` handles
|
|
39
|
+
# UnknownType. This hacky solution should be removed in the future.
|
|
40
|
+
__no_type_check__ = True
|
|
41
|
+
|
|
42
|
+
name: str
|
|
43
|
+
args: list[Any] = []
|
|
44
|
+
|
|
45
|
+
def sym_jsonify(self, **kwargs) -> utils.JSONValueType:
|
|
46
|
+
json_dict = {'_type': 'type', 'name': self.name}
|
|
47
|
+
if self.args:
|
|
48
|
+
json_dict['args'] = utils.to_json(self.args, **kwargs)
|
|
49
|
+
return json_dict
|
|
50
|
+
|
|
51
|
+
def format(
|
|
52
|
+
self,
|
|
53
|
+
compact: bool = False,
|
|
54
|
+
verbose: bool = True,
|
|
55
|
+
root_indent: int = 0,
|
|
56
|
+
**kwargs
|
|
57
|
+
) -> str:
|
|
58
|
+
s = f'<unknown-type {self.name}>'
|
|
59
|
+
if self.args:
|
|
60
|
+
s += f'[{", ".join(repr(x) for x in self.args)}]'
|
|
61
|
+
return s
|
|
62
|
+
|
|
63
|
+
def __call__(self, **kwargs):
|
|
64
|
+
return UnknownTypedObject(
|
|
65
|
+
type_name=self.name, **kwargs
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class UnknownCallable(UnknownSymbol):
|
|
70
|
+
"""Symbolic object for representing unknown callables."""
|
|
71
|
+
|
|
72
|
+
auto_register = False
|
|
73
|
+
name: str
|
|
74
|
+
CALLABLE_TYPE: ClassVar[Literal['function', 'method']]
|
|
75
|
+
|
|
76
|
+
def sym_jsonify(self, **kwargs) -> utils.JSONValueType:
|
|
77
|
+
return {'_type': self.CALLABLE_TYPE, 'name': self.name}
|
|
78
|
+
|
|
79
|
+
def format(
|
|
80
|
+
self,
|
|
81
|
+
compact: bool = False,
|
|
82
|
+
verbose: bool = True,
|
|
83
|
+
root_indent: int = 0,
|
|
84
|
+
**kwargs
|
|
85
|
+
) -> str:
|
|
86
|
+
return f'<unknown-{self.CALLABLE_TYPE} {self.name}>'
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class UnknownFunction(UnknownCallable):
|
|
90
|
+
"""Symbolic objject for representing unknown functions."""
|
|
91
|
+
|
|
92
|
+
auto_register = True
|
|
93
|
+
__serialization_key__ = 'unknown_function'
|
|
94
|
+
CALLABLE_TYPE = 'function'
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class UnknownMethod(UnknownCallable):
|
|
98
|
+
"""Symbolic object for representing unknown methods."""
|
|
99
|
+
|
|
100
|
+
auto_register = True
|
|
101
|
+
__serialization_key__ = 'unknown_method'
|
|
102
|
+
CALLABLE_TYPE = 'method'
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class UnknownTypedObject(UnknownSymbol):
|
|
106
|
+
"""Symbolic object for representing objects of unknown-type."""
|
|
107
|
+
|
|
108
|
+
auto_register = True
|
|
109
|
+
__serialization_key__ = 'unknown_object'
|
|
110
|
+
|
|
111
|
+
type_name: str
|
|
112
|
+
__kwargs__: Annotated[
|
|
113
|
+
Any,
|
|
114
|
+
(
|
|
115
|
+
'Fields of the original object will be kept as symbolic attributes '
|
|
116
|
+
'of this object so they can be accessed through `__getattr__`.'
|
|
117
|
+
)
|
|
118
|
+
]
|
|
119
|
+
|
|
120
|
+
def sym_jsonify(self, **kwargs) -> utils.JSONValueType:
|
|
121
|
+
"""Converts current object to a dict of plain Python objects."""
|
|
122
|
+
json_dict = self._sym_attributes.to_json(
|
|
123
|
+
exclude_keys=set(['type_name']), **kwargs
|
|
124
|
+
)
|
|
125
|
+
assert isinstance(json_dict, dict)
|
|
126
|
+
json_dict[utils.JSONConvertible.TYPE_NAME_KEY] = self.type_name
|
|
127
|
+
return json_dict
|
|
128
|
+
|
|
129
|
+
def format(
|
|
130
|
+
self,
|
|
131
|
+
compact: bool = False,
|
|
132
|
+
verbose: bool = True,
|
|
133
|
+
root_indent: int = 0,
|
|
134
|
+
**kwargs
|
|
135
|
+
) -> str:
|
|
136
|
+
exclude_keys = kwargs.pop('exclude_keys', set())
|
|
137
|
+
exclude_keys.add('type_name')
|
|
138
|
+
kwargs['exclude_keys'] = exclude_keys
|
|
139
|
+
return self._sym_attributes.format(
|
|
140
|
+
compact,
|
|
141
|
+
verbose,
|
|
142
|
+
root_indent,
|
|
143
|
+
cls_name=f'<unknown-type {self.type_name}>',
|
|
144
|
+
key_as_attribute=True,
|
|
145
|
+
bracket_type=utils.BracketType.ROUND,
|
|
146
|
+
**kwargs,
|
|
147
|
+
)
|