pyglove 0.4.5.dev20240319__py3-none-any.whl → 0.4.5.dev202501132210__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 +54 -20
- pyglove/core/coding/__init__.py +42 -0
- pyglove/core/coding/errors.py +111 -0
- pyglove/core/coding/errors_test.py +98 -0
- pyglove/core/coding/execution.py +309 -0
- pyglove/core/coding/execution_test.py +333 -0
- pyglove/core/{object_utils/codegen.py → coding/function_generation.py} +10 -4
- pyglove/core/{object_utils/codegen_test.py → coding/function_generation_test.py} +5 -7
- pyglove/core/coding/parsing.py +153 -0
- pyglove/core/coding/parsing_test.py +150 -0
- pyglove/core/coding/permissions.py +100 -0
- pyglove/core/coding/permissions_test.py +93 -0
- pyglove/core/geno/base.py +54 -41
- pyglove/core/geno/base_test.py +2 -4
- pyglove/core/geno/categorical.py +37 -28
- pyglove/core/geno/custom.py +19 -16
- pyglove/core/geno/numerical.py +20 -17
- pyglove/core/geno/space.py +4 -5
- pyglove/core/hyper/base.py +6 -6
- pyglove/core/hyper/categorical.py +94 -55
- pyglove/core/hyper/custom.py +7 -7
- pyglove/core/hyper/custom_test.py +9 -10
- pyglove/core/hyper/derived.py +30 -22
- pyglove/core/hyper/derived_test.py +2 -4
- pyglove/core/hyper/dynamic_evaluation.py +5 -6
- pyglove/core/hyper/evolvable.py +57 -46
- pyglove/core/hyper/numerical.py +48 -24
- pyglove/core/hyper/numerical_test.py +9 -9
- pyglove/core/hyper/object_template.py +58 -46
- pyglove/core/io/__init__.py +1 -0
- pyglove/core/io/file_system.py +17 -7
- pyglove/core/io/file_system_test.py +2 -0
- pyglove/core/io/sequence.py +299 -0
- pyglove/core/io/sequence_test.py +124 -0
- pyglove/core/logging_test.py +0 -2
- pyglove/core/patching/object_factory.py +4 -4
- pyglove/core/patching/pattern_based.py +4 -4
- pyglove/core/patching/rule_based.py +17 -5
- pyglove/core/patching/rule_based_test.py +27 -4
- pyglove/core/symbolic/__init__.py +2 -7
- pyglove/core/symbolic/base.py +320 -183
- pyglove/core/symbolic/base_test.py +123 -19
- pyglove/core/symbolic/boilerplate.py +7 -13
- pyglove/core/symbolic/boilerplate_test.py +25 -23
- pyglove/core/symbolic/class_wrapper.py +48 -45
- pyglove/core/symbolic/class_wrapper_test.py +2 -2
- pyglove/core/symbolic/compounding.py +9 -15
- pyglove/core/symbolic/compounding_test.py +2 -4
- pyglove/core/symbolic/dict.py +154 -110
- pyglove/core/symbolic/dict_test.py +238 -130
- pyglove/core/symbolic/diff.py +199 -10
- pyglove/core/symbolic/diff_test.py +226 -0
- pyglove/core/symbolic/flags.py +1 -1
- pyglove/core/symbolic/functor.py +29 -26
- pyglove/core/symbolic/functor_test.py +102 -50
- pyglove/core/symbolic/inferred.py +2 -2
- pyglove/core/symbolic/list.py +81 -50
- pyglove/core/symbolic/list_test.py +119 -97
- pyglove/core/symbolic/object.py +225 -113
- pyglove/core/symbolic/object_test.py +320 -108
- pyglove/core/symbolic/origin.py +17 -14
- pyglove/core/symbolic/origin_test.py +4 -2
- pyglove/core/symbolic/pure_symbolic.py +4 -3
- pyglove/core/symbolic/ref.py +108 -21
- pyglove/core/symbolic/ref_test.py +93 -0
- pyglove/core/symbolic/symbolize_test.py +10 -2
- pyglove/core/tuning/local_backend.py +2 -2
- pyglove/core/tuning/protocols.py +3 -3
- pyglove/core/tuning/sample_test.py +3 -3
- pyglove/core/typing/__init__.py +14 -5
- pyglove/core/typing/annotation_conversion.py +43 -27
- pyglove/core/typing/annotation_conversion_test.py +23 -0
- pyglove/core/typing/callable_ext.py +241 -3
- pyglove/core/typing/callable_ext_test.py +255 -0
- pyglove/core/typing/callable_signature.py +510 -66
- pyglove/core/typing/callable_signature_test.py +619 -99
- pyglove/core/typing/class_schema.py +229 -154
- pyglove/core/typing/class_schema_test.py +149 -95
- pyglove/core/typing/custom_typing.py +5 -4
- pyglove/core/typing/inspect.py +63 -0
- pyglove/core/typing/inspect_test.py +39 -0
- pyglove/core/typing/key_specs.py +10 -11
- pyglove/core/typing/key_specs_test.py +7 -4
- pyglove/core/typing/type_conversion.py +4 -5
- pyglove/core/typing/type_conversion_test.py +12 -12
- pyglove/core/typing/typed_missing.py +6 -7
- pyglove/core/typing/typed_missing_test.py +7 -8
- pyglove/core/typing/value_specs.py +604 -362
- pyglove/core/typing/value_specs_test.py +328 -90
- pyglove/core/utils/__init__.py +164 -0
- pyglove/core/{object_utils → utils}/common_traits.py +3 -67
- pyglove/core/utils/common_traits_test.py +36 -0
- pyglove/core/{object_utils → utils}/docstr_utils.py +23 -0
- pyglove/core/{object_utils → utils}/docstr_utils_test.py +36 -4
- pyglove/core/{object_utils → utils}/error_utils.py +78 -9
- pyglove/core/{object_utils → utils}/error_utils_test.py +61 -5
- pyglove/core/utils/formatting.py +464 -0
- pyglove/core/utils/formatting_test.py +453 -0
- pyglove/core/{object_utils → utils}/hierarchical.py +23 -25
- pyglove/core/{object_utils → utils}/hierarchical_test.py +3 -5
- pyglove/core/{object_utils → utils}/json_conversion.py +177 -52
- pyglove/core/{object_utils → utils}/json_conversion_test.py +97 -16
- pyglove/core/{object_utils → utils}/missing.py +3 -3
- pyglove/core/{object_utils → utils}/missing_test.py +2 -4
- pyglove/core/utils/text_color.py +128 -0
- pyglove/core/utils/text_color_test.py +94 -0
- pyglove/core/{object_utils → utils}/thread_local_test.py +1 -3
- pyglove/core/utils/timing.py +236 -0
- pyglove/core/utils/timing_test.py +154 -0
- pyglove/core/{object_utils → utils}/value_location.py +275 -6
- pyglove/core/utils/value_location_test.py +707 -0
- pyglove/core/views/__init__.py +32 -0
- pyglove/core/views/base.py +804 -0
- pyglove/core/views/base_test.py +580 -0
- pyglove/core/views/html/__init__.py +27 -0
- pyglove/core/views/html/base.py +547 -0
- pyglove/core/views/html/base_test.py +830 -0
- pyglove/core/views/html/controls/__init__.py +35 -0
- pyglove/core/views/html/controls/base.py +275 -0
- pyglove/core/views/html/controls/label.py +207 -0
- pyglove/core/views/html/controls/label_test.py +157 -0
- pyglove/core/views/html/controls/progress_bar.py +183 -0
- pyglove/core/views/html/controls/progress_bar_test.py +97 -0
- pyglove/core/views/html/controls/tab.py +320 -0
- pyglove/core/views/html/controls/tab_test.py +87 -0
- pyglove/core/views/html/controls/tooltip.py +99 -0
- pyglove/core/views/html/controls/tooltip_test.py +99 -0
- pyglove/core/views/html/tree_view.py +1517 -0
- pyglove/core/views/html/tree_view_test.py +1461 -0
- {pyglove-0.4.5.dev20240319.dist-info → pyglove-0.4.5.dev202501132210.dist-info}/METADATA +18 -4
- pyglove-0.4.5.dev202501132210.dist-info/RECORD +214 -0
- {pyglove-0.4.5.dev20240319.dist-info → pyglove-0.4.5.dev202501132210.dist-info}/WHEEL +1 -1
- pyglove/core/object_utils/__init__.py +0 -154
- pyglove/core/object_utils/common_traits_test.py +0 -82
- pyglove/core/object_utils/formatting.py +0 -234
- pyglove/core/object_utils/formatting_test.py +0 -223
- pyglove/core/object_utils/value_location_test.py +0 -385
- pyglove/core/symbolic/schema_utils.py +0 -327
- pyglove/core/symbolic/schema_utils_test.py +0 -57
- pyglove/core/typing/class_schema_utils.py +0 -202
- pyglove/core/typing/class_schema_utils_test.py +0 -194
- pyglove-0.4.5.dev20240319.dist-info/RECORD +0 -185
- /pyglove/core/{object_utils → utils}/thread_local.py +0 -0
- {pyglove-0.4.5.dev20240319.dist-info → pyglove-0.4.5.dev202501132210.dist-info}/LICENSE +0 -0
- {pyglove-0.4.5.dev20240319.dist-info → pyglove-0.4.5.dev202501132210.dist-info}/top_level.txt +0 -0
@@ -1,327 +0,0 @@
|
|
1
|
-
# Copyright 2022 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
|
-
"""Utilities for handling schema for symbolic classes."""
|
15
|
-
|
16
|
-
import types
|
17
|
-
from typing import Any, Dict, List, Optional, Sequence, Tuple, Union
|
18
|
-
|
19
|
-
from pyglove.core import object_utils
|
20
|
-
from pyglove.core import typing as pg_typing
|
21
|
-
from pyglove.core.symbolic import base
|
22
|
-
from pyglove.core.symbolic import flags
|
23
|
-
|
24
|
-
|
25
|
-
def augment_schema(
|
26
|
-
schema: pg_typing.Schema,
|
27
|
-
fields: List[
|
28
|
-
Union[
|
29
|
-
pg_typing.Field,
|
30
|
-
Tuple[Union[str, pg_typing.KeySpec], pg_typing.ValueSpec, str],
|
31
|
-
Tuple[Union[str, pg_typing.KeySpec], pg_typing.ValueSpec, str, Any],
|
32
|
-
]
|
33
|
-
],
|
34
|
-
extend: bool = True,
|
35
|
-
*,
|
36
|
-
init_arg_list: Optional[Sequence[str]] = None,
|
37
|
-
metadata: Optional[Dict[str, Any]] = None,
|
38
|
-
description: Optional[str] = None,
|
39
|
-
) -> pg_typing.Schema:
|
40
|
-
"""Gets the augmented schema from class with extra fields and metadata.
|
41
|
-
|
42
|
-
Args:
|
43
|
-
schema: The original schema.
|
44
|
-
fields: A list of `pg.typing.Field` or equivalent tuple representation as
|
45
|
-
(<key>, <value-spec>, [description], [metadata-objects]). `key` should be
|
46
|
-
a string. `value-spec` should be pg_typing.ValueSpec classes or
|
47
|
-
equivalent, e.g. primitive values which will be converted to ValueSpec
|
48
|
-
implementation according to its type and used as its default value.
|
49
|
-
`description` is optional only when field overrides a field from its
|
50
|
-
parent class. `metadata-objects` is an optional list of any type, which
|
51
|
-
can be used to generate code according to the schema.
|
52
|
-
extend: If True, extend existing schema using `fields`. Otherwise replace
|
53
|
-
existing schema with a new schema created from `fields`.
|
54
|
-
init_arg_list: An optional sequence of strings as the positional argument
|
55
|
-
list for `__init__`. This is helpful when symbolic attributes are
|
56
|
-
inherited from base classes or the user want to change its order. If not
|
57
|
-
provided, the `init_arg_list` will be automatically generated from
|
58
|
-
symbolic attributes defined from ``pg.members`` in their declaration
|
59
|
-
order, from the base classes to the subclass.
|
60
|
-
metadata: Optional dict of user objects as class-level metadata which will
|
61
|
-
be attached to class schema.
|
62
|
-
description: An optional description to set.
|
63
|
-
|
64
|
-
Returns:
|
65
|
-
The augmented schema (new copy).
|
66
|
-
"""
|
67
|
-
metadata = metadata or {}
|
68
|
-
if init_arg_list is None:
|
69
|
-
init_arg_list = metadata.get('init_arg_list', None)
|
70
|
-
metadata = object_utils.merge([schema.metadata, metadata])
|
71
|
-
|
72
|
-
# NOTE(daiyip): Consider to inherit `init_arg_list` from the parent when
|
73
|
-
# there is no new field.
|
74
|
-
metadata['init_arg_list'] = init_arg_list
|
75
|
-
|
76
|
-
return formalize_schema(
|
77
|
-
pg_typing.create_schema(
|
78
|
-
fields=fields,
|
79
|
-
name=schema.name,
|
80
|
-
base_schema_list=[schema] if extend else [],
|
81
|
-
description=description or schema.description,
|
82
|
-
allow_nonconst_keys=True,
|
83
|
-
metadata=metadata,
|
84
|
-
)
|
85
|
-
)
|
86
|
-
|
87
|
-
|
88
|
-
def update_schema(
|
89
|
-
cls,
|
90
|
-
fields: List[
|
91
|
-
Union[
|
92
|
-
pg_typing.Field,
|
93
|
-
Tuple[Union[str, pg_typing.KeySpec], pg_typing.ValueSpec, str],
|
94
|
-
Tuple[Union[str, pg_typing.KeySpec], pg_typing.ValueSpec, str, Any],
|
95
|
-
]
|
96
|
-
],
|
97
|
-
extend: bool = True,
|
98
|
-
*,
|
99
|
-
init_arg_list: Optional[Sequence[str]] = None,
|
100
|
-
metadata: Optional[Dict[str, Any]] = None,
|
101
|
-
description: Optional[str] = None,
|
102
|
-
serialization_key: Optional[str] = None,
|
103
|
-
additional_keys: Optional[List[str]] = None,
|
104
|
-
add_to_registry: bool = True,
|
105
|
-
) -> None:
|
106
|
-
"""Updates the schema for a ``pg.Object`` subclass.
|
107
|
-
|
108
|
-
This function allows the user to update the symbolic fields associated
|
109
|
-
with a symbolic class. It was intended to support meta-programming
|
110
|
-
scenarios in which symbolic fields are dynamically generated.
|
111
|
-
|
112
|
-
Example::
|
113
|
-
|
114
|
-
class A(pg.Object):
|
115
|
-
pass
|
116
|
-
|
117
|
-
# Add symbolic field 'x' to class A.
|
118
|
-
pg.symbolic.update_schema(A, [
|
119
|
-
('x', schema.Int())
|
120
|
-
])
|
121
|
-
|
122
|
-
# B inherits the symbolic field 'x' from A.
|
123
|
-
class B(A):
|
124
|
-
pass
|
125
|
-
|
126
|
-
# Wipe out the symbolic field 'x' from B.
|
127
|
-
pg.symbolic.update_schema(B, [], extend=False)
|
128
|
-
|
129
|
-
See also: :func:`pyglove.members`, :func:`pyglove.functor` and
|
130
|
-
:func:`pyglove.symbolize`.
|
131
|
-
|
132
|
-
Args:
|
133
|
-
cls: A symbolic Object subclass.
|
134
|
-
fields: A list of `pg.typing.Field` or equivalent tuple representation as
|
135
|
-
(<key>, <value-spec>, [description], [metadata-objects]). `key` should be
|
136
|
-
a string. `value-spec` should be pg_typing.ValueSpec classes or
|
137
|
-
equivalent, e.g. primitive values which will be converted to ValueSpec
|
138
|
-
implementation according to its type and used as its default value.
|
139
|
-
`description` is optional only when field overrides a field from its
|
140
|
-
parent class. `metadata-objects` is an optional list of any type, which
|
141
|
-
can be used to generate code according to the schema.
|
142
|
-
extend: If True, extend existing schema using `fields`. Otherwise replace
|
143
|
-
existing schema with a new schema created from `fields`.
|
144
|
-
init_arg_list: An optional sequence of strings as the positional argument
|
145
|
-
list for `__init__`. This is helpful when symbolic attributes are
|
146
|
-
inherited from base classes or the user want to change its order. If not
|
147
|
-
provided, the `init_arg_list` will be automatically generated from
|
148
|
-
symbolic attributes defined from ``pg.members`` in their declaration
|
149
|
-
order, from the base classes to the subclass.
|
150
|
-
metadata: Optional dict of user objects as class-level metadata which will
|
151
|
-
be attached to class schema.
|
152
|
-
description: An optional description to set.
|
153
|
-
serialization_key: An optional string to be used as the serialization key
|
154
|
-
for the class during `sym_jsonify`. If None, `cls.__type_name__` will be
|
155
|
-
used. This is introduced for scenarios when we want to relocate a class,
|
156
|
-
before the downstream can recognize the new location, we need the class to
|
157
|
-
serialize it using previous key.
|
158
|
-
additional_keys: An optional list of strings as additional keys to
|
159
|
-
deserialize an object of the registered class. This can be useful when we
|
160
|
-
need to relocate or rename the registered class while being able to load
|
161
|
-
existing serialized JSON values.
|
162
|
-
add_to_registry: If True, the newly created functor class will be added to
|
163
|
-
the registry for deserialization.
|
164
|
-
"""
|
165
|
-
cls.apply_schema(
|
166
|
-
augment_schema(
|
167
|
-
cls.__schema__,
|
168
|
-
fields=fields,
|
169
|
-
extend=extend,
|
170
|
-
init_arg_list=init_arg_list,
|
171
|
-
metadata=metadata,
|
172
|
-
description=description,
|
173
|
-
)
|
174
|
-
)
|
175
|
-
|
176
|
-
if add_to_registry:
|
177
|
-
cls.register_for_deserialization(serialization_key, additional_keys)
|
178
|
-
|
179
|
-
|
180
|
-
def function_schema(
|
181
|
-
func: types.FunctionType,
|
182
|
-
args: Optional[
|
183
|
-
List[
|
184
|
-
Union[
|
185
|
-
Tuple[Tuple[str, pg_typing.KeySpec], pg_typing.ValueSpec, str],
|
186
|
-
Tuple[
|
187
|
-
Tuple[str, pg_typing.KeySpec], pg_typing.ValueSpec, str, Any
|
188
|
-
],
|
189
|
-
]
|
190
|
-
]
|
191
|
-
] = None, # pylint: disable=bad-continuation
|
192
|
-
returns: Optional[pg_typing.ValueSpec] = None,
|
193
|
-
*,
|
194
|
-
auto_typing: bool = True,
|
195
|
-
auto_doc: bool = True,
|
196
|
-
) -> pg_typing.Schema:
|
197
|
-
"""Returns the schema from the signature of a function."""
|
198
|
-
args_docstr = None
|
199
|
-
description = None
|
200
|
-
if auto_doc:
|
201
|
-
docstr = object_utils.docstr(func)
|
202
|
-
if docstr:
|
203
|
-
args_docstr = docstr.args
|
204
|
-
description = schema_description_from_docstr(docstr)
|
205
|
-
|
206
|
-
signature = pg_typing.get_signature(func, auto_typing=auto_typing)
|
207
|
-
arg_fields = pg_typing.get_arg_fields(signature, args, args_docstr)
|
208
|
-
|
209
|
-
if returns is not None and pg_typing.MISSING_VALUE != returns.default:
|
210
|
-
raise ValueError('return value spec should not have default value.')
|
211
|
-
returns = returns or signature.return_value
|
212
|
-
|
213
|
-
# Generate init_arg_list from signature.
|
214
|
-
init_arg_list = [arg.name for arg in signature.args]
|
215
|
-
if signature.varargs:
|
216
|
-
init_arg_list.append(f'*{signature.varargs.name}')
|
217
|
-
|
218
|
-
return formalize_schema(
|
219
|
-
pg_typing.create_schema(
|
220
|
-
fields=arg_fields,
|
221
|
-
name=f'{func.__module__}.{func.__name__}',
|
222
|
-
metadata={
|
223
|
-
'init_arg_list': init_arg_list,
|
224
|
-
'varargs_name': getattr(signature.varargs, 'name', None),
|
225
|
-
'varkw_name': getattr(signature.varkw, 'name', None),
|
226
|
-
'returns': returns,
|
227
|
-
},
|
228
|
-
description=description,
|
229
|
-
allow_nonconst_keys=True,
|
230
|
-
)
|
231
|
-
)
|
232
|
-
|
233
|
-
|
234
|
-
def validate_init_arg_list(
|
235
|
-
init_arg_list: List[str], cls_schema: pg_typing.Schema) -> None:
|
236
|
-
"""Validate init arg list."""
|
237
|
-
for i, arg in enumerate(init_arg_list):
|
238
|
-
is_vararg = False
|
239
|
-
if i == len(init_arg_list) - 1 and arg.startswith('*'):
|
240
|
-
arg = arg[1:]
|
241
|
-
is_vararg = True
|
242
|
-
field = cls_schema.get_field(arg)
|
243
|
-
if field is None:
|
244
|
-
raise TypeError(
|
245
|
-
f'Argument {arg!r} from `init_arg_list` is not defined as a '
|
246
|
-
f'symbolic field. init_arg_list={init_arg_list!r}.')
|
247
|
-
if is_vararg and not isinstance(field.value, pg_typing.List):
|
248
|
-
raise TypeError(
|
249
|
-
f'Variable positional argument {arg!r} should be declared with '
|
250
|
-
f'`pg.typing.List(...)`. Encountered {field.value!r}.')
|
251
|
-
|
252
|
-
|
253
|
-
def auto_init_arg_list(cls):
|
254
|
-
"""Generate the init_arg_list metadata from an pg.Object subclass."""
|
255
|
-
# Inherit from the first non-empty base if they have the same signature.
|
256
|
-
# This allows to bypass interface-only bases.
|
257
|
-
init_arg_list = None
|
258
|
-
for base_cls in cls.__bases__:
|
259
|
-
schema = getattr(base_cls, '__schema__', None)
|
260
|
-
if isinstance(schema, pg_typing.Schema):
|
261
|
-
if list(schema.keys()) == list(cls.__schema__.keys()):
|
262
|
-
init_arg_list = base_cls.init_arg_list
|
263
|
-
else:
|
264
|
-
break
|
265
|
-
if init_arg_list is None:
|
266
|
-
# Automatically generate from the field definitions in their
|
267
|
-
# declaration order from base classes to subclasses.
|
268
|
-
init_arg_list = [
|
269
|
-
str(key)
|
270
|
-
for key in cls.__schema__.fields.keys()
|
271
|
-
if isinstance(key, pg_typing.ConstStrKey)
|
272
|
-
]
|
273
|
-
return init_arg_list
|
274
|
-
|
275
|
-
|
276
|
-
def formalize_schema(schema: pg_typing.Schema) -> pg_typing.Schema: # pylint: disable=redefined-outer-name
|
277
|
-
"""Formalize default values in the schema."""
|
278
|
-
|
279
|
-
def _formalize_field(path: object_utils.KeyPath, node: Any) -> bool:
|
280
|
-
"""Formalize field."""
|
281
|
-
if isinstance(node, pg_typing.Field):
|
282
|
-
field = node
|
283
|
-
if (not flags.is_empty_field_description_allowed()
|
284
|
-
and not field.description):
|
285
|
-
raise ValueError(
|
286
|
-
f'Field description must not be empty (path={path}).')
|
287
|
-
|
288
|
-
field.value.set_default(
|
289
|
-
field.apply(
|
290
|
-
field.default_value,
|
291
|
-
allow_partial=True,
|
292
|
-
transform_fn=base.symbolic_transform_fn(allow_partial=True)),
|
293
|
-
use_default_apply=False)
|
294
|
-
if isinstance(field.value, pg_typing.Dict):
|
295
|
-
if field.value.schema is not None:
|
296
|
-
field.value.schema.set_name(f'{schema.name}.{path.path}')
|
297
|
-
object_utils.traverse(field.value.schema.fields, _formalize_field,
|
298
|
-
None, path)
|
299
|
-
elif isinstance(field.value, pg_typing.List):
|
300
|
-
_formalize_field(object_utils.KeyPath(0, path), field.value.element)
|
301
|
-
elif isinstance(field.value, pg_typing.Tuple):
|
302
|
-
for i, elem in enumerate(field.value.elements):
|
303
|
-
_formalize_field(object_utils.KeyPath(i, path), elem)
|
304
|
-
elif isinstance(field.value, pg_typing.Union):
|
305
|
-
for i, c in enumerate(field.value.candidates):
|
306
|
-
_formalize_field(
|
307
|
-
object_utils.KeyPath(i, path),
|
308
|
-
pg_typing.Field(field.key, c, 'Union sub-type.'))
|
309
|
-
return True
|
310
|
-
|
311
|
-
object_utils.traverse(schema.fields, _formalize_field)
|
312
|
-
return schema
|
313
|
-
|
314
|
-
|
315
|
-
def schema_description_from_docstr(
|
316
|
-
docstr: Optional[object_utils.DocStr],
|
317
|
-
include_long_description: bool = False) -> Optional[str]:
|
318
|
-
"""Gets schema description from DocStr."""
|
319
|
-
if docstr is None:
|
320
|
-
return None
|
321
|
-
description = docstr.short_description or ''
|
322
|
-
if include_long_description:
|
323
|
-
if docstr.blank_after_short_description:
|
324
|
-
description += '\n'
|
325
|
-
if docstr.long_description:
|
326
|
-
description += '\n' + docstr.long_description
|
327
|
-
return description.rstrip('\n')
|
@@ -1,57 +0,0 @@
|
|
1
|
-
# Copyright 2022 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
|
-
"""Tests for pyglove.symbolize."""
|
15
|
-
|
16
|
-
import unittest
|
17
|
-
|
18
|
-
from pyglove.core import object_utils
|
19
|
-
from pyglove.core.symbolic import schema_utils
|
20
|
-
|
21
|
-
|
22
|
-
class SchemaDescriptionFromDocStrTest(unittest.TestCase):
|
23
|
-
"""Tests for `schema_description_from_docstr`."""
|
24
|
-
|
25
|
-
def test_none_doc_str(self):
|
26
|
-
self.assertIsNone(schema_utils.schema_description_from_docstr(None))
|
27
|
-
|
28
|
-
def test_short_description_only(self):
|
29
|
-
docstr = object_utils.DocStr.parse(
|
30
|
-
"""This is a function.""")
|
31
|
-
self.assertEqual(
|
32
|
-
schema_utils.schema_description_from_docstr(docstr),
|
33
|
-
'This is a function.')
|
34
|
-
self.assertEqual(
|
35
|
-
schema_utils.schema_description_from_docstr(
|
36
|
-
docstr, include_long_description=True),
|
37
|
-
'This is a function.')
|
38
|
-
|
39
|
-
def test_long_description_only(self):
|
40
|
-
docstr = object_utils.DocStr.parse(
|
41
|
-
"""This is a function.
|
42
|
-
|
43
|
-
This is the longer explanation of the function.
|
44
|
-
|
45
|
-
""")
|
46
|
-
self.assertEqual(
|
47
|
-
schema_utils.schema_description_from_docstr(docstr),
|
48
|
-
'This is a function.')
|
49
|
-
self.assertEqual(
|
50
|
-
schema_utils.schema_description_from_docstr(
|
51
|
-
docstr, include_long_description=True),
|
52
|
-
('This is a function.\n\n'
|
53
|
-
'This is the longer explanation of the function.'))
|
54
|
-
|
55
|
-
|
56
|
-
if __name__ == '__main__':
|
57
|
-
unittest.main()
|
@@ -1,202 +0,0 @@
|
|
1
|
-
# Copyright 2022 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
|
-
"""Typing helpers."""
|
15
|
-
|
16
|
-
from typing import Any, Dict, List, Optional, Tuple, Union
|
17
|
-
|
18
|
-
from pyglove.core import object_utils
|
19
|
-
from pyglove.core.typing import callable_signature
|
20
|
-
from pyglove.core.typing import class_schema
|
21
|
-
from pyglove.core.typing import key_specs as ks
|
22
|
-
from pyglove.core.typing import value_specs as vs
|
23
|
-
|
24
|
-
|
25
|
-
def get_arg_fields(
|
26
|
-
signature: callable_signature.Signature,
|
27
|
-
args: Optional[List[Union[
|
28
|
-
Tuple[Union[str, class_schema.KeySpec], class_schema.ValueSpec, str],
|
29
|
-
Tuple[Union[str, class_schema.KeySpec],
|
30
|
-
class_schema.ValueSpec, str, Any]]]
|
31
|
-
] = None, # pylint: disable=bad-continuation
|
32
|
-
args_docstr: Optional[Dict[str, object_utils.DocStrArgument]] = None
|
33
|
-
) -> List[class_schema.Field]:
|
34
|
-
"""Get schema fields for the arguments from a function or method signature.
|
35
|
-
|
36
|
-
Args:
|
37
|
-
signature: A `Signature` object.
|
38
|
-
args: (Optional) explicit value specifications for the arguments, which is a
|
39
|
-
list of tuples in:
|
40
|
-
(<argumment-name>, <value-spec>, [description], [metadata-objects]).
|
41
|
-
|
42
|
-
* `argument-name` - a string or a `StrKey` object. This name must exist
|
43
|
-
in the signature's argument list, unless the signature has a
|
44
|
-
``**kwargs``, where the argument name can be an acceptable key in the
|
45
|
-
dict that is passed to the ``**kwargs``. If the argument name is a
|
46
|
-
``StrKey`` object, it specifies a field that matches any keys beyond
|
47
|
-
the regular arguments for the ``**kwargs``.
|
48
|
-
* `value-spec` - a ``ValueSpec`` object asssociate with the argument
|
49
|
-
name.
|
50
|
-
* `description` - an optional string as the description for the argument.
|
51
|
-
* `metadata-objects` - an optional list of any type, which can be
|
52
|
-
used to generate code according to the schema.
|
53
|
-
args_docstr: (Optional) a dict of argument names to
|
54
|
-
:class:`pg.object_utils.DocStrArgument` object. If present, they will
|
55
|
-
be used as the description for the ``Field`` objects.
|
56
|
-
|
57
|
-
Returns:
|
58
|
-
`Field` objects for the arguments from the `signature` in declaration order.
|
59
|
-
If an argument is not present in `args`, it will be considered an `Any`.
|
60
|
-
Otherwise it will create a `Field` from the explicit specifications. Default
|
61
|
-
values for the arguments will be automatially propagated from the signature
|
62
|
-
to the fields.
|
63
|
-
|
64
|
-
Raises:
|
65
|
-
KeyError: If argument names defined in `args` does not match with the
|
66
|
-
arguments from the signature.
|
67
|
-
TypeError: The value spec defined in `args` is not compatible with the value
|
68
|
-
spec inspected from the signature.
|
69
|
-
ValueError: The value spec defined in `args` does not align with the default
|
70
|
-
values from the signature.
|
71
|
-
"""
|
72
|
-
arg_dict = dict()
|
73
|
-
kwarg_spec = None
|
74
|
-
varargs_spec = None
|
75
|
-
|
76
|
-
def maybe_add_description(arg_name, field):
|
77
|
-
if args_docstr and not field.description:
|
78
|
-
arg_docstr = args_docstr.get(arg_name, None)
|
79
|
-
if arg_docstr is not None:
|
80
|
-
field.set_description(arg_docstr.description)
|
81
|
-
return field
|
82
|
-
|
83
|
-
func_arg_names = set(signature.arg_names)
|
84
|
-
# Extra legal argument names that are out of function signature, it is not
|
85
|
-
# empty only when function allow **kwargs.
|
86
|
-
extra_arg_names = []
|
87
|
-
for arg in args or []:
|
88
|
-
if isinstance(arg[0], ks.StrKey):
|
89
|
-
if kwarg_spec is not None:
|
90
|
-
raise KeyError(
|
91
|
-
f'{signature.id}: multiple StrKey found in '
|
92
|
-
f'symbolic arguments declaration.')
|
93
|
-
kwarg_spec = arg
|
94
|
-
else:
|
95
|
-
assert isinstance(arg[0], (str, ks.ConstStrKey))
|
96
|
-
if arg[0] in arg_dict:
|
97
|
-
raise KeyError(
|
98
|
-
f'{signature.id}: multiple symbolic fields '
|
99
|
-
f'found for argument {arg[0]!r}.')
|
100
|
-
if signature.varargs and signature.varargs.name == arg[0]:
|
101
|
-
varargs_spec = arg
|
102
|
-
|
103
|
-
elif arg[0] not in func_arg_names:
|
104
|
-
if signature.has_varkw:
|
105
|
-
extra_arg_names.append(arg[0])
|
106
|
-
else:
|
107
|
-
raise KeyError(
|
108
|
-
f'{signature.id}: found extra symbolic argument {arg[0]!r}.')
|
109
|
-
arg_dict[arg[0]] = arg
|
110
|
-
|
111
|
-
def get_arg_field(arg_spec):
|
112
|
-
arg_name = arg_spec.name
|
113
|
-
decl_spec = arg_spec.value_spec
|
114
|
-
if arg_name not in arg_dict:
|
115
|
-
# Automatic generate symbolic declaration for missing arguments.
|
116
|
-
arg_spec = (arg_name, decl_spec)
|
117
|
-
else:
|
118
|
-
arg_spec = arg_dict[arg_name]
|
119
|
-
if not decl_spec.is_compatible(arg_spec[1]):
|
120
|
-
raise TypeError(
|
121
|
-
f'{signature.id}: the value spec ({arg_spec[1]!r}) of symbolic '
|
122
|
-
f'argument {arg_name} is not compatible with the value spec '
|
123
|
-
f'({decl_spec!r}) from function signature.')
|
124
|
-
if arg_spec[1].default in [object_utils.MISSING_VALUE, None]:
|
125
|
-
arg_spec[1].extend(decl_spec).set_default(decl_spec.default)
|
126
|
-
elif (decl_spec.default != arg_spec[1].default
|
127
|
-
and (not isinstance(arg_spec[1], vs.Dict)
|
128
|
-
or decl_spec.default != object_utils.MISSING_VALUE)):
|
129
|
-
raise ValueError(
|
130
|
-
f'{signature.id}: the default value ({arg_spec[1].default!r}) '
|
131
|
-
f'of symbolic argument {arg_name!r} does not equal to the default '
|
132
|
-
f'value ({decl_spec.default!r}) specified at function signature '
|
133
|
-
f'declaration.')
|
134
|
-
return maybe_add_description(arg_name, class_schema.Field(*arg_spec))
|
135
|
-
# Add positional named arguments.
|
136
|
-
arg_fields: List[class_schema.Field] = [
|
137
|
-
get_arg_field(arg) for arg in signature.args]
|
138
|
-
|
139
|
-
# Add positional wildcard arguments.
|
140
|
-
if signature.varargs:
|
141
|
-
if varargs_spec is None:
|
142
|
-
varargs_spec = (
|
143
|
-
ks.ConstStrKey(signature.varargs.name),
|
144
|
-
vs.List(vs.Any()))
|
145
|
-
elif not isinstance(varargs_spec[1], vs.List):
|
146
|
-
raise ValueError(
|
147
|
-
f'{signature.id}: the value spec for positional wildcard argument '
|
148
|
-
f'{varargs_spec[0]!r} must be a `pg.typing.List` instance. '
|
149
|
-
f'Encountered: {varargs_spec[1]!r}.')
|
150
|
-
varargs_spec[1].set_default([])
|
151
|
-
vararg_field = maybe_add_description(
|
152
|
-
f'*{signature.varargs.name}', class_schema.Field(*varargs_spec))
|
153
|
-
arg_fields.append(vararg_field)
|
154
|
-
|
155
|
-
# Add keyword-only arguments.
|
156
|
-
arg_fields.extend([get_arg_field(arg) for arg in signature.kwonlyargs])
|
157
|
-
|
158
|
-
# Add extra arguments that are keyword wildcard.
|
159
|
-
for arg_name in extra_arg_names:
|
160
|
-
arg_field = maybe_add_description(
|
161
|
-
arg_name,
|
162
|
-
class_schema.Field(*arg_dict[arg_name]))
|
163
|
-
arg_fields.append(arg_field)
|
164
|
-
|
165
|
-
# Add keyword wildcard arguments.
|
166
|
-
if signature.varkw:
|
167
|
-
if kwarg_spec is None:
|
168
|
-
kwarg_spec = (ks.StrKey(), vs.Any())
|
169
|
-
varkw_field = maybe_add_description(
|
170
|
-
f'**{signature.varkw.name}', class_schema.Field(*kwarg_spec))
|
171
|
-
arg_fields.append(varkw_field)
|
172
|
-
return arg_fields
|
173
|
-
|
174
|
-
|
175
|
-
def ensure_value_spec(
|
176
|
-
value_spec: class_schema.ValueSpec,
|
177
|
-
src_spec: class_schema.ValueSpec,
|
178
|
-
root_path: Optional[object_utils.KeyPath] = None
|
179
|
-
) -> Optional[class_schema.ValueSpec]:
|
180
|
-
"""Extract counter part from value spec that matches dest spec type.
|
181
|
-
|
182
|
-
Args:
|
183
|
-
value_spec: Value spec.
|
184
|
-
src_spec: Destination value spec.
|
185
|
-
root_path: An optional path for the value to include in error message.
|
186
|
-
|
187
|
-
Returns:
|
188
|
-
value_spec of src_spec_type
|
189
|
-
|
190
|
-
Raises:
|
191
|
-
TypeError: When value_spec cannot match src_spec_type.
|
192
|
-
"""
|
193
|
-
if isinstance(value_spec, vs.Union):
|
194
|
-
value_spec = value_spec.get_candidate(src_spec)
|
195
|
-
if isinstance(value_spec, vs.Any):
|
196
|
-
return None
|
197
|
-
if not src_spec.is_compatible(value_spec):
|
198
|
-
raise TypeError(
|
199
|
-
object_utils.message_on_path(
|
200
|
-
f'Source spec {src_spec} is not compatible with destination '
|
201
|
-
f'spec {value_spec}.', root_path))
|
202
|
-
return value_spec
|