pyglove 0.4.5.dev20240318__py3-none-any.whl → 0.4.5.dev202501132210__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyglove/core/__init__.py +54 -20
- pyglove/core/coding/__init__.py +42 -0
- pyglove/core/coding/errors.py +111 -0
- pyglove/core/coding/errors_test.py +98 -0
- pyglove/core/coding/execution.py +309 -0
- pyglove/core/coding/execution_test.py +333 -0
- pyglove/core/{object_utils/codegen.py → coding/function_generation.py} +10 -4
- pyglove/core/{object_utils/codegen_test.py → coding/function_generation_test.py} +5 -7
- pyglove/core/coding/parsing.py +153 -0
- pyglove/core/coding/parsing_test.py +150 -0
- pyglove/core/coding/permissions.py +100 -0
- pyglove/core/coding/permissions_test.py +93 -0
- pyglove/core/geno/base.py +54 -41
- pyglove/core/geno/base_test.py +2 -4
- pyglove/core/geno/categorical.py +37 -28
- pyglove/core/geno/custom.py +19 -16
- pyglove/core/geno/numerical.py +20 -17
- pyglove/core/geno/space.py +4 -5
- pyglove/core/hyper/base.py +6 -6
- pyglove/core/hyper/categorical.py +94 -55
- pyglove/core/hyper/custom.py +7 -7
- pyglove/core/hyper/custom_test.py +9 -10
- pyglove/core/hyper/derived.py +30 -22
- pyglove/core/hyper/derived_test.py +2 -4
- pyglove/core/hyper/dynamic_evaluation.py +5 -6
- pyglove/core/hyper/evolvable.py +57 -46
- pyglove/core/hyper/numerical.py +48 -24
- pyglove/core/hyper/numerical_test.py +9 -9
- pyglove/core/hyper/object_template.py +58 -46
- pyglove/core/io/__init__.py +1 -0
- pyglove/core/io/file_system.py +17 -7
- pyglove/core/io/file_system_test.py +2 -0
- pyglove/core/io/sequence.py +299 -0
- pyglove/core/io/sequence_test.py +124 -0
- pyglove/core/logging_test.py +0 -2
- pyglove/core/patching/object_factory.py +4 -4
- pyglove/core/patching/pattern_based.py +4 -4
- pyglove/core/patching/rule_based.py +17 -5
- pyglove/core/patching/rule_based_test.py +27 -4
- pyglove/core/symbolic/__init__.py +2 -7
- pyglove/core/symbolic/base.py +320 -183
- pyglove/core/symbolic/base_test.py +123 -19
- pyglove/core/symbolic/boilerplate.py +7 -13
- pyglove/core/symbolic/boilerplate_test.py +25 -23
- pyglove/core/symbolic/class_wrapper.py +48 -45
- pyglove/core/symbolic/class_wrapper_test.py +2 -2
- pyglove/core/symbolic/compounding.py +9 -15
- pyglove/core/symbolic/compounding_test.py +2 -4
- pyglove/core/symbolic/dict.py +154 -110
- pyglove/core/symbolic/dict_test.py +238 -130
- pyglove/core/symbolic/diff.py +199 -10
- pyglove/core/symbolic/diff_test.py +226 -0
- pyglove/core/symbolic/flags.py +1 -1
- pyglove/core/symbolic/functor.py +29 -26
- pyglove/core/symbolic/functor_test.py +102 -50
- pyglove/core/symbolic/inferred.py +2 -2
- pyglove/core/symbolic/list.py +81 -50
- pyglove/core/symbolic/list_test.py +119 -97
- pyglove/core/symbolic/object.py +225 -113
- pyglove/core/symbolic/object_test.py +320 -108
- pyglove/core/symbolic/origin.py +17 -14
- pyglove/core/symbolic/origin_test.py +4 -2
- pyglove/core/symbolic/pure_symbolic.py +4 -3
- pyglove/core/symbolic/ref.py +108 -21
- pyglove/core/symbolic/ref_test.py +93 -0
- pyglove/core/symbolic/symbolize_test.py +10 -2
- pyglove/core/tuning/local_backend.py +2 -2
- pyglove/core/tuning/protocols.py +3 -3
- pyglove/core/tuning/sample_test.py +3 -3
- pyglove/core/typing/__init__.py +14 -5
- pyglove/core/typing/annotation_conversion.py +43 -27
- pyglove/core/typing/annotation_conversion_test.py +23 -0
- pyglove/core/typing/callable_ext.py +241 -3
- pyglove/core/typing/callable_ext_test.py +255 -0
- pyglove/core/typing/callable_signature.py +510 -66
- pyglove/core/typing/callable_signature_test.py +619 -99
- pyglove/core/typing/class_schema.py +229 -154
- pyglove/core/typing/class_schema_test.py +149 -95
- pyglove/core/typing/custom_typing.py +5 -4
- pyglove/core/typing/inspect.py +63 -0
- pyglove/core/typing/inspect_test.py +39 -0
- pyglove/core/typing/key_specs.py +10 -11
- pyglove/core/typing/key_specs_test.py +7 -4
- pyglove/core/typing/type_conversion.py +4 -5
- pyglove/core/typing/type_conversion_test.py +12 -12
- pyglove/core/typing/typed_missing.py +6 -7
- pyglove/core/typing/typed_missing_test.py +7 -8
- pyglove/core/typing/value_specs.py +604 -362
- pyglove/core/typing/value_specs_test.py +328 -90
- pyglove/core/utils/__init__.py +164 -0
- pyglove/core/{object_utils → utils}/common_traits.py +3 -67
- pyglove/core/utils/common_traits_test.py +36 -0
- pyglove/core/{object_utils → utils}/docstr_utils.py +23 -0
- pyglove/core/{object_utils → utils}/docstr_utils_test.py +36 -4
- pyglove/core/{object_utils → utils}/error_utils.py +78 -9
- pyglove/core/{object_utils → utils}/error_utils_test.py +61 -5
- pyglove/core/utils/formatting.py +464 -0
- pyglove/core/utils/formatting_test.py +453 -0
- pyglove/core/{object_utils → utils}/hierarchical.py +23 -25
- pyglove/core/{object_utils → utils}/hierarchical_test.py +3 -5
- pyglove/core/{object_utils → utils}/json_conversion.py +177 -52
- pyglove/core/{object_utils → utils}/json_conversion_test.py +97 -16
- pyglove/core/{object_utils → utils}/missing.py +3 -3
- pyglove/core/{object_utils → utils}/missing_test.py +2 -4
- pyglove/core/utils/text_color.py +128 -0
- pyglove/core/utils/text_color_test.py +94 -0
- pyglove/core/{object_utils → utils}/thread_local_test.py +1 -3
- pyglove/core/utils/timing.py +236 -0
- pyglove/core/utils/timing_test.py +154 -0
- pyglove/core/{object_utils → utils}/value_location.py +275 -6
- pyglove/core/utils/value_location_test.py +707 -0
- pyglove/core/views/__init__.py +32 -0
- pyglove/core/views/base.py +804 -0
- pyglove/core/views/base_test.py +580 -0
- pyglove/core/views/html/__init__.py +27 -0
- pyglove/core/views/html/base.py +547 -0
- pyglove/core/views/html/base_test.py +830 -0
- pyglove/core/views/html/controls/__init__.py +35 -0
- pyglove/core/views/html/controls/base.py +275 -0
- pyglove/core/views/html/controls/label.py +207 -0
- pyglove/core/views/html/controls/label_test.py +157 -0
- pyglove/core/views/html/controls/progress_bar.py +183 -0
- pyglove/core/views/html/controls/progress_bar_test.py +97 -0
- pyglove/core/views/html/controls/tab.py +320 -0
- pyglove/core/views/html/controls/tab_test.py +87 -0
- pyglove/core/views/html/controls/tooltip.py +99 -0
- pyglove/core/views/html/controls/tooltip_test.py +99 -0
- pyglove/core/views/html/tree_view.py +1517 -0
- pyglove/core/views/html/tree_view_test.py +1461 -0
- {pyglove-0.4.5.dev20240318.dist-info → pyglove-0.4.5.dev202501132210.dist-info}/METADATA +18 -4
- pyglove-0.4.5.dev202501132210.dist-info/RECORD +214 -0
- {pyglove-0.4.5.dev20240318.dist-info → pyglove-0.4.5.dev202501132210.dist-info}/WHEEL +1 -1
- pyglove/core/object_utils/__init__.py +0 -154
- pyglove/core/object_utils/common_traits_test.py +0 -82
- pyglove/core/object_utils/formatting.py +0 -234
- pyglove/core/object_utils/formatting_test.py +0 -223
- pyglove/core/object_utils/value_location_test.py +0 -385
- pyglove/core/symbolic/schema_utils.py +0 -327
- pyglove/core/symbolic/schema_utils_test.py +0 -57
- pyglove/core/typing/class_schema_utils.py +0 -202
- pyglove/core/typing/class_schema_utils_test.py +0 -194
- pyglove-0.4.5.dev20240318.dist-info/RECORD +0 -185
- /pyglove/core/{object_utils → utils}/thread_local.py +0 -0
- {pyglove-0.4.5.dev20240318.dist-info → pyglove-0.4.5.dev202501132210.dist-info}/LICENSE +0 -0
- {pyglove-0.4.5.dev20240318.dist-info → pyglove-0.4.5.dev202501132210.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
|
@@ -154,16 +165,19 @@ class ValueSpecBase(ValueSpec):
|
|
154
165
|
"""Returns the default value."""
|
155
166
|
return self._default
|
156
167
|
|
157
|
-
def set_default(
|
158
|
-
|
159
|
-
|
168
|
+
def set_default(
|
169
|
+
self,
|
170
|
+
default: typing.Any,
|
171
|
+
use_default_apply: bool = True,
|
172
|
+
root_path: typing.Optional[utils.KeyPath] = None,
|
173
|
+
) -> ValueSpec:
|
160
174
|
"""Set default value and returns `self`."""
|
161
175
|
# NOTE(daiyip): Default can be schema.MissingValue types, all are
|
162
176
|
# normalized to MISSING_VALUE for consistency.
|
163
177
|
if MISSING_VALUE == default:
|
164
178
|
default = MISSING_VALUE
|
165
179
|
if MISSING_VALUE != default and use_default_apply:
|
166
|
-
default = self.apply(default, allow_partial=True)
|
180
|
+
default = self.apply(default, allow_partial=True, root_path=root_path)
|
167
181
|
self._default = default
|
168
182
|
return self
|
169
183
|
|
@@ -174,7 +188,7 @@ class ValueSpecBase(ValueSpec):
|
|
174
188
|
if MISSING_VALUE != permanent_value:
|
175
189
|
self.set_default(permanent_value, use_default_apply=apply_before_use)
|
176
190
|
elif MISSING_VALUE == self._default:
|
177
|
-
raise ValueError(f'Cannot freeze {self} without a default value.')
|
191
|
+
raise ValueError(f'Cannot freeze {self!r} without a default value.')
|
178
192
|
self._frozen = True
|
179
193
|
return self
|
180
194
|
|
@@ -191,8 +205,18 @@ class ValueSpecBase(ValueSpec):
|
|
191
205
|
|
192
206
|
def extend(self, base: ValueSpec) -> ValueSpec:
|
193
207
|
"""Extend current value spec on top of a base spec."""
|
194
|
-
if base.frozen:
|
195
|
-
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
|
+
)
|
196
220
|
|
197
221
|
if self._transform is None:
|
198
222
|
self._transform = base.transform
|
@@ -207,7 +231,7 @@ class ValueSpecBase(ValueSpec):
|
|
207
231
|
f'no compatible type found in Union.')
|
208
232
|
base = base_counterpart
|
209
233
|
|
210
|
-
if not isinstance(self, base.__class__):
|
234
|
+
if not isinstance(self, (base.__class__, Enum)):
|
211
235
|
raise TypeError(f'{self!r} cannot extend {base!r}: incompatible type.')
|
212
236
|
if not base.is_noneable and self._is_noneable:
|
213
237
|
raise TypeError(f'{self!r} cannot extend {base!r}: '
|
@@ -222,15 +246,14 @@ class ValueSpecBase(ValueSpec):
|
|
222
246
|
self,
|
223
247
|
value: typing.Any,
|
224
248
|
allow_partial: bool = False,
|
225
|
-
child_transform: typing.Optional[
|
226
|
-
[
|
227
|
-
|
228
|
-
]
|
229
|
-
|
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
|
230
254
|
"""Apply spec to validate and complete value."""
|
231
|
-
root_path = root_path or
|
232
|
-
|
233
|
-
if self.frozen:
|
255
|
+
root_path = root_path or utils.KeyPath()
|
256
|
+
if self.frozen and self.default is not _FROZEN_VALUE_PLACEHOLDER:
|
234
257
|
# Always return the default value if a field is frozen.
|
235
258
|
if MISSING_VALUE != value and self.default != value:
|
236
259
|
raise ValueError(
|
@@ -268,8 +291,8 @@ class ValueSpecBase(ValueSpec):
|
|
268
291
|
value = self._transform(value)
|
269
292
|
except Exception as e: # pylint: disable=broad-except
|
270
293
|
raise e.__class__(
|
271
|
-
|
272
|
-
|
294
|
+
utils.message_on_path(str(e), root_path)
|
295
|
+
).with_traceback(sys.exc_info()[2])
|
273
296
|
|
274
297
|
return self.skip_user_transform.apply(
|
275
298
|
value,
|
@@ -285,9 +308,12 @@ class ValueSpecBase(ValueSpec):
|
|
285
308
|
converter = type_conversion.get_converter(type(value), self.value_type)
|
286
309
|
if converter is None:
|
287
310
|
raise TypeError(
|
288
|
-
|
311
|
+
utils.message_on_path(
|
289
312
|
f'Expect {self.value_type} '
|
290
|
-
f'but encountered {type(value)!r}: {value}.',
|
313
|
+
f'but encountered {type(value)!r}: {value}.',
|
314
|
+
root_path,
|
315
|
+
)
|
316
|
+
)
|
291
317
|
value = converter(value)
|
292
318
|
|
293
319
|
# NOTE(daiyip): child nodes validation and transformation is done before
|
@@ -301,15 +327,18 @@ class ValueSpecBase(ValueSpec):
|
|
301
327
|
self._validate(root_path, value)
|
302
328
|
return value
|
303
329
|
|
304
|
-
def _validate(self, path:
|
330
|
+
def _validate(self, path: utils.KeyPath, value: typing.Any):
|
305
331
|
"""Validation on applied value. Child class can override."""
|
306
332
|
|
307
|
-
def _apply(
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
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:
|
313
342
|
"""Customized apply so each subclass can override."""
|
314
343
|
del allow_partial
|
315
344
|
del child_transform
|
@@ -317,7 +346,7 @@ class ValueSpecBase(ValueSpec):
|
|
317
346
|
return value
|
318
347
|
|
319
348
|
def is_compatible(self, other: ValueSpec) -> bool:
|
320
|
-
"""Returns if current spec
|
349
|
+
"""Returns if current spec can receive all values from the other spec."""
|
321
350
|
if self is other:
|
322
351
|
return True
|
323
352
|
if not isinstance(other, self.__class__):
|
@@ -369,15 +398,25 @@ class ValueSpecBase(ValueSpec):
|
|
369
398
|
def __ror__(self, other: typing.Any) -> bool:
|
370
399
|
return Union[other, self]
|
371
400
|
|
372
|
-
def format(
|
401
|
+
def format(
|
402
|
+
self,
|
403
|
+
compact: bool = False,
|
404
|
+
verbose: bool = True,
|
405
|
+
root_indent: int = 0,
|
406
|
+
**kwargs
|
407
|
+
) -> str:
|
373
408
|
"""Format this object."""
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
409
|
+
return utils.kvlist_str(
|
410
|
+
[
|
411
|
+
('default', self._default, MISSING_VALUE),
|
412
|
+
('noneable', self._is_noneable, False),
|
413
|
+
('frozen', self._frozen, False),
|
414
|
+
],
|
415
|
+
label=self.__class__.__name__,
|
416
|
+
compact=compact,
|
417
|
+
verbose=verbose,
|
418
|
+
root_indent=root_indent,
|
419
|
+
**kwargs,
|
381
420
|
)
|
382
421
|
|
383
422
|
|
@@ -405,6 +444,12 @@ class PrimitiveType(ValueSpecBase):
|
|
405
444
|
value_type, default, is_noneable=is_noneable, frozen=frozen
|
406
445
|
)
|
407
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
|
+
|
408
453
|
|
409
454
|
class Bool(PrimitiveType):
|
410
455
|
"""Value spec for boolean type.
|
@@ -512,15 +557,18 @@ class Str(Generic, PrimitiveType):
|
|
512
557
|
**kwargs,
|
513
558
|
)
|
514
559
|
|
515
|
-
def _validate(self, path:
|
560
|
+
def _validate(self, path: utils.KeyPath, value: str) -> None:
|
516
561
|
"""Validates applied value."""
|
517
562
|
if not self._regex:
|
518
563
|
return
|
519
564
|
if not self._regex.match(value):
|
520
565
|
raise ValueError(
|
521
|
-
|
566
|
+
utils.message_on_path(
|
522
567
|
f'String {value!r} does not match '
|
523
|
-
f'regular expression {self._regex.pattern!r}.',
|
568
|
+
f'regular expression {self._regex.pattern!r}.',
|
569
|
+
path,
|
570
|
+
)
|
571
|
+
)
|
524
572
|
|
525
573
|
@property
|
526
574
|
def regex(self):
|
@@ -546,17 +594,27 @@ class Str(Generic, PrimitiveType):
|
|
546
594
|
"""Annotate with PyType annotation."""
|
547
595
|
return str
|
548
596
|
|
549
|
-
def format(
|
597
|
+
def format(
|
598
|
+
self,
|
599
|
+
compact: bool = False,
|
600
|
+
verbose: bool = True,
|
601
|
+
root_indent: int = 0,
|
602
|
+
**kwargs
|
603
|
+
) -> str:
|
550
604
|
"""Format this object."""
|
551
605
|
regex_pattern = self._regex.pattern if self._regex else None
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
606
|
+
return utils.kvlist_str(
|
607
|
+
[
|
608
|
+
('default', self._default, MISSING_VALUE),
|
609
|
+
('regex', regex_pattern, None),
|
610
|
+
('noneable', self._is_noneable, False),
|
611
|
+
('frozen', self._frozen, False),
|
612
|
+
],
|
613
|
+
label=self.__class__.__name__,
|
614
|
+
compact=compact,
|
615
|
+
verbose=verbose,
|
616
|
+
root_indent=root_indent,
|
617
|
+
**kwargs,
|
560
618
|
)
|
561
619
|
|
562
620
|
def _eq(self, other: 'Str') -> bool:
|
@@ -615,15 +673,17 @@ class Number(Generic, PrimitiveType):
|
|
615
673
|
"""Returns maximum value of acceptable values."""
|
616
674
|
return self._max_value
|
617
675
|
|
618
|
-
def _validate(self, path:
|
619
|
-
value: numbers.Number) -> None:
|
676
|
+
def _validate(self, path: utils.KeyPath, value: numbers.Number) -> None:
|
620
677
|
"""Validates applied value."""
|
621
678
|
if ((self._min_value is not None and value < self._min_value) or
|
622
679
|
(self._max_value is not None and value > self._max_value)):
|
623
680
|
raise ValueError(
|
624
|
-
|
681
|
+
utils.message_on_path(
|
625
682
|
f'Value {value} is out of range '
|
626
|
-
f'(min={self._min_value}, max={self._max_value}).',
|
683
|
+
f'(min={self._min_value}, max={self._max_value}).',
|
684
|
+
path,
|
685
|
+
)
|
686
|
+
)
|
627
687
|
|
628
688
|
def _extend(self, base: 'Number') -> None:
|
629
689
|
"""Number specific extend."""
|
@@ -632,19 +692,23 @@ class Number(Generic, PrimitiveType):
|
|
632
692
|
if min_value is None:
|
633
693
|
min_value = base.min_value
|
634
694
|
elif min_value < base.min_value:
|
635
|
-
raise TypeError(
|
695
|
+
raise TypeError(
|
696
|
+
f'{self!r} cannot extend {base!r}: min_value is smaller.'
|
697
|
+
)
|
636
698
|
|
637
699
|
max_value = self._max_value
|
638
700
|
if base.max_value is not None:
|
639
701
|
if max_value is None:
|
640
702
|
max_value = base.max_value
|
641
703
|
elif max_value > base.max_value:
|
642
|
-
raise TypeError(
|
704
|
+
raise TypeError(
|
705
|
+
f'{self!r} cannot extend {base!r}: max_value is larger.'
|
706
|
+
)
|
643
707
|
|
644
708
|
if (min_value is not None and max_value is not None and
|
645
709
|
min_value > max_value):
|
646
710
|
raise TypeError(
|
647
|
-
f'{self} cannot extend {base}: '
|
711
|
+
f'{self!r} cannot extend {base!r}: '
|
648
712
|
f'min_value ({min_value}) is greater than max_value ({max_value}) '
|
649
713
|
'after extension.')
|
650
714
|
self._min_value = min_value
|
@@ -664,17 +728,27 @@ class Number(Generic, PrimitiveType):
|
|
664
728
|
return (self.min_value == other.min_value
|
665
729
|
and self.max_value == other.max_value)
|
666
730
|
|
667
|
-
def format(
|
731
|
+
def format(
|
732
|
+
self,
|
733
|
+
compact: bool = False,
|
734
|
+
verbose: bool = True,
|
735
|
+
root_indent: int = 0,
|
736
|
+
**kwargs
|
737
|
+
) -> str:
|
668
738
|
"""Format this object."""
|
669
|
-
|
670
|
-
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
|
675
|
-
|
676
|
-
|
677
|
-
|
739
|
+
return utils.kvlist_str(
|
740
|
+
[
|
741
|
+
('default', self._default, MISSING_VALUE),
|
742
|
+
('min', self._min_value, None),
|
743
|
+
('max', self._max_value, None),
|
744
|
+
('noneable', self._is_noneable, False),
|
745
|
+
('frozen', self._frozen, False),
|
746
|
+
],
|
747
|
+
label=self.__class__.__name__,
|
748
|
+
compact=compact,
|
749
|
+
verbose=verbose,
|
750
|
+
root_indent=root_indent,
|
751
|
+
**kwargs,
|
678
752
|
)
|
679
753
|
|
680
754
|
def to_json(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]:
|
@@ -862,6 +936,12 @@ class Enum(Generic, PrimitiveType):
|
|
862
936
|
value_type, default, is_noneable=is_noneable, frozen=frozen
|
863
937
|
)
|
864
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
|
+
|
865
945
|
def noneable(self) -> 'Enum':
|
866
946
|
"""Noneable is specially treated for Enum."""
|
867
947
|
if None not in self._values:
|
@@ -874,19 +954,31 @@ class Enum(Generic, PrimitiveType):
|
|
874
954
|
"""Returns all acceptable values of this spec."""
|
875
955
|
return self._values
|
876
956
|
|
877
|
-
def _validate(self, path:
|
957
|
+
def _validate(self, path: utils.KeyPath, value: typing.Any) -> None:
|
878
958
|
"""Validates applied value."""
|
879
959
|
if value not in self._values:
|
880
960
|
raise ValueError(
|
881
|
-
|
882
|
-
f'Value {value!r} is not in candidate list {self._values}.',
|
883
|
-
|
961
|
+
utils.message_on_path(
|
962
|
+
f'Value {value!r} is not in candidate list {self._values}.', path
|
963
|
+
)
|
964
|
+
)
|
884
965
|
|
885
966
|
def _extend(self, base: 'Enum') -> None:
|
886
967
|
"""Enum specific extend."""
|
887
|
-
|
888
|
-
|
889
|
-
|
968
|
+
for v in self._values:
|
969
|
+
try:
|
970
|
+
_ = base.apply(v)
|
971
|
+
except (TypeError, ValueError)as e:
|
972
|
+
raise TypeError(
|
973
|
+
f'{self!r} cannot extend {base!r}: '
|
974
|
+
f'{repr(v)} is not an acceptable value.'
|
975
|
+
) from e
|
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)
|
890
982
|
|
891
983
|
def _is_compatible(self, other: 'Enum') -> bool:
|
892
984
|
"""Enum specific compatibility check."""
|
@@ -906,15 +998,25 @@ class Enum(Generic, PrimitiveType):
|
|
906
998
|
def _eq(self, other: 'Enum') -> bool:
|
907
999
|
return self.values == other.values
|
908
1000
|
|
909
|
-
def format(
|
1001
|
+
def format(
|
1002
|
+
self,
|
1003
|
+
compact: bool = False,
|
1004
|
+
verbose: bool = True,
|
1005
|
+
root_indent: int = 0,
|
1006
|
+
**kwargs
|
1007
|
+
) -> str:
|
910
1008
|
"""Format this object."""
|
911
|
-
|
912
|
-
|
913
|
-
|
914
|
-
|
915
|
-
|
916
|
-
|
917
|
-
|
1009
|
+
return utils.kvlist_str(
|
1010
|
+
[
|
1011
|
+
('default', self._default, MISSING_VALUE),
|
1012
|
+
('values', self._values, None),
|
1013
|
+
('frozen', self._frozen, False),
|
1014
|
+
],
|
1015
|
+
label=self.__class__.__name__,
|
1016
|
+
compact=compact,
|
1017
|
+
verbose=verbose,
|
1018
|
+
root_indent=root_indent,
|
1019
|
+
**kwargs,
|
918
1020
|
)
|
919
1021
|
|
920
1022
|
def to_json(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]:
|
@@ -1016,6 +1118,12 @@ class List(Generic, ValueSpecBase):
|
|
1016
1118
|
list, default, transform, is_noneable=is_noneable, frozen=frozen
|
1017
1119
|
)
|
1018
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
|
+
|
1019
1127
|
@property
|
1020
1128
|
def element(self) -> Field:
|
1021
1129
|
"""Returns Field specification of list element."""
|
@@ -1036,12 +1144,15 @@ class List(Generic, ValueSpecBase):
|
|
1036
1144
|
"""Returns max size of the list."""
|
1037
1145
|
return self._element.key.max_value # pytype: disable=attribute-error # bind-properties
|
1038
1146
|
|
1039
|
-
def _apply(
|
1040
|
-
|
1041
|
-
|
1042
|
-
|
1043
|
-
|
1044
|
-
|
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:
|
1045
1156
|
"""List specific apply."""
|
1046
1157
|
# NOTE(daiyip): for symbolic List, write access using `__setitem__` will
|
1047
1158
|
# trigger permission error when `accessor_writable` is set to False.
|
@@ -1058,27 +1169,35 @@ class List(Generic, ValueSpecBase):
|
|
1058
1169
|
getitem = getattr(value, 'sym_getattr', value.__getitem__)
|
1059
1170
|
for i in range(len(value)):
|
1060
1171
|
v = self._element.apply(
|
1061
|
-
getitem(i),
|
1062
|
-
|
1172
|
+
getitem(i),
|
1173
|
+
allow_partial=allow_partial,
|
1174
|
+
transform_fn=child_transform,
|
1175
|
+
root_path=utils.KeyPath(i, root_path),
|
1176
|
+
)
|
1063
1177
|
if getitem(i) is not v:
|
1064
1178
|
set_item(i, v)
|
1065
1179
|
return value
|
1066
1180
|
|
1067
|
-
def _validate(
|
1068
|
-
self, path: object_utils.KeyPath, value: typing.List[typing.Any]):
|
1181
|
+
def _validate(self, path: utils.KeyPath, value: typing.List[typing.Any]):
|
1069
1182
|
"""Validates applied value."""
|
1070
1183
|
if len(value) < self.min_size:
|
1071
1184
|
raise ValueError(
|
1072
|
-
|
1185
|
+
utils.message_on_path(
|
1073
1186
|
f'Length of list {value!r} is less than '
|
1074
|
-
f'min size ({self.min_size}).',
|
1187
|
+
f'min size ({self.min_size}).',
|
1188
|
+
path,
|
1189
|
+
)
|
1190
|
+
)
|
1075
1191
|
|
1076
1192
|
if self.max_size is not None:
|
1077
1193
|
if len(value) > self.max_size:
|
1078
1194
|
raise ValueError(
|
1079
|
-
|
1195
|
+
utils.message_on_path(
|
1080
1196
|
f'Length of list {value!r} is greater than '
|
1081
|
-
f'max size ({self.max_size}).',
|
1197
|
+
f'max size ({self.max_size}).',
|
1198
|
+
path,
|
1199
|
+
)
|
1200
|
+
)
|
1082
1201
|
|
1083
1202
|
def _extend(self, base: 'List') -> None:
|
1084
1203
|
"""List specific extend."""
|
@@ -1103,34 +1222,23 @@ class List(Generic, ValueSpecBase):
|
|
1103
1222
|
compact: bool = False,
|
1104
1223
|
verbose: bool = True,
|
1105
1224
|
root_indent: int = 0,
|
1106
|
-
*,
|
1107
|
-
markdown: bool = False,
|
1108
|
-
hide_default_values: bool = True,
|
1109
|
-
hide_missing_values: bool = True,
|
1110
1225
|
**kwargs,
|
1111
1226
|
) -> str:
|
1112
1227
|
"""Format this object."""
|
1113
|
-
|
1114
|
-
|
1115
|
-
|
1116
|
-
|
1117
|
-
|
1118
|
-
|
1119
|
-
|
1120
|
-
|
1121
|
-
|
1122
|
-
|
1123
|
-
|
1124
|
-
|
1125
|
-
|
1126
|
-
|
1127
|
-
hide_missing_values=hide_missing_values,
|
1128
|
-
**kwargs), 'MISSING_VALUE'),
|
1129
|
-
('noneable', self._is_noneable, False),
|
1130
|
-
('frozen', self._frozen, False),
|
1131
|
-
])
|
1132
|
-
return object_utils.maybe_markdown_quote(
|
1133
|
-
f'{self.__class__.__name__}({details})', markdown
|
1228
|
+
return utils.kvlist_str(
|
1229
|
+
[
|
1230
|
+
('', self._element.value, None),
|
1231
|
+
('min_size', self.min_size, 0),
|
1232
|
+
('max_size', self.max_size, None),
|
1233
|
+
('default', self._default, MISSING_VALUE),
|
1234
|
+
('noneable', self._is_noneable, False),
|
1235
|
+
('frozen', self._frozen, False),
|
1236
|
+
],
|
1237
|
+
label=self.__class__.__name__,
|
1238
|
+
compact=compact,
|
1239
|
+
verbose=verbose,
|
1240
|
+
root_indent=root_indent,
|
1241
|
+
**kwargs,
|
1134
1242
|
)
|
1135
1243
|
|
1136
1244
|
def to_json(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]:
|
@@ -1276,6 +1384,12 @@ class Tuple(Generic, ValueSpecBase):
|
|
1276
1384
|
tuple, default, transform, is_noneable=is_noneable, frozen=frozen
|
1277
1385
|
)
|
1278
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
|
+
|
1279
1393
|
@property
|
1280
1394
|
def fixed_length(self) -> bool:
|
1281
1395
|
"""Returns True if current Tuple spec is fixed length."""
|
@@ -1317,35 +1431,50 @@ class Tuple(Generic, ValueSpecBase):
|
|
1317
1431
|
"""Returns length of this tuple."""
|
1318
1432
|
return len(self._elements) if self.fixed_length else 0
|
1319
1433
|
|
1320
|
-
def _apply(
|
1321
|
-
|
1322
|
-
|
1323
|
-
|
1324
|
-
|
1325
|
-
|
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:
|
1326
1443
|
"""Tuple specific apply."""
|
1327
1444
|
if self.fixed_length:
|
1328
1445
|
if len(value) != len(self.elements):
|
1329
1446
|
raise ValueError(
|
1330
|
-
|
1447
|
+
utils.message_on_path(
|
1331
1448
|
f'Length of input tuple ({len(value)}) does not match the '
|
1332
1449
|
f'length of spec ({len(self.elements)}). '
|
1333
|
-
f'Input: {value}, Spec: {self}',
|
1450
|
+
f'Input: {value}, Spec: {self!r}',
|
1451
|
+
root_path,
|
1452
|
+
)
|
1453
|
+
)
|
1334
1454
|
else:
|
1335
1455
|
if len(value) < self.min_size:
|
1336
1456
|
raise ValueError(
|
1337
|
-
|
1457
|
+
utils.message_on_path(
|
1338
1458
|
f'Length of tuple {value} is less than '
|
1339
|
-
f'min size ({self.min_size}).',
|
1459
|
+
f'min size ({self.min_size}).',
|
1460
|
+
root_path,
|
1461
|
+
)
|
1462
|
+
)
|
1340
1463
|
if self.max_size is not None and len(value) > self.max_size:
|
1341
1464
|
raise ValueError(
|
1342
|
-
|
1465
|
+
utils.message_on_path(
|
1343
1466
|
f'Length of tuple {value} is greater than '
|
1344
|
-
f'max size ({self.max_size}).',
|
1467
|
+
f'max size ({self.max_size}).',
|
1468
|
+
root_path,
|
1469
|
+
)
|
1470
|
+
)
|
1345
1471
|
return tuple([
|
1346
1472
|
self._elements[i if self.fixed_length else 0].apply( # pylint: disable=g-complex-comprehension
|
1347
|
-
v,
|
1348
|
-
|
1473
|
+
v,
|
1474
|
+
allow_partial=allow_partial,
|
1475
|
+
transform_fn=child_transform,
|
1476
|
+
root_path=utils.KeyPath(i, root_path),
|
1477
|
+
)
|
1349
1478
|
for i, v in enumerate(value)
|
1350
1479
|
])
|
1351
1480
|
|
@@ -1354,34 +1483,34 @@ class Tuple(Generic, ValueSpecBase):
|
|
1354
1483
|
if self.fixed_length and base.fixed_length:
|
1355
1484
|
if len(self.elements) != len(base.elements):
|
1356
1485
|
raise TypeError(
|
1357
|
-
f'{self} cannot extend {base}: unmatched number of elements.')
|
1486
|
+
f'{self!r} cannot extend {base!r}: unmatched number of elements.')
|
1358
1487
|
for i, element in enumerate(self._elements):
|
1359
1488
|
element.extend(base.elements[i])
|
1360
1489
|
elif self.fixed_length and not base.fixed_length:
|
1361
1490
|
if base.min_size > len(self):
|
1362
1491
|
raise TypeError(
|
1363
|
-
f'{self} cannot extend {base} as it has '
|
1492
|
+
f'{self!r} cannot extend {base!r} as it has '
|
1364
1493
|
f'less elements than required.')
|
1365
1494
|
if base.max_size is not None and base.max_size < len(self):
|
1366
1495
|
raise TypeError(
|
1367
|
-
f'{self} cannot extend {base} as it has '
|
1496
|
+
f'{self!r} cannot extend {base!r} as it has '
|
1368
1497
|
f'more elements than required.')
|
1369
1498
|
for i, element in enumerate(self._elements):
|
1370
1499
|
element.extend(base.elements[0])
|
1371
1500
|
elif not self.fixed_length and base.fixed_length:
|
1372
1501
|
raise TypeError(
|
1373
|
-
f'{self} cannot extend {base}: a variable length tuple '
|
1502
|
+
f'{self!r} cannot extend {base!r}: a variable length tuple '
|
1374
1503
|
f'cannot extend a fixed length tuple.')
|
1375
1504
|
else:
|
1376
1505
|
assert not self.fixed_length and not base.fixed_length
|
1377
1506
|
if self.min_size != 0 and self.min_size < base.min_size:
|
1378
1507
|
raise TypeError(
|
1379
|
-
f'{self} cannot extend {base} as it has smaller min size.')
|
1508
|
+
f'{self!r} cannot extend {base!r} as it has smaller min size.')
|
1380
1509
|
if (self.max_size is not None
|
1381
1510
|
and base.max_size is not None
|
1382
1511
|
and self.max_size > base.max_size):
|
1383
1512
|
raise TypeError(
|
1384
|
-
f'{self} cannot extend {base} as it has greater max size.')
|
1513
|
+
f'{self!r} cannot extend {base!r} as it has greater max size.')
|
1385
1514
|
if self._min_size == 0:
|
1386
1515
|
self._min_size = base.min_size
|
1387
1516
|
if self._max_size is None:
|
@@ -1426,56 +1555,30 @@ class Tuple(Generic, ValueSpecBase):
|
|
1426
1555
|
compact: bool = False,
|
1427
1556
|
verbose: bool = True,
|
1428
1557
|
root_indent: int = 0,
|
1429
|
-
*,
|
1430
|
-
markdown: bool = False,
|
1431
|
-
hide_default_values: bool = True,
|
1432
|
-
hide_missing_values: bool = True,
|
1433
1558
|
**kwargs,
|
1434
1559
|
) -> str:
|
1435
1560
|
"""Format this object."""
|
1436
1561
|
if self.fixed_length:
|
1437
|
-
|
1438
|
-
|
1439
|
-
('', object_utils.format(
|
1440
|
-
element_values,
|
1441
|
-
compact=compact,
|
1442
|
-
verbose=verbose,
|
1443
|
-
root_indent=root_indent,
|
1444
|
-
**kwargs), None),
|
1445
|
-
('default', object_utils.format(
|
1446
|
-
self._default,
|
1447
|
-
compact=compact,
|
1448
|
-
verbose=verbose,
|
1449
|
-
root_indent=root_indent + 1,
|
1450
|
-
hide_default_values=hide_default_values,
|
1451
|
-
hide_missing_values=hide_missing_values,
|
1452
|
-
**kwargs), 'MISSING_VALUE'),
|
1453
|
-
('noneable', self._is_noneable, False),
|
1454
|
-
('frozen', self._frozen, False),
|
1455
|
-
])
|
1456
|
-
s = f'{self.__class__.__name__}({details})'
|
1562
|
+
value = [f.value for f in self._elements]
|
1563
|
+
default_min, default_max = self._min_size, self._max_size
|
1457
1564
|
else:
|
1458
|
-
|
1459
|
-
|
1460
|
-
|
1461
|
-
|
1462
|
-
|
1463
|
-
|
1464
|
-
|
1465
|
-
|
1466
|
-
|
1467
|
-
|
1468
|
-
|
1469
|
-
|
1470
|
-
|
1471
|
-
|
1472
|
-
|
1473
|
-
|
1474
|
-
|
1475
|
-
('noneable', self._is_noneable, False),
|
1476
|
-
])
|
1477
|
-
s = f'{self.__class__.__name__}({details})'
|
1478
|
-
return object_utils.maybe_markdown_quote(s, markdown)
|
1565
|
+
value = self._elements[0].value
|
1566
|
+
default_min, default_max = 0, None
|
1567
|
+
return utils.kvlist_str(
|
1568
|
+
[
|
1569
|
+
('', value, None),
|
1570
|
+
('default', self._default, MISSING_VALUE),
|
1571
|
+
('min_size', self._min_size, default_min),
|
1572
|
+
('max_size', self._max_size, default_max),
|
1573
|
+
('noneable', self._is_noneable, False),
|
1574
|
+
('frozen', self._frozen, False),
|
1575
|
+
],
|
1576
|
+
label=self.__class__.__name__,
|
1577
|
+
compact=compact,
|
1578
|
+
verbose=verbose,
|
1579
|
+
root_indent=root_indent,
|
1580
|
+
**kwargs,
|
1581
|
+
)
|
1479
1582
|
|
1480
1583
|
def to_json(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]:
|
1481
1584
|
if self.fixed_length:
|
@@ -1547,12 +1650,12 @@ class Dict(Generic, ValueSpecBase):
|
|
1547
1650
|
|
1548
1651
|
def __init__(
|
1549
1652
|
self,
|
1550
|
-
schema: typing.
|
1551
|
-
|
1552
|
-
|
1553
|
-
|
1554
|
-
|
1555
|
-
|
1653
|
+
schema: typing.Union[
|
1654
|
+
class_schema.ValueSpec,
|
1655
|
+
Schema,
|
1656
|
+
typing.Dict[class_schema.FieldKeyDef, class_schema.FieldValueDef],
|
1657
|
+
typing.List[typing.Union[Field, class_schema.FieldDef]],
|
1658
|
+
None,
|
1556
1659
|
] = None, # pylint: disable=bad-whitespace
|
1557
1660
|
default: typing.Any = MISSING_VALUE,
|
1558
1661
|
transform: typing.Optional[
|
@@ -1564,11 +1667,17 @@ class Dict(Generic, ValueSpecBase):
|
|
1564
1667
|
"""Constructor.
|
1565
1668
|
|
1566
1669
|
Args:
|
1567
|
-
schema: (Optional) a Schema object for this Dict, or a dict of
|
1568
|
-
to
|
1569
|
-
|
1570
|
-
|
1571
|
-
|
1670
|
+
schema: (Optional) a Schema object for this Dict, or a dict of field key
|
1671
|
+
to field value definition, or a list of field definitions, or a value
|
1672
|
+
spec.
|
1673
|
+
If None, it specifies a schema-less Dict that may accept arbitrary
|
1674
|
+
key/value pairs.
|
1675
|
+
If a value spec, it specifies a Dict that may accept arbitrary keys with
|
1676
|
+
values constrained by the value spec.
|
1677
|
+
A field definition is a tuple of
|
1678
|
+
(<key_spec>, <value_spec>, [description], [metadata]).
|
1679
|
+
A field value definition is a tuple of
|
1680
|
+
(<value_spec>, [description], [metadata]).
|
1572
1681
|
default: Default value. If MISSING_VALUE, the default value will be
|
1573
1682
|
computed according to the schema.
|
1574
1683
|
transform: (Optional) user-defined function to be called on the input
|
@@ -1577,8 +1686,16 @@ class Dict(Generic, ValueSpecBase):
|
|
1577
1686
|
is_noneable: If True, None is acceptable.
|
1578
1687
|
frozen: If True, values other than the default value is not accceptable.
|
1579
1688
|
"""
|
1580
|
-
if schema is not None
|
1581
|
-
|
1689
|
+
if schema is not None:
|
1690
|
+
if not isinstance(schema, (Schema, list, dict)):
|
1691
|
+
schema = [
|
1692
|
+
(
|
1693
|
+
key_specs.StrKey(),
|
1694
|
+
ValueSpec.from_annotation(schema, auto_typing=True)
|
1695
|
+
)
|
1696
|
+
]
|
1697
|
+
if not isinstance(schema, Schema):
|
1698
|
+
schema = class_schema.create_schema(schema, allow_nonconst_keys=True)
|
1582
1699
|
|
1583
1700
|
self._schema = typing.cast(typing.Optional[Schema], schema)
|
1584
1701
|
super().__init__(
|
@@ -1589,6 +1706,9 @@ class Dict(Generic, ValueSpecBase):
|
|
1589
1706
|
if MISSING_VALUE == default:
|
1590
1707
|
self.set_default(default)
|
1591
1708
|
|
1709
|
+
def __call__(self, *args, **kwargs) -> typing.Any:
|
1710
|
+
return self.apply(dict(*args, **kwargs))
|
1711
|
+
|
1592
1712
|
@property
|
1593
1713
|
def schema(self) -> typing.Optional[Schema]:
|
1594
1714
|
"""Returns the schema of this dict spec."""
|
@@ -1601,7 +1721,10 @@ class Dict(Generic, ValueSpecBase):
|
|
1601
1721
|
return self
|
1602
1722
|
|
1603
1723
|
def set_default(
|
1604
|
-
self,
|
1724
|
+
self,
|
1725
|
+
default: typing.Any,
|
1726
|
+
use_default_apply: bool = True,
|
1727
|
+
root_path: typing.Optional[utils.KeyPath] = None,
|
1605
1728
|
) -> ValueSpec:
|
1606
1729
|
if MISSING_VALUE == default and self._schema:
|
1607
1730
|
self._use_generated_default = True
|
@@ -1621,12 +1744,15 @@ class Dict(Generic, ValueSpecBase):
|
|
1621
1744
|
forward_refs.update(field.value.forward_refs)
|
1622
1745
|
return forward_refs
|
1623
1746
|
|
1624
|
-
def _apply(
|
1625
|
-
|
1626
|
-
|
1627
|
-
|
1628
|
-
|
1629
|
-
|
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:
|
1630
1756
|
"""Dict specific apply."""
|
1631
1757
|
if not self._schema:
|
1632
1758
|
return value
|
@@ -1634,7 +1760,8 @@ class Dict(Generic, ValueSpecBase):
|
|
1634
1760
|
value,
|
1635
1761
|
allow_partial=allow_partial,
|
1636
1762
|
child_transform=child_transform,
|
1637
|
-
root_path=root_path
|
1763
|
+
root_path=root_path
|
1764
|
+
)
|
1638
1765
|
|
1639
1766
|
def _extend(self, base: 'Dict') -> None:
|
1640
1767
|
"""Dict specific extension."""
|
@@ -1666,28 +1793,24 @@ class Dict(Generic, ValueSpecBase):
|
|
1666
1793
|
compact: bool = False,
|
1667
1794
|
verbose: bool = True,
|
1668
1795
|
root_indent: int = 0,
|
1669
|
-
*,
|
1670
|
-
markdown: bool = False,
|
1671
1796
|
**kwargs,
|
1672
1797
|
) -> str:
|
1673
1798
|
"""Format this object."""
|
1674
|
-
|
1675
|
-
|
1676
|
-
|
1677
|
-
|
1678
|
-
|
1679
|
-
|
1680
|
-
|
1681
|
-
|
1682
|
-
|
1683
|
-
|
1684
|
-
|
1685
|
-
|
1686
|
-
|
1687
|
-
|
1688
|
-
|
1689
|
-
return object_utils.maybe_markdown_quote(
|
1690
|
-
f'{self.__class__.__name__}({details})', markdown
|
1799
|
+
return utils.kvlist_str(
|
1800
|
+
[
|
1801
|
+
(
|
1802
|
+
'fields',
|
1803
|
+
list(self._schema.values()) if self._schema else None,
|
1804
|
+
None,
|
1805
|
+
),
|
1806
|
+
('noneable', self._is_noneable, False),
|
1807
|
+
('frozen', self._frozen, False),
|
1808
|
+
],
|
1809
|
+
label=self.__class__.__name__,
|
1810
|
+
compact=compact,
|
1811
|
+
verbose=verbose,
|
1812
|
+
root_indent=root_indent,
|
1813
|
+
**kwargs,
|
1691
1814
|
)
|
1692
1815
|
|
1693
1816
|
def to_json(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]:
|
@@ -1746,7 +1869,11 @@ class Object(Generic, ValueSpecBase):
|
|
1746
1869
|
|
1747
1870
|
def __init__(
|
1748
1871
|
self,
|
1749
|
-
t: typing.Union[
|
1872
|
+
t: typing.Union[
|
1873
|
+
typing.Type[typing.Any],
|
1874
|
+
class_schema.ForwardRef,
|
1875
|
+
str
|
1876
|
+
],
|
1750
1877
|
default: typing.Any = MISSING_VALUE,
|
1751
1878
|
transform: typing.Optional[
|
1752
1879
|
typing.Callable[[typing.Any], typing.Any]
|
@@ -1770,7 +1897,9 @@ class Object(Generic, ValueSpecBase):
|
|
1770
1897
|
|
1771
1898
|
forward_ref = None
|
1772
1899
|
type_args = []
|
1773
|
-
if isinstance(t,
|
1900
|
+
if isinstance(t, class_schema.ForwardRef):
|
1901
|
+
forward_ref = t
|
1902
|
+
elif isinstance(t, str):
|
1774
1903
|
forward_ref = class_schema.ForwardRef(_get_spec_callsite_module(), t)
|
1775
1904
|
elif isinstance(t, type):
|
1776
1905
|
if t is object:
|
@@ -1784,6 +1913,9 @@ class Object(Generic, ValueSpecBase):
|
|
1784
1913
|
t, default, transform, is_noneable=is_noneable, frozen=frozen
|
1785
1914
|
)
|
1786
1915
|
|
1916
|
+
def __call__(self, *args, **kwargs) -> typing.Any:
|
1917
|
+
return self.apply(self.cls(*args, **kwargs))
|
1918
|
+
|
1787
1919
|
@property
|
1788
1920
|
def forward_refs(self) -> typing.Set[class_schema.ForwardRef]:
|
1789
1921
|
"""Returns forward references used in this spec."""
|
@@ -1802,19 +1934,24 @@ class Object(Generic, ValueSpecBase):
|
|
1802
1934
|
def value_type(self) -> typing.Type[typing.Any]:
|
1803
1935
|
return self.cls
|
1804
1936
|
|
1805
|
-
def _apply(
|
1806
|
-
|
1807
|
-
|
1808
|
-
|
1809
|
-
|
1810
|
-
|
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:
|
1811
1946
|
"""Object specific apply."""
|
1812
1947
|
del child_transform
|
1813
|
-
if isinstance(value,
|
1948
|
+
if isinstance(value, utils.MaybePartial):
|
1814
1949
|
if not allow_partial and value.is_partial:
|
1815
1950
|
raise ValueError(
|
1816
|
-
|
1817
|
-
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
|
+
)
|
1818
1955
|
return value
|
1819
1956
|
|
1820
1957
|
def extend(self, base: ValueSpec) -> ValueSpec:
|
@@ -1858,10 +1995,6 @@ class Object(Generic, ValueSpecBase):
|
|
1858
1995
|
compact: bool = False,
|
1859
1996
|
verbose: bool = True,
|
1860
1997
|
root_indent: int = 0,
|
1861
|
-
*,
|
1862
|
-
markdown: bool = False,
|
1863
|
-
hide_default_values: bool = True,
|
1864
|
-
hide_missing_values: bool = True,
|
1865
1998
|
**kwargs,
|
1866
1999
|
) -> str:
|
1867
2000
|
"""Format this object."""
|
@@ -1869,22 +2002,18 @@ class Object(Generic, ValueSpecBase):
|
|
1869
2002
|
name = self._forward_ref.name
|
1870
2003
|
else:
|
1871
2004
|
name = self._value_type.__name__
|
1872
|
-
|
1873
|
-
|
1874
|
-
|
1875
|
-
|
1876
|
-
self.
|
1877
|
-
|
1878
|
-
|
1879
|
-
|
1880
|
-
|
1881
|
-
|
1882
|
-
|
1883
|
-
|
1884
|
-
('frozen', self._frozen, False),
|
1885
|
-
])
|
1886
|
-
return object_utils.maybe_markdown_quote(
|
1887
|
-
f'{self.__class__.__name__}({details})', markdown
|
2005
|
+
return utils.kvlist_str(
|
2006
|
+
[
|
2007
|
+
('', utils.RawText(name), None),
|
2008
|
+
('default', self._default, MISSING_VALUE),
|
2009
|
+
('noneable', self._is_noneable, False),
|
2010
|
+
('frozen', self._frozen, False),
|
2011
|
+
],
|
2012
|
+
label=self.__class__.__name__,
|
2013
|
+
compact=compact,
|
2014
|
+
verbose=verbose,
|
2015
|
+
root_indent=root_indent,
|
2016
|
+
**kwargs,
|
1888
2017
|
)
|
1889
2018
|
|
1890
2019
|
def to_json(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]:
|
@@ -1990,6 +2119,10 @@ class Callable(Generic, ValueSpecBase):
|
|
1990
2119
|
frozen=frozen,
|
1991
2120
|
)
|
1992
2121
|
|
2122
|
+
def __call__(self, *args, **kwargs) -> typing.Any:
|
2123
|
+
del args, kwargs
|
2124
|
+
raise TypeError(f'{self!r} cannot be instantiated.')
|
2125
|
+
|
1993
2126
|
@functools.cached_property
|
1994
2127
|
def forward_refs(self) -> typing.Set[class_schema.ForwardRef]:
|
1995
2128
|
"""Returns forward references used in this spec."""
|
@@ -2017,25 +2150,29 @@ class Callable(Generic, ValueSpecBase):
|
|
2017
2150
|
"""Value spec for return value."""
|
2018
2151
|
return self._return_value
|
2019
2152
|
|
2020
|
-
def _validate(self, path:
|
2153
|
+
def _validate(self, path: utils.KeyPath, value: typing.Any) -> None:
|
2021
2154
|
"""Validate applied value."""
|
2022
2155
|
if not callable(value):
|
2023
2156
|
raise TypeError(
|
2024
|
-
|
2025
|
-
|
2157
|
+
utils.message_on_path(f'Value is not callable: {value!r}.', path)
|
2158
|
+
)
|
2026
2159
|
|
2027
2160
|
# Shortcircuit if there is no signature to check.
|
2028
2161
|
if not (self._args or self._kw or self._return_value):
|
2029
2162
|
return
|
2030
2163
|
|
2031
|
-
signature = callable_signature.
|
2164
|
+
signature = callable_signature.signature(
|
2165
|
+
value, auto_typing=False, auto_doc=False
|
2166
|
+
)
|
2032
2167
|
|
2033
2168
|
if len(self._args) > len(signature.args) and not signature.has_varargs:
|
2034
2169
|
raise TypeError(
|
2035
|
-
|
2170
|
+
utils.message_on_path(
|
2036
2171
|
f'{signature.id} only take {len(signature.args)} positional '
|
2037
2172
|
f'arguments, while {len(self._args)} is required by {self!r}.',
|
2038
|
-
path
|
2173
|
+
path,
|
2174
|
+
)
|
2175
|
+
)
|
2039
2176
|
|
2040
2177
|
# Check positional arguments.
|
2041
2178
|
for i in range(min(len(self._args), len(signature.args))):
|
@@ -2043,22 +2180,27 @@ class Callable(Generic, ValueSpecBase):
|
|
2043
2180
|
dest_spec = signature.args[i].value_spec
|
2044
2181
|
if not dest_spec.is_compatible(src_spec):
|
2045
2182
|
raise TypeError(
|
2046
|
-
|
2183
|
+
utils.message_on_path(
|
2047
2184
|
f'Value spec of positional argument {i} is not compatible. '
|
2048
2185
|
f'Expected: {dest_spec!r}, Actual: {src_spec!r}.',
|
2049
|
-
path
|
2186
|
+
path,
|
2187
|
+
)
|
2188
|
+
)
|
2050
2189
|
if len(self._args) > len(signature.args):
|
2051
|
-
assert signature.
|
2052
|
-
assert signature.varargs
|
2053
|
-
dest_spec = signature.varargs.value_spec
|
2190
|
+
assert signature.varargs
|
2191
|
+
assert isinstance(signature.varargs.value_spec, List), signature.varargs
|
2192
|
+
dest_spec = signature.varargs.value_spec.element.value
|
2054
2193
|
for i in range(len(signature.args), len(self._args)):
|
2055
2194
|
src_spec = self._args[i]
|
2056
2195
|
if not dest_spec.is_compatible(src_spec):
|
2057
2196
|
raise TypeError(
|
2058
|
-
|
2197
|
+
utils.message_on_path(
|
2059
2198
|
f'Value spec of positional argument {i} is not compatible '
|
2060
2199
|
f'with the value spec of *{signature.varargs.name}. '
|
2061
|
-
f'Expected: {dest_spec!r}, Actual: {src_spec!r}.',
|
2200
|
+
f'Expected: {dest_spec!r}, Actual: {src_spec!r}.',
|
2201
|
+
path,
|
2202
|
+
)
|
2203
|
+
)
|
2062
2204
|
|
2063
2205
|
# Check keyword arguments.
|
2064
2206
|
dest_args = signature.args + signature.kwonlyargs
|
@@ -2071,36 +2213,46 @@ class Callable(Generic, ValueSpecBase):
|
|
2071
2213
|
if dest_spec is not None:
|
2072
2214
|
if not dest_spec.is_compatible(src_spec):
|
2073
2215
|
raise TypeError(
|
2074
|
-
|
2216
|
+
utils.message_on_path(
|
2075
2217
|
f'Value spec of keyword argument {arg_name!r} is not '
|
2076
2218
|
f'compatible. Expected: {src_spec!r}, Actual: {dest_spec!r}.',
|
2077
|
-
path
|
2078
|
-
|
2079
|
-
|
2080
|
-
|
2219
|
+
path,
|
2220
|
+
)
|
2221
|
+
)
|
2222
|
+
elif signature.varkw:
|
2223
|
+
assert isinstance(signature.varkw.value_spec, Dict), signature.varkw
|
2224
|
+
varkw_value_spec = signature.varkw.value_spec.schema.dynamic_field.value # pytype: disable=attribute-error
|
2225
|
+
if not varkw_value_spec.is_compatible(src_spec):
|
2081
2226
|
raise TypeError(
|
2082
|
-
|
2227
|
+
utils.message_on_path(
|
2083
2228
|
f'Value spec of keyword argument {arg_name!r} is not '
|
2084
|
-
|
2229
|
+
'compatible with the value spec of '
|
2085
2230
|
f'**{signature.varkw.name}. '
|
2086
|
-
f'Expected: {
|
2087
|
-
f'Actual: {src_spec!r}.',
|
2231
|
+
f'Expected: {varkw_value_spec!r}, '
|
2232
|
+
f'Actual: {src_spec!r}.',
|
2233
|
+
path,
|
2234
|
+
)
|
2235
|
+
)
|
2088
2236
|
else:
|
2089
2237
|
raise TypeError(
|
2090
|
-
|
2238
|
+
utils.message_on_path(
|
2091
2239
|
f'Keyword argument {arg_name!r} does not exist in {value!r}.',
|
2092
|
-
path
|
2240
|
+
path,
|
2241
|
+
)
|
2242
|
+
)
|
2093
2243
|
|
2094
2244
|
# Check return value
|
2095
2245
|
if (self._return_value and signature.return_value
|
2096
2246
|
and not isinstance(signature.return_value, Any)
|
2097
2247
|
and not self._return_value.is_compatible(signature.return_value)):
|
2098
2248
|
raise TypeError(
|
2099
|
-
|
2100
|
-
|
2249
|
+
utils.message_on_path(
|
2250
|
+
'Value spec for return value is not compatible. '
|
2101
2251
|
f'Expected: {self._return_value!r}, '
|
2102
2252
|
f'Actual: {signature.return_value!r} ({value!r}).',
|
2103
|
-
path
|
2253
|
+
path,
|
2254
|
+
)
|
2255
|
+
)
|
2104
2256
|
|
2105
2257
|
def _extend(self, base: 'Callable') -> None:
|
2106
2258
|
"""Callable specific extension."""
|
@@ -2163,19 +2315,28 @@ class Callable(Generic, ValueSpecBase):
|
|
2163
2315
|
and self._kw == other.kw
|
2164
2316
|
and self._return_value == other.return_value)
|
2165
2317
|
|
2166
|
-
def format(
|
2318
|
+
def format(
|
2319
|
+
self,
|
2320
|
+
compact: bool = False,
|
2321
|
+
verbose: bool = True,
|
2322
|
+
root_indent: int = 0,
|
2323
|
+
**kwargs,
|
2324
|
+
) -> str:
|
2167
2325
|
"""Format this spec."""
|
2168
|
-
|
2169
|
-
|
2170
|
-
|
2171
|
-
|
2172
|
-
|
2173
|
-
self._default,
|
2174
|
-
|
2175
|
-
|
2176
|
-
|
2177
|
-
|
2178
|
-
|
2326
|
+
return utils.kvlist_str(
|
2327
|
+
[
|
2328
|
+
('args', self._args, []),
|
2329
|
+
('kw', self._kw, []),
|
2330
|
+
('returns', self._return_value, None),
|
2331
|
+
('default', self._default, MISSING_VALUE),
|
2332
|
+
('noneable', self._is_noneable, False),
|
2333
|
+
('frozen', self._frozen, False),
|
2334
|
+
],
|
2335
|
+
label=self.__class__.__name__,
|
2336
|
+
compact=compact,
|
2337
|
+
verbose=verbose,
|
2338
|
+
root_indent=root_indent,
|
2339
|
+
**kwargs,
|
2179
2340
|
)
|
2180
2341
|
|
2181
2342
|
def to_json(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]:
|
@@ -2261,14 +2422,14 @@ class Functor(Callable):
|
|
2261
2422
|
returns=returns,
|
2262
2423
|
default=default,
|
2263
2424
|
transform=transform,
|
2264
|
-
callable_type=
|
2425
|
+
callable_type=utils.Functor,
|
2265
2426
|
is_noneable=is_noneable,
|
2266
2427
|
frozen=frozen,
|
2267
2428
|
)
|
2268
2429
|
|
2269
2430
|
def _annotate(self) -> typing.Any:
|
2270
2431
|
"""Annotate with PyType annotation."""
|
2271
|
-
return
|
2432
|
+
return utils.Functor
|
2272
2433
|
|
2273
2434
|
def to_json(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]:
|
2274
2435
|
exclude_keys = kwargs.pop('exclude_keys', set())
|
@@ -2315,6 +2476,10 @@ class Type(Generic, ValueSpecBase):
|
|
2315
2476
|
self._forward_ref = forward_ref
|
2316
2477
|
super().__init__(type, default, is_noneable=is_noneable, frozen=frozen)
|
2317
2478
|
|
2479
|
+
def __call__(self, *args, **kwargs) -> typing.Any:
|
2480
|
+
del args, kwargs
|
2481
|
+
return self.type
|
2482
|
+
|
2318
2483
|
@property
|
2319
2484
|
def type(self) -> typing.Type[typing.Any]:
|
2320
2485
|
"""Returns desired type."""
|
@@ -2329,12 +2494,14 @@ class Type(Generic, ValueSpecBase):
|
|
2329
2494
|
return set()
|
2330
2495
|
return set([self._forward_ref])
|
2331
2496
|
|
2332
|
-
def _validate(self, path:
|
2497
|
+
def _validate(self, path: utils.KeyPath, value: typing.Type) -> None: # pylint: disable=g-bare-generic
|
2333
2498
|
"""Validate applied value."""
|
2334
2499
|
if self.type_resolved and not pg_inspect.is_subclass(value, self.type):
|
2335
2500
|
raise ValueError(
|
2336
|
-
|
2337
|
-
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
|
+
)
|
2338
2505
|
|
2339
2506
|
def _is_compatible(self, other: 'Type') -> bool:
|
2340
2507
|
"""Type specific compatiblity check."""
|
@@ -2362,16 +2529,26 @@ class Type(Generic, ValueSpecBase):
|
|
2362
2529
|
return self.type == other.type
|
2363
2530
|
return self.forward_refs == other.forward_refs
|
2364
2531
|
|
2365
|
-
def format(
|
2532
|
+
def format(
|
2533
|
+
self,
|
2534
|
+
compact: bool = False,
|
2535
|
+
verbose: bool = True,
|
2536
|
+
root_indent: int = 0,
|
2537
|
+
**kwargs,
|
2538
|
+
) -> str:
|
2366
2539
|
"""Format this object."""
|
2367
|
-
|
2368
|
-
|
2369
|
-
|
2370
|
-
|
2371
|
-
|
2372
|
-
|
2373
|
-
|
2374
|
-
|
2540
|
+
return utils.kvlist_str(
|
2541
|
+
[
|
2542
|
+
('', self._expected_type, None),
|
2543
|
+
('default', self._default, MISSING_VALUE),
|
2544
|
+
('noneable', self._is_noneable, False),
|
2545
|
+
('frozen', self._frozen, False),
|
2546
|
+
],
|
2547
|
+
label=self.__class__.__name__,
|
2548
|
+
compact=compact,
|
2549
|
+
verbose=verbose,
|
2550
|
+
root_indent=root_indent,
|
2551
|
+
**kwargs,
|
2375
2552
|
)
|
2376
2553
|
|
2377
2554
|
def to_json(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]:
|
@@ -2493,6 +2670,10 @@ class Union(Generic, ValueSpecBase):
|
|
2493
2670
|
frozen=frozen,
|
2494
2671
|
)
|
2495
2672
|
|
2673
|
+
def __call__(self, *args, **kwargs) -> typing.Any:
|
2674
|
+
del args, kwargs
|
2675
|
+
raise TypeError(f'{self!r} cannot be instantiated.')
|
2676
|
+
|
2496
2677
|
@functools.cached_property
|
2497
2678
|
def forward_refs(self) -> typing.Set[class_schema.ForwardRef]:
|
2498
2679
|
"""Returns forward references used in this spec."""
|
@@ -2557,44 +2738,63 @@ class Union(Generic, ValueSpecBase):
|
|
2557
2738
|
return c
|
2558
2739
|
return None
|
2559
2740
|
|
2560
|
-
def _apply(
|
2561
|
-
|
2562
|
-
|
2563
|
-
|
2564
|
-
|
2565
|
-
|
2566
|
-
|
2567
|
-
|
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:
|
2568
2750
|
"""Union specific apply."""
|
2751
|
+
# Match strong-typed candidates first.
|
2752
|
+
if not self.type_resolved:
|
2753
|
+
return value
|
2754
|
+
|
2569
2755
|
for c in self._candidates:
|
2570
|
-
if (c.
|
2571
|
-
and (c.value_type is None or isinstance(value, c.value_type))):
|
2756
|
+
if c.value_type is not None and isinstance(value, c.value_type):
|
2572
2757
|
return c.apply(
|
2573
2758
|
value,
|
2574
2759
|
allow_partial=allow_partial,
|
2575
2760
|
child_transform=child_transform,
|
2576
|
-
root_path=root_path
|
2761
|
+
root_path=root_path
|
2762
|
+
)
|
2763
|
+
|
2764
|
+
def _try_candidate(c, value) -> typing.Tuple[typing.Any, bool]:
|
2765
|
+
try:
|
2766
|
+
return c.apply(
|
2767
|
+
value, allow_partial=allow_partial,
|
2768
|
+
child_transform=child_transform, root_path=root_path
|
2769
|
+
), True
|
2770
|
+
except TypeError:
|
2771
|
+
return value, False
|
2772
|
+
|
2773
|
+
# Match non-strong-typed candidates (e.g. Callable).
|
2774
|
+
for c in self._candidates:
|
2775
|
+
if c.value_type is None:
|
2776
|
+
value, success = _try_candidate(c, value)
|
2777
|
+
if success:
|
2778
|
+
return value
|
2577
2779
|
|
2578
2780
|
# NOTE(daiyip): This code is to support consider A as B scenario when there
|
2579
2781
|
# is a converter from A to B (converter may return value that is not B). A
|
2580
2782
|
# use case is that tf.Variable is not a tf.Tensor, but value spec of
|
2581
2783
|
# tf.Tensor should be able to accept tf.Variable.
|
2582
|
-
matched_candidate = None
|
2583
2784
|
for c in self._candidates:
|
2584
|
-
if c.
|
2585
|
-
|
2586
|
-
|
2587
|
-
|
2588
|
-
|
2589
|
-
|
2590
|
-
|
2591
|
-
|
2592
|
-
|
2593
|
-
|
2594
|
-
|
2595
|
-
|
2596
|
-
|
2597
|
-
return value
|
2785
|
+
if c.value_type is None:
|
2786
|
+
continue
|
2787
|
+
converter = type_conversion.get_converter(type(value), c.value_type)
|
2788
|
+
if converter is not None:
|
2789
|
+
return c.apply(
|
2790
|
+
converter(value),
|
2791
|
+
allow_partial=allow_partial,
|
2792
|
+
child_transform=child_transform,
|
2793
|
+
root_path=root_path
|
2794
|
+
)
|
2795
|
+
raise TypeError(
|
2796
|
+
f'{value!r} does not match any candidate of {self!r}.'
|
2797
|
+
)
|
2598
2798
|
|
2599
2799
|
def _extend(self, base: 'Union') -> None:
|
2600
2800
|
"""Union specific extension."""
|
@@ -2645,26 +2845,22 @@ class Union(Generic, ValueSpecBase):
|
|
2645
2845
|
compact: bool = False,
|
2646
2846
|
verbose: bool = True,
|
2647
2847
|
root_indent: int = 0,
|
2648
|
-
*,
|
2649
|
-
markdown: bool = False,
|
2650
2848
|
**kwargs,
|
2651
2849
|
) -> str:
|
2652
2850
|
"""Format this object."""
|
2653
|
-
|
2654
|
-
|
2655
|
-
|
2656
|
-
self.
|
2657
|
-
|
2658
|
-
|
2659
|
-
|
2660
|
-
|
2661
|
-
|
2662
|
-
|
2663
|
-
|
2664
|
-
('
|
2665
|
-
|
2666
|
-
return object_utils.maybe_markdown_quote(
|
2667
|
-
f'{self.__class__.__name__}({details})', markdown
|
2851
|
+
return utils.kvlist_str(
|
2852
|
+
[
|
2853
|
+
('', self._candidates, None),
|
2854
|
+
('default', self._default, MISSING_VALUE),
|
2855
|
+
('noneable', self._is_noneable, False),
|
2856
|
+
('frozen', self._frozen, False),
|
2857
|
+
],
|
2858
|
+
label=self.__class__.__name__,
|
2859
|
+
compact=compact,
|
2860
|
+
verbose=verbose,
|
2861
|
+
root_indent=root_indent,
|
2862
|
+
list_wrap_threshold=kwargs.pop('list_wrap_threshold', 20),
|
2863
|
+
**kwargs,
|
2668
2864
|
)
|
2669
2865
|
|
2670
2866
|
def to_json(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]:
|
@@ -2796,20 +2992,33 @@ class Any(ValueSpecBase):
|
|
2796
2992
|
)
|
2797
2993
|
self._annotation = annotation
|
2798
2994
|
|
2995
|
+
def __call__(self, *args, **kwargs) -> typing.Any:
|
2996
|
+
del args, kwargs
|
2997
|
+
raise TypeError(f'{self!r} cannot be instantiated.')
|
2998
|
+
|
2799
2999
|
def is_compatible(self, other: ValueSpec) -> bool:
|
2800
3000
|
"""Any is compatible with any ValueSpec."""
|
2801
3001
|
return True
|
2802
3002
|
|
2803
|
-
def format(
|
3003
|
+
def format(
|
3004
|
+
self,
|
3005
|
+
compact: bool = False,
|
3006
|
+
verbose: bool = True,
|
3007
|
+
root_indent: int = 0,
|
3008
|
+
**kwargs,
|
3009
|
+
) -> str:
|
2804
3010
|
"""Format this object."""
|
2805
|
-
|
2806
|
-
|
2807
|
-
|
2808
|
-
|
2809
|
-
|
2810
|
-
|
2811
|
-
|
2812
|
-
|
3011
|
+
return utils.kvlist_str(
|
3012
|
+
[
|
3013
|
+
('default', self._default, MISSING_VALUE),
|
3014
|
+
('frozen', self._frozen, False),
|
3015
|
+
('annotation', self._annotation, MISSING_VALUE),
|
3016
|
+
],
|
3017
|
+
label=self.__class__.__name__,
|
3018
|
+
compact=compact,
|
3019
|
+
verbose=verbose,
|
3020
|
+
root_indent=root_indent,
|
3021
|
+
**kwargs,
|
2813
3022
|
)
|
2814
3023
|
|
2815
3024
|
def annotate(self, annotation: typing.Any) -> 'Any':
|
@@ -2874,3 +3083,36 @@ def _get_spec_callsite_module():
|
|
2874
3083
|
ValueSpec.ListType = List
|
2875
3084
|
ValueSpec.DictType = Dict
|
2876
3085
|
ValueSpec.ObjectType = Object
|
3086
|
+
|
3087
|
+
|
3088
|
+
def ensure_value_spec(
|
3089
|
+
value_spec: class_schema.ValueSpec,
|
3090
|
+
src_spec: class_schema.ValueSpec,
|
3091
|
+
root_path: typing.Optional[utils.KeyPath] = None,
|
3092
|
+
) -> typing.Optional[class_schema.ValueSpec]:
|
3093
|
+
"""Extract counter part from value spec that matches dest spec type.
|
3094
|
+
|
3095
|
+
Args:
|
3096
|
+
value_spec: Value spec.
|
3097
|
+
src_spec: Destination value spec.
|
3098
|
+
root_path: An optional path for the value to include in error message.
|
3099
|
+
|
3100
|
+
Returns:
|
3101
|
+
value_spec of src_spec_type
|
3102
|
+
|
3103
|
+
Raises:
|
3104
|
+
TypeError: When value_spec cannot match src_spec_type.
|
3105
|
+
"""
|
3106
|
+
if isinstance(value_spec, Union):
|
3107
|
+
value_spec = value_spec.get_candidate(src_spec)
|
3108
|
+
if isinstance(value_spec, Any):
|
3109
|
+
return None
|
3110
|
+
if not src_spec.is_compatible(value_spec):
|
3111
|
+
raise TypeError(
|
3112
|
+
utils.message_on_path(
|
3113
|
+
f'Source spec {src_spec} is not compatible with destination '
|
3114
|
+
f'spec {value_spec}.',
|
3115
|
+
root_path,
|
3116
|
+
)
|
3117
|
+
)
|
3118
|
+
return value_spec
|