pyglove 0.5.0.dev202508250811__py3-none-any.whl → 0.5.0.dev202511300809__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyglove/core/__init__.py +8 -1
- 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/logging.py +45 -1
- pyglove/core/logging_test.py +12 -21
- pyglove/core/monitoring.py +657 -0
- pyglove/core/monitoring_test.py +289 -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 +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 +2 -0
- pyglove/core/utils/contextual.py +9 -4
- pyglove/core/utils/contextual_test.py +10 -0
- pyglove/core/utils/error_utils.py +59 -25
- 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.dev202508250811.dist-info → pyglove-0.5.0.dev202511300809.dist-info}/METADATA +8 -1
- {pyglove-0.5.0.dev202508250811.dist-info → pyglove-0.5.0.dev202511300809.dist-info}/RECORD +44 -40
- {pyglove-0.5.0.dev202508250811.dist-info → pyglove-0.5.0.dev202511300809.dist-info}/WHEEL +0 -0
- {pyglove-0.5.0.dev202508250811.dist-info → pyglove-0.5.0.dev202511300809.dist-info}/licenses/LICENSE +0 -0
- {pyglove-0.5.0.dev202508250811.dist-info → pyglove-0.5.0.dev202511300809.dist-info}/top_level.txt +0 -0
|
@@ -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
|
+
)
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# Copyright 2025 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
|
+
|
|
15
|
+
import unittest
|
|
16
|
+
from pyglove.core import utils
|
|
17
|
+
from pyglove.core.symbolic import unknown_symbols
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class UnknownTypeTest(unittest.TestCase):
|
|
21
|
+
|
|
22
|
+
def test_basics(self):
|
|
23
|
+
t = unknown_symbols.UnknownType(name='__main__.ABC', args=[int, str])
|
|
24
|
+
self.assertEqual(t.name, '__main__.ABC')
|
|
25
|
+
self.assertEqual(t.args, [int, str])
|
|
26
|
+
self.assertEqual(
|
|
27
|
+
repr(t),
|
|
28
|
+
'<unknown-type __main__.ABC>[<class \'int\'>, <class \'str\'>]'
|
|
29
|
+
)
|
|
30
|
+
self.assertEqual(
|
|
31
|
+
t.to_json(),
|
|
32
|
+
{
|
|
33
|
+
'_type': 'type',
|
|
34
|
+
'name': '__main__.ABC',
|
|
35
|
+
'args': [
|
|
36
|
+
{'_type': 'type', 'name': 'builtins.int'},
|
|
37
|
+
{'_type': 'type', 'name': 'builtins.str'},
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
)
|
|
41
|
+
self.assertEqual(utils.from_json(t.to_json(), convert_unknown=True), t)
|
|
42
|
+
self.assertEqual(
|
|
43
|
+
t(x=1, y=2),
|
|
44
|
+
unknown_symbols.UnknownTypedObject(type_name='__main__.ABC', x=1, y=2)
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class UnknownFunctionTest(unittest.TestCase):
|
|
49
|
+
|
|
50
|
+
def test_basics(self):
|
|
51
|
+
t = unknown_symbols.UnknownFunction(name='__main__.foo')
|
|
52
|
+
self.assertEqual(t.name, '__main__.foo')
|
|
53
|
+
self.assertEqual(repr(t), '<unknown-function __main__.foo>')
|
|
54
|
+
self.assertEqual(
|
|
55
|
+
t.to_json(),
|
|
56
|
+
{
|
|
57
|
+
'_type': 'function',
|
|
58
|
+
'name': '__main__.foo',
|
|
59
|
+
}
|
|
60
|
+
)
|
|
61
|
+
self.assertEqual(utils.from_json(t.to_json(), convert_unknown=True), t)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class UnknownMethodTest(unittest.TestCase):
|
|
65
|
+
|
|
66
|
+
def test_basics(self):
|
|
67
|
+
t = unknown_symbols.UnknownMethod(name='__main__.ABC.bar')
|
|
68
|
+
self.assertEqual(t.name, '__main__.ABC.bar')
|
|
69
|
+
self.assertEqual(repr(t), '<unknown-method __main__.ABC.bar>')
|
|
70
|
+
self.assertEqual(
|
|
71
|
+
t.to_json(),
|
|
72
|
+
{
|
|
73
|
+
'_type': 'method',
|
|
74
|
+
'name': '__main__.ABC.bar',
|
|
75
|
+
}
|
|
76
|
+
)
|
|
77
|
+
self.assertEqual(utils.from_json(t.to_json(), convert_unknown=True), t)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class UnknownObjectTest(unittest.TestCase):
|
|
81
|
+
|
|
82
|
+
def test_basics(self):
|
|
83
|
+
v = unknown_symbols.UnknownTypedObject(type_name='__main__.ABC', x=1)
|
|
84
|
+
self.assertEqual(v.type_name, '__main__.ABC')
|
|
85
|
+
self.assertEqual(v.x, 1)
|
|
86
|
+
self.assertEqual(repr(v), '<unknown-type __main__.ABC>(x=1)')
|
|
87
|
+
self.assertEqual(
|
|
88
|
+
str(v), '<unknown-type __main__.ABC>(\n x = 1\n)')
|
|
89
|
+
self.assertEqual(
|
|
90
|
+
v.to_json(),
|
|
91
|
+
{
|
|
92
|
+
'_type': '__main__.ABC',
|
|
93
|
+
'x': 1,
|
|
94
|
+
}
|
|
95
|
+
)
|
|
96
|
+
self.assertEqual(utils.from_json(v.to_json(), convert_unknown=True), v)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
if __name__ == '__main__':
|
|
100
|
+
unittest.main()
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
import builtins
|
|
17
17
|
import collections
|
|
18
18
|
import inspect
|
|
19
|
+
import sys
|
|
19
20
|
import types
|
|
20
21
|
import typing
|
|
21
22
|
|
|
@@ -197,6 +198,8 @@ def annotation_from_str(
|
|
|
197
198
|
def _resolve(type_id: str):
|
|
198
199
|
|
|
199
200
|
def _as_forward_ref() -> typing.ForwardRef:
|
|
201
|
+
if sys.version_info >= (3, 14):
|
|
202
|
+
return typing.ForwardRef(type_id) # pytype: disable=not-callable
|
|
200
203
|
return typing.ForwardRef(type_id, False, parent_module) # pytype: disable=not-callable
|
|
201
204
|
|
|
202
205
|
def _resolve_name(name: str, parent_obj: typing.Any):
|
|
@@ -318,6 +321,9 @@ def _value_spec_from_type_annotation(
|
|
|
318
321
|
parent_module: typing.Optional[types.ModuleType] = None
|
|
319
322
|
) -> class_schema.ValueSpec:
|
|
320
323
|
"""Creates a value spec from type annotation."""
|
|
324
|
+
if isinstance(annotation, class_schema.ValueSpec):
|
|
325
|
+
return annotation
|
|
326
|
+
|
|
321
327
|
if isinstance(annotation, str) and not accept_value_as_annotation:
|
|
322
328
|
annotation = annotation_from_str(annotation, parent_module)
|
|
323
329
|
|
|
@@ -454,7 +460,8 @@ def _value_spec_from_annotation(
|
|
|
454
460
|
"""Creates a value spec from annotation."""
|
|
455
461
|
if isinstance(annotation, class_schema.ValueSpec):
|
|
456
462
|
return annotation
|
|
457
|
-
|
|
463
|
+
|
|
464
|
+
if annotation == inspect.Parameter.empty:
|
|
458
465
|
return vs.Any()
|
|
459
466
|
|
|
460
467
|
if annotation is None:
|