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
|
@@ -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:
|
|
@@ -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
|
|
|
@@ -396,6 +388,9 @@ class ValueSpecFromAnnotationTest(unittest.TestCase):
|
|
|
396
388
|
self.assertEqual(
|
|
397
389
|
ValueSpec.from_annotation(typing.Dict[str, int], True),
|
|
398
390
|
vs.Dict([(ks.StrKey(), vs.Int())]))
|
|
391
|
+
self.assertEqual(
|
|
392
|
+
ValueSpec.from_annotation(typing.Dict[str, vs.Int()], True),
|
|
393
|
+
vs.Dict([(ks.StrKey(), vs.Int())]))
|
|
399
394
|
self.assertEqual(
|
|
400
395
|
ValueSpec.from_annotation(typing.Mapping[str, int], True),
|
|
401
396
|
vs.Dict([(ks.StrKey(), vs.Int())]))
|
|
@@ -582,6 +582,16 @@ class ValueSpec(utils.Formattable, utils.JSONConvertible):
|
|
|
582
582
|
del include_type_name, include_subclasses, inline_nested_refs, kwargs
|
|
583
583
|
assert False, 'Overridden in `json_schema.py`.'
|
|
584
584
|
|
|
585
|
+
@classmethod
|
|
586
|
+
def from_json_schema(
|
|
587
|
+
cls,
|
|
588
|
+
json_schema: Dict[str, Any],
|
|
589
|
+
class_fn: Optional[Callable[[str, 'Schema'], Type[Any]]] = None
|
|
590
|
+
) -> 'ValueSpec':
|
|
591
|
+
"""Converts a JSON schema to a value spec."""
|
|
592
|
+
del json_schema, class_fn
|
|
593
|
+
assert False, 'Overridden in `json_schema.py`.'
|
|
594
|
+
|
|
585
595
|
|
|
586
596
|
class Field(utils.Formattable, utils.JSONConvertible):
|
|
587
597
|
"""Class that represents the definition of one or a group of attributes.
|
|
@@ -1219,6 +1229,9 @@ class Schema(utils.Formattable, utils.JSONConvertible):
|
|
|
1219
1229
|
f'(parent=\'{root_path}\')'
|
|
1220
1230
|
)
|
|
1221
1231
|
|
|
1232
|
+
# Symbolic.Dict uses `sym_getattr` to support getting symbolic attributes.
|
|
1233
|
+
get_value = getattr(dict_obj, 'sym_getattr', dict_obj.get)
|
|
1234
|
+
|
|
1222
1235
|
for key_spec, keys in matched_keys.items():
|
|
1223
1236
|
field = self._fields[key_spec]
|
|
1224
1237
|
# For missing const keys, we add to keys collection to add missing value.
|
|
@@ -1226,7 +1239,7 @@ class Schema(utils.Formattable, utils.JSONConvertible):
|
|
|
1226
1239
|
keys.append(str(key_spec))
|
|
1227
1240
|
for key in keys:
|
|
1228
1241
|
if dict_obj:
|
|
1229
|
-
value =
|
|
1242
|
+
value = get_value(key, utils.MISSING_VALUE)
|
|
1230
1243
|
else:
|
|
1231
1244
|
value = utils.MISSING_VALUE
|
|
1232
1245
|
# NOTE(daiyip): field.default_value may be MISSING_VALUE too
|
|
@@ -1375,6 +1388,16 @@ class Schema(utils.Formattable, utils.JSONConvertible):
|
|
|
1375
1388
|
def __ne__(self, other: Any) -> bool:
|
|
1376
1389
|
return not self.__eq__(other)
|
|
1377
1390
|
|
|
1391
|
+
@classmethod
|
|
1392
|
+
def from_json_schema(
|
|
1393
|
+
cls,
|
|
1394
|
+
json_schema: Dict[str, Any],
|
|
1395
|
+
class_fn: Optional[Callable[[str, 'Schema'], Type[Any]]] = None
|
|
1396
|
+
) -> 'Schema':
|
|
1397
|
+
"""Converts a JSON schema to a schema."""
|
|
1398
|
+
del json_schema, class_fn
|
|
1399
|
+
assert False, 'Overridden in `json_schema.py`.'
|
|
1400
|
+
|
|
1378
1401
|
|
|
1379
1402
|
FieldDef = Union[
|
|
1380
1403
|
# Key, Value spec/annotation.
|