pyglove 0.5.0.dev202510230131__py3-none-any.whl → 0.5.0.dev202601060812__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/io/file_system.py +452 -2
- pyglove/core/io/file_system_test.py +442 -0
- pyglove/core/symbolic/__init__.py +7 -0
- pyglove/core/symbolic/base.py +24 -9
- pyglove/core/symbolic/list_test.py +1 -2
- pyglove/core/symbolic/object.py +2 -1
- pyglove/core/symbolic/object_test.py +13 -10
- pyglove/core/symbolic/unknown_symbols.py +147 -0
- pyglove/core/symbolic/unknown_symbols_test.py +100 -0
- pyglove/core/typing/annotation_conversion.py +3 -0
- pyglove/core/typing/annotation_conversion_test.py +11 -19
- 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/json_conversion.py +107 -49
- pyglove/core/utils/json_conversion_test.py +85 -10
- 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.dev202510230131.dist-info → pyglove-0.5.0.dev202601060812.dist-info}/METADATA +8 -1
- {pyglove-0.5.0.dev202510230131.dist-info → pyglove-0.5.0.dev202601060812.dist-info}/RECORD +25 -23
- {pyglove-0.5.0.dev202510230131.dist-info → pyglove-0.5.0.dev202601060812.dist-info}/WHEEL +0 -0
- {pyglove-0.5.0.dev202510230131.dist-info → pyglove-0.5.0.dev202601060812.dist-info}/licenses/LICENSE +0 -0
- {pyglove-0.5.0.dev202510230131.dist-info → pyglove-0.5.0.dev202601060812.dist-info}/top_level.txt +0 -0
|
@@ -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):
|
|
@@ -34,6 +34,12 @@ class Foo:
|
|
|
34
34
|
_MODULE = sys.modules[__name__]
|
|
35
35
|
|
|
36
36
|
|
|
37
|
+
def typing_forward_ref(name: str) -> typing.ForwardRef:
|
|
38
|
+
if sys.version_info >= (3, 14):
|
|
39
|
+
return typing.ForwardRef(name)
|
|
40
|
+
return typing.ForwardRef(name, False, _MODULE)
|
|
41
|
+
|
|
42
|
+
|
|
37
43
|
class AnnotationFromStrTest(unittest.TestCase):
|
|
38
44
|
"""Tests for annotation_from_str."""
|
|
39
45
|
|
|
@@ -69,7 +75,7 @@ class AnnotationFromStrTest(unittest.TestCase):
|
|
|
69
75
|
)
|
|
70
76
|
self.assertEqual(
|
|
71
77
|
annotation_conversion.annotation_from_str('list[Foo.Baz]', _MODULE),
|
|
72
|
-
list[
|
|
78
|
+
list[typing_forward_ref('Foo.Baz')]
|
|
73
79
|
)
|
|
74
80
|
|
|
75
81
|
def test_generic_types(self):
|
|
@@ -138,18 +144,12 @@ class AnnotationFromStrTest(unittest.TestCase):
|
|
|
138
144
|
self.assertEqual(
|
|
139
145
|
annotation_conversion.annotation_from_str(
|
|
140
146
|
'AAA', _MODULE),
|
|
141
|
-
|
|
142
|
-
'AAA', False, _MODULE
|
|
143
|
-
)
|
|
147
|
+
typing_forward_ref('AAA')
|
|
144
148
|
)
|
|
145
149
|
self.assertEqual(
|
|
146
150
|
annotation_conversion.annotation_from_str(
|
|
147
151
|
'typing.List[AAA]', _MODULE),
|
|
148
|
-
typing.List[
|
|
149
|
-
typing.ForwardRef(
|
|
150
|
-
'AAA', False, _MODULE
|
|
151
|
-
)
|
|
152
|
-
]
|
|
152
|
+
typing.List[typing_forward_ref('AAA')]
|
|
153
153
|
)
|
|
154
154
|
|
|
155
155
|
def test_reloading(self):
|
|
@@ -157,20 +157,12 @@ class AnnotationFromStrTest(unittest.TestCase):
|
|
|
157
157
|
self.assertEqual(
|
|
158
158
|
annotation_conversion.annotation_from_str(
|
|
159
159
|
'typing.List[Foo]', _MODULE),
|
|
160
|
-
typing.List[
|
|
161
|
-
typing.ForwardRef(
|
|
162
|
-
'Foo', False, _MODULE
|
|
163
|
-
)
|
|
164
|
-
]
|
|
160
|
+
typing.List[typing_forward_ref('Foo')]
|
|
165
161
|
)
|
|
166
162
|
self.assertEqual(
|
|
167
163
|
annotation_conversion.annotation_from_str(
|
|
168
164
|
'typing.List[Foo.Bar]', _MODULE),
|
|
169
|
-
typing.List[
|
|
170
|
-
typing.ForwardRef(
|
|
171
|
-
'Foo.Bar', False, _MODULE
|
|
172
|
-
)
|
|
173
|
-
]
|
|
165
|
+
typing.List[typing_forward_ref('Foo.Bar')]
|
|
174
166
|
)
|
|
175
167
|
delattr(_MODULE, '__reloading__')
|
|
176
168
|
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
import calendar
|
|
17
17
|
import datetime
|
|
18
|
+
import sys
|
|
18
19
|
from typing import Any, Callable, Optional, Tuple, Type, Union
|
|
19
20
|
|
|
20
21
|
from pyglove.core import utils
|
|
@@ -135,9 +136,22 @@ def _register_builtin_converters():
|
|
|
135
136
|
register_converter(int, float, float)
|
|
136
137
|
|
|
137
138
|
# int <=> datetime.datetime.
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
139
|
+
if sys.version_info >= (3, 11):
|
|
140
|
+
register_converter(
|
|
141
|
+
int,
|
|
142
|
+
datetime.datetime,
|
|
143
|
+
lambda x: datetime.datetime.fromtimestamp(x, datetime.UTC)
|
|
144
|
+
)
|
|
145
|
+
else:
|
|
146
|
+
register_converter(
|
|
147
|
+
int, datetime.datetime, datetime.datetime.utcfromtimestamp
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
register_converter(
|
|
151
|
+
datetime.datetime,
|
|
152
|
+
int,
|
|
153
|
+
lambda x: calendar.timegm(x.timetuple())
|
|
154
|
+
)
|
|
141
155
|
|
|
142
156
|
# string <=> KeyPath.
|
|
143
157
|
register_converter(str, utils.KeyPath, utils.KeyPath.parse)
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
import calendar
|
|
15
15
|
import datetime
|
|
16
|
+
import sys
|
|
16
17
|
import typing
|
|
17
18
|
import unittest
|
|
18
19
|
|
|
@@ -130,12 +131,16 @@ class BuiltInConversionsTest(unittest.TestCase):
|
|
|
130
131
|
def test_datetime_to_int(self):
|
|
131
132
|
"""Test built-in converter between int and datetime.datetime."""
|
|
132
133
|
timestamp = calendar.timegm(datetime.datetime.now().timetuple())
|
|
133
|
-
|
|
134
|
+
if sys.version_info >= (3, 11):
|
|
135
|
+
now = datetime.datetime.fromtimestamp(timestamp, datetime.UTC)
|
|
136
|
+
else:
|
|
137
|
+
now = datetime.datetime.utcfromtimestamp(timestamp)
|
|
134
138
|
self.assertEqual(vs.Object(datetime.datetime).apply(timestamp), now)
|
|
135
139
|
self.assertEqual(vs.Int().apply(now), timestamp)
|
|
136
140
|
self.assertEqual(
|
|
137
141
|
type_conversion.get_json_value_converter(datetime.datetime)(now),
|
|
138
|
-
timestamp
|
|
142
|
+
timestamp
|
|
143
|
+
)
|
|
139
144
|
|
|
140
145
|
def test_keypath_to_str(self):
|
|
141
146
|
"""Test built-in converter between string and KeyPath."""
|
|
@@ -1930,8 +1930,12 @@ class Object(Generic, ValueSpecBase):
|
|
|
1930
1930
|
elif isinstance(t, type):
|
|
1931
1931
|
if t is object:
|
|
1932
1932
|
raise TypeError('<class \'object\'> is too general for Object spec.')
|
|
1933
|
+
elif getattr(t, '__no_type_check__', False):
|
|
1934
|
+
t = object
|
|
1933
1935
|
elif not pg_inspect.is_generic(t):
|
|
1934
|
-
raise TypeError(
|
|
1936
|
+
raise TypeError(
|
|
1937
|
+
f'"cls" for Object spec should be a type or str. Encountered: {t!r}.'
|
|
1938
|
+
)
|
|
1935
1939
|
|
|
1936
1940
|
self._forward_ref = forward_ref
|
|
1937
1941
|
self._type_args = type_args
|
|
@@ -2193,6 +2193,11 @@ class ObjectTest(ValueSpecTest):
|
|
|
2193
2193
|
self.assertEqual(
|
|
2194
2194
|
vs.Object(forward_ref('Foo')).forward_refs, set([forward_ref('Foo')]))
|
|
2195
2195
|
|
|
2196
|
+
def test_no_type_check_special_handling(self):
|
|
2197
|
+
x = self.A()
|
|
2198
|
+
setattr(x, '__no_type_check__', True)
|
|
2199
|
+
self.assertIs(vs.Object(x).cls, object)
|
|
2200
|
+
|
|
2196
2201
|
def test_default(self):
|
|
2197
2202
|
self.assertEqual(vs.Object(self.A).default, typed_missing.MISSING_VALUE)
|
|
2198
2203
|
a = self.A()
|
|
@@ -685,7 +685,7 @@ def from_json(
|
|
|
685
685
|
*,
|
|
686
686
|
context: Optional[JSONConversionContext] = None,
|
|
687
687
|
auto_import: bool = True,
|
|
688
|
-
|
|
688
|
+
convert_unknown: bool = False,
|
|
689
689
|
**kwargs
|
|
690
690
|
) -> Any:
|
|
691
691
|
"""Deserializes a (maybe) JSONConvertible value from JSON value.
|
|
@@ -697,8 +697,13 @@ def from_json(
|
|
|
697
697
|
identify its parent module and automatically import it. For example,
|
|
698
698
|
if the type is 'foo.bar.A', PyGlove will try to import 'foo.bar' and
|
|
699
699
|
find the class 'A' within the imported module.
|
|
700
|
-
|
|
701
|
-
|
|
700
|
+
convert_unknown: If True, when a '_type' is not registered and cannot
|
|
701
|
+
be imported, PyGlove will create objects of:
|
|
702
|
+
- `pg.symbolic.UnknownType` for unknown types;
|
|
703
|
+
- `pg.symbolic.UnknownTypedObject` for objects of unknown types;
|
|
704
|
+
- `pg.symbolic.UnknownFunction` for unknown functions;
|
|
705
|
+
- `pg.symbolic.UnknownMethod` for unknown methods.
|
|
706
|
+
If False, TypeError will be raised.
|
|
702
707
|
**kwargs: Keyword arguments that will be passed to JSONConvertible.__init__.
|
|
703
708
|
|
|
704
709
|
Returns:
|
|
@@ -707,14 +712,21 @@ def from_json(
|
|
|
707
712
|
if context is None:
|
|
708
713
|
if (isinstance(json_value, dict)
|
|
709
714
|
and (context_node := json_value.get(JSONConvertible.CONTEXT_KEY))):
|
|
710
|
-
context = JSONConversionContext.from_json(
|
|
715
|
+
context = JSONConversionContext.from_json(
|
|
716
|
+
context_node,
|
|
717
|
+
auto_import=auto_import,
|
|
718
|
+
convert_unknown=convert_unknown,
|
|
719
|
+
**kwargs
|
|
720
|
+
)
|
|
711
721
|
json_value = json_value[JSONConvertible.ROOT_VALUE_KEY]
|
|
712
722
|
else:
|
|
713
723
|
context = JSONConversionContext()
|
|
714
724
|
|
|
715
725
|
typename_resolved = kwargs.pop('_typename_resolved', False)
|
|
716
726
|
if not typename_resolved:
|
|
717
|
-
json_value = resolve_typenames(
|
|
727
|
+
json_value = resolve_typenames(
|
|
728
|
+
json_value, auto_import=auto_import, convert_unknown=convert_unknown
|
|
729
|
+
)
|
|
718
730
|
|
|
719
731
|
def child_from(v):
|
|
720
732
|
return from_json(v, context=context, _typename_resolved=True, **kwargs)
|
|
@@ -743,7 +755,7 @@ def from_json(
|
|
|
743
755
|
def resolve_typenames(
|
|
744
756
|
json_value: JSONValueType,
|
|
745
757
|
auto_import: bool = True,
|
|
746
|
-
|
|
758
|
+
convert_unknown: bool = False,
|
|
747
759
|
) -> JSONValueType:
|
|
748
760
|
"""Inplace resolves the "_type" keys with their factories in a JSON tree."""
|
|
749
761
|
|
|
@@ -755,11 +767,11 @@ def resolve_typenames(
|
|
|
755
767
|
return False
|
|
756
768
|
type_name = v[JSONConvertible.TYPE_NAME_KEY]
|
|
757
769
|
if type_name == 'type':
|
|
758
|
-
factory_fn = _type_from_json
|
|
770
|
+
factory_fn = _type_from_json(convert_unknown)
|
|
759
771
|
elif type_name == 'function':
|
|
760
|
-
factory_fn = _function_from_json
|
|
772
|
+
factory_fn = _function_from_json(convert_unknown)
|
|
761
773
|
elif type_name == 'method':
|
|
762
|
-
factory_fn = _method_from_json
|
|
774
|
+
factory_fn = _method_from_json(convert_unknown)
|
|
763
775
|
else:
|
|
764
776
|
cls = JSONConvertible.class_from_typename(type_name)
|
|
765
777
|
if cls is None:
|
|
@@ -768,32 +780,38 @@ def resolve_typenames(
|
|
|
768
780
|
cls = _load_symbol(type_name)
|
|
769
781
|
assert inspect.isclass(cls), cls
|
|
770
782
|
except (ModuleNotFoundError, AttributeError) as e:
|
|
771
|
-
if not
|
|
783
|
+
if not convert_unknown:
|
|
772
784
|
raise TypeError(
|
|
773
785
|
f'Cannot load class {type_name!r}.\n'
|
|
774
|
-
'Try pass `
|
|
775
|
-
'without depending on the type.'
|
|
786
|
+
'Try pass `convert_unknown=True` to load the object into '
|
|
787
|
+
'`pg.symbolic.UnknownObject` without depending on the type.'
|
|
776
788
|
) from e
|
|
777
|
-
elif not
|
|
789
|
+
elif not convert_unknown:
|
|
778
790
|
raise TypeError(
|
|
779
791
|
f'Type name \'{type_name}\' is not registered '
|
|
780
792
|
'with a `pg.JSONConvertible` subclass.\n'
|
|
781
|
-
'Try pass `auto_import=True` to load the type from its module
|
|
782
|
-
'or pass `auto_dict=True` to load the object into a dict '
|
|
783
|
-
'without depending on the type.'
|
|
793
|
+
'Try pass `auto_import=True` to load the type from its module.'
|
|
784
794
|
)
|
|
785
795
|
|
|
786
796
|
factory_fn = getattr(cls, 'from_json', None)
|
|
787
|
-
if cls is not None and factory_fn is None and not
|
|
797
|
+
if cls is not None and factory_fn is None and not convert_unknown:
|
|
788
798
|
raise TypeError(
|
|
789
799
|
f'{cls} is not a `pg.JSONConvertible` subclass.'
|
|
790
|
-
'Try pass `
|
|
791
|
-
'without depending on the type.'
|
|
800
|
+
'Try pass `convert_unknown=True` to load the object into a '
|
|
801
|
+
'`pg.symbolic.UnknownObject` without depending on the type.'
|
|
792
802
|
)
|
|
793
803
|
|
|
794
|
-
if factory_fn is None and
|
|
795
|
-
|
|
796
|
-
|
|
804
|
+
if factory_fn is None and convert_unknown:
|
|
805
|
+
type_name = v[JSONConvertible.TYPE_NAME_KEY]
|
|
806
|
+
def _factory_fn(json_value: Dict[str, Any], **kwargs):
|
|
807
|
+
del kwargs
|
|
808
|
+
# See `pg.symbolic.UnknownObject` for details.
|
|
809
|
+
unknown_object_cls = JSONConvertible.class_from_typename(
|
|
810
|
+
'unknown_object'
|
|
811
|
+
)
|
|
812
|
+
return unknown_object_cls(type_name=type_name, **json_value) # pytype: disable=wrong-keyword-args
|
|
813
|
+
|
|
814
|
+
v[JSONConvertible.TYPE_NAME_KEY] = _factory_fn
|
|
797
815
|
return True
|
|
798
816
|
assert factory_fn is not None
|
|
799
817
|
|
|
@@ -992,39 +1010,79 @@ def _load_symbol(type_name: str) -> Any:
|
|
|
992
1010
|
return symbol
|
|
993
1011
|
|
|
994
1012
|
|
|
995
|
-
def _type_from_json(
|
|
1013
|
+
def _type_from_json(convert_unknown: bool) -> Callable[..., Any]:
|
|
996
1014
|
"""Loads a type from a JSON dict."""
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1015
|
+
def _fn(json_value: Dict[str, str], **kwargs) -> Type[Any]:
|
|
1016
|
+
del kwargs
|
|
1017
|
+
try:
|
|
1018
|
+
t = _load_symbol(json_value['name'])
|
|
1019
|
+
if 'args' in json_value:
|
|
1020
|
+
return _bind_type_args(
|
|
1021
|
+
t, from_json(json_value['args'], _typename_resolved=True)
|
|
1022
|
+
)
|
|
1023
|
+
return t
|
|
1024
|
+
except (ModuleNotFoundError, AttributeError) as e:
|
|
1025
|
+
if not convert_unknown:
|
|
1026
|
+
raise TypeError(
|
|
1027
|
+
f'Cannot load type {json_value["name"]!r}.\n'
|
|
1028
|
+
'Try pass `convert_unknown=True` to load the object '
|
|
1029
|
+
'into `pg.UnknownType` without depending on the type.'
|
|
1030
|
+
) from e
|
|
1031
|
+
# See `pg.symbolic.UnknownType` for details.
|
|
1032
|
+
json_value[JSONConvertible.TYPE_NAME_KEY] = 'unknown_type'
|
|
1033
|
+
return from_json(json_value)
|
|
1034
|
+
return _fn
|
|
1004
1035
|
|
|
1005
1036
|
|
|
1006
1037
|
def _function_from_json(
|
|
1007
|
-
|
|
1038
|
+
convert_unknown: bool
|
|
1039
|
+
) -> Callable[..., types.FunctionType]:
|
|
1008
1040
|
"""Loads a function from a JSON dict."""
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
code
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1041
|
+
def _fn(json_value: Dict[str, str], **kwargs) -> types.FunctionType:
|
|
1042
|
+
del kwargs
|
|
1043
|
+
function_name = json_value['name']
|
|
1044
|
+
if 'code' in json_value:
|
|
1045
|
+
code = marshal.loads(
|
|
1046
|
+
base64.decodebytes(json_value['code'].encode('utf-8')))
|
|
1047
|
+
defaults = from_json(json_value['defaults'], _typename_resolved=True)
|
|
1048
|
+
return types.FunctionType(
|
|
1049
|
+
code=code,
|
|
1050
|
+
globals=globals(),
|
|
1051
|
+
argdefs=defaults,
|
|
1052
|
+
)
|
|
1053
|
+
else:
|
|
1054
|
+
try:
|
|
1055
|
+
return _load_symbol(function_name)
|
|
1056
|
+
except (ModuleNotFoundError, AttributeError) as e:
|
|
1057
|
+
if not convert_unknown:
|
|
1058
|
+
raise TypeError(
|
|
1059
|
+
f'Cannot load function {function_name!r}.\n'
|
|
1060
|
+
'Try pass `convert_unknown=True` to load the object into '
|
|
1061
|
+
'`pg.UnknownFunction` without depending on the type.'
|
|
1062
|
+
) from e
|
|
1063
|
+
json_value[JSONConvertible.TYPE_NAME_KEY] = 'unknown_function'
|
|
1064
|
+
return from_json(json_value)
|
|
1065
|
+
return _fn
|
|
1066
|
+
|
|
1067
|
+
|
|
1068
|
+
def _method_from_json(
|
|
1069
|
+
convert_unknown: bool
|
|
1070
|
+
) -> Callable[..., types.MethodType]:
|
|
1025
1071
|
"""Loads a class method from a JSON dict."""
|
|
1026
|
-
|
|
1027
|
-
|
|
1072
|
+
def _fn(json_value: Dict[str, str], **kwargs) -> types.MethodType:
|
|
1073
|
+
del kwargs
|
|
1074
|
+
try:
|
|
1075
|
+
return _load_symbol(json_value['name'])
|
|
1076
|
+
except (ModuleNotFoundError, AttributeError) as e:
|
|
1077
|
+
if not convert_unknown:
|
|
1078
|
+
raise TypeError(
|
|
1079
|
+
f'Cannot load method {json_value["name"]!r}.\n'
|
|
1080
|
+
'Try pass `convert_unknown=True` to load the object '
|
|
1081
|
+
'into `pg.UnknownMethod` without depending on the type.'
|
|
1082
|
+
) from e
|
|
1083
|
+
json_value[JSONConvertible.TYPE_NAME_KEY] = 'unknown_method'
|
|
1084
|
+
return from_json(json_value)
|
|
1085
|
+
return _fn
|
|
1028
1086
|
|
|
1029
1087
|
|
|
1030
1088
|
def _bind_type_args(t, args):
|