pyglove 0.4.5.dev202501050808__py3-none-any.whl → 0.4.5.dev202501060809__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 +24 -21
- 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 +2 -4
- 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/base.py +167 -131
- pyglove/core/symbolic/base_test.py +17 -19
- pyglove/core/symbolic/boilerplate.py +4 -5
- pyglove/core/symbolic/class_wrapper.py +9 -9
- pyglove/core/symbolic/compounding.py +2 -2
- pyglove/core/symbolic/compounding_test.py +2 -4
- pyglove/core/symbolic/dict.py +70 -54
- pyglove/core/symbolic/dict_test.py +117 -100
- 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 +42 -40
- pyglove/core/symbolic/object_test.py +95 -88
- pyglove/core/symbolic/origin.py +5 -7
- pyglove/core/symbolic/pure_symbolic.py +4 -3
- pyglove/core/symbolic/ref.py +12 -8
- pyglove/core/tuning/local_backend.py +2 -2
- pyglove/core/tuning/protocols.py +3 -3
- pyglove/core/typing/annotation_conversion.py +3 -3
- pyglove/core/typing/callable_ext.py +11 -13
- pyglove/core/typing/callable_signature.py +19 -18
- pyglove/core/typing/callable_signature_test.py +3 -5
- pyglove/core/typing/class_schema.py +48 -44
- pyglove/core/typing/class_schema_test.py +3 -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 +210 -141
- pyglove/core/typing/value_specs_test.py +12 -13
- pyglove/core/utils/__init__.py +159 -0
- pyglove/core/{object_utils → utils}/common_traits_test.py +1 -3
- 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_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/{object_utils → utils}/thread_local_test.py +1 -3
- pyglove/core/{object_utils → utils}/timing.py +3 -3
- pyglove/core/{object_utils → utils}/timing_test.py +2 -3
- 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 +14 -15
- pyglove/core/views/html/controls/base.py +5 -5
- pyglove/core/views/html/controls/progress_bar.py +3 -5
- pyglove/core/views/html/tree_view.py +37 -35
- {pyglove-0.4.5.dev202501050808.dist-info → pyglove-0.4.5.dev202501060809.dist-info}/METADATA +1 -1
- {pyglove-0.4.5.dev202501050808.dist-info → pyglove-0.4.5.dev202501060809.dist-info}/RECORD +90 -90
- {pyglove-0.4.5.dev202501050808.dist-info → pyglove-0.4.5.dev202501060809.dist-info}/WHEEL +1 -1
- pyglove/core/object_utils/__init__.py +0 -161
- /pyglove/core/{object_utils → utils}/common_traits.py +0 -0
- /pyglove/core/{object_utils → utils}/docstr_utils.py +0 -0
- /pyglove/core/{object_utils → utils}/json_conversion.py +0 -0
- /pyglove/core/{object_utils → utils}/thread_local.py +0 -0
- {pyglove-0.4.5.dev202501050808.dist-info → pyglove-0.4.5.dev202501060809.dist-info}/LICENSE +0 -0
- {pyglove-0.4.5.dev202501050808.dist-info → pyglove-0.4.5.dev202501060809.dist-info}/top_level.txt +0 -0
pyglove/core/symbolic/ref.py
CHANGED
@@ -16,8 +16,8 @@
|
|
16
16
|
import functools
|
17
17
|
import typing
|
18
18
|
from typing import Any, Callable, List, Optional, Tuple, Type
|
19
|
-
from pyglove.core import object_utils
|
20
19
|
from pyglove.core import typing as pg_typing
|
20
|
+
from pyglove.core import utils
|
21
21
|
from pyglove.core.symbolic import base
|
22
22
|
from pyglove.core.symbolic import object as pg_object
|
23
23
|
from pyglove.core.views.html import tree_view
|
@@ -100,7 +100,7 @@ class Ref(
|
|
100
100
|
return object.__new__(cls)
|
101
101
|
return value
|
102
102
|
|
103
|
-
@
|
103
|
+
@utils.explicit_method_override
|
104
104
|
def __init__(self, value: Any, **kwargs) -> None:
|
105
105
|
super().__init__(**kwargs)
|
106
106
|
if isinstance(value, Ref):
|
@@ -127,12 +127,13 @@ class Ref(
|
|
127
127
|
|
128
128
|
def custom_apply(
|
129
129
|
self,
|
130
|
-
path:
|
130
|
+
path: utils.KeyPath,
|
131
131
|
value_spec: pg_typing.ValueSpec,
|
132
132
|
allow_partial: bool = False,
|
133
|
-
child_transform: Optional[
|
134
|
-
[
|
135
|
-
|
133
|
+
child_transform: Optional[
|
134
|
+
Callable[[utils.KeyPath, pg_typing.Field, Any], Any]
|
135
|
+
] = None,
|
136
|
+
) -> Tuple[bool, Any]:
|
136
137
|
"""Validate candidates during value_spec binding time."""
|
137
138
|
del child_transform
|
138
139
|
# Check if the field being assigned could accept the referenced value.
|
@@ -166,9 +167,12 @@ class Ref(
|
|
166
167
|
root_indent: int = 0,
|
167
168
|
**kwargs: Any,
|
168
169
|
) -> str:
|
169
|
-
value_str =
|
170
|
+
value_str = utils.format(
|
170
171
|
self._value,
|
171
|
-
compact=compact,
|
172
|
+
compact=compact,
|
173
|
+
verbose=verbose,
|
174
|
+
root_indent=root_indent + 1,
|
175
|
+
)
|
172
176
|
if compact:
|
173
177
|
return f'{self.__class__.__name__}({value_str})'
|
174
178
|
else:
|
@@ -21,8 +21,8 @@ from typing import Any, Callable, Dict, List, Optional, Sequence
|
|
21
21
|
|
22
22
|
from pyglove.core import geno
|
23
23
|
from pyglove.core import logging
|
24
|
-
from pyglove.core import object_utils
|
25
24
|
from pyglove.core import symbolic
|
25
|
+
from pyglove.core import utils
|
26
26
|
from pyglove.core.tuning import backend
|
27
27
|
from pyglove.core.tuning.early_stopping import EarlyStoppingPolicy
|
28
28
|
from pyglove.core.tuning.protocols import Feedback
|
@@ -278,7 +278,7 @@ class _InMemoryResult(Result):
|
|
278
278
|
('step', self._best_trial.final_measurement.step),
|
279
279
|
('dna', self._best_trial.dna.format(compact=True))
|
280
280
|
])
|
281
|
-
return
|
281
|
+
return utils.format(json_repr, compact, False, root_indent, **kwargs)
|
282
282
|
|
283
283
|
|
284
284
|
@backend.add_backend('in-memory')
|
pyglove/core/tuning/protocols.py
CHANGED
@@ -22,9 +22,9 @@ from typing import Any, Dict, List, Optional, Sequence, Tuple, Type, Union
|
|
22
22
|
|
23
23
|
from pyglove.core import geno
|
24
24
|
from pyglove.core import logging
|
25
|
-
from pyglove.core import object_utils
|
26
25
|
from pyglove.core import symbolic
|
27
26
|
from pyglove.core import typing as pg_typing
|
27
|
+
from pyglove.core import utils
|
28
28
|
|
29
29
|
|
30
30
|
class _DataEntity(symbolic.Object):
|
@@ -116,7 +116,7 @@ class Trial(_DataEntity):
|
|
116
116
|
return tuple(metric_values) if len(metric_values) > 1 else metric_values[0]
|
117
117
|
|
118
118
|
|
119
|
-
class Result(
|
119
|
+
class Result(utils.Formattable):
|
120
120
|
"""Interface for tuning result."""
|
121
121
|
|
122
122
|
@property
|
@@ -416,7 +416,7 @@ class Feedback(metaclass=abc.ABCMeta):
|
|
416
416
|
error_stack = traceback.format_exc()
|
417
417
|
logging.warning('Skipping trial on unhandled exception: %s', error_stack)
|
418
418
|
self.skip(error_stack)
|
419
|
-
return
|
419
|
+
return utils.catch_errors(exceptions, skip_on_exception)
|
420
420
|
|
421
421
|
@contextlib.contextmanager
|
422
422
|
def ignore_race_condition(self):
|
@@ -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:
|
@@ -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,
|
@@ -22,7 +22,7 @@ import typing
|
|
22
22
|
from typing import Any, Callable, Dict, List, Optional, Union
|
23
23
|
|
24
24
|
from pyglove.core import coding
|
25
|
-
from pyglove.core import
|
25
|
+
from pyglove.core import utils
|
26
26
|
from pyglove.core.typing import class_schema
|
27
27
|
from pyglove.core.typing import key_specs as ks
|
28
28
|
|
@@ -141,7 +141,7 @@ class CallableType(enum.Enum):
|
|
141
141
|
METHOD = 2
|
142
142
|
|
143
143
|
|
144
|
-
class Signature(
|
144
|
+
class Signature(utils.Formattable):
|
145
145
|
"""PY3 function signature."""
|
146
146
|
|
147
147
|
def __init__(self,
|
@@ -257,7 +257,7 @@ class Signature(object_utils.Formattable):
|
|
257
257
|
**kwargs,
|
258
258
|
) -> str:
|
259
259
|
"""Format current object."""
|
260
|
-
return
|
260
|
+
return utils.kvlist_str(
|
261
261
|
[
|
262
262
|
('', self.id, ''),
|
263
263
|
('args', self.args, []),
|
@@ -271,7 +271,7 @@ class Signature(object_utils.Formattable):
|
|
271
271
|
compact=compact,
|
272
272
|
verbose=verbose,
|
273
273
|
root_indent=root_indent,
|
274
|
-
**kwargs
|
274
|
+
**kwargs,
|
275
275
|
)
|
276
276
|
|
277
277
|
def annotate(
|
@@ -288,7 +288,7 @@ class Signature(object_utils.Formattable):
|
|
288
288
|
return_value = class_schema.ValueSpec.from_annotation(
|
289
289
|
return_value, auto_typing=True
|
290
290
|
)
|
291
|
-
if
|
291
|
+
if utils.MISSING_VALUE != return_value.default:
|
292
292
|
raise ValueError('return value spec should not have default value.')
|
293
293
|
self.return_value = return_value
|
294
294
|
|
@@ -337,12 +337,12 @@ class Signature(object_utils.Formattable):
|
|
337
337
|
or field.value.default is None
|
338
338
|
):
|
339
339
|
field.value.set_default(
|
340
|
-
arg.value_spec.default, root_path=
|
340
|
+
arg.value_spec.default, root_path=utils.KeyPath(arg.name)
|
341
341
|
)
|
342
342
|
if arg.value_spec.default != field.value.default:
|
343
343
|
if field.value.is_noneable and not arg.value_spec.has_default:
|
344
|
-
|
345
|
-
field.value.set_default(
|
344
|
+
# Special handling noneable which always comes with a default.
|
345
|
+
field.value.set_default(utils.MISSING_VALUE)
|
346
346
|
elif not (
|
347
347
|
# Special handling Dict type which always has default.
|
348
348
|
isinstance(field.value, class_schema.ValueSpec.DictType)
|
@@ -549,7 +549,7 @@ class Signature(object_utils.Formattable):
|
|
549
549
|
if not callable(callable_object):
|
550
550
|
raise TypeError(f'{callable_object!r} is not callable.')
|
551
551
|
|
552
|
-
if isinstance(callable_object,
|
552
|
+
if isinstance(callable_object, utils.Functor):
|
553
553
|
assert callable_object.__signature__ is not None
|
554
554
|
return callable_object.__signature__
|
555
555
|
|
@@ -566,15 +566,15 @@ class Signature(object_utils.Formattable):
|
|
566
566
|
description = None
|
567
567
|
args_doc = {}
|
568
568
|
if func.__doc__:
|
569
|
-
cls_doc =
|
569
|
+
cls_doc = utils.DocStr.parse(func.__doc__)
|
570
570
|
description = cls_doc.short_description
|
571
571
|
args_doc.update(cls_doc.args)
|
572
572
|
|
573
573
|
if func.__init__.__doc__:
|
574
|
-
init_doc =
|
574
|
+
init_doc = utils.DocStr.parse(func.__init__.__doc__)
|
575
575
|
args_doc.update(init_doc.args)
|
576
|
-
docstr =
|
577
|
-
|
576
|
+
docstr = utils.DocStr(
|
577
|
+
utils.DocStrStyle.GOOGLE,
|
578
578
|
short_description=description,
|
579
579
|
long_description=None,
|
580
580
|
examples=[],
|
@@ -593,7 +593,7 @@ class Signature(object_utils.Formattable):
|
|
593
593
|
else CallableType.FUNCTION
|
594
594
|
)
|
595
595
|
if auto_doc:
|
596
|
-
docstr =
|
596
|
+
docstr = utils.docstr(func)
|
597
597
|
sig = inspect.signature(func)
|
598
598
|
|
599
599
|
module_name = getattr(func, '__module__', None)
|
@@ -617,7 +617,7 @@ class Signature(object_utils.Formattable):
|
|
617
617
|
module_name: Optional[str] = None,
|
618
618
|
qualname: Optional[str] = None,
|
619
619
|
auto_typing: bool = False,
|
620
|
-
docstr: Union[str,
|
620
|
+
docstr: Union[str, utils.DocStr, None] = None,
|
621
621
|
parent_module: Optional[types.ModuleType] = None,
|
622
622
|
) -> 'Signature':
|
623
623
|
"""Returns PyGlove signature from Python signature.
|
@@ -644,7 +644,7 @@ class Signature(object_utils.Formattable):
|
|
644
644
|
varkw = None
|
645
645
|
|
646
646
|
if isinstance(docstr, str):
|
647
|
-
docstr =
|
647
|
+
docstr = utils.DocStr.parse(docstr)
|
648
648
|
|
649
649
|
def make_arg_spec(param: inspect.Parameter) -> Argument:
|
650
650
|
"""Makes argument spec from inspect.Parameter."""
|
@@ -708,7 +708,7 @@ class Signature(object_utils.Formattable):
|
|
708
708
|
force_missing_as_default: bool = False,
|
709
709
|
arg_prefix: str = ''):
|
710
710
|
s = [f'{arg_prefix}{arg_name}']
|
711
|
-
if arg_spec.annotation !=
|
711
|
+
if arg_spec.annotation != utils.MISSING_VALUE:
|
712
712
|
s.append(f': _annotation_{arg_name}')
|
713
713
|
exec_locals[f'_annotation_{arg_name}'] = arg_spec.annotation
|
714
714
|
if not arg_prefix and (force_missing_as_default or arg_spec.has_default):
|
@@ -761,7 +761,8 @@ class Signature(object_utils.Formattable):
|
|
761
761
|
exec_globals=exec_globals,
|
762
762
|
exec_locals=exec_locals,
|
763
763
|
return_type=getattr(
|
764
|
-
self.return_value, 'annotation', coding.NO_TYPE_ANNOTATION
|
764
|
+
self.return_value, 'annotation', coding.NO_TYPE_ANNOTATION
|
765
|
+
),
|
765
766
|
)
|
766
767
|
fn.__module__ = self.module_name
|
767
768
|
fn.__name__ = self.name
|
@@ -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
|
@@ -367,7 +367,7 @@ class ValueSpec(object_utils.Formattable, object_utils.JSONConvertible):
|
|
367
367
|
self,
|
368
368
|
default: Any,
|
369
369
|
use_default_apply: bool = True,
|
370
|
-
root_path: Optional[
|
370
|
+
root_path: Optional[utils.KeyPath] = None,
|
371
371
|
) -> 'ValueSpec':
|
372
372
|
"""Sets the default value and returns `self`.
|
373
373
|
|
@@ -398,13 +398,14 @@ class ValueSpec(object_utils.Formattable, object_utils.JSONConvertible):
|
|
398
398
|
@property
|
399
399
|
def has_default(self) -> bool:
|
400
400
|
"""Returns True if the default value is provided."""
|
401
|
-
return self.default !=
|
401
|
+
return self.default != utils.MISSING_VALUE
|
402
402
|
|
403
403
|
@abc.abstractmethod
|
404
404
|
def freeze(
|
405
405
|
self,
|
406
|
-
permanent_value: Any =
|
407
|
-
apply_before_use: bool = True
|
406
|
+
permanent_value: Any = utils.MISSING_VALUE,
|
407
|
+
apply_before_use: bool = True,
|
408
|
+
) -> 'ValueSpec':
|
408
409
|
"""Sets the default value using a permanent value and freezes current spec.
|
409
410
|
|
410
411
|
A frozen value spec will not accept any value that is not the default
|
@@ -471,10 +472,11 @@ class ValueSpec(object_utils.Formattable, object_utils.JSONConvertible):
|
|
471
472
|
self,
|
472
473
|
value: Any,
|
473
474
|
allow_partial: bool = False,
|
474
|
-
child_transform: Optional[
|
475
|
-
[
|
476
|
-
|
477
|
-
|
475
|
+
child_transform: Optional[
|
476
|
+
Callable[[utils.KeyPath, 'Field', Any], Any]
|
477
|
+
] = None,
|
478
|
+
root_path: Optional[utils.KeyPath] = None,
|
479
|
+
) -> Any:
|
478
480
|
"""Validates, completes and transforms the input value.
|
479
481
|
|
480
482
|
Here is the procedure of ``apply``::
|
@@ -551,7 +553,7 @@ class ValueSpec(object_utils.Formattable, object_utils.JSONConvertible):
|
|
551
553
|
assert False, 'Overridden in `annotation_conversion.py`.'
|
552
554
|
|
553
555
|
|
554
|
-
class Field(
|
556
|
+
class Field(utils.Formattable, utils.JSONConvertible):
|
555
557
|
"""Class that represents the definition of one or a group of attributes.
|
556
558
|
|
557
559
|
``Field`` is held by a :class:`pyglove.Schema` object for defining the
|
@@ -681,9 +683,11 @@ class Field(object_utils.Formattable, object_utils.JSONConvertible):
|
|
681
683
|
self,
|
682
684
|
value: Any,
|
683
685
|
allow_partial: bool = False,
|
684
|
-
transform_fn: Optional[
|
685
|
-
[
|
686
|
-
|
686
|
+
transform_fn: Optional[
|
687
|
+
Callable[[utils.KeyPath, 'Field', Any], Any]
|
688
|
+
] = None,
|
689
|
+
root_path: Optional[utils.KeyPath] = None,
|
690
|
+
) -> Any:
|
687
691
|
"""Apply current field to a value, which validate and complete the value.
|
688
692
|
|
689
693
|
Args:
|
@@ -735,7 +739,7 @@ class Field(object_utils.Formattable, object_utils.JSONConvertible):
|
|
735
739
|
**kwargs,
|
736
740
|
) -> str:
|
737
741
|
"""Format this field into a string."""
|
738
|
-
return
|
742
|
+
return utils.kvlist_str(
|
739
743
|
[
|
740
744
|
('key', self._key, None),
|
741
745
|
('value', self._value, None),
|
@@ -746,7 +750,7 @@ class Field(object_utils.Formattable, object_utils.JSONConvertible):
|
|
746
750
|
compact=compact,
|
747
751
|
verbose=verbose,
|
748
752
|
root_indent=root_indent,
|
749
|
-
**kwargs
|
753
|
+
**kwargs,
|
750
754
|
)
|
751
755
|
|
752
756
|
def to_json(self, **kwargs: Any) -> Dict[str, Any]:
|
@@ -775,7 +779,7 @@ class Field(object_utils.Formattable, object_utils.JSONConvertible):
|
|
775
779
|
return not self.__eq__(other)
|
776
780
|
|
777
781
|
|
778
|
-
class Schema(
|
782
|
+
class Schema(utils.Formattable, utils.JSONConvertible):
|
779
783
|
"""Class that represents a schema.
|
780
784
|
|
781
785
|
PyGlove's runtime type system is based on the concept of ``Schema`` (
|
@@ -959,13 +963,12 @@ class Schema(object_utils.Formattable, object_utils.JSONConvertible):
|
|
959
963
|
parent_field: Field,
|
960
964
|
child_field: Field) -> Field:
|
961
965
|
"""Merge function on field with the same key."""
|
962
|
-
if parent_field !=
|
963
|
-
if
|
966
|
+
if parent_field != utils.MISSING_VALUE:
|
967
|
+
if utils.MISSING_VALUE == child_field:
|
964
968
|
if (not self._allow_nonconst_keys and not parent_field.key.is_const):
|
965
|
-
hints =
|
966
|
-
('base', base.name, None),
|
967
|
-
|
968
|
-
])
|
969
|
+
hints = utils.kvlist_str(
|
970
|
+
[('base', base.name, None), ('path', path, None)]
|
971
|
+
)
|
969
972
|
raise ValueError(
|
970
973
|
f'Non-const key {parent_field.key} is not allowed to be '
|
971
974
|
f'added to the schema. ({hints})')
|
@@ -974,16 +977,15 @@ class Schema(object_utils.Formattable, object_utils.JSONConvertible):
|
|
974
977
|
try:
|
975
978
|
child_field.extend(parent_field)
|
976
979
|
except Exception as e: # pylint: disable=broad-except
|
977
|
-
hints =
|
978
|
-
('base', base.name, None),
|
979
|
-
|
980
|
-
])
|
980
|
+
hints = utils.kvlist_str(
|
981
|
+
[('base', base.name, None), ('path', path, None)]
|
982
|
+
)
|
981
983
|
raise e.__class__(f'{e} ({hints})').with_traceback(
|
982
984
|
sys.exc_info()[2])
|
983
985
|
return child_field
|
984
986
|
|
985
|
-
self._fields =
|
986
|
-
self._metadata =
|
987
|
+
self._fields = utils.merge([base.fields, self.fields], _merge_field)
|
988
|
+
self._metadata = utils.merge([base.metadata, self.metadata])
|
987
989
|
|
988
990
|
# Inherit dynamic field from base if it's not present in the child.
|
989
991
|
if self._dynamic_field is None:
|
@@ -1106,8 +1108,8 @@ class Schema(object_utils.Formattable, object_utils.JSONConvertible):
|
|
1106
1108
|
dict_obj: Dict[str, Any],
|
1107
1109
|
allow_partial: bool = False,
|
1108
1110
|
child_transform: Optional[Callable[
|
1109
|
-
[
|
1110
|
-
root_path: Optional[
|
1111
|
+
[utils.KeyPath, Field, Any], Any]] = None,
|
1112
|
+
root_path: Optional[utils.KeyPath] = None,
|
1111
1113
|
) -> Dict[str, Any]: # pyformat: disable
|
1112
1114
|
# pyformat: disable
|
1113
1115
|
"""Apply this schema to a dict object, validate and transform it.
|
@@ -1164,18 +1166,18 @@ class Schema(object_utils.Formattable, object_utils.JSONConvertible):
|
|
1164
1166
|
keys.append(str(key_spec))
|
1165
1167
|
for key in keys:
|
1166
1168
|
if dict_obj:
|
1167
|
-
value = dict_obj.get(key,
|
1169
|
+
value = dict_obj.get(key, utils.MISSING_VALUE)
|
1168
1170
|
else:
|
1169
|
-
value =
|
1171
|
+
value = utils.MISSING_VALUE
|
1170
1172
|
# NOTE(daiyip): field.default_value may be MISSING_VALUE too
|
1171
1173
|
# or partial.
|
1172
|
-
if
|
1174
|
+
if utils.MISSING_VALUE == value:
|
1173
1175
|
value = copy.deepcopy(field.default_value)
|
1174
1176
|
new_value = field.apply(
|
1175
1177
|
value,
|
1176
1178
|
allow_partial=allow_partial,
|
1177
1179
|
transform_fn=child_transform,
|
1178
|
-
root_path=
|
1180
|
+
root_path=utils.KeyPath(key, root_path),
|
1179
1181
|
)
|
1180
1182
|
|
1181
1183
|
# NOTE(daiyip): `pg.Dict.__getitem__`` has special logics in handling
|
@@ -1189,10 +1191,12 @@ class Schema(object_utils.Formattable, object_utils.JSONConvertible):
|
|
1189
1191
|
dict_obj[key] = new_value
|
1190
1192
|
return dict_obj
|
1191
1193
|
|
1192
|
-
def validate(
|
1193
|
-
|
1194
|
-
|
1195
|
-
|
1194
|
+
def validate(
|
1195
|
+
self,
|
1196
|
+
dict_obj: Dict[str, Any],
|
1197
|
+
allow_partial: bool = False,
|
1198
|
+
root_path: Optional[utils.KeyPath] = None,
|
1199
|
+
) -> None:
|
1196
1200
|
"""Validates whether dict object is conformed with the schema."""
|
1197
1201
|
self.apply(
|
1198
1202
|
copy.deepcopy(dict_obj),
|
@@ -1257,12 +1261,12 @@ class Schema(object_utils.Formattable, object_utils.JSONConvertible):
|
|
1257
1261
|
root_indent: int = 0,
|
1258
1262
|
*,
|
1259
1263
|
cls_name: Optional[str] = None,
|
1260
|
-
bracket_type:
|
1264
|
+
bracket_type: utils.BracketType = utils.BracketType.ROUND,
|
1261
1265
|
fields_only: bool = False,
|
1262
1266
|
**kwargs,
|
1263
1267
|
) -> str:
|
1264
1268
|
"""Format current Schema into nicely printed string."""
|
1265
|
-
return
|
1269
|
+
return utils.kvlist_str(
|
1266
1270
|
[
|
1267
1271
|
('name', self.name, None),
|
1268
1272
|
('description', self.description, None),
|
@@ -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.class_schema."""
|
15
|
-
|
16
14
|
import copy
|
17
15
|
import inspect
|
18
16
|
import sys
|
19
17
|
from typing import Optional, Union, 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 class_schema
|
25
23
|
from pyglove.core.typing import custom_typing
|
@@ -204,7 +202,7 @@ class FieldTest(unittest.TestCase):
|
|
204
202
|
|
205
203
|
def test_json_conversion(self):
|
206
204
|
def assert_json_conversion(f):
|
207
|
-
self.assertEqual(
|
205
|
+
self.assertEqual(utils.from_json(f.to_json()), f)
|
208
206
|
|
209
207
|
assert_json_conversion(Field('a', vs.Int()))
|
210
208
|
assert_json_conversion(Field('a', vs.Int(), 'description'))
|
@@ -822,7 +820,7 @@ class SchemaTest(unittest.TestCase):
|
|
822
820
|
schema = self._create_test_schema()
|
823
821
|
schema.set_description('Foo')
|
824
822
|
schema.set_name('Bar')
|
825
|
-
schema_copy =
|
823
|
+
schema_copy = utils.from_json(schema.to_json())
|
826
824
|
|
827
825
|
# This compares fields only
|
828
826
|
self.assertEqual(schema_copy, schema)
|
@@ -16,7 +16,7 @@
|
|
16
16
|
import abc
|
17
17
|
from typing import Any, Callable, Optional, Tuple
|
18
18
|
|
19
|
-
from pyglove.core import
|
19
|
+
from pyglove.core import utils
|
20
20
|
from pyglove.core.typing import class_schema
|
21
21
|
|
22
22
|
|
@@ -34,11 +34,12 @@ class CustomTyping(metaclass=abc.ABCMeta):
|
|
34
34
|
@abc.abstractmethod
|
35
35
|
def custom_apply(
|
36
36
|
self,
|
37
|
-
path:
|
37
|
+
path: utils.KeyPath,
|
38
38
|
value_spec: class_schema.ValueSpec,
|
39
39
|
allow_partial: bool,
|
40
|
-
child_transform: Optional[
|
41
|
-
[
|
40
|
+
child_transform: Optional[
|
41
|
+
Callable[[utils.KeyPath, class_schema.Field, Any], Any]
|
42
|
+
] = None,
|
42
43
|
) -> Tuple[bool, Any]:
|
43
44
|
"""Custom apply on a value based on its original value spec.
|
44
45
|
|