pyglove 0.4.5.dev20240319__py3-none-any.whl → 0.4.5.dev202501132210__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. pyglove/core/__init__.py +54 -20
  2. pyglove/core/coding/__init__.py +42 -0
  3. pyglove/core/coding/errors.py +111 -0
  4. pyglove/core/coding/errors_test.py +98 -0
  5. pyglove/core/coding/execution.py +309 -0
  6. pyglove/core/coding/execution_test.py +333 -0
  7. pyglove/core/{object_utils/codegen.py → coding/function_generation.py} +10 -4
  8. pyglove/core/{object_utils/codegen_test.py → coding/function_generation_test.py} +5 -7
  9. pyglove/core/coding/parsing.py +153 -0
  10. pyglove/core/coding/parsing_test.py +150 -0
  11. pyglove/core/coding/permissions.py +100 -0
  12. pyglove/core/coding/permissions_test.py +93 -0
  13. pyglove/core/geno/base.py +54 -41
  14. pyglove/core/geno/base_test.py +2 -4
  15. pyglove/core/geno/categorical.py +37 -28
  16. pyglove/core/geno/custom.py +19 -16
  17. pyglove/core/geno/numerical.py +20 -17
  18. pyglove/core/geno/space.py +4 -5
  19. pyglove/core/hyper/base.py +6 -6
  20. pyglove/core/hyper/categorical.py +94 -55
  21. pyglove/core/hyper/custom.py +7 -7
  22. pyglove/core/hyper/custom_test.py +9 -10
  23. pyglove/core/hyper/derived.py +30 -22
  24. pyglove/core/hyper/derived_test.py +2 -4
  25. pyglove/core/hyper/dynamic_evaluation.py +5 -6
  26. pyglove/core/hyper/evolvable.py +57 -46
  27. pyglove/core/hyper/numerical.py +48 -24
  28. pyglove/core/hyper/numerical_test.py +9 -9
  29. pyglove/core/hyper/object_template.py +58 -46
  30. pyglove/core/io/__init__.py +1 -0
  31. pyglove/core/io/file_system.py +17 -7
  32. pyglove/core/io/file_system_test.py +2 -0
  33. pyglove/core/io/sequence.py +299 -0
  34. pyglove/core/io/sequence_test.py +124 -0
  35. pyglove/core/logging_test.py +0 -2
  36. pyglove/core/patching/object_factory.py +4 -4
  37. pyglove/core/patching/pattern_based.py +4 -4
  38. pyglove/core/patching/rule_based.py +17 -5
  39. pyglove/core/patching/rule_based_test.py +27 -4
  40. pyglove/core/symbolic/__init__.py +2 -7
  41. pyglove/core/symbolic/base.py +320 -183
  42. pyglove/core/symbolic/base_test.py +123 -19
  43. pyglove/core/symbolic/boilerplate.py +7 -13
  44. pyglove/core/symbolic/boilerplate_test.py +25 -23
  45. pyglove/core/symbolic/class_wrapper.py +48 -45
  46. pyglove/core/symbolic/class_wrapper_test.py +2 -2
  47. pyglove/core/symbolic/compounding.py +9 -15
  48. pyglove/core/symbolic/compounding_test.py +2 -4
  49. pyglove/core/symbolic/dict.py +154 -110
  50. pyglove/core/symbolic/dict_test.py +238 -130
  51. pyglove/core/symbolic/diff.py +199 -10
  52. pyglove/core/symbolic/diff_test.py +226 -0
  53. pyglove/core/symbolic/flags.py +1 -1
  54. pyglove/core/symbolic/functor.py +29 -26
  55. pyglove/core/symbolic/functor_test.py +102 -50
  56. pyglove/core/symbolic/inferred.py +2 -2
  57. pyglove/core/symbolic/list.py +81 -50
  58. pyglove/core/symbolic/list_test.py +119 -97
  59. pyglove/core/symbolic/object.py +225 -113
  60. pyglove/core/symbolic/object_test.py +320 -108
  61. pyglove/core/symbolic/origin.py +17 -14
  62. pyglove/core/symbolic/origin_test.py +4 -2
  63. pyglove/core/symbolic/pure_symbolic.py +4 -3
  64. pyglove/core/symbolic/ref.py +108 -21
  65. pyglove/core/symbolic/ref_test.py +93 -0
  66. pyglove/core/symbolic/symbolize_test.py +10 -2
  67. pyglove/core/tuning/local_backend.py +2 -2
  68. pyglove/core/tuning/protocols.py +3 -3
  69. pyglove/core/tuning/sample_test.py +3 -3
  70. pyglove/core/typing/__init__.py +14 -5
  71. pyglove/core/typing/annotation_conversion.py +43 -27
  72. pyglove/core/typing/annotation_conversion_test.py +23 -0
  73. pyglove/core/typing/callable_ext.py +241 -3
  74. pyglove/core/typing/callable_ext_test.py +255 -0
  75. pyglove/core/typing/callable_signature.py +510 -66
  76. pyglove/core/typing/callable_signature_test.py +619 -99
  77. pyglove/core/typing/class_schema.py +229 -154
  78. pyglove/core/typing/class_schema_test.py +149 -95
  79. pyglove/core/typing/custom_typing.py +5 -4
  80. pyglove/core/typing/inspect.py +63 -0
  81. pyglove/core/typing/inspect_test.py +39 -0
  82. pyglove/core/typing/key_specs.py +10 -11
  83. pyglove/core/typing/key_specs_test.py +7 -4
  84. pyglove/core/typing/type_conversion.py +4 -5
  85. pyglove/core/typing/type_conversion_test.py +12 -12
  86. pyglove/core/typing/typed_missing.py +6 -7
  87. pyglove/core/typing/typed_missing_test.py +7 -8
  88. pyglove/core/typing/value_specs.py +604 -362
  89. pyglove/core/typing/value_specs_test.py +328 -90
  90. pyglove/core/utils/__init__.py +164 -0
  91. pyglove/core/{object_utils → utils}/common_traits.py +3 -67
  92. pyglove/core/utils/common_traits_test.py +36 -0
  93. pyglove/core/{object_utils → utils}/docstr_utils.py +23 -0
  94. pyglove/core/{object_utils → utils}/docstr_utils_test.py +36 -4
  95. pyglove/core/{object_utils → utils}/error_utils.py +78 -9
  96. pyglove/core/{object_utils → utils}/error_utils_test.py +61 -5
  97. pyglove/core/utils/formatting.py +464 -0
  98. pyglove/core/utils/formatting_test.py +453 -0
  99. pyglove/core/{object_utils → utils}/hierarchical.py +23 -25
  100. pyglove/core/{object_utils → utils}/hierarchical_test.py +3 -5
  101. pyglove/core/{object_utils → utils}/json_conversion.py +177 -52
  102. pyglove/core/{object_utils → utils}/json_conversion_test.py +97 -16
  103. pyglove/core/{object_utils → utils}/missing.py +3 -3
  104. pyglove/core/{object_utils → utils}/missing_test.py +2 -4
  105. pyglove/core/utils/text_color.py +128 -0
  106. pyglove/core/utils/text_color_test.py +94 -0
  107. pyglove/core/{object_utils → utils}/thread_local_test.py +1 -3
  108. pyglove/core/utils/timing.py +236 -0
  109. pyglove/core/utils/timing_test.py +154 -0
  110. pyglove/core/{object_utils → utils}/value_location.py +275 -6
  111. pyglove/core/utils/value_location_test.py +707 -0
  112. pyglove/core/views/__init__.py +32 -0
  113. pyglove/core/views/base.py +804 -0
  114. pyglove/core/views/base_test.py +580 -0
  115. pyglove/core/views/html/__init__.py +27 -0
  116. pyglove/core/views/html/base.py +547 -0
  117. pyglove/core/views/html/base_test.py +830 -0
  118. pyglove/core/views/html/controls/__init__.py +35 -0
  119. pyglove/core/views/html/controls/base.py +275 -0
  120. pyglove/core/views/html/controls/label.py +207 -0
  121. pyglove/core/views/html/controls/label_test.py +157 -0
  122. pyglove/core/views/html/controls/progress_bar.py +183 -0
  123. pyglove/core/views/html/controls/progress_bar_test.py +97 -0
  124. pyglove/core/views/html/controls/tab.py +320 -0
  125. pyglove/core/views/html/controls/tab_test.py +87 -0
  126. pyglove/core/views/html/controls/tooltip.py +99 -0
  127. pyglove/core/views/html/controls/tooltip_test.py +99 -0
  128. pyglove/core/views/html/tree_view.py +1517 -0
  129. pyglove/core/views/html/tree_view_test.py +1461 -0
  130. {pyglove-0.4.5.dev20240319.dist-info → pyglove-0.4.5.dev202501132210.dist-info}/METADATA +18 -4
  131. pyglove-0.4.5.dev202501132210.dist-info/RECORD +214 -0
  132. {pyglove-0.4.5.dev20240319.dist-info → pyglove-0.4.5.dev202501132210.dist-info}/WHEEL +1 -1
  133. pyglove/core/object_utils/__init__.py +0 -154
  134. pyglove/core/object_utils/common_traits_test.py +0 -82
  135. pyglove/core/object_utils/formatting.py +0 -234
  136. pyglove/core/object_utils/formatting_test.py +0 -223
  137. pyglove/core/object_utils/value_location_test.py +0 -385
  138. pyglove/core/symbolic/schema_utils.py +0 -327
  139. pyglove/core/symbolic/schema_utils_test.py +0 -57
  140. pyglove/core/typing/class_schema_utils.py +0 -202
  141. pyglove/core/typing/class_schema_utils_test.py +0 -194
  142. pyglove-0.4.5.dev20240319.dist-info/RECORD +0 -185
  143. /pyglove/core/{object_utils → utils}/thread_local.py +0 -0
  144. {pyglove-0.4.5.dev20240319.dist-info → pyglove-0.4.5.dev202501132210.dist-info}/LICENSE +0 -0
  145. {pyglove-0.4.5.dev20240319.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 object_utils
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 = object_utils.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(self,
158
- default: typing.Any,
159
- use_default_apply: bool = True) -> ValueSpec:
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'Cannot extend a frozen value spec: {base}')
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[typing.Callable[
226
- [object_utils.KeyPath, Field, typing.Any],
227
- typing.Any
228
- ]] = None,
229
- root_path: typing.Optional[object_utils.KeyPath] = None) -> typing.Any: # pyformat: disable pylint: disable=line-too-long
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 object_utils.KeyPath()
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
- object_utils.message_on_path(str(e), root_path)
272
- ).with_traceback(sys.exc_info()[2])
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
- object_utils.message_on_path(
311
+ utils.message_on_path(
289
312
  f'Expect {self.value_type} '
290
- f'but encountered {type(value)!r}: {value}.', root_path))
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: object_utils.KeyPath, value: typing.Any):
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(self,
308
- value: typing.Any,
309
- allow_partial: bool,
310
- child_transform: typing.Callable[
311
- [object_utils.KeyPath, Field, typing.Any], typing.Any],
312
- root_path: object_utils.KeyPath) -> typing.Any:
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 is compatible with the other value 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(self, *, markdown: bool = False, **kwargs) -> str:
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
- details = object_utils.kvlist_str([
375
- ('default', object_utils.quote_if_str(self._default), MISSING_VALUE),
376
- ('noneable', self._is_noneable, False),
377
- ('frozen', self._frozen, False)
378
- ])
379
- return object_utils.maybe_markdown_quote(
380
- f'{self.__class__.__name__}({details})', markdown
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: object_utils.KeyPath, value: str) -> None:
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
- object_utils.message_on_path(
566
+ utils.message_on_path(
522
567
  f'String {value!r} does not match '
523
- f'regular expression {self._regex.pattern!r}.', path))
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(self, *, markdown: bool = False, **kwargs) -> str:
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
- details = object_utils.kvlist_str([
553
- ('default', object_utils.quote_if_str(self._default), MISSING_VALUE),
554
- ('regex', object_utils.quote_if_str(regex_pattern), None),
555
- ('noneable', self._is_noneable, False),
556
- ('frozen', self._frozen, False)
557
- ])
558
- return object_utils.maybe_markdown_quote(
559
- f'{self.__class__.__name__}({details})', markdown
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: object_utils.KeyPath,
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
- object_utils.message_on_path(
681
+ utils.message_on_path(
625
682
  f'Value {value} is out of range '
626
- f'(min={self._min_value}, max={self._max_value}).', path))
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(f'{self} cannot extend {base}: min_value is smaller.')
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(f'{self} cannot extend {base}: max_value is larger.')
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(self, *, markdown: bool = False, **kwargs) -> str:
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
- details = object_utils.kvlist_str([
670
- ('default', self._default, MISSING_VALUE),
671
- ('min', self._min_value, None),
672
- ('max', self._max_value, None),
673
- ('noneable', self._is_noneable, False),
674
- ('frozen', self._frozen, False)
675
- ])
676
- return object_utils.maybe_markdown_quote(
677
- f'{self.__class__.__name__}({details})', markdown
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: object_utils.KeyPath, value: typing.Any) -> None:
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
- object_utils.message_on_path(
882
- f'Value {value!r} is not in candidate list {self._values}.',
883
- path))
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
- if not set(base.values).issuperset(set(self._values)):
888
- raise TypeError(
889
- f'{self} cannot extend {base}: values in base should be super set.')
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(self, *, markdown: bool = False, **kwargs) -> str:
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
- details = object_utils.kvlist_str([
912
- ('default', object_utils.quote_if_str(self._default), MISSING_VALUE),
913
- ('values', self._values, None),
914
- ('frozen', self._frozen, False),
915
- ])
916
- return object_utils.maybe_markdown_quote(
917
- f'{self.__class__.__name__}({details})', markdown
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(self,
1040
- value: typing.List[typing.Any],
1041
- allow_partial: bool,
1042
- child_transform: typing.Callable[
1043
- [object_utils.KeyPath, Field, typing.Any], typing.Any],
1044
- root_path: object_utils.KeyPath) -> typing.Any:
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), allow_partial=allow_partial, transform_fn=child_transform,
1062
- root_path=object_utils.KeyPath(i, root_path))
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
- object_utils.message_on_path(
1185
+ utils.message_on_path(
1073
1186
  f'Length of list {value!r} is less than '
1074
- f'min size ({self.min_size}).', path))
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
- object_utils.message_on_path(
1195
+ utils.message_on_path(
1080
1196
  f'Length of list {value!r} is greater than '
1081
- f'max size ({self.max_size}).', path))
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
- details = object_utils.kvlist_str([
1114
- ('', self._element.value.format(
1115
- compact=compact,
1116
- verbose=verbose,
1117
- root_indent=root_indent,
1118
- **kwargs), None),
1119
- ('min_size', self.min_size, 0),
1120
- ('max_size', self.max_size, None),
1121
- ('default', object_utils.format(
1122
- self._default,
1123
- compact=compact,
1124
- verbose=verbose,
1125
- root_indent=root_indent + 1,
1126
- hide_default_values=hide_default_values,
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(self,
1321
- value: typing.Tuple[typing.Any, ...],
1322
- allow_partial: bool,
1323
- child_transform: typing.Callable[
1324
- [object_utils.KeyPath, Field, typing.Any], typing.Any],
1325
- root_path: object_utils.KeyPath) -> typing.Any:
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
- object_utils.message_on_path(
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}', root_path))
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
- object_utils.message_on_path(
1457
+ utils.message_on_path(
1338
1458
  f'Length of tuple {value} is less than '
1339
- f'min size ({self.min_size}).', root_path))
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
- object_utils.message_on_path(
1465
+ utils.message_on_path(
1343
1466
  f'Length of tuple {value} is greater than '
1344
- f'max size ({self.max_size}).', root_path))
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, allow_partial=allow_partial, transform_fn=child_transform,
1348
- root_path=object_utils.KeyPath(i, root_path))
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
- element_values = [f.value for f in self._elements]
1438
- details = object_utils.kvlist_str([
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
- details = object_utils.kvlist_str([
1459
- ('', object_utils.format(
1460
- self._elements[0].value,
1461
- compact=compact,
1462
- verbose=verbose,
1463
- root_indent=root_indent,
1464
- **kwargs), None),
1465
- ('default', object_utils.format(
1466
- self._default,
1467
- compact=compact,
1468
- verbose=verbose,
1469
- root_indent=root_indent + 1,
1470
- hide_default_values=hide_default_values,
1471
- hide_missing_values=hide_missing_values,
1472
- **kwargs), 'MISSING_VALUE'),
1473
- ('min_size', self._min_size, 0),
1474
- ('max_size', self._max_size, None),
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.Optional[
1551
- typing.Union[
1552
- Schema,
1553
- typing.Dict[str, typing.Any],
1554
- typing.List[typing.Union[Field, typing.Tuple]] # pylint: disable=g-bare-generic
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 str key
1568
- to their value specs, or a list of Field or Field equivalents:
1569
- tuple of (<key_spec>, <value_spec>, [description], [metadata]) When this
1570
- field is empty, it specifies a schema-less Dict that may accept
1571
- arbitrary key/value pairs.
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 and not isinstance(schema, Schema):
1581
- schema = class_schema.create_schema(schema, allow_nonconst_keys=True)
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, default: typing.Any, use_default_apply: bool = True
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(self,
1625
- value: typing.Dict[typing.Any, typing.Any],
1626
- allow_partial: bool,
1627
- child_transform: typing.Callable[
1628
- [object_utils.KeyPath, Field, typing.Any], typing.Any],
1629
- root_path: object_utils.KeyPath) -> typing.Any:
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
- schema_details = ''
1675
- if self._schema:
1676
- schema_details = self._schema.format(
1677
- compact,
1678
- verbose,
1679
- root_indent,
1680
- cls_name='',
1681
- bracket_type=object_utils.BracketType.CURLY,
1682
- **kwargs)
1683
-
1684
- details = object_utils.kvlist_str([
1685
- ('', schema_details, ''),
1686
- ('noneable', self._is_noneable, False),
1687
- ('frozen', self._frozen, False),
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[typing.Type[typing.Any], str],
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, str):
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(self,
1806
- value: typing.Any,
1807
- allow_partial: bool,
1808
- child_transform: typing.Callable[
1809
- [object_utils.KeyPath, Field, typing.Any], typing.Any],
1810
- root_path: object_utils.KeyPath) -> typing.Any:
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, object_utils.MaybePartial):
1948
+ if isinstance(value, utils.MaybePartial):
1814
1949
  if not allow_partial and value.is_partial:
1815
1950
  raise ValueError(
1816
- object_utils.message_on_path(
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
- details = object_utils.kvlist_str([
1874
- ('', name, None),
1875
- ('default', object_utils.format(
1876
- self._default,
1877
- compact,
1878
- verbose,
1879
- root_indent,
1880
- hide_default_values=hide_default_values,
1881
- hide_missing_values=hide_missing_values,
1882
- **kwargs), 'MISSING_VALUE'),
1883
- ('noneable', self._is_noneable, False),
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: object_utils.KeyPath, value: typing.Any) -> None:
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
- object_utils.message_on_path(
2025
- f'Value is not callable: {value!r}.', path))
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.get_signature(value)
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
- object_utils.message_on_path(
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
- object_utils.message_on_path(
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.has_varargs
2052
- assert signature.varargs # for pytype
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
- object_utils.message_on_path(
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}.', path))
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
- object_utils.message_on_path(
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
- elif signature.has_varkw:
2079
- assert signature.varkw # for pytype
2080
- if not signature.varkw.value_spec.is_compatible(src_spec):
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
- object_utils.message_on_path(
2227
+ utils.message_on_path(
2083
2228
  f'Value spec of keyword argument {arg_name!r} is not '
2084
- f'compatible with the value spec of '
2229
+ 'compatible with the value spec of '
2085
2230
  f'**{signature.varkw.name}. '
2086
- f'Expected: {signature.varkw.value_spec!r}, '
2087
- f'Actual: {src_spec!r}.', path))
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
- object_utils.message_on_path(
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
- object_utils.message_on_path(
2100
- f'Value spec for return value is not compatible. '
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(self, *, markdown: bool = False, **kwargs) -> str:
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
- details = object_utils.kvlist_str([
2169
- ('args', object_utils.format(self._args, **kwargs), '[]'),
2170
- ('kw', object_utils.format(self._kw, **kwargs), '[]'),
2171
- ('returns', object_utils.format(self._return_value, **kwargs), 'None'),
2172
- ('default', object_utils.format(
2173
- self._default, **kwargs), 'MISSING_VALUE'),
2174
- ('noneable', self._is_noneable, False),
2175
- ('frozen', self._frozen, False)
2176
- ])
2177
- return object_utils.maybe_markdown_quote(
2178
- f'{self.__class__.__name__}({details})', markdown
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=object_utils.Functor,
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 object_utils.Functor
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: object_utils.KeyPath, value: typing.Type) -> None: # pylint: disable=g-bare-generic
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
- object_utils.message_on_path(
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(self, *, markdown: bool = False, **kwargs):
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
- details = object_utils.kvlist_str([
2368
- ('', self._expected_type, None),
2369
- ('default', self._default, MISSING_VALUE),
2370
- ('noneable', self._is_noneable, False),
2371
- ('frozen', self._frozen, False),
2372
- ])
2373
- return object_utils.maybe_markdown_quote(
2374
- f'{self.__class__.__name__}({details})', markdown
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(self,
2561
- value: typing.Any,
2562
- allow_partial: bool,
2563
- child_transform: typing.Callable[
2564
- [object_utils.KeyPath, Field, typing.Any],
2565
- typing.Any
2566
- ],
2567
- root_path: object_utils.KeyPath) -> typing.Any:
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.type_resolved
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.type_resolved and type_conversion.get_converter(
2585
- type(value), c.value_type) is not None:
2586
- matched_candidate = c
2587
- break
2588
-
2589
- if self.type_resolved:
2590
- # `_apply` is entered only when there is a type match or conversion path.
2591
- assert matched_candidate is not None
2592
- return matched_candidate.apply(
2593
- value, allow_partial, child_transform, root_path)
2594
-
2595
- # Return value directly if the forward declaration of the current union
2596
- # is unsolved.
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
- list_wrap_threshold = kwargs.pop('list_wrap_threshold', 20)
2654
- details = object_utils.kvlist_str([
2655
- ('', object_utils.format(
2656
- self._candidates,
2657
- compact,
2658
- verbose,
2659
- root_indent + 1,
2660
- list_wrap_threshold=list_wrap_threshold,
2661
- **kwargs), None),
2662
- ('default', object_utils.quote_if_str(self._default), MISSING_VALUE),
2663
- ('noneable', self._is_noneable, False),
2664
- ('frozen', self._frozen, False),
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(self, *, markdown: bool = False, **kwargs) -> str:
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
- details = object_utils.kvlist_str([
2806
- ('default', object_utils.format(self._default, **kwargs),
2807
- 'MISSING_VALUE'),
2808
- ('frozen', self._frozen, False),
2809
- ('annotation', self._annotation, MISSING_VALUE)
2810
- ])
2811
- return object_utils.maybe_markdown_quote(
2812
- f'{self.__class__.__name__}({details})', markdown
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