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
@@ -22,7 +22,7 @@ import re
|
|
22
22
|
import sys
|
23
23
|
import typing
|
24
24
|
import __main__
|
25
|
-
from pyglove.core import
|
25
|
+
from pyglove.core import utils
|
26
26
|
from pyglove.core.typing import callable_signature
|
27
27
|
from pyglove.core.typing import class_schema
|
28
28
|
from pyglove.core.typing import inspect as pg_inspect
|
@@ -35,7 +35,18 @@ from pyglove.core.typing.class_schema import ValueSpec
|
|
35
35
|
from pyglove.core.typing.custom_typing import CustomTyping
|
36
36
|
|
37
37
|
|
38
|
-
MISSING_VALUE =
|
38
|
+
MISSING_VALUE = utils.MISSING_VALUE
|
39
|
+
|
40
|
+
|
41
|
+
class _FrozenValuePlaceholder(CustomTyping):
|
42
|
+
"""Placeholder for to-be-assigned frozen value."""
|
43
|
+
|
44
|
+
def custom_apply(self, *args, **kwargs) -> typing.Tuple[bool, typing.Any]:
|
45
|
+
return (False, self)
|
46
|
+
|
47
|
+
|
48
|
+
_FROZEN_VALUE_PLACEHOLDER = _FrozenValuePlaceholder()
|
49
|
+
|
39
50
|
|
40
51
|
# Type alias for ValueSpec object or Python annotation that could be converted
|
41
52
|
# to ValueSpec via `pg.typing.ValueSpec.from_annotation()`. This type alias is
|
@@ -158,7 +169,7 @@ class ValueSpecBase(ValueSpec):
|
|
158
169
|
self,
|
159
170
|
default: typing.Any,
|
160
171
|
use_default_apply: bool = True,
|
161
|
-
root_path: typing.Optional[
|
172
|
+
root_path: typing.Optional[utils.KeyPath] = None,
|
162
173
|
) -> ValueSpec:
|
163
174
|
"""Set default value and returns `self`."""
|
164
175
|
# NOTE(daiyip): Default can be schema.MissingValue types, all are
|
@@ -194,8 +205,18 @@ class ValueSpecBase(ValueSpec):
|
|
194
205
|
|
195
206
|
def extend(self, base: ValueSpec) -> ValueSpec:
|
196
207
|
"""Extend current value spec on top of a base spec."""
|
197
|
-
if base.frozen:
|
198
|
-
raise TypeError(f'
|
208
|
+
if base.frozen and (not self.frozen or self.default != base.default):
|
209
|
+
raise TypeError(f'{self!r} cannot extend a frozen value spec: {base!r}')
|
210
|
+
|
211
|
+
# Special handling for extending enum.
|
212
|
+
if self.frozen and isinstance(base, Enum):
|
213
|
+
if self.default in base.values:
|
214
|
+
return Enum(MISSING_VALUE, base.values).freeze(self.default)
|
215
|
+
else:
|
216
|
+
raise TypeError(
|
217
|
+
f'{self!r} cannot extend {base!r} with incompatible '
|
218
|
+
f'frozen value: {self.default!r} '
|
219
|
+
)
|
199
220
|
|
200
221
|
if self._transform is None:
|
201
222
|
self._transform = base.transform
|
@@ -225,14 +246,14 @@ class ValueSpecBase(ValueSpec):
|
|
225
246
|
self,
|
226
247
|
value: typing.Any,
|
227
248
|
allow_partial: bool = False,
|
228
|
-
child_transform: typing.Optional[
|
229
|
-
[
|
230
|
-
|
231
|
-
]
|
232
|
-
|
249
|
+
child_transform: typing.Optional[
|
250
|
+
typing.Callable[[utils.KeyPath, Field, typing.Any], typing.Any]
|
251
|
+
] = None,
|
252
|
+
root_path: typing.Optional[utils.KeyPath] = None,
|
253
|
+
) -> typing.Any: # pyformat: disable pylint: disable=line-too-long
|
233
254
|
"""Apply spec to validate and complete value."""
|
234
|
-
root_path = root_path or
|
235
|
-
if self.frozen:
|
255
|
+
root_path = root_path or utils.KeyPath()
|
256
|
+
if self.frozen and self.default is not _FROZEN_VALUE_PLACEHOLDER:
|
236
257
|
# Always return the default value if a field is frozen.
|
237
258
|
if MISSING_VALUE != value and self.default != value:
|
238
259
|
raise ValueError(
|
@@ -270,8 +291,8 @@ class ValueSpecBase(ValueSpec):
|
|
270
291
|
value = self._transform(value)
|
271
292
|
except Exception as e: # pylint: disable=broad-except
|
272
293
|
raise e.__class__(
|
273
|
-
|
274
|
-
|
294
|
+
utils.message_on_path(str(e), root_path)
|
295
|
+
).with_traceback(sys.exc_info()[2])
|
275
296
|
|
276
297
|
return self.skip_user_transform.apply(
|
277
298
|
value,
|
@@ -287,9 +308,12 @@ class ValueSpecBase(ValueSpec):
|
|
287
308
|
converter = type_conversion.get_converter(type(value), self.value_type)
|
288
309
|
if converter is None:
|
289
310
|
raise TypeError(
|
290
|
-
|
311
|
+
utils.message_on_path(
|
291
312
|
f'Expect {self.value_type} '
|
292
|
-
f'but encountered {type(value)!r}: {value}.',
|
313
|
+
f'but encountered {type(value)!r}: {value}.',
|
314
|
+
root_path,
|
315
|
+
)
|
316
|
+
)
|
293
317
|
value = converter(value)
|
294
318
|
|
295
319
|
# NOTE(daiyip): child nodes validation and transformation is done before
|
@@ -303,15 +327,18 @@ class ValueSpecBase(ValueSpec):
|
|
303
327
|
self._validate(root_path, value)
|
304
328
|
return value
|
305
329
|
|
306
|
-
def _validate(self, path:
|
330
|
+
def _validate(self, path: utils.KeyPath, value: typing.Any):
|
307
331
|
"""Validation on applied value. Child class can override."""
|
308
332
|
|
309
|
-
def _apply(
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
333
|
+
def _apply(
|
334
|
+
self,
|
335
|
+
value: typing.Any,
|
336
|
+
allow_partial: bool,
|
337
|
+
child_transform: typing.Callable[
|
338
|
+
[utils.KeyPath, Field, typing.Any], typing.Any
|
339
|
+
],
|
340
|
+
root_path: utils.KeyPath,
|
341
|
+
) -> typing.Any:
|
315
342
|
"""Customized apply so each subclass can override."""
|
316
343
|
del allow_partial
|
317
344
|
del child_transform
|
@@ -319,7 +346,7 @@ class ValueSpecBase(ValueSpec):
|
|
319
346
|
return value
|
320
347
|
|
321
348
|
def is_compatible(self, other: ValueSpec) -> bool:
|
322
|
-
"""Returns if current spec
|
349
|
+
"""Returns if current spec can receive all values from the other spec."""
|
323
350
|
if self is other:
|
324
351
|
return True
|
325
352
|
if not isinstance(other, self.__class__):
|
@@ -379,17 +406,17 @@ class ValueSpecBase(ValueSpec):
|
|
379
406
|
**kwargs
|
380
407
|
) -> str:
|
381
408
|
"""Format this object."""
|
382
|
-
return
|
409
|
+
return utils.kvlist_str(
|
383
410
|
[
|
384
411
|
('default', self._default, MISSING_VALUE),
|
385
412
|
('noneable', self._is_noneable, False),
|
386
|
-
('frozen', self._frozen, False)
|
413
|
+
('frozen', self._frozen, False),
|
387
414
|
],
|
388
415
|
label=self.__class__.__name__,
|
389
416
|
compact=compact,
|
390
417
|
verbose=verbose,
|
391
418
|
root_indent=root_indent,
|
392
|
-
**kwargs
|
419
|
+
**kwargs,
|
393
420
|
)
|
394
421
|
|
395
422
|
|
@@ -417,6 +444,12 @@ class PrimitiveType(ValueSpecBase):
|
|
417
444
|
value_type, default, is_noneable=is_noneable, frozen=frozen
|
418
445
|
)
|
419
446
|
|
447
|
+
def __call__(self, *args, **kwargs) -> typing.Any:
|
448
|
+
del kwargs
|
449
|
+
if (not args and self.has_default) or self.frozen:
|
450
|
+
return self.default
|
451
|
+
return self.apply(self.value_type(*args))
|
452
|
+
|
420
453
|
|
421
454
|
class Bool(PrimitiveType):
|
422
455
|
"""Value spec for boolean type.
|
@@ -524,15 +557,18 @@ class Str(Generic, PrimitiveType):
|
|
524
557
|
**kwargs,
|
525
558
|
)
|
526
559
|
|
527
|
-
def _validate(self, path:
|
560
|
+
def _validate(self, path: utils.KeyPath, value: str) -> None:
|
528
561
|
"""Validates applied value."""
|
529
562
|
if not self._regex:
|
530
563
|
return
|
531
564
|
if not self._regex.match(value):
|
532
565
|
raise ValueError(
|
533
|
-
|
566
|
+
utils.message_on_path(
|
534
567
|
f'String {value!r} does not match '
|
535
|
-
f'regular expression {self._regex.pattern!r}.',
|
568
|
+
f'regular expression {self._regex.pattern!r}.',
|
569
|
+
path,
|
570
|
+
)
|
571
|
+
)
|
536
572
|
|
537
573
|
@property
|
538
574
|
def regex(self):
|
@@ -567,7 +603,7 @@ class Str(Generic, PrimitiveType):
|
|
567
603
|
) -> str:
|
568
604
|
"""Format this object."""
|
569
605
|
regex_pattern = self._regex.pattern if self._regex else None
|
570
|
-
return
|
606
|
+
return utils.kvlist_str(
|
571
607
|
[
|
572
608
|
('default', self._default, MISSING_VALUE),
|
573
609
|
('regex', regex_pattern, None),
|
@@ -578,7 +614,7 @@ class Str(Generic, PrimitiveType):
|
|
578
614
|
compact=compact,
|
579
615
|
verbose=verbose,
|
580
616
|
root_indent=root_indent,
|
581
|
-
**kwargs
|
617
|
+
**kwargs,
|
582
618
|
)
|
583
619
|
|
584
620
|
def _eq(self, other: 'Str') -> bool:
|
@@ -637,15 +673,17 @@ class Number(Generic, PrimitiveType):
|
|
637
673
|
"""Returns maximum value of acceptable values."""
|
638
674
|
return self._max_value
|
639
675
|
|
640
|
-
def _validate(self, path:
|
641
|
-
value: numbers.Number) -> None:
|
676
|
+
def _validate(self, path: utils.KeyPath, value: numbers.Number) -> None:
|
642
677
|
"""Validates applied value."""
|
643
678
|
if ((self._min_value is not None and value < self._min_value) or
|
644
679
|
(self._max_value is not None and value > self._max_value)):
|
645
680
|
raise ValueError(
|
646
|
-
|
681
|
+
utils.message_on_path(
|
647
682
|
f'Value {value} is out of range '
|
648
|
-
f'(min={self._min_value}, max={self._max_value}).',
|
683
|
+
f'(min={self._min_value}, max={self._max_value}).',
|
684
|
+
path,
|
685
|
+
)
|
686
|
+
)
|
649
687
|
|
650
688
|
def _extend(self, base: 'Number') -> None:
|
651
689
|
"""Number specific extend."""
|
@@ -698,19 +736,19 @@ class Number(Generic, PrimitiveType):
|
|
698
736
|
**kwargs
|
699
737
|
) -> str:
|
700
738
|
"""Format this object."""
|
701
|
-
return
|
739
|
+
return utils.kvlist_str(
|
702
740
|
[
|
703
741
|
('default', self._default, MISSING_VALUE),
|
704
742
|
('min', self._min_value, None),
|
705
743
|
('max', self._max_value, None),
|
706
744
|
('noneable', self._is_noneable, False),
|
707
|
-
('frozen', self._frozen, False)
|
745
|
+
('frozen', self._frozen, False),
|
708
746
|
],
|
709
747
|
label=self.__class__.__name__,
|
710
748
|
compact=compact,
|
711
749
|
verbose=verbose,
|
712
750
|
root_indent=root_indent,
|
713
|
-
**kwargs
|
751
|
+
**kwargs,
|
714
752
|
)
|
715
753
|
|
716
754
|
def to_json(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]:
|
@@ -898,6 +936,12 @@ class Enum(Generic, PrimitiveType):
|
|
898
936
|
value_type, default, is_noneable=is_noneable, frozen=frozen
|
899
937
|
)
|
900
938
|
|
939
|
+
def __call__(self, *args, **kwargs) -> typing.Any:
|
940
|
+
del kwargs
|
941
|
+
if (not args and self.has_default) or self.frozen:
|
942
|
+
return self.default
|
943
|
+
return self.apply(*args)
|
944
|
+
|
901
945
|
def noneable(self) -> 'Enum':
|
902
946
|
"""Noneable is specially treated for Enum."""
|
903
947
|
if None not in self._values:
|
@@ -910,13 +954,14 @@ class Enum(Generic, PrimitiveType):
|
|
910
954
|
"""Returns all acceptable values of this spec."""
|
911
955
|
return self._values
|
912
956
|
|
913
|
-
def _validate(self, path:
|
957
|
+
def _validate(self, path: utils.KeyPath, value: typing.Any) -> None:
|
914
958
|
"""Validates applied value."""
|
915
959
|
if value not in self._values:
|
916
960
|
raise ValueError(
|
917
|
-
|
918
|
-
f'Value {value!r} is not in candidate list {self._values}.',
|
919
|
-
|
961
|
+
utils.message_on_path(
|
962
|
+
f'Value {value!r} is not in candidate list {self._values}.', path
|
963
|
+
)
|
964
|
+
)
|
920
965
|
|
921
966
|
def _extend(self, base: 'Enum') -> None:
|
922
967
|
"""Enum specific extend."""
|
@@ -929,6 +974,12 @@ class Enum(Generic, PrimitiveType):
|
|
929
974
|
f'{repr(v)} is not an acceptable value.'
|
930
975
|
) from e
|
931
976
|
|
977
|
+
def is_compatible(self, other: ValueSpec) -> bool:
|
978
|
+
"""Enum specific compatibility check."""
|
979
|
+
if other.frozen and other.default in self.values:
|
980
|
+
return True
|
981
|
+
return super().is_compatible(other)
|
982
|
+
|
932
983
|
def _is_compatible(self, other: 'Enum') -> bool:
|
933
984
|
"""Enum specific compatibility check."""
|
934
985
|
for v in other.values:
|
@@ -955,7 +1006,7 @@ class Enum(Generic, PrimitiveType):
|
|
955
1006
|
**kwargs
|
956
1007
|
) -> str:
|
957
1008
|
"""Format this object."""
|
958
|
-
return
|
1009
|
+
return utils.kvlist_str(
|
959
1010
|
[
|
960
1011
|
('default', self._default, MISSING_VALUE),
|
961
1012
|
('values', self._values, None),
|
@@ -965,7 +1016,7 @@ class Enum(Generic, PrimitiveType):
|
|
965
1016
|
compact=compact,
|
966
1017
|
verbose=verbose,
|
967
1018
|
root_indent=root_indent,
|
968
|
-
**kwargs
|
1019
|
+
**kwargs,
|
969
1020
|
)
|
970
1021
|
|
971
1022
|
def to_json(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]:
|
@@ -1067,6 +1118,12 @@ class List(Generic, ValueSpecBase):
|
|
1067
1118
|
list, default, transform, is_noneable=is_noneable, frozen=frozen
|
1068
1119
|
)
|
1069
1120
|
|
1121
|
+
def __call__(self, *args, **kwargs) -> typing.Any:
|
1122
|
+
del kwargs
|
1123
|
+
if (not args and self.has_default) or self.frozen:
|
1124
|
+
return self.default
|
1125
|
+
return self.apply(list(*args))
|
1126
|
+
|
1070
1127
|
@property
|
1071
1128
|
def element(self) -> Field:
|
1072
1129
|
"""Returns Field specification of list element."""
|
@@ -1087,12 +1144,15 @@ class List(Generic, ValueSpecBase):
|
|
1087
1144
|
"""Returns max size of the list."""
|
1088
1145
|
return self._element.key.max_value # pytype: disable=attribute-error # bind-properties
|
1089
1146
|
|
1090
|
-
def _apply(
|
1091
|
-
|
1092
|
-
|
1093
|
-
|
1094
|
-
|
1095
|
-
|
1147
|
+
def _apply(
|
1148
|
+
self,
|
1149
|
+
value: typing.List[typing.Any],
|
1150
|
+
allow_partial: bool,
|
1151
|
+
child_transform: typing.Callable[
|
1152
|
+
[utils.KeyPath, Field, typing.Any], typing.Any
|
1153
|
+
],
|
1154
|
+
root_path: utils.KeyPath,
|
1155
|
+
) -> typing.Any:
|
1096
1156
|
"""List specific apply."""
|
1097
1157
|
# NOTE(daiyip): for symbolic List, write access using `__setitem__` will
|
1098
1158
|
# trigger permission error when `accessor_writable` is set to False.
|
@@ -1109,27 +1169,35 @@ class List(Generic, ValueSpecBase):
|
|
1109
1169
|
getitem = getattr(value, 'sym_getattr', value.__getitem__)
|
1110
1170
|
for i in range(len(value)):
|
1111
1171
|
v = self._element.apply(
|
1112
|
-
getitem(i),
|
1113
|
-
|
1172
|
+
getitem(i),
|
1173
|
+
allow_partial=allow_partial,
|
1174
|
+
transform_fn=child_transform,
|
1175
|
+
root_path=utils.KeyPath(i, root_path),
|
1176
|
+
)
|
1114
1177
|
if getitem(i) is not v:
|
1115
1178
|
set_item(i, v)
|
1116
1179
|
return value
|
1117
1180
|
|
1118
|
-
def _validate(
|
1119
|
-
self, path: object_utils.KeyPath, value: typing.List[typing.Any]):
|
1181
|
+
def _validate(self, path: utils.KeyPath, value: typing.List[typing.Any]):
|
1120
1182
|
"""Validates applied value."""
|
1121
1183
|
if len(value) < self.min_size:
|
1122
1184
|
raise ValueError(
|
1123
|
-
|
1185
|
+
utils.message_on_path(
|
1124
1186
|
f'Length of list {value!r} is less than '
|
1125
|
-
f'min size ({self.min_size}).',
|
1187
|
+
f'min size ({self.min_size}).',
|
1188
|
+
path,
|
1189
|
+
)
|
1190
|
+
)
|
1126
1191
|
|
1127
1192
|
if self.max_size is not None:
|
1128
1193
|
if len(value) > self.max_size:
|
1129
1194
|
raise ValueError(
|
1130
|
-
|
1195
|
+
utils.message_on_path(
|
1131
1196
|
f'Length of list {value!r} is greater than '
|
1132
|
-
f'max size ({self.max_size}).',
|
1197
|
+
f'max size ({self.max_size}).',
|
1198
|
+
path,
|
1199
|
+
)
|
1200
|
+
)
|
1133
1201
|
|
1134
1202
|
def _extend(self, base: 'List') -> None:
|
1135
1203
|
"""List specific extend."""
|
@@ -1157,7 +1225,7 @@ class List(Generic, ValueSpecBase):
|
|
1157
1225
|
**kwargs,
|
1158
1226
|
) -> str:
|
1159
1227
|
"""Format this object."""
|
1160
|
-
return
|
1228
|
+
return utils.kvlist_str(
|
1161
1229
|
[
|
1162
1230
|
('', self._element.value, None),
|
1163
1231
|
('min_size', self.min_size, 0),
|
@@ -1170,7 +1238,7 @@ class List(Generic, ValueSpecBase):
|
|
1170
1238
|
compact=compact,
|
1171
1239
|
verbose=verbose,
|
1172
1240
|
root_indent=root_indent,
|
1173
|
-
**kwargs
|
1241
|
+
**kwargs,
|
1174
1242
|
)
|
1175
1243
|
|
1176
1244
|
def to_json(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]:
|
@@ -1316,6 +1384,12 @@ class Tuple(Generic, ValueSpecBase):
|
|
1316
1384
|
tuple, default, transform, is_noneable=is_noneable, frozen=frozen
|
1317
1385
|
)
|
1318
1386
|
|
1387
|
+
def __call__(self, *args, **kwargs) -> typing.Any:
|
1388
|
+
del kwargs
|
1389
|
+
if (not args and self.has_default) or self.frozen:
|
1390
|
+
return self.default
|
1391
|
+
return self.apply(tuple(*args))
|
1392
|
+
|
1319
1393
|
@property
|
1320
1394
|
def fixed_length(self) -> bool:
|
1321
1395
|
"""Returns True if current Tuple spec is fixed length."""
|
@@ -1357,35 +1431,50 @@ class Tuple(Generic, ValueSpecBase):
|
|
1357
1431
|
"""Returns length of this tuple."""
|
1358
1432
|
return len(self._elements) if self.fixed_length else 0
|
1359
1433
|
|
1360
|
-
def _apply(
|
1361
|
-
|
1362
|
-
|
1363
|
-
|
1364
|
-
|
1365
|
-
|
1434
|
+
def _apply(
|
1435
|
+
self,
|
1436
|
+
value: typing.Tuple[typing.Any, ...],
|
1437
|
+
allow_partial: bool,
|
1438
|
+
child_transform: typing.Callable[
|
1439
|
+
[utils.KeyPath, Field, typing.Any], typing.Any
|
1440
|
+
],
|
1441
|
+
root_path: utils.KeyPath,
|
1442
|
+
) -> typing.Any:
|
1366
1443
|
"""Tuple specific apply."""
|
1367
1444
|
if self.fixed_length:
|
1368
1445
|
if len(value) != len(self.elements):
|
1369
1446
|
raise ValueError(
|
1370
|
-
|
1447
|
+
utils.message_on_path(
|
1371
1448
|
f'Length of input tuple ({len(value)}) does not match the '
|
1372
1449
|
f'length of spec ({len(self.elements)}). '
|
1373
|
-
f'Input: {value}, Spec: {self!r}',
|
1450
|
+
f'Input: {value}, Spec: {self!r}',
|
1451
|
+
root_path,
|
1452
|
+
)
|
1453
|
+
)
|
1374
1454
|
else:
|
1375
1455
|
if len(value) < self.min_size:
|
1376
1456
|
raise ValueError(
|
1377
|
-
|
1457
|
+
utils.message_on_path(
|
1378
1458
|
f'Length of tuple {value} is less than '
|
1379
|
-
f'min size ({self.min_size}).',
|
1459
|
+
f'min size ({self.min_size}).',
|
1460
|
+
root_path,
|
1461
|
+
)
|
1462
|
+
)
|
1380
1463
|
if self.max_size is not None and len(value) > self.max_size:
|
1381
1464
|
raise ValueError(
|
1382
|
-
|
1465
|
+
utils.message_on_path(
|
1383
1466
|
f'Length of tuple {value} is greater than '
|
1384
|
-
f'max size ({self.max_size}).',
|
1467
|
+
f'max size ({self.max_size}).',
|
1468
|
+
root_path,
|
1469
|
+
)
|
1470
|
+
)
|
1385
1471
|
return tuple([
|
1386
1472
|
self._elements[i if self.fixed_length else 0].apply( # pylint: disable=g-complex-comprehension
|
1387
|
-
v,
|
1388
|
-
|
1473
|
+
v,
|
1474
|
+
allow_partial=allow_partial,
|
1475
|
+
transform_fn=child_transform,
|
1476
|
+
root_path=utils.KeyPath(i, root_path),
|
1477
|
+
)
|
1389
1478
|
for i, v in enumerate(value)
|
1390
1479
|
])
|
1391
1480
|
|
@@ -1475,7 +1564,7 @@ class Tuple(Generic, ValueSpecBase):
|
|
1475
1564
|
else:
|
1476
1565
|
value = self._elements[0].value
|
1477
1566
|
default_min, default_max = 0, None
|
1478
|
-
return
|
1567
|
+
return utils.kvlist_str(
|
1479
1568
|
[
|
1480
1569
|
('', value, None),
|
1481
1570
|
('default', self._default, MISSING_VALUE),
|
@@ -1488,7 +1577,7 @@ class Tuple(Generic, ValueSpecBase):
|
|
1488
1577
|
compact=compact,
|
1489
1578
|
verbose=verbose,
|
1490
1579
|
root_indent=root_indent,
|
1491
|
-
**kwargs
|
1580
|
+
**kwargs,
|
1492
1581
|
)
|
1493
1582
|
|
1494
1583
|
def to_json(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]:
|
@@ -1617,6 +1706,9 @@ class Dict(Generic, ValueSpecBase):
|
|
1617
1706
|
if MISSING_VALUE == default:
|
1618
1707
|
self.set_default(default)
|
1619
1708
|
|
1709
|
+
def __call__(self, *args, **kwargs) -> typing.Any:
|
1710
|
+
return self.apply(dict(*args, **kwargs))
|
1711
|
+
|
1620
1712
|
@property
|
1621
1713
|
def schema(self) -> typing.Optional[Schema]:
|
1622
1714
|
"""Returns the schema of this dict spec."""
|
@@ -1632,7 +1724,7 @@ class Dict(Generic, ValueSpecBase):
|
|
1632
1724
|
self,
|
1633
1725
|
default: typing.Any,
|
1634
1726
|
use_default_apply: bool = True,
|
1635
|
-
root_path: typing.Optional[
|
1727
|
+
root_path: typing.Optional[utils.KeyPath] = None,
|
1636
1728
|
) -> ValueSpec:
|
1637
1729
|
if MISSING_VALUE == default and self._schema:
|
1638
1730
|
self._use_generated_default = True
|
@@ -1652,12 +1744,15 @@ class Dict(Generic, ValueSpecBase):
|
|
1652
1744
|
forward_refs.update(field.value.forward_refs)
|
1653
1745
|
return forward_refs
|
1654
1746
|
|
1655
|
-
def _apply(
|
1656
|
-
|
1657
|
-
|
1658
|
-
|
1659
|
-
|
1660
|
-
|
1747
|
+
def _apply(
|
1748
|
+
self,
|
1749
|
+
value: typing.Dict[typing.Any, typing.Any],
|
1750
|
+
allow_partial: bool,
|
1751
|
+
child_transform: typing.Callable[
|
1752
|
+
[utils.KeyPath, Field, typing.Any], typing.Any
|
1753
|
+
],
|
1754
|
+
root_path: utils.KeyPath,
|
1755
|
+
) -> typing.Any:
|
1661
1756
|
"""Dict specific apply."""
|
1662
1757
|
if not self._schema:
|
1663
1758
|
return value
|
@@ -1701,11 +1796,13 @@ class Dict(Generic, ValueSpecBase):
|
|
1701
1796
|
**kwargs,
|
1702
1797
|
) -> str:
|
1703
1798
|
"""Format this object."""
|
1704
|
-
return
|
1799
|
+
return utils.kvlist_str(
|
1705
1800
|
[
|
1706
|
-
(
|
1707
|
-
|
1708
|
-
|
1801
|
+
(
|
1802
|
+
'fields',
|
1803
|
+
list(self._schema.values()) if self._schema else None,
|
1804
|
+
None,
|
1805
|
+
),
|
1709
1806
|
('noneable', self._is_noneable, False),
|
1710
1807
|
('frozen', self._frozen, False),
|
1711
1808
|
],
|
@@ -1816,6 +1913,9 @@ class Object(Generic, ValueSpecBase):
|
|
1816
1913
|
t, default, transform, is_noneable=is_noneable, frozen=frozen
|
1817
1914
|
)
|
1818
1915
|
|
1916
|
+
def __call__(self, *args, **kwargs) -> typing.Any:
|
1917
|
+
return self.apply(self.cls(*args, **kwargs))
|
1918
|
+
|
1819
1919
|
@property
|
1820
1920
|
def forward_refs(self) -> typing.Set[class_schema.ForwardRef]:
|
1821
1921
|
"""Returns forward references used in this spec."""
|
@@ -1834,19 +1934,24 @@ class Object(Generic, ValueSpecBase):
|
|
1834
1934
|
def value_type(self) -> typing.Type[typing.Any]:
|
1835
1935
|
return self.cls
|
1836
1936
|
|
1837
|
-
def _apply(
|
1838
|
-
|
1839
|
-
|
1840
|
-
|
1841
|
-
|
1842
|
-
|
1937
|
+
def _apply(
|
1938
|
+
self,
|
1939
|
+
value: typing.Any,
|
1940
|
+
allow_partial: bool,
|
1941
|
+
child_transform: typing.Callable[
|
1942
|
+
[utils.KeyPath, Field, typing.Any], typing.Any
|
1943
|
+
],
|
1944
|
+
root_path: utils.KeyPath,
|
1945
|
+
) -> typing.Any:
|
1843
1946
|
"""Object specific apply."""
|
1844
1947
|
del child_transform
|
1845
|
-
if isinstance(value,
|
1948
|
+
if isinstance(value, utils.MaybePartial):
|
1846
1949
|
if not allow_partial and value.is_partial:
|
1847
1950
|
raise ValueError(
|
1848
|
-
|
1849
|
-
f'Object {value} is not fully bound.', root_path
|
1951
|
+
utils.message_on_path(
|
1952
|
+
f'Object {value} is not fully bound.', root_path
|
1953
|
+
)
|
1954
|
+
)
|
1850
1955
|
return value
|
1851
1956
|
|
1852
1957
|
def extend(self, base: ValueSpec) -> ValueSpec:
|
@@ -1897,9 +2002,9 @@ class Object(Generic, ValueSpecBase):
|
|
1897
2002
|
name = self._forward_ref.name
|
1898
2003
|
else:
|
1899
2004
|
name = self._value_type.__name__
|
1900
|
-
return
|
2005
|
+
return utils.kvlist_str(
|
1901
2006
|
[
|
1902
|
-
('',
|
2007
|
+
('', utils.RawText(name), None),
|
1903
2008
|
('default', self._default, MISSING_VALUE),
|
1904
2009
|
('noneable', self._is_noneable, False),
|
1905
2010
|
('frozen', self._frozen, False),
|
@@ -1908,7 +2013,7 @@ class Object(Generic, ValueSpecBase):
|
|
1908
2013
|
compact=compact,
|
1909
2014
|
verbose=verbose,
|
1910
2015
|
root_indent=root_indent,
|
1911
|
-
**kwargs
|
2016
|
+
**kwargs,
|
1912
2017
|
)
|
1913
2018
|
|
1914
2019
|
def to_json(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]:
|
@@ -2014,6 +2119,10 @@ class Callable(Generic, ValueSpecBase):
|
|
2014
2119
|
frozen=frozen,
|
2015
2120
|
)
|
2016
2121
|
|
2122
|
+
def __call__(self, *args, **kwargs) -> typing.Any:
|
2123
|
+
del args, kwargs
|
2124
|
+
raise TypeError(f'{self!r} cannot be instantiated.')
|
2125
|
+
|
2017
2126
|
@functools.cached_property
|
2018
2127
|
def forward_refs(self) -> typing.Set[class_schema.ForwardRef]:
|
2019
2128
|
"""Returns forward references used in this spec."""
|
@@ -2041,12 +2150,12 @@ class Callable(Generic, ValueSpecBase):
|
|
2041
2150
|
"""Value spec for return value."""
|
2042
2151
|
return self._return_value
|
2043
2152
|
|
2044
|
-
def _validate(self, path:
|
2153
|
+
def _validate(self, path: utils.KeyPath, value: typing.Any) -> None:
|
2045
2154
|
"""Validate applied value."""
|
2046
2155
|
if not callable(value):
|
2047
2156
|
raise TypeError(
|
2048
|
-
|
2049
|
-
|
2157
|
+
utils.message_on_path(f'Value is not callable: {value!r}.', path)
|
2158
|
+
)
|
2050
2159
|
|
2051
2160
|
# Shortcircuit if there is no signature to check.
|
2052
2161
|
if not (self._args or self._kw or self._return_value):
|
@@ -2058,10 +2167,12 @@ class Callable(Generic, ValueSpecBase):
|
|
2058
2167
|
|
2059
2168
|
if len(self._args) > len(signature.args) and not signature.has_varargs:
|
2060
2169
|
raise TypeError(
|
2061
|
-
|
2170
|
+
utils.message_on_path(
|
2062
2171
|
f'{signature.id} only take {len(signature.args)} positional '
|
2063
2172
|
f'arguments, while {len(self._args)} is required by {self!r}.',
|
2064
|
-
path
|
2173
|
+
path,
|
2174
|
+
)
|
2175
|
+
)
|
2065
2176
|
|
2066
2177
|
# Check positional arguments.
|
2067
2178
|
for i in range(min(len(self._args), len(signature.args))):
|
@@ -2069,10 +2180,12 @@ class Callable(Generic, ValueSpecBase):
|
|
2069
2180
|
dest_spec = signature.args[i].value_spec
|
2070
2181
|
if not dest_spec.is_compatible(src_spec):
|
2071
2182
|
raise TypeError(
|
2072
|
-
|
2183
|
+
utils.message_on_path(
|
2073
2184
|
f'Value spec of positional argument {i} is not compatible. '
|
2074
2185
|
f'Expected: {dest_spec!r}, Actual: {src_spec!r}.',
|
2075
|
-
path
|
2186
|
+
path,
|
2187
|
+
)
|
2188
|
+
)
|
2076
2189
|
if len(self._args) > len(signature.args):
|
2077
2190
|
assert signature.varargs
|
2078
2191
|
assert isinstance(signature.varargs.value_spec, List), signature.varargs
|
@@ -2081,10 +2194,13 @@ class Callable(Generic, ValueSpecBase):
|
|
2081
2194
|
src_spec = self._args[i]
|
2082
2195
|
if not dest_spec.is_compatible(src_spec):
|
2083
2196
|
raise TypeError(
|
2084
|
-
|
2197
|
+
utils.message_on_path(
|
2085
2198
|
f'Value spec of positional argument {i} is not compatible '
|
2086
2199
|
f'with the value spec of *{signature.varargs.name}. '
|
2087
|
-
f'Expected: {dest_spec!r}, Actual: {src_spec!r}.',
|
2200
|
+
f'Expected: {dest_spec!r}, Actual: {src_spec!r}.',
|
2201
|
+
path,
|
2202
|
+
)
|
2203
|
+
)
|
2088
2204
|
|
2089
2205
|
# Check keyword arguments.
|
2090
2206
|
dest_args = signature.args + signature.kwonlyargs
|
@@ -2097,37 +2213,46 @@ class Callable(Generic, ValueSpecBase):
|
|
2097
2213
|
if dest_spec is not None:
|
2098
2214
|
if not dest_spec.is_compatible(src_spec):
|
2099
2215
|
raise TypeError(
|
2100
|
-
|
2216
|
+
utils.message_on_path(
|
2101
2217
|
f'Value spec of keyword argument {arg_name!r} is not '
|
2102
2218
|
f'compatible. Expected: {src_spec!r}, Actual: {dest_spec!r}.',
|
2103
|
-
path
|
2219
|
+
path,
|
2220
|
+
)
|
2221
|
+
)
|
2104
2222
|
elif signature.varkw:
|
2105
2223
|
assert isinstance(signature.varkw.value_spec, Dict), signature.varkw
|
2106
2224
|
varkw_value_spec = signature.varkw.value_spec.schema.dynamic_field.value # pytype: disable=attribute-error
|
2107
2225
|
if not varkw_value_spec.is_compatible(src_spec):
|
2108
2226
|
raise TypeError(
|
2109
|
-
|
2227
|
+
utils.message_on_path(
|
2110
2228
|
f'Value spec of keyword argument {arg_name!r} is not '
|
2111
|
-
|
2229
|
+
'compatible with the value spec of '
|
2112
2230
|
f'**{signature.varkw.name}. '
|
2113
2231
|
f'Expected: {varkw_value_spec!r}, '
|
2114
|
-
f'Actual: {src_spec!r}.',
|
2232
|
+
f'Actual: {src_spec!r}.',
|
2233
|
+
path,
|
2234
|
+
)
|
2235
|
+
)
|
2115
2236
|
else:
|
2116
2237
|
raise TypeError(
|
2117
|
-
|
2238
|
+
utils.message_on_path(
|
2118
2239
|
f'Keyword argument {arg_name!r} does not exist in {value!r}.',
|
2119
|
-
path
|
2240
|
+
path,
|
2241
|
+
)
|
2242
|
+
)
|
2120
2243
|
|
2121
2244
|
# Check return value
|
2122
2245
|
if (self._return_value and signature.return_value
|
2123
2246
|
and not isinstance(signature.return_value, Any)
|
2124
2247
|
and not self._return_value.is_compatible(signature.return_value)):
|
2125
2248
|
raise TypeError(
|
2126
|
-
|
2127
|
-
|
2249
|
+
utils.message_on_path(
|
2250
|
+
'Value spec for return value is not compatible. '
|
2128
2251
|
f'Expected: {self._return_value!r}, '
|
2129
2252
|
f'Actual: {signature.return_value!r} ({value!r}).',
|
2130
|
-
path
|
2253
|
+
path,
|
2254
|
+
)
|
2255
|
+
)
|
2131
2256
|
|
2132
2257
|
def _extend(self, base: 'Callable') -> None:
|
2133
2258
|
"""Callable specific extension."""
|
@@ -2198,14 +2323,14 @@ class Callable(Generic, ValueSpecBase):
|
|
2198
2323
|
**kwargs,
|
2199
2324
|
) -> str:
|
2200
2325
|
"""Format this spec."""
|
2201
|
-
return
|
2326
|
+
return utils.kvlist_str(
|
2202
2327
|
[
|
2203
2328
|
('args', self._args, []),
|
2204
2329
|
('kw', self._kw, []),
|
2205
2330
|
('returns', self._return_value, None),
|
2206
2331
|
('default', self._default, MISSING_VALUE),
|
2207
2332
|
('noneable', self._is_noneable, False),
|
2208
|
-
('frozen', self._frozen, False)
|
2333
|
+
('frozen', self._frozen, False),
|
2209
2334
|
],
|
2210
2335
|
label=self.__class__.__name__,
|
2211
2336
|
compact=compact,
|
@@ -2297,14 +2422,14 @@ class Functor(Callable):
|
|
2297
2422
|
returns=returns,
|
2298
2423
|
default=default,
|
2299
2424
|
transform=transform,
|
2300
|
-
callable_type=
|
2425
|
+
callable_type=utils.Functor,
|
2301
2426
|
is_noneable=is_noneable,
|
2302
2427
|
frozen=frozen,
|
2303
2428
|
)
|
2304
2429
|
|
2305
2430
|
def _annotate(self) -> typing.Any:
|
2306
2431
|
"""Annotate with PyType annotation."""
|
2307
|
-
return
|
2432
|
+
return utils.Functor
|
2308
2433
|
|
2309
2434
|
def to_json(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]:
|
2310
2435
|
exclude_keys = kwargs.pop('exclude_keys', set())
|
@@ -2351,6 +2476,10 @@ class Type(Generic, ValueSpecBase):
|
|
2351
2476
|
self._forward_ref = forward_ref
|
2352
2477
|
super().__init__(type, default, is_noneable=is_noneable, frozen=frozen)
|
2353
2478
|
|
2479
|
+
def __call__(self, *args, **kwargs) -> typing.Any:
|
2480
|
+
del args, kwargs
|
2481
|
+
return self.type
|
2482
|
+
|
2354
2483
|
@property
|
2355
2484
|
def type(self) -> typing.Type[typing.Any]:
|
2356
2485
|
"""Returns desired type."""
|
@@ -2365,12 +2494,14 @@ class Type(Generic, ValueSpecBase):
|
|
2365
2494
|
return set()
|
2366
2495
|
return set([self._forward_ref])
|
2367
2496
|
|
2368
|
-
def _validate(self, path:
|
2497
|
+
def _validate(self, path: utils.KeyPath, value: typing.Type) -> None: # pylint: disable=g-bare-generic
|
2369
2498
|
"""Validate applied value."""
|
2370
2499
|
if self.type_resolved and not pg_inspect.is_subclass(value, self.type):
|
2371
2500
|
raise ValueError(
|
2372
|
-
|
2373
|
-
f'{value!r} is not a subclass of {self.type!r}', path
|
2501
|
+
utils.message_on_path(
|
2502
|
+
f'{value!r} is not a subclass of {self.type!r}', path
|
2503
|
+
)
|
2504
|
+
)
|
2374
2505
|
|
2375
2506
|
def _is_compatible(self, other: 'Type') -> bool:
|
2376
2507
|
"""Type specific compatiblity check."""
|
@@ -2406,7 +2537,7 @@ class Type(Generic, ValueSpecBase):
|
|
2406
2537
|
**kwargs,
|
2407
2538
|
) -> str:
|
2408
2539
|
"""Format this object."""
|
2409
|
-
return
|
2540
|
+
return utils.kvlist_str(
|
2410
2541
|
[
|
2411
2542
|
('', self._expected_type, None),
|
2412
2543
|
('default', self._default, MISSING_VALUE),
|
@@ -2417,7 +2548,7 @@ class Type(Generic, ValueSpecBase):
|
|
2417
2548
|
compact=compact,
|
2418
2549
|
verbose=verbose,
|
2419
2550
|
root_indent=root_indent,
|
2420
|
-
**kwargs
|
2551
|
+
**kwargs,
|
2421
2552
|
)
|
2422
2553
|
|
2423
2554
|
def to_json(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]:
|
@@ -2539,6 +2670,10 @@ class Union(Generic, ValueSpecBase):
|
|
2539
2670
|
frozen=frozen,
|
2540
2671
|
)
|
2541
2672
|
|
2673
|
+
def __call__(self, *args, **kwargs) -> typing.Any:
|
2674
|
+
del args, kwargs
|
2675
|
+
raise TypeError(f'{self!r} cannot be instantiated.')
|
2676
|
+
|
2542
2677
|
@functools.cached_property
|
2543
2678
|
def forward_refs(self) -> typing.Set[class_schema.ForwardRef]:
|
2544
2679
|
"""Returns forward references used in this spec."""
|
@@ -2603,14 +2738,15 @@ class Union(Generic, ValueSpecBase):
|
|
2603
2738
|
return c
|
2604
2739
|
return None
|
2605
2740
|
|
2606
|
-
def _apply(
|
2607
|
-
|
2608
|
-
|
2609
|
-
|
2610
|
-
|
2611
|
-
|
2612
|
-
|
2613
|
-
|
2741
|
+
def _apply(
|
2742
|
+
self,
|
2743
|
+
value: typing.Any,
|
2744
|
+
allow_partial: bool,
|
2745
|
+
child_transform: typing.Callable[
|
2746
|
+
[utils.KeyPath, Field, typing.Any], typing.Any
|
2747
|
+
],
|
2748
|
+
root_path: utils.KeyPath,
|
2749
|
+
) -> typing.Any:
|
2614
2750
|
"""Union specific apply."""
|
2615
2751
|
# Match strong-typed candidates first.
|
2616
2752
|
if not self.type_resolved:
|
@@ -2712,7 +2848,7 @@ class Union(Generic, ValueSpecBase):
|
|
2712
2848
|
**kwargs,
|
2713
2849
|
) -> str:
|
2714
2850
|
"""Format this object."""
|
2715
|
-
return
|
2851
|
+
return utils.kvlist_str(
|
2716
2852
|
[
|
2717
2853
|
('', self._candidates, None),
|
2718
2854
|
('default', self._default, MISSING_VALUE),
|
@@ -2724,7 +2860,7 @@ class Union(Generic, ValueSpecBase):
|
|
2724
2860
|
verbose=verbose,
|
2725
2861
|
root_indent=root_indent,
|
2726
2862
|
list_wrap_threshold=kwargs.pop('list_wrap_threshold', 20),
|
2727
|
-
**kwargs
|
2863
|
+
**kwargs,
|
2728
2864
|
)
|
2729
2865
|
|
2730
2866
|
def to_json(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]:
|
@@ -2856,6 +2992,10 @@ class Any(ValueSpecBase):
|
|
2856
2992
|
)
|
2857
2993
|
self._annotation = annotation
|
2858
2994
|
|
2995
|
+
def __call__(self, *args, **kwargs) -> typing.Any:
|
2996
|
+
del args, kwargs
|
2997
|
+
raise TypeError(f'{self!r} cannot be instantiated.')
|
2998
|
+
|
2859
2999
|
def is_compatible(self, other: ValueSpec) -> bool:
|
2860
3000
|
"""Any is compatible with any ValueSpec."""
|
2861
3001
|
return True
|
@@ -2868,17 +3008,17 @@ class Any(ValueSpecBase):
|
|
2868
3008
|
**kwargs,
|
2869
3009
|
) -> str:
|
2870
3010
|
"""Format this object."""
|
2871
|
-
return
|
3011
|
+
return utils.kvlist_str(
|
2872
3012
|
[
|
2873
3013
|
('default', self._default, MISSING_VALUE),
|
2874
3014
|
('frozen', self._frozen, False),
|
2875
|
-
('annotation', self._annotation, MISSING_VALUE)
|
3015
|
+
('annotation', self._annotation, MISSING_VALUE),
|
2876
3016
|
],
|
2877
3017
|
label=self.__class__.__name__,
|
2878
3018
|
compact=compact,
|
2879
3019
|
verbose=verbose,
|
2880
3020
|
root_indent=root_indent,
|
2881
|
-
**kwargs
|
3021
|
+
**kwargs,
|
2882
3022
|
)
|
2883
3023
|
|
2884
3024
|
def annotate(self, annotation: typing.Any) -> 'Any':
|
@@ -2948,7 +3088,7 @@ ValueSpec.ObjectType = Object
|
|
2948
3088
|
def ensure_value_spec(
|
2949
3089
|
value_spec: class_schema.ValueSpec,
|
2950
3090
|
src_spec: class_schema.ValueSpec,
|
2951
|
-
root_path: typing.Optional[
|
3091
|
+
root_path: typing.Optional[utils.KeyPath] = None,
|
2952
3092
|
) -> typing.Optional[class_schema.ValueSpec]:
|
2953
3093
|
"""Extract counter part from value spec that matches dest spec type.
|
2954
3094
|
|
@@ -2969,7 +3109,10 @@ def ensure_value_spec(
|
|
2969
3109
|
return None
|
2970
3110
|
if not src_spec.is_compatible(value_spec):
|
2971
3111
|
raise TypeError(
|
2972
|
-
|
3112
|
+
utils.message_on_path(
|
2973
3113
|
f'Source spec {src_spec} is not compatible with destination '
|
2974
|
-
f'spec {value_spec}.',
|
3114
|
+
f'spec {value_spec}.',
|
3115
|
+
root_path,
|
3116
|
+
)
|
3117
|
+
)
|
2975
3118
|
return value_spec
|