pyglove 0.4.5.dev202411132359__py3-none-any.whl → 0.4.5.dev202501250807__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 +40 -21
- 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 +312 -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 +53 -38
- pyglove/core/geno/base_test.py +2 -4
- pyglove/core/geno/categorical.py +36 -27
- pyglove/core/geno/custom.py +18 -15
- pyglove/core/geno/numerical.py +19 -16
- pyglove/core/geno/space.py +3 -4
- pyglove/core/hyper/base.py +6 -6
- pyglove/core/hyper/categorical.py +91 -52
- 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 +3 -5
- pyglove/core/hyper/dynamic_evaluation.py +3 -4
- 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/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 +4 -3
- pyglove/core/symbolic/__init__.py +4 -0
- pyglove/core/symbolic/base.py +200 -136
- pyglove/core/symbolic/base_test.py +17 -19
- pyglove/core/symbolic/boilerplate.py +4 -5
- pyglove/core/symbolic/class_wrapper.py +10 -14
- pyglove/core/symbolic/class_wrapper_test.py +2 -2
- pyglove/core/symbolic/compounding.py +2 -2
- pyglove/core/symbolic/compounding_test.py +2 -4
- pyglove/core/symbolic/contextual_object.py +288 -0
- pyglove/core/symbolic/contextual_object_test.py +327 -0
- pyglove/core/symbolic/dict.py +115 -87
- pyglove/core/symbolic/dict_test.py +188 -131
- pyglove/core/symbolic/diff.py +12 -12
- pyglove/core/symbolic/flags.py +1 -1
- pyglove/core/symbolic/functor.py +16 -15
- pyglove/core/symbolic/functor_test.py +2 -4
- pyglove/core/symbolic/inferred.py +2 -2
- pyglove/core/symbolic/list.py +70 -47
- pyglove/core/symbolic/list_test.py +117 -98
- pyglove/core/symbolic/object.py +59 -58
- pyglove/core/symbolic/object_test.py +143 -90
- pyglove/core/symbolic/origin.py +5 -7
- pyglove/core/symbolic/pure_symbolic.py +4 -3
- pyglove/core/symbolic/ref.py +33 -16
- pyglove/core/symbolic/ref_test.py +17 -0
- pyglove/core/tuning/local_backend.py +2 -2
- pyglove/core/tuning/protocols.py +3 -3
- pyglove/core/typing/annotation_conversion.py +8 -3
- pyglove/core/typing/annotation_conversion_test.py +8 -0
- pyglove/core/typing/callable_ext.py +11 -13
- pyglove/core/typing/callable_signature.py +22 -19
- pyglove/core/typing/callable_signature_test.py +3 -5
- pyglove/core/typing/class_schema.py +93 -54
- pyglove/core/typing/class_schema_test.py +4 -5
- pyglove/core/typing/custom_typing.py +5 -4
- pyglove/core/typing/key_specs.py +5 -7
- pyglove/core/typing/key_specs_test.py +4 -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 +287 -144
- pyglove/core/typing/value_specs_test.py +148 -25
- pyglove/core/utils/__init__.py +172 -0
- pyglove/core/{object_utils → utils}/common_traits.py +2 -2
- pyglove/core/{object_utils → utils}/common_traits_test.py +1 -3
- pyglove/core/utils/contextual.py +147 -0
- pyglove/core/utils/contextual_test.py +88 -0
- pyglove/core/{object_utils → utils}/docstr_utils_test.py +1 -3
- pyglove/core/{object_utils → utils}/error_utils.py +3 -3
- pyglove/core/{object_utils → utils}/error_utils_test.py +1 -1
- pyglove/core/{object_utils → utils}/formatting.py +1 -1
- pyglove/core/{object_utils → utils}/formatting_test.py +1 -2
- 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 +1 -1
- pyglove/core/{object_utils → utils}/json_conversion_test.py +1 -3
- pyglove/core/{object_utils → utils}/missing.py +2 -2
- 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/{object_utils → utils}/timing.py +21 -10
- pyglove/core/{object_utils → utils}/timing_test.py +14 -12
- pyglove/core/{object_utils → utils}/value_location.py +2 -2
- pyglove/core/{object_utils → utils}/value_location_test.py +2 -4
- pyglove/core/views/base.py +25 -29
- pyglove/core/views/html/base.py +15 -16
- pyglove/core/views/html/controls/base.py +46 -9
- pyglove/core/views/html/controls/label.py +13 -2
- pyglove/core/views/html/controls/label_test.py +27 -8
- pyglove/core/views/html/controls/progress_bar.py +3 -5
- pyglove/core/views/html/controls/progress_bar_test.py +2 -2
- pyglove/core/views/html/controls/tab.py +217 -66
- pyglove/core/views/html/controls/tab_test.py +46 -15
- pyglove/core/views/html/tree_view.py +39 -37
- {pyglove-0.4.5.dev202411132359.dist-info → pyglove-0.4.5.dev202501250807.dist-info}/METADATA +17 -3
- pyglove-0.4.5.dev202501250807.dist-info/RECORD +218 -0
- {pyglove-0.4.5.dev202411132359.dist-info → pyglove-0.4.5.dev202501250807.dist-info}/WHEEL +1 -1
- pyglove/core/object_utils/__init__.py +0 -164
- pyglove-0.4.5.dev202411132359.dist-info/RECORD +0 -203
- /pyglove/core/{object_utils → utils}/docstr_utils.py +0 -0
- /pyglove/core/{object_utils → utils}/thread_local.py +0 -0
- {pyglove-0.4.5.dev202411132359.dist-info → pyglove-0.4.5.dev202501250807.dist-info}/LICENSE +0 -0
- {pyglove-0.4.5.dev202411132359.dist-info → pyglove-0.4.5.dev202501250807.dist-info}/top_level.txt +0 -0
@@ -18,7 +18,7 @@ import inspect
|
|
18
18
|
import types
|
19
19
|
import typing
|
20
20
|
|
21
|
-
from pyglove.core import
|
21
|
+
from pyglove.core import utils
|
22
22
|
from pyglove.core.typing import annotated
|
23
23
|
from pyglove.core.typing import class_schema
|
24
24
|
from pyglove.core.typing import inspect as pg_inspect
|
@@ -73,7 +73,7 @@ def _value_spec_from_default_value(
|
|
73
73
|
elif isinstance(value, tuple):
|
74
74
|
value_spec = vs.Tuple(
|
75
75
|
[_value_spec_from_default_value(elem, False) for elem in value])
|
76
|
-
elif inspect.isfunction(value) or isinstance(value,
|
76
|
+
elif inspect.isfunction(value) or isinstance(value, utils.Functor):
|
77
77
|
value_spec = vs.Callable()
|
78
78
|
elif not isinstance(value, type):
|
79
79
|
value_spec = vs.Object(type(value))
|
@@ -132,7 +132,7 @@ def _value_spec_from_type_annotation(
|
|
132
132
|
return vs.Union([vs.List(elem), vs.Tuple(elem)])
|
133
133
|
# Handling literals.
|
134
134
|
elif origin is typing.Literal:
|
135
|
-
return vs.Enum(
|
135
|
+
return vs.Enum(utils.MISSING_VALUE, args)
|
136
136
|
# Handling dict.
|
137
137
|
elif origin in (dict, typing.Dict, collections.abc.Mapping):
|
138
138
|
if not args:
|
@@ -177,6 +177,11 @@ def _value_spec_from_type_annotation(
|
|
177
177
|
if optional:
|
178
178
|
spec = spec.noneable()
|
179
179
|
return spec
|
180
|
+
elif origin is typing.Final:
|
181
|
+
return _value_spec_from_type_annotation(
|
182
|
+
args[0],
|
183
|
+
accept_value_as_annotation=False
|
184
|
+
).freeze(vs._FROZEN_VALUE_PLACEHOLDER) # pylint: disable=protected-access
|
180
185
|
elif isinstance(annotation, typing.ForwardRef):
|
181
186
|
annotation = annotation.__forward_arg__
|
182
187
|
if parent_module is not None:
|
@@ -306,6 +306,14 @@ class ValueSpecFromAnnotationTest(unittest.TestCase):
|
|
306
306
|
ValueSpec.from_annotation(int | str, True),
|
307
307
|
vs.Union([vs.Int(), vs.Str()]))
|
308
308
|
|
309
|
+
def test_final(self):
|
310
|
+
self.assertEqual(
|
311
|
+
ValueSpec.from_annotation(
|
312
|
+
typing.Final[int], True
|
313
|
+
).set_default(1),
|
314
|
+
vs.Int().freeze(1)
|
315
|
+
)
|
316
|
+
|
309
317
|
|
310
318
|
if __name__ == '__main__':
|
311
319
|
unittest.main()
|
@@ -19,14 +19,14 @@ import inspect
|
|
19
19
|
import types
|
20
20
|
from typing import Any, Callable, Dict, Iterator, List, Optional, Sequence, Tuple, Union
|
21
21
|
|
22
|
-
from pyglove.core import
|
22
|
+
from pyglove.core import utils
|
23
23
|
from pyglove.core.typing import callable_signature
|
24
24
|
|
25
25
|
|
26
26
|
_TLS_KEY_PRESET_KWARGS = '__preset_kwargs__'
|
27
27
|
|
28
28
|
|
29
|
-
class PresetArgValue(
|
29
|
+
class PresetArgValue(utils.Formattable):
|
30
30
|
"""Value placeholder for arguments whose value will be provided by presets.
|
31
31
|
|
32
32
|
Example:
|
@@ -39,12 +39,12 @@ class PresetArgValue(object_utils.Formattable):
|
|
39
39
|
print(foo(x=1)) # 2: y=1
|
40
40
|
"""
|
41
41
|
|
42
|
-
def __init__(self, default: Any =
|
42
|
+
def __init__(self, default: Any = utils.MISSING_VALUE):
|
43
43
|
self.default = default
|
44
44
|
|
45
45
|
@property
|
46
46
|
def has_default(self) -> bool:
|
47
|
-
return self.default !=
|
47
|
+
return self.default != utils.MISSING_VALUE
|
48
48
|
|
49
49
|
def __eq__(self, other: Any) -> bool:
|
50
50
|
return isinstance(other, PresetArgValue) and (
|
@@ -55,13 +55,13 @@ class PresetArgValue(object_utils.Formattable):
|
|
55
55
|
return not self.__eq__(other)
|
56
56
|
|
57
57
|
def format(self, *args, **kwargs):
|
58
|
-
return
|
58
|
+
return utils.kvlist_str(
|
59
59
|
[
|
60
|
-
('default', self.default,
|
60
|
+
('default', self.default, utils.MISSING_VALUE),
|
61
61
|
],
|
62
62
|
label='PresetArgValue',
|
63
63
|
*args,
|
64
|
-
**kwargs
|
64
|
+
**kwargs,
|
65
65
|
)
|
66
66
|
|
67
67
|
@classmethod
|
@@ -182,15 +182,15 @@ def preset_args(
|
|
182
182
|
Current preset kwargs.
|
183
183
|
"""
|
184
184
|
|
185
|
-
parent_presets =
|
185
|
+
parent_presets = utils.thread_local_peek(
|
186
186
|
_TLS_KEY_PRESET_KWARGS, _ArgPresets()
|
187
187
|
)
|
188
188
|
current_preset = parent_presets.derive(kwargs, preset_name, inherit_preset)
|
189
|
-
|
189
|
+
utils.thread_local_push(_TLS_KEY_PRESET_KWARGS, current_preset)
|
190
190
|
try:
|
191
191
|
yield current_preset
|
192
192
|
finally:
|
193
|
-
|
193
|
+
utils.thread_local_pop(_TLS_KEY_PRESET_KWARGS, None)
|
194
194
|
|
195
195
|
|
196
196
|
def enable_preset_args(
|
@@ -243,9 +243,7 @@ def enable_preset_args(
|
|
243
243
|
@functools.wraps(func)
|
244
244
|
def _func(*args, **kwargs):
|
245
245
|
# Map positional arguments to keyword arguments.
|
246
|
-
presets =
|
247
|
-
_TLS_KEY_PRESET_KWARGS, None
|
248
|
-
)
|
246
|
+
presets = utils.thread_local_peek(_TLS_KEY_PRESET_KWARGS, None)
|
249
247
|
preset_kwargs = presets.get_preset(preset_name) if presets else {}
|
250
248
|
args, kwargs = PresetArgValue.resolve_args(
|
251
249
|
args, kwargs, positional_arg_names, arg_defaults, preset_kwargs,
|
@@ -21,7 +21,8 @@ import types
|
|
21
21
|
import typing
|
22
22
|
from typing import Any, Callable, Dict, List, Optional, Union
|
23
23
|
|
24
|
-
from pyglove.core import
|
24
|
+
from pyglove.core import coding
|
25
|
+
from pyglove.core import utils
|
25
26
|
from pyglove.core.typing import class_schema
|
26
27
|
from pyglove.core.typing import key_specs as ks
|
27
28
|
|
@@ -140,7 +141,7 @@ class CallableType(enum.Enum):
|
|
140
141
|
METHOD = 2
|
141
142
|
|
142
143
|
|
143
|
-
class Signature(
|
144
|
+
class Signature(utils.Formattable):
|
144
145
|
"""PY3 function signature."""
|
145
146
|
|
146
147
|
def __init__(self,
|
@@ -256,7 +257,7 @@ class Signature(object_utils.Formattable):
|
|
256
257
|
**kwargs,
|
257
258
|
) -> str:
|
258
259
|
"""Format current object."""
|
259
|
-
return
|
260
|
+
return utils.kvlist_str(
|
260
261
|
[
|
261
262
|
('', self.id, ''),
|
262
263
|
('args', self.args, []),
|
@@ -270,7 +271,7 @@ class Signature(object_utils.Formattable):
|
|
270
271
|
compact=compact,
|
271
272
|
verbose=verbose,
|
272
273
|
root_indent=root_indent,
|
273
|
-
**kwargs
|
274
|
+
**kwargs,
|
274
275
|
)
|
275
276
|
|
276
277
|
def annotate(
|
@@ -287,7 +288,7 @@ class Signature(object_utils.Formattable):
|
|
287
288
|
return_value = class_schema.ValueSpec.from_annotation(
|
288
289
|
return_value, auto_typing=True
|
289
290
|
)
|
290
|
-
if
|
291
|
+
if utils.MISSING_VALUE != return_value.default:
|
291
292
|
raise ValueError('return value spec should not have default value.')
|
292
293
|
self.return_value = return_value
|
293
294
|
|
@@ -336,12 +337,12 @@ class Signature(object_utils.Formattable):
|
|
336
337
|
or field.value.default is None
|
337
338
|
):
|
338
339
|
field.value.set_default(
|
339
|
-
arg.value_spec.default, root_path=
|
340
|
+
arg.value_spec.default, root_path=utils.KeyPath(arg.name)
|
340
341
|
)
|
341
342
|
if arg.value_spec.default != field.value.default:
|
342
343
|
if field.value.is_noneable and not arg.value_spec.has_default:
|
343
|
-
|
344
|
-
field.value.set_default(
|
344
|
+
# Special handling noneable which always comes with a default.
|
345
|
+
field.value.set_default(utils.MISSING_VALUE)
|
345
346
|
elif not (
|
346
347
|
# Special handling Dict type which always has default.
|
347
348
|
isinstance(field.value, class_schema.ValueSpec.DictType)
|
@@ -548,7 +549,7 @@ class Signature(object_utils.Formattable):
|
|
548
549
|
if not callable(callable_object):
|
549
550
|
raise TypeError(f'{callable_object!r} is not callable.')
|
550
551
|
|
551
|
-
if isinstance(callable_object,
|
552
|
+
if isinstance(callable_object, utils.Functor):
|
552
553
|
assert callable_object.__signature__ is not None
|
553
554
|
return callable_object.__signature__
|
554
555
|
|
@@ -565,15 +566,15 @@ class Signature(object_utils.Formattable):
|
|
565
566
|
description = None
|
566
567
|
args_doc = {}
|
567
568
|
if func.__doc__:
|
568
|
-
cls_doc =
|
569
|
+
cls_doc = utils.DocStr.parse(func.__doc__)
|
569
570
|
description = cls_doc.short_description
|
570
571
|
args_doc.update(cls_doc.args)
|
571
572
|
|
572
573
|
if func.__init__.__doc__:
|
573
|
-
init_doc =
|
574
|
+
init_doc = utils.DocStr.parse(func.__init__.__doc__)
|
574
575
|
args_doc.update(init_doc.args)
|
575
|
-
docstr =
|
576
|
-
|
576
|
+
docstr = utils.DocStr(
|
577
|
+
utils.DocStrStyle.GOOGLE,
|
577
578
|
short_description=description,
|
578
579
|
long_description=None,
|
579
580
|
examples=[],
|
@@ -592,7 +593,7 @@ class Signature(object_utils.Formattable):
|
|
592
593
|
else CallableType.FUNCTION
|
593
594
|
)
|
594
595
|
if auto_doc:
|
595
|
-
docstr =
|
596
|
+
docstr = utils.docstr(func)
|
596
597
|
sig = inspect.signature(func)
|
597
598
|
|
598
599
|
module_name = getattr(func, '__module__', None)
|
@@ -616,7 +617,7 @@ class Signature(object_utils.Formattable):
|
|
616
617
|
module_name: Optional[str] = None,
|
617
618
|
qualname: Optional[str] = None,
|
618
619
|
auto_typing: bool = False,
|
619
|
-
docstr: Union[str,
|
620
|
+
docstr: Union[str, utils.DocStr, None] = None,
|
620
621
|
parent_module: Optional[types.ModuleType] = None,
|
621
622
|
) -> 'Signature':
|
622
623
|
"""Returns PyGlove signature from Python signature.
|
@@ -643,7 +644,7 @@ class Signature(object_utils.Formattable):
|
|
643
644
|
varkw = None
|
644
645
|
|
645
646
|
if isinstance(docstr, str):
|
646
|
-
docstr =
|
647
|
+
docstr = utils.DocStr.parse(docstr)
|
647
648
|
|
648
649
|
def make_arg_spec(param: inspect.Parameter) -> Argument:
|
649
650
|
"""Makes argument spec from inspect.Parameter."""
|
@@ -707,7 +708,7 @@ class Signature(object_utils.Formattable):
|
|
707
708
|
force_missing_as_default: bool = False,
|
708
709
|
arg_prefix: str = ''):
|
709
710
|
s = [f'{arg_prefix}{arg_name}']
|
710
|
-
if arg_spec.annotation !=
|
711
|
+
if arg_spec.annotation != utils.MISSING_VALUE:
|
711
712
|
s.append(f': _annotation_{arg_name}')
|
712
713
|
exec_locals[f'_annotation_{arg_name}'] = arg_spec.annotation
|
713
714
|
if not arg_prefix and (force_missing_as_default or arg_spec.has_default):
|
@@ -753,14 +754,16 @@ class Signature(object_utils.Formattable):
|
|
753
754
|
)
|
754
755
|
|
755
756
|
# Generate function.
|
756
|
-
fn =
|
757
|
+
fn = coding.make_function(
|
757
758
|
self.name,
|
758
759
|
args=args,
|
759
760
|
body=body,
|
760
761
|
exec_globals=exec_globals,
|
761
762
|
exec_locals=exec_locals,
|
762
763
|
return_type=getattr(
|
763
|
-
self.return_value, 'annotation',
|
764
|
+
self.return_value, 'annotation', coding.NO_TYPE_ANNOTATION
|
765
|
+
),
|
766
|
+
)
|
764
767
|
fn.__module__ = self.module_name
|
765
768
|
fn.__name__ = self.name
|
766
769
|
fn.__qualname__ = self.qualname
|
@@ -11,15 +11,13 @@
|
|
11
11
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
12
|
# See the License for the specific language governing permissions and
|
13
13
|
# limitations under the License.
|
14
|
-
"""Tests for pyglove.core.typing.callable_signature."""
|
15
|
-
|
16
14
|
import copy
|
17
15
|
import dataclasses
|
18
16
|
import inspect
|
19
17
|
from typing import List
|
20
18
|
import unittest
|
21
19
|
|
22
|
-
from pyglove.core import
|
20
|
+
from pyglove.core import utils
|
23
21
|
from pyglove.core.typing import annotation_conversion # pylint: disable=unused-import
|
24
22
|
from pyglove.core.typing import callable_signature
|
25
23
|
from pyglove.core.typing import class_schema
|
@@ -628,8 +626,8 @@ class FromCallableTest(unittest.TestCase):
|
|
628
626
|
self.assertIsNotNone(signature.varkw)
|
629
627
|
|
630
628
|
def test_signature_with_forward_declarations(self):
|
631
|
-
signature = callable_signature.signature(
|
632
|
-
self.assertIs(signature.get_value_spec('parent').cls,
|
629
|
+
signature = callable_signature.signature(utils.KeyPath)
|
630
|
+
self.assertIs(signature.get_value_spec('parent').cls, utils.KeyPath)
|
633
631
|
|
634
632
|
|
635
633
|
class FromSchemaTest(unittest.TestCase):
|
@@ -20,10 +20,10 @@ import sys
|
|
20
20
|
import types
|
21
21
|
from typing import Any, Callable, Dict, Iterable, List, Optional, Sequence, Set, Tuple, Type, Union
|
22
22
|
|
23
|
-
from pyglove.core import
|
23
|
+
from pyglove.core import utils
|
24
24
|
|
25
25
|
|
26
|
-
class KeySpec(
|
26
|
+
class KeySpec(utils.Formattable, utils.JSONConvertible):
|
27
27
|
"""Interface for key specifications.
|
28
28
|
|
29
29
|
A key specification determines what keys are acceptable for a symbolic
|
@@ -94,7 +94,7 @@ class KeySpec(object_utils.Formattable, object_utils.JSONConvertible):
|
|
94
94
|
assert False, 'Overridden in `key_specs.py`.'
|
95
95
|
|
96
96
|
|
97
|
-
class ForwardRef(
|
97
|
+
class ForwardRef(utils.Formattable):
|
98
98
|
"""Forward type reference."""
|
99
99
|
|
100
100
|
def __init__(self, module: types.ModuleType, name: str):
|
@@ -147,7 +147,7 @@ class ForwardRef(object_utils.Formattable):
|
|
147
147
|
**kwargs
|
148
148
|
) -> str:
|
149
149
|
"""Format this object."""
|
150
|
-
return
|
150
|
+
return utils.kvlist_str(
|
151
151
|
[
|
152
152
|
('module', self.module.__name__, None),
|
153
153
|
('name', self.name, None),
|
@@ -180,7 +180,7 @@ class ForwardRef(object_utils.Formattable):
|
|
180
180
|
return ForwardRef(self.module, self.name)
|
181
181
|
|
182
182
|
|
183
|
-
class ValueSpec(
|
183
|
+
class ValueSpec(utils.Formattable, utils.JSONConvertible):
|
184
184
|
"""Interface for value specifications.
|
185
185
|
|
186
186
|
A value specification defines what values are acceptable for a symbolic
|
@@ -344,6 +344,10 @@ class ValueSpec(object_utils.Formattable, object_utils.JSONConvertible):
|
|
344
344
|
Tuple[Type[Any], ...]]: # pyformat: disable
|
345
345
|
"""Returns acceptable (resolved) value type(s)."""
|
346
346
|
|
347
|
+
@abc.abstractmethod
|
348
|
+
def __call__(self, *args, **kwargs) -> Any:
|
349
|
+
"""Instantiates a value based on the spec.."""
|
350
|
+
|
347
351
|
@property
|
348
352
|
@abc.abstractmethod
|
349
353
|
def forward_refs(self) -> Set[ForwardRef]:
|
@@ -363,7 +367,7 @@ class ValueSpec(object_utils.Formattable, object_utils.JSONConvertible):
|
|
363
367
|
self,
|
364
368
|
default: Any,
|
365
369
|
use_default_apply: bool = True,
|
366
|
-
root_path: Optional[
|
370
|
+
root_path: Optional[utils.KeyPath] = None,
|
367
371
|
) -> 'ValueSpec':
|
368
372
|
"""Sets the default value and returns `self`.
|
369
373
|
|
@@ -394,13 +398,14 @@ class ValueSpec(object_utils.Formattable, object_utils.JSONConvertible):
|
|
394
398
|
@property
|
395
399
|
def has_default(self) -> bool:
|
396
400
|
"""Returns True if the default value is provided."""
|
397
|
-
return self.default !=
|
401
|
+
return self.default != utils.MISSING_VALUE
|
398
402
|
|
399
403
|
@abc.abstractmethod
|
400
404
|
def freeze(
|
401
405
|
self,
|
402
|
-
permanent_value: Any =
|
403
|
-
apply_before_use: bool = True
|
406
|
+
permanent_value: Any = utils.MISSING_VALUE,
|
407
|
+
apply_before_use: bool = True,
|
408
|
+
) -> 'ValueSpec':
|
404
409
|
"""Sets the default value using a permanent value and freezes current spec.
|
405
410
|
|
406
411
|
A frozen value spec will not accept any value that is not the default
|
@@ -467,10 +472,11 @@ class ValueSpec(object_utils.Formattable, object_utils.JSONConvertible):
|
|
467
472
|
self,
|
468
473
|
value: Any,
|
469
474
|
allow_partial: bool = False,
|
470
|
-
child_transform: Optional[
|
471
|
-
[
|
472
|
-
|
473
|
-
|
475
|
+
child_transform: Optional[
|
476
|
+
Callable[[utils.KeyPath, 'Field', Any], Any]
|
477
|
+
] = None,
|
478
|
+
root_path: Optional[utils.KeyPath] = None,
|
479
|
+
) -> Any:
|
474
480
|
"""Validates, completes and transforms the input value.
|
475
481
|
|
476
482
|
Here is the procedure of ``apply``::
|
@@ -547,7 +553,7 @@ class ValueSpec(object_utils.Formattable, object_utils.JSONConvertible):
|
|
547
553
|
assert False, 'Overridden in `annotation_conversion.py`.'
|
548
554
|
|
549
555
|
|
550
|
-
class Field(
|
556
|
+
class Field(utils.Formattable, utils.JSONConvertible):
|
551
557
|
"""Class that represents the definition of one or a group of attributes.
|
552
558
|
|
553
559
|
``Field`` is held by a :class:`pyglove.Schema` object for defining the
|
@@ -587,7 +593,9 @@ class Field(object_utils.Formattable, object_utils.JSONConvertible):
|
|
587
593
|
key_spec: Union[KeySpec, str],
|
588
594
|
value_spec: ValueSpec,
|
589
595
|
description: Optional[str] = None,
|
590
|
-
metadata: Optional[Dict[str, Any]] = None
|
596
|
+
metadata: Optional[Dict[str, Any]] = None,
|
597
|
+
origin: Optional[Type[Any]] = None,
|
598
|
+
) -> None:
|
591
599
|
"""Constructor.
|
592
600
|
|
593
601
|
Args:
|
@@ -596,6 +604,7 @@ class Field(object_utils.Formattable, object_utils.JSONConvertible):
|
|
596
604
|
value_spec: Value specification of the field.
|
597
605
|
description: Description of the field.
|
598
606
|
metadata: A dict of objects as metadata for the field.
|
607
|
+
origin: The class that this field originates from.
|
599
608
|
|
600
609
|
Raises:
|
601
610
|
ValueError: metadata is not a dict.
|
@@ -606,6 +615,7 @@ class Field(object_utils.Formattable, object_utils.JSONConvertible):
|
|
606
615
|
self._key = key_spec
|
607
616
|
self._value = value_spec
|
608
617
|
self._description = description
|
618
|
+
self._origin = origin
|
609
619
|
|
610
620
|
if metadata and not isinstance(metadata, dict):
|
611
621
|
raise ValueError('metadata must be a dict.')
|
@@ -661,6 +671,15 @@ class Field(object_utils.Formattable, object_utils.JSONConvertible):
|
|
661
671
|
"""
|
662
672
|
return self._metadata
|
663
673
|
|
674
|
+
@property
|
675
|
+
def origin(self) -> Optional[Type[Any]]:
|
676
|
+
"""The class that this field originates from."""
|
677
|
+
return self._origin
|
678
|
+
|
679
|
+
def set_origin(self, origin: Type[Any]) -> None:
|
680
|
+
"""Sets the origin (source class) of this field."""
|
681
|
+
self._origin = origin
|
682
|
+
|
664
683
|
def extend(self, base_field: 'Field') -> 'Field':
|
665
684
|
"""Extend current field based on a base field."""
|
666
685
|
self.key.extend(base_field.key)
|
@@ -677,9 +696,11 @@ class Field(object_utils.Formattable, object_utils.JSONConvertible):
|
|
677
696
|
self,
|
678
697
|
value: Any,
|
679
698
|
allow_partial: bool = False,
|
680
|
-
transform_fn: Optional[
|
681
|
-
[
|
682
|
-
|
699
|
+
transform_fn: Optional[
|
700
|
+
Callable[[utils.KeyPath, 'Field', Any], Any]
|
701
|
+
] = None,
|
702
|
+
root_path: Optional[utils.KeyPath] = None,
|
703
|
+
) -> Any:
|
683
704
|
"""Apply current field to a value, which validate and complete the value.
|
684
705
|
|
685
706
|
Args:
|
@@ -731,18 +752,19 @@ class Field(object_utils.Formattable, object_utils.JSONConvertible):
|
|
731
752
|
**kwargs,
|
732
753
|
) -> str:
|
733
754
|
"""Format this field into a string."""
|
734
|
-
return
|
755
|
+
return utils.kvlist_str(
|
735
756
|
[
|
736
757
|
('key', self._key, None),
|
737
758
|
('value', self._value, None),
|
738
759
|
('description', self._description, None),
|
739
760
|
('metadata', self._metadata, {}),
|
761
|
+
('origin', self._origin, None),
|
740
762
|
],
|
741
763
|
label=self.__class__.__name__,
|
742
764
|
compact=compact,
|
743
765
|
verbose=verbose,
|
744
766
|
root_indent=root_indent,
|
745
|
-
**kwargs
|
767
|
+
**kwargs,
|
746
768
|
)
|
747
769
|
|
748
770
|
def to_json(self, **kwargs: Any) -> Dict[str, Any]:
|
@@ -771,7 +793,7 @@ class Field(object_utils.Formattable, object_utils.JSONConvertible):
|
|
771
793
|
return not self.__eq__(other)
|
772
794
|
|
773
795
|
|
774
|
-
class Schema(
|
796
|
+
class Schema(utils.Formattable, utils.JSONConvertible):
|
775
797
|
"""Class that represents a schema.
|
776
798
|
|
777
799
|
PyGlove's runtime type system is based on the concept of ``Schema`` (
|
@@ -861,7 +883,9 @@ class Schema(object_utils.Formattable, object_utils.JSONConvertible):
|
|
861
883
|
description: Optional[str] = None,
|
862
884
|
*,
|
863
885
|
allow_nonconst_keys: bool = False,
|
864
|
-
metadata: Optional[Dict[str, Any]] = None
|
886
|
+
metadata: Optional[Dict[str, Any]] = None,
|
887
|
+
for_cls: Optional[Type[Any]] = None,
|
888
|
+
):
|
865
889
|
"""Constructor.
|
866
890
|
|
867
891
|
Args:
|
@@ -874,6 +898,7 @@ class Schema(object_utils.Formattable, object_utils.JSONConvertible):
|
|
874
898
|
description: Optional str as the description for the schema.
|
875
899
|
allow_nonconst_keys: Whether immediate fields can use non-const keys.
|
876
900
|
metadata: Optional dict of user objects as schema-level metadata.
|
901
|
+
for_cls: Optional class that this schema applies to.
|
877
902
|
|
878
903
|
Raises:
|
879
904
|
TypeError: Argument `fields` is not a list.
|
@@ -895,6 +920,11 @@ class Schema(object_utils.Formattable, object_utils.JSONConvertible):
|
|
895
920
|
self._description = description
|
896
921
|
self._metadata = metadata or {}
|
897
922
|
|
923
|
+
if for_cls is not None:
|
924
|
+
for f in fields:
|
925
|
+
if f.origin is None:
|
926
|
+
f.set_origin(for_cls)
|
927
|
+
|
898
928
|
self._dynamic_field = None
|
899
929
|
for f in fields:
|
900
930
|
if not f.key.is_const:
|
@@ -930,21 +960,27 @@ class Schema(object_utils.Formattable, object_utils.JSONConvertible):
|
|
930
960
|
Returns:
|
931
961
|
The merged schema.
|
932
962
|
"""
|
933
|
-
|
934
|
-
fields = []
|
963
|
+
fields = {}
|
935
964
|
kw_field = None
|
936
965
|
for schema in schema_list:
|
937
966
|
for key, field in schema.fields.items():
|
938
|
-
if key.is_const
|
939
|
-
fields
|
940
|
-
|
941
|
-
|
967
|
+
if key.is_const:
|
968
|
+
if key not in fields or (
|
969
|
+
field.origin is not None
|
970
|
+
and fields[key].origin is not None
|
971
|
+
and issubclass(field.origin, fields[key].origin)
|
972
|
+
):
|
973
|
+
fields[key] = field
|
974
|
+
elif kw_field is None:
|
942
975
|
kw_field = field
|
943
976
|
|
944
977
|
if kw_field is not None:
|
945
|
-
fields.
|
978
|
+
fields[kw_field.key] = kw_field
|
946
979
|
return Schema(
|
947
|
-
fields,
|
980
|
+
list(fields.values()),
|
981
|
+
name=name,
|
982
|
+
description=description,
|
983
|
+
allow_nonconst_keys=True
|
948
984
|
)
|
949
985
|
|
950
986
|
def extend(self, base: 'Schema') -> 'Schema':
|
@@ -955,13 +991,12 @@ class Schema(object_utils.Formattable, object_utils.JSONConvertible):
|
|
955
991
|
parent_field: Field,
|
956
992
|
child_field: Field) -> Field:
|
957
993
|
"""Merge function on field with the same key."""
|
958
|
-
if parent_field !=
|
959
|
-
if
|
994
|
+
if parent_field != utils.MISSING_VALUE:
|
995
|
+
if utils.MISSING_VALUE == child_field:
|
960
996
|
if (not self._allow_nonconst_keys and not parent_field.key.is_const):
|
961
|
-
hints =
|
962
|
-
('base', base.name, None),
|
963
|
-
|
964
|
-
])
|
997
|
+
hints = utils.kvlist_str(
|
998
|
+
[('base', base.name, None), ('path', path, None)]
|
999
|
+
)
|
965
1000
|
raise ValueError(
|
966
1001
|
f'Non-const key {parent_field.key} is not allowed to be '
|
967
1002
|
f'added to the schema. ({hints})')
|
@@ -970,16 +1005,15 @@ class Schema(object_utils.Formattable, object_utils.JSONConvertible):
|
|
970
1005
|
try:
|
971
1006
|
child_field.extend(parent_field)
|
972
1007
|
except Exception as e: # pylint: disable=broad-except
|
973
|
-
hints =
|
974
|
-
('base', base.name, None),
|
975
|
-
|
976
|
-
])
|
1008
|
+
hints = utils.kvlist_str(
|
1009
|
+
[('base', base.name, None), ('path', path, None)]
|
1010
|
+
)
|
977
1011
|
raise e.__class__(f'{e} ({hints})').with_traceback(
|
978
1012
|
sys.exc_info()[2])
|
979
1013
|
return child_field
|
980
1014
|
|
981
|
-
self._fields =
|
982
|
-
self._metadata =
|
1015
|
+
self._fields = utils.merge([base.fields, self.fields], _merge_field)
|
1016
|
+
self._metadata = utils.merge([base.metadata, self.metadata])
|
983
1017
|
|
984
1018
|
# Inherit dynamic field from base if it's not present in the child.
|
985
1019
|
if self._dynamic_field is None:
|
@@ -1102,8 +1136,8 @@ class Schema(object_utils.Formattable, object_utils.JSONConvertible):
|
|
1102
1136
|
dict_obj: Dict[str, Any],
|
1103
1137
|
allow_partial: bool = False,
|
1104
1138
|
child_transform: Optional[Callable[
|
1105
|
-
[
|
1106
|
-
root_path: Optional[
|
1139
|
+
[utils.KeyPath, Field, Any], Any]] = None,
|
1140
|
+
root_path: Optional[utils.KeyPath] = None,
|
1107
1141
|
) -> Dict[str, Any]: # pyformat: disable
|
1108
1142
|
# pyformat: disable
|
1109
1143
|
"""Apply this schema to a dict object, validate and transform it.
|
@@ -1160,18 +1194,18 @@ class Schema(object_utils.Formattable, object_utils.JSONConvertible):
|
|
1160
1194
|
keys.append(str(key_spec))
|
1161
1195
|
for key in keys:
|
1162
1196
|
if dict_obj:
|
1163
|
-
value = dict_obj.get(key,
|
1197
|
+
value = dict_obj.get(key, utils.MISSING_VALUE)
|
1164
1198
|
else:
|
1165
|
-
value =
|
1199
|
+
value = utils.MISSING_VALUE
|
1166
1200
|
# NOTE(daiyip): field.default_value may be MISSING_VALUE too
|
1167
1201
|
# or partial.
|
1168
|
-
if
|
1202
|
+
if utils.MISSING_VALUE == value:
|
1169
1203
|
value = copy.deepcopy(field.default_value)
|
1170
1204
|
new_value = field.apply(
|
1171
1205
|
value,
|
1172
1206
|
allow_partial=allow_partial,
|
1173
1207
|
transform_fn=child_transform,
|
1174
|
-
root_path=
|
1208
|
+
root_path=utils.KeyPath(key, root_path),
|
1175
1209
|
)
|
1176
1210
|
|
1177
1211
|
# NOTE(daiyip): `pg.Dict.__getitem__`` has special logics in handling
|
@@ -1185,10 +1219,12 @@ class Schema(object_utils.Formattable, object_utils.JSONConvertible):
|
|
1185
1219
|
dict_obj[key] = new_value
|
1186
1220
|
return dict_obj
|
1187
1221
|
|
1188
|
-
def validate(
|
1189
|
-
|
1190
|
-
|
1191
|
-
|
1222
|
+
def validate(
|
1223
|
+
self,
|
1224
|
+
dict_obj: Dict[str, Any],
|
1225
|
+
allow_partial: bool = False,
|
1226
|
+
root_path: Optional[utils.KeyPath] = None,
|
1227
|
+
) -> None:
|
1192
1228
|
"""Validates whether dict object is conformed with the schema."""
|
1193
1229
|
self.apply(
|
1194
1230
|
copy.deepcopy(dict_obj),
|
@@ -1253,12 +1289,12 @@ class Schema(object_utils.Formattable, object_utils.JSONConvertible):
|
|
1253
1289
|
root_indent: int = 0,
|
1254
1290
|
*,
|
1255
1291
|
cls_name: Optional[str] = None,
|
1256
|
-
bracket_type:
|
1292
|
+
bracket_type: utils.BracketType = utils.BracketType.ROUND,
|
1257
1293
|
fields_only: bool = False,
|
1258
1294
|
**kwargs,
|
1259
1295
|
) -> str:
|
1260
1296
|
"""Format current Schema into nicely printed string."""
|
1261
|
-
return
|
1297
|
+
return utils.kvlist_str(
|
1262
1298
|
[
|
1263
1299
|
('name', self.name, None),
|
1264
1300
|
('description', self.description, None),
|
@@ -1407,6 +1443,7 @@ def create_schema(
|
|
1407
1443
|
allow_nonconst_keys: bool = False,
|
1408
1444
|
metadata: Optional[Dict[str, Any]] = None,
|
1409
1445
|
description: Optional[str] = None,
|
1446
|
+
for_cls: Optional[Type[Any]] = None,
|
1410
1447
|
parent_module: Optional[types.ModuleType] = None
|
1411
1448
|
) -> Schema:
|
1412
1449
|
"""Creates ``Schema`` from a list of ``Field``s or equivalences.
|
@@ -1448,6 +1485,7 @@ def create_schema(
|
|
1448
1485
|
allow_nonconst_keys: Whether to allow non const keys in schema.
|
1449
1486
|
metadata: Optional dict of user objects as schema-level metadata.
|
1450
1487
|
description: Optional description of the schema.
|
1488
|
+
for_cls: (Optional) the class that this schema applies to.
|
1451
1489
|
parent_module: (Optional) parent module for defining this schema, which will
|
1452
1490
|
be used for forward reference lookup.
|
1453
1491
|
|
@@ -1471,6 +1509,7 @@ def create_schema(
|
|
1471
1509
|
allow_nonconst_keys=allow_nonconst_keys,
|
1472
1510
|
metadata=metadata,
|
1473
1511
|
description=description,
|
1512
|
+
for_cls=for_cls,
|
1474
1513
|
)
|
1475
1514
|
|
1476
1515
|
|