pyglove 0.4.5.dev202412140808__py3-none-any.whl → 0.4.5.dev202412160810__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.
@@ -164,11 +164,22 @@ class ObjectMeta(abc.ABCMeta):
164
164
  if key is None:
165
165
  continue
166
166
 
167
+ # Skip class-level attributes that are not symbolic fields.
168
+ if typing.get_origin(attr_annotation) is typing.ClassVar:
169
+ continue
170
+
167
171
  field = pg_typing.Field.from_annotation(key, attr_annotation)
168
172
  if isinstance(key, pg_typing.ConstStrKey):
169
173
  attr_value = cls.__dict__.get(attr_name, pg_typing.MISSING_VALUE)
170
174
  if attr_value != pg_typing.MISSING_VALUE:
171
175
  field.value.set_default(attr_value)
176
+
177
+ if (field.value.frozen and
178
+ field.value.default is
179
+ pg_typing.value_specs._FROZEN_VALUE_PLACEHOLDER): # pylint: disable=protected-access
180
+ raise TypeError(
181
+ f'Field {field.key!r} is marked as final but has no default value.'
182
+ )
172
183
  fields.append(field)
173
184
 
174
185
  # Trigger event so subclass could modify the fields.
@@ -324,6 +324,26 @@ class ObjectTest(unittest.TestCase):
324
324
  self.assertEqual(e.x, 1)
325
325
  self.assertEqual(e.y, 3)
326
326
 
327
+ class F(Object):
328
+ x: typing.Literal[1, 'a']
329
+
330
+ class G(F):
331
+ x: typing.Final[int] = 1
332
+ y: typing.ClassVar[int] = 2
333
+
334
+ self.assertEqual(G().x, 1)
335
+ self.assertEqual(G.y, 2)
336
+
337
+ with self.assertRaisesRegex(
338
+ ValueError, 'Frozen field is not assignable'):
339
+ G(x=2)
340
+
341
+ with self.assertRaisesRegex(
342
+ TypeError, 'Field x is marked as final but has no default value'):
343
+
344
+ class H(Object): # pylint: disable=unused-variable
345
+ x: typing.Final[int] # pylint: disable=invalid-name
346
+
327
347
  def test_init_arg_list(self):
328
348
 
329
349
  def _update_init_arg_list(cls, init_arg_list):
@@ -14,16 +14,30 @@
14
14
  """Symbolic reference."""
15
15
 
16
16
  import functools
17
- import numbers
18
- from typing import Any, Callable, List, Optional, Tuple
17
+ import typing
18
+ from typing import Any, Callable, List, Optional, Tuple, Type
19
19
  from pyglove.core import object_utils
20
20
  from pyglove.core import typing as pg_typing
21
21
  from pyglove.core.symbolic import base
22
- from pyglove.core.symbolic.object import Object
22
+ from pyglove.core.symbolic import object as pg_object
23
23
  from pyglove.core.views.html import tree_view
24
24
 
25
25
 
26
- class Ref(Object, base.Inferential, tree_view.HtmlTreeView.Extension):
26
+ class RefMeta(pg_object.ObjectMeta):
27
+ """Metaclass for Ref."""
28
+
29
+ def __getitem__(cls, type_arg: Type[Any]) -> Any: # pylint: disable=no-self-argument
30
+ if typing.TYPE_CHECKING:
31
+ return type_arg
32
+ return pg_typing.Object(type_arg, transform=Ref)
33
+
34
+
35
+ class Ref(
36
+ pg_object.Object,
37
+ base.Inferential,
38
+ tree_view.HtmlTreeView.Extension,
39
+ metaclass=RefMeta
40
+ ):
27
41
  """Symbolic reference.
28
42
 
29
43
  When adding a symbolic node to a symbolic tree, it undergoes a copy operation
@@ -82,9 +96,9 @@ class Ref(Object, base.Inferential, tree_view.HtmlTreeView.Extension):
82
96
 
83
97
  def __new__(cls, value: Any, **kwargs):
84
98
  del kwargs
85
- if isinstance(value, (bool, numbers.Number, str)):
86
- return value
87
- return object.__new__(cls)
99
+ if isinstance(value, (base.Symbolic, list, dict)):
100
+ return object.__new__(cls)
101
+ return value
88
102
 
89
103
  @object_utils.explicit_method_override
90
104
  def __init__(self, value: Any, **kwargs) -> None:
@@ -241,4 +255,3 @@ def deref(value: base.Symbolic, recursive: bool = False) -> Any:
241
255
  return v
242
256
  return value.rebind(_deref, raise_on_no_change=False)
243
257
  return value
244
-
@@ -16,6 +16,7 @@
16
16
  import copy
17
17
  import inspect
18
18
  import pickle
19
+ import typing
19
20
  from typing import Any
20
21
  import unittest
21
22
 
@@ -202,6 +203,22 @@ class RefTest(unittest.TestCase):
202
203
  """
203
204
  )
204
205
 
206
+ def test_annotation(self):
207
+ typing.TYPE_CHECKING = True
208
+ assert ref.Ref[ValueError] is ValueError
209
+ typing.TYPE_CHECKING = False
210
+
211
+ class Bar(Object):
212
+ x: int
213
+
214
+ class Foo(Object):
215
+ y: ref.Ref[Bar]
216
+
217
+ f = Foo(Bar(1))
218
+ self.assertIsInstance(f.sym_getattr('y'), ref.Ref)
219
+ self.assertEqual(f.y.sym_path, '')
220
+ self.assertIsInstance(ref.Ref[Bar](1), ref.Ref)
221
+
205
222
 
206
223
  if __name__ == '__main__':
207
224
  unittest.main()
@@ -177,6 +177,11 @@ def _value_spec_from_type_annotation(
177
177
  if optional:
178
178
  spec = spec.noneable()
179
179
  return spec
180
+ elif origin is typing.Final:
181
+ return _value_spec_from_type_annotation(
182
+ args[0],
183
+ accept_value_as_annotation=False
184
+ ).freeze(vs._FROZEN_VALUE_PLACEHOLDER) # pylint: disable=protected-access
180
185
  elif isinstance(annotation, typing.ForwardRef):
181
186
  annotation = annotation.__forward_arg__
182
187
  if parent_module is not None:
@@ -306,6 +306,14 @@ class ValueSpecFromAnnotationTest(unittest.TestCase):
306
306
  ValueSpec.from_annotation(int | str, True),
307
307
  vs.Union([vs.Int(), vs.Str()]))
308
308
 
309
+ def test_final(self):
310
+ self.assertEqual(
311
+ ValueSpec.from_annotation(
312
+ typing.Final[int], True
313
+ ).set_default(1),
314
+ vs.Int().freeze(1)
315
+ )
316
+
309
317
 
310
318
  if __name__ == '__main__':
311
319
  unittest.main()
@@ -344,6 +344,10 @@ class ValueSpec(object_utils.Formattable, object_utils.JSONConvertible):
344
344
  Tuple[Type[Any], ...]]: # pyformat: disable
345
345
  """Returns acceptable (resolved) value type(s)."""
346
346
 
347
+ @abc.abstractmethod
348
+ def __call__(self, *args, **kwargs) -> Any:
349
+ """Instantiates a value based on the spec.."""
350
+
347
351
  @property
348
352
  @abc.abstractmethod
349
353
  def forward_refs(self) -> Set[ForwardRef]:
@@ -37,6 +37,17 @@ from pyglove.core.typing.custom_typing import CustomTyping
37
37
 
38
38
  MISSING_VALUE = object_utils.MISSING_VALUE
39
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
+
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
42
53
  # just for better readability.
@@ -194,8 +205,18 @@ class ValueSpecBase(ValueSpec):
194
205
 
195
206
  def extend(self, base: ValueSpec) -> ValueSpec:
196
207
  """Extend current value spec on top of a base spec."""
197
- if base.frozen:
198
- raise TypeError(f'Cannot extend a frozen value spec: {base!r}')
208
+ if base.frozen and (not self.frozen or self.default != base.default):
209
+ raise TypeError(f'{self!r} cannot extend a frozen value spec: {base!r}')
210
+
211
+ # Special handling for extending enum.
212
+ if self.frozen and isinstance(base, Enum):
213
+ if self.default in base.values:
214
+ return Enum(MISSING_VALUE, base.values).freeze(self.default)
215
+ else:
216
+ raise TypeError(
217
+ f'{self!r} cannot extend {base!r} with incompatible '
218
+ f'frozen value: {self.default!r} '
219
+ )
199
220
 
200
221
  if self._transform is None:
201
222
  self._transform = base.transform
@@ -232,7 +253,7 @@ class ValueSpecBase(ValueSpec):
232
253
  root_path: typing.Optional[object_utils.KeyPath] = None) -> typing.Any: # pyformat: disable pylint: disable=line-too-long
233
254
  """Apply spec to validate and complete value."""
234
255
  root_path = root_path or object_utils.KeyPath()
235
- if self.frozen:
256
+ if self.frozen and self.default is not _FROZEN_VALUE_PLACEHOLDER:
236
257
  # Always return the default value if a field is frozen.
237
258
  if MISSING_VALUE != value and self.default != value:
238
259
  raise ValueError(
@@ -270,7 +291,8 @@ class ValueSpecBase(ValueSpec):
270
291
  value = self._transform(value)
271
292
  except Exception as e: # pylint: disable=broad-except
272
293
  raise e.__class__(
273
- object_utils.message_on_path(str(e), root_path)
294
+ object_utils.message_on_path(
295
+ str(e), root_path)
274
296
  ).with_traceback(sys.exc_info()[2])
275
297
 
276
298
  return self.skip_user_transform.apply(
@@ -319,7 +341,7 @@ class ValueSpecBase(ValueSpec):
319
341
  return value
320
342
 
321
343
  def is_compatible(self, other: ValueSpec) -> bool:
322
- """Returns if current spec is compatible with the other value spec."""
344
+ """Returns if current spec can receive all values from the other spec."""
323
345
  if self is other:
324
346
  return True
325
347
  if not isinstance(other, self.__class__):
@@ -417,6 +439,12 @@ class PrimitiveType(ValueSpecBase):
417
439
  value_type, default, is_noneable=is_noneable, frozen=frozen
418
440
  )
419
441
 
442
+ def __call__(self, *args, **kwargs) -> typing.Any:
443
+ del kwargs
444
+ if (not args and self.has_default) or self.frozen:
445
+ return self.default
446
+ return self.apply(self.value_type(*args))
447
+
420
448
 
421
449
  class Bool(PrimitiveType):
422
450
  """Value spec for boolean type.
@@ -898,6 +926,12 @@ class Enum(Generic, PrimitiveType):
898
926
  value_type, default, is_noneable=is_noneable, frozen=frozen
899
927
  )
900
928
 
929
+ def __call__(self, *args, **kwargs) -> typing.Any:
930
+ del kwargs
931
+ if (not args and self.has_default) or self.frozen:
932
+ return self.default
933
+ return self.apply(*args)
934
+
901
935
  def noneable(self) -> 'Enum':
902
936
  """Noneable is specially treated for Enum."""
903
937
  if None not in self._values:
@@ -929,6 +963,12 @@ class Enum(Generic, PrimitiveType):
929
963
  f'{repr(v)} is not an acceptable value.'
930
964
  ) from e
931
965
 
966
+ def is_compatible(self, other: ValueSpec) -> bool:
967
+ """Enum specific compatibility check."""
968
+ if other.frozen and other.default in self.values:
969
+ return True
970
+ return super().is_compatible(other)
971
+
932
972
  def _is_compatible(self, other: 'Enum') -> bool:
933
973
  """Enum specific compatibility check."""
934
974
  for v in other.values:
@@ -1067,6 +1107,12 @@ class List(Generic, ValueSpecBase):
1067
1107
  list, default, transform, is_noneable=is_noneable, frozen=frozen
1068
1108
  )
1069
1109
 
1110
+ def __call__(self, *args, **kwargs) -> typing.Any:
1111
+ del kwargs
1112
+ if (not args and self.has_default) or self.frozen:
1113
+ return self.default
1114
+ return self.apply(list(*args))
1115
+
1070
1116
  @property
1071
1117
  def element(self) -> Field:
1072
1118
  """Returns Field specification of list element."""
@@ -1316,6 +1362,12 @@ class Tuple(Generic, ValueSpecBase):
1316
1362
  tuple, default, transform, is_noneable=is_noneable, frozen=frozen
1317
1363
  )
1318
1364
 
1365
+ def __call__(self, *args, **kwargs) -> typing.Any:
1366
+ del kwargs
1367
+ if (not args and self.has_default) or self.frozen:
1368
+ return self.default
1369
+ return self.apply(tuple(*args))
1370
+
1319
1371
  @property
1320
1372
  def fixed_length(self) -> bool:
1321
1373
  """Returns True if current Tuple spec is fixed length."""
@@ -1617,6 +1669,9 @@ class Dict(Generic, ValueSpecBase):
1617
1669
  if MISSING_VALUE == default:
1618
1670
  self.set_default(default)
1619
1671
 
1672
+ def __call__(self, *args, **kwargs) -> typing.Any:
1673
+ return self.apply(dict(*args, **kwargs))
1674
+
1620
1675
  @property
1621
1676
  def schema(self) -> typing.Optional[Schema]:
1622
1677
  """Returns the schema of this dict spec."""
@@ -1816,6 +1871,9 @@ class Object(Generic, ValueSpecBase):
1816
1871
  t, default, transform, is_noneable=is_noneable, frozen=frozen
1817
1872
  )
1818
1873
 
1874
+ def __call__(self, *args, **kwargs) -> typing.Any:
1875
+ return self.apply(self.cls(*args, **kwargs))
1876
+
1819
1877
  @property
1820
1878
  def forward_refs(self) -> typing.Set[class_schema.ForwardRef]:
1821
1879
  """Returns forward references used in this spec."""
@@ -2014,6 +2072,10 @@ class Callable(Generic, ValueSpecBase):
2014
2072
  frozen=frozen,
2015
2073
  )
2016
2074
 
2075
+ def __call__(self, *args, **kwargs) -> typing.Any:
2076
+ del args, kwargs
2077
+ raise TypeError(f'{self!r} cannot be instantiated.')
2078
+
2017
2079
  @functools.cached_property
2018
2080
  def forward_refs(self) -> typing.Set[class_schema.ForwardRef]:
2019
2081
  """Returns forward references used in this spec."""
@@ -2351,6 +2413,10 @@ class Type(Generic, ValueSpecBase):
2351
2413
  self._forward_ref = forward_ref
2352
2414
  super().__init__(type, default, is_noneable=is_noneable, frozen=frozen)
2353
2415
 
2416
+ def __call__(self, *args, **kwargs) -> typing.Any:
2417
+ del args, kwargs
2418
+ return self.type
2419
+
2354
2420
  @property
2355
2421
  def type(self) -> typing.Type[typing.Any]:
2356
2422
  """Returns desired type."""
@@ -2539,6 +2605,10 @@ class Union(Generic, ValueSpecBase):
2539
2605
  frozen=frozen,
2540
2606
  )
2541
2607
 
2608
+ def __call__(self, *args, **kwargs) -> typing.Any:
2609
+ del args, kwargs
2610
+ raise TypeError(f'{self!r} cannot be instantiated.')
2611
+
2542
2612
  @functools.cached_property
2543
2613
  def forward_refs(self) -> typing.Set[class_schema.ForwardRef]:
2544
2614
  """Returns forward references used in this spec."""
@@ -2856,6 +2926,10 @@ class Any(ValueSpecBase):
2856
2926
  )
2857
2927
  self._annotation = annotation
2858
2928
 
2929
+ def __call__(self, *args, **kwargs) -> typing.Any:
2930
+ del args, kwargs
2931
+ raise TypeError(f'{self!r} cannot be instantiated.')
2932
+
2859
2933
  def is_compatible(self, other: ValueSpec) -> bool:
2860
2934
  """Any is compatible with any ValueSpec."""
2861
2935
  return True
@@ -99,6 +99,13 @@ class BoolTest(ValueSpecTest):
99
99
  with self.assertRaisesRegex(ValueError, 'Value cannot be None'):
100
100
  vs.Bool().apply(None)
101
101
 
102
+ def test_instantiation(self):
103
+ self.assertTrue(vs.Bool()(True))
104
+ self.assertFalse(vs.Bool()(False))
105
+ self.assertIsNone(vs.Bool().noneable()())
106
+ self.assertFalse(vs.Bool()())
107
+ self.assertTrue(vs.Bool().freeze(True)(False))
108
+
102
109
  def test_is_compatible(self):
103
110
  v = vs.Bool()
104
111
  self.assertTrue(v.is_compatible(v))
@@ -117,6 +124,12 @@ class BoolTest(ValueSpecTest):
117
124
  # Child may extend a noneable base into non-noneable.
118
125
  self.assertFalse(vs.Bool().extend(vs.Bool().noneable()).is_noneable)
119
126
 
127
+ # A frozen child may extend a enum with its value as candidate.
128
+ self.assertEqual(
129
+ vs.Bool().freeze(True).extend(vs.Enum(2, [2, True])),
130
+ vs.Enum(True, [2, True]).freeze(),
131
+ )
132
+
120
133
  # Child cannot extend a base with different type.
121
134
  with self.assertRaisesRegex(
122
135
  TypeError, '.* cannot extend .*: incompatible type.'):
@@ -127,6 +140,11 @@ class BoolTest(ValueSpecTest):
127
140
  TypeError, '.* cannot extend .*: None is not allowed in base spec.'):
128
141
  vs.Bool().noneable().extend(vs.Bool())
129
142
 
143
+ # Child cannot extend a non-noneable base to noneable.
144
+ with self.assertRaisesRegex(
145
+ TypeError, '.* cannot extend .* with incompatible frozen value'):
146
+ vs.Bool().freeze(True).extend(vs.Enum(False, [2, False]))
147
+
130
148
  def test_freeze(self):
131
149
  self.assertFalse(vs.Bool().frozen)
132
150
 
@@ -144,7 +162,7 @@ class BoolTest(ValueSpecTest):
144
162
  self.assertTrue(v.default)
145
163
 
146
164
  with self.assertRaisesRegex(
147
- TypeError, 'Cannot extend a frozen value spec.'):
165
+ TypeError, '.* cannot extend a frozen value spec'):
148
166
  vs.Bool().extend(v)
149
167
 
150
168
  with self.assertRaisesRegex(
@@ -224,6 +242,12 @@ class StrTest(ValueSpecTest):
224
242
  self.assertNotEqual(vs.Str(), vs.Str(regex='.*'))
225
243
  self.assertNotEqual(vs.Str(regex='a'), vs.Str(regex='.*'))
226
244
 
245
+ def test_instantiation(self):
246
+ self.assertEqual(vs.Str()('abc'), 'abc')
247
+ self.assertIsNone(vs.Str().noneable()())
248
+ self.assertEqual(vs.Str()(), '')
249
+ self.assertEqual(vs.Str().freeze('abc')('def'), 'abc')
250
+
227
251
  def test_apply(self):
228
252
  self.assertEqual(vs.Str().apply('a'), 'a')
229
253
  self.assertEqual(vs.Str(regex='a.*').apply('a1'), 'a1')
@@ -266,6 +290,12 @@ class StrTest(ValueSpecTest):
266
290
  # Child may extend a noneable base into non-noneable.
267
291
  self.assertFalse(vs.Str().extend(vs.Str().noneable()).is_noneable)
268
292
 
293
+ # A frozen child may extend a enum with its value as candidate.
294
+ self.assertEqual(
295
+ vs.Str().freeze('a').extend(vs.Enum(2, [2, 'a'])),
296
+ vs.Enum('a', [2, 'a']).freeze(),
297
+ )
298
+
269
299
  # Child cannot extend a base of different type.
270
300
  with self.assertRaisesRegex(
271
301
  TypeError, '.* cannot extend .*: incompatible type.'):
@@ -276,6 +306,11 @@ class StrTest(ValueSpecTest):
276
306
  TypeError, '.* cannot extend .*: None is not allowed in base spec.'):
277
307
  vs.Str().noneable().extend(vs.Str())
278
308
 
309
+ # Child cannot extend a non-noneable base to noneable.
310
+ with self.assertRaisesRegex(
311
+ TypeError, '.* cannot extend .* with incompatible frozen value'):
312
+ vs.Str().freeze('b').extend(vs.Enum('a', ['a', False]))
313
+
279
314
  def test_freeze(self):
280
315
  self.assertFalse(vs.Str().frozen)
281
316
 
@@ -293,7 +328,7 @@ class StrTest(ValueSpecTest):
293
328
  self.assertEqual(v.default, 'foo')
294
329
 
295
330
  with self.assertRaisesRegex(
296
- TypeError, 'Cannot extend a frozen value spec.'):
331
+ TypeError, '.* cannot extend a frozen value spec'):
297
332
  vs.Str().extend(v)
298
333
 
299
334
  with self.assertRaisesRegex(
@@ -307,6 +342,7 @@ class StrTest(ValueSpecTest):
307
342
  self.assert_json_conversion(vs.Str().noneable().freeze('abc'))
308
343
  self.assert_json_conversion_key(vs.Str(), 'pyglove.typing.Str')
309
344
 
345
+
310
346
  class IntTest(ValueSpecTest):
311
347
  """Tests for `Int`."""
312
348
 
@@ -377,6 +413,16 @@ class IntTest(ValueSpecTest):
377
413
  ValueError, '"max_value" must be equal or greater than "min_value".'):
378
414
  vs.Int(min_value=1, max_value=0)
379
415
 
416
+ def test_instantiation(self):
417
+ self.assertEqual(vs.Int()(1), 1)
418
+ self.assertEqual(vs.Int()(), 0)
419
+ self.assertIsNone(vs.Int().noneable()())
420
+ self.assertEqual(vs.Int().freeze(1)(0), 1)
421
+ with self.assertRaisesRegex(
422
+ ValueError, 'Value .* is out of range'
423
+ ):
424
+ vs.Int(min_value=1)()
425
+
380
426
  def test_apply(self):
381
427
  self.assertEqual(vs.Int().apply(1), 1)
382
428
  self.assertEqual(vs.Int(min_value=1, max_value=1).apply(1), 1)
@@ -434,6 +480,12 @@ class IntTest(ValueSpecTest):
434
480
  vs.Int(min_value=1),
435
481
  )
436
482
 
483
+ # A frozen child may extend a enum with its value as candidate.
484
+ self.assertEqual(
485
+ vs.Int().freeze(2).extend(vs.Enum(2, [2, 'a'])),
486
+ vs.Enum(2, [2, 'a']).freeze(),
487
+ )
488
+
437
489
  with self.assertRaisesRegex(TypeError,
438
490
  '.* cannot extend .*: incompatible type.'):
439
491
  vs.Int().extend(vs.Bool())
@@ -466,6 +518,11 @@ class IntTest(ValueSpecTest):
466
518
  TypeError, '.* cannot extend .*: no compatible type found in Union.'):
467
519
  vs.Int().extend(vs.Union([vs.Bool(), vs.Str()]))
468
520
 
521
+ # Child cannot extend a non-noneable base to noneable.
522
+ with self.assertRaisesRegex(
523
+ TypeError, '.* cannot extend .* with incompatible frozen value'):
524
+ vs.Int().freeze(1).extend(vs.Enum('a', ['a', False]))
525
+
469
526
  def test_freeze(self):
470
527
  self.assertFalse(vs.Int().frozen)
471
528
 
@@ -483,7 +540,7 @@ class IntTest(ValueSpecTest):
483
540
  self.assertEqual(v.default, 1)
484
541
 
485
542
  with self.assertRaisesRegex(
486
- TypeError, 'Cannot extend a frozen value spec.'):
543
+ TypeError, '.* cannot extend a frozen value spec'):
487
544
  vs.Int().extend(v)
488
545
 
489
546
  with self.assertRaisesRegex(
@@ -571,6 +628,16 @@ class FloatTest(ValueSpecTest):
571
628
  ValueError, '"max_value" must be equal or greater than "min_value".'):
572
629
  vs.Float(min_value=1., max_value=0.)
573
630
 
631
+ def test_instantiation(self):
632
+ self.assertEqual(vs.Float()(1), 1.0)
633
+ self.assertEqual(vs.Float()(), 0.0)
634
+ self.assertIsNone(vs.Float().noneable()())
635
+ self.assertEqual(vs.Float().freeze(1.0)(0), 1.0)
636
+ with self.assertRaisesRegex(
637
+ ValueError, 'Value .* is out of range'
638
+ ):
639
+ vs.Float(min_value=1)()
640
+
574
641
  def test_apply(self):
575
642
  self.assertEqual(vs.Float().apply(1.), 1.)
576
643
  self.assertEqual(vs.Float().apply(1), 1.)
@@ -617,6 +684,12 @@ class FloatTest(ValueSpecTest):
617
684
  # Child may extend a noneable base into non-noneable.
618
685
  self.assertFalse(vs.Float().extend(vs.Float().noneable()).is_noneable)
619
686
 
687
+ # A frozen child may extend a enum with its value as candidate.
688
+ self.assertEqual(
689
+ vs.Float().freeze(2.5).extend(vs.Enum(2.5, [2.5, 'a'])),
690
+ vs.Enum(2.5, [2.5, 'a']).freeze(),
691
+ )
692
+
620
693
  with self.assertRaisesRegex(
621
694
  TypeError, '.* cannot extend .*: incompatible type.'):
622
695
  vs.Float().extend(vs.Int())
@@ -645,6 +718,11 @@ class FloatTest(ValueSpecTest):
645
718
  'min_value .* is greater than max_value .* after extension'):
646
719
  vs.Float(min_value=1.).extend(vs.Float(max_value=0.))
647
720
 
721
+ # Child cannot extend a non-noneable base to noneable.
722
+ with self.assertRaisesRegex(
723
+ TypeError, '.* cannot extend .* with incompatible frozen value'):
724
+ vs.Float().freeze(1.0).extend(vs.Enum('a', ['a', False]))
725
+
648
726
  def test_freeze(self):
649
727
  self.assertFalse(vs.Float().frozen)
650
728
 
@@ -662,7 +740,7 @@ class FloatTest(ValueSpecTest):
662
740
  self.assertEqual(v.default, 1.0)
663
741
 
664
742
  with self.assertRaisesRegex(
665
- TypeError, 'Cannot extend a frozen value spec.'):
743
+ TypeError, '.* cannot extend a frozen value spec'):
666
744
  vs.Float().extend(v)
667
745
 
668
746
  with self.assertRaisesRegex(
@@ -768,6 +846,11 @@ class EnumTest(ValueSpecTest):
768
846
  ValueError, 'Enum default value \'a\' is not in candidate list.'):
769
847
  vs.Enum('a', ['b'])
770
848
 
849
+ def test_instantiation(self):
850
+ self.assertEqual(vs.Enum(1, [1, 2, 3])(), 1)
851
+ self.assertEqual(vs.Enum(1, [1, 2, 3])(2), 2)
852
+ self.assertEqual(vs.Enum(1, [1, 2, 3]).freeze(2)(3), 2)
853
+
771
854
  def test_apply(self):
772
855
  self.assertEqual(vs.Enum('a', ['a']).apply('a'), 'a')
773
856
  self.assertIsNone(vs.Enum('a', ['a', None]).apply(None))
@@ -784,6 +867,8 @@ class EnumTest(ValueSpecTest):
784
867
  self.assertTrue(
785
868
  vs.Enum(0, [0, 1]).is_compatible(vs.Enum(0, [0, 1])))
786
869
  self.assertTrue(vs.Enum(0, [0, 1]).is_compatible(vs.Enum(0, [0])))
870
+ self.assertTrue(vs.Enum(0, [0, 'a']).is_compatible(vs.Int().freeze(0)))
871
+ self.assertTrue(vs.Enum(0, [0, 'a']).is_compatible(vs.Str().freeze('a')))
787
872
  self.assertFalse(vs.Enum(0, [0]).is_compatible(vs.Enum(0, [0, 1])))
788
873
  self.assertFalse(vs.Enum(0, [0]).is_compatible(vs.Int()))
789
874
 
@@ -814,7 +899,7 @@ class EnumTest(ValueSpecTest):
814
899
  self.assertEqual(v.default, 'a')
815
900
 
816
901
  with self.assertRaisesRegex(
817
- TypeError, 'Cannot extend a frozen value spec.'):
902
+ TypeError, '.* cannot extend a frozen value spec'):
818
903
  vs.Enum('c', ['a', 'b', 'c']).extend(v)
819
904
 
820
905
  def test_json_conversion(self):
@@ -942,6 +1027,12 @@ class ListTest(ValueSpecTest):
942
1027
  'Either "size" or "min_size"/"max_size" pair can be specified.'):
943
1028
  vs.List(vs.Int(), size=5, min_size=1)
944
1029
 
1030
+ def test_instantiation(self):
1031
+ self.assertEqual(vs.List(vs.Int())([1]), [1])
1032
+ self.assertEqual(vs.List(vs.Int())(), [])
1033
+ self.assertIsNone(vs.List(vs.Int()).noneable()())
1034
+ self.assertEqual(vs.List(vs.Int()).freeze([0])([]), [0])
1035
+
945
1036
  def test_apply(self):
946
1037
  self.assertEqual(vs.List(vs.Int()).apply([]), [])
947
1038
  self.assertEqual(vs.List(vs.Int()).apply([1]), [1])
@@ -1107,7 +1198,7 @@ class ListTest(ValueSpecTest):
1107
1198
  self.assertEqual(v.default, [1])
1108
1199
 
1109
1200
  with self.assertRaisesRegex(
1110
- TypeError, 'Cannot extend a frozen value spec.'):
1201
+ TypeError, '.* cannot extend a frozen value spec'):
1111
1202
  vs.List(vs.Int()).extend(v)
1112
1203
 
1113
1204
  with self.assertRaisesRegex(
@@ -1301,6 +1392,12 @@ class TupleTest(ValueSpecTest):
1301
1392
  '<(type|class) \'int\'>.'):
1302
1393
  vs.Tuple([vs.Int()], default=1)
1303
1394
 
1395
+ def test_instantiation(self):
1396
+ self.assertEqual(vs.Tuple(vs.Int())([1]), (1,))
1397
+ self.assertEqual(vs.Tuple(vs.Int())(), ())
1398
+ self.assertIsNone(vs.Tuple(vs.Int()).noneable()())
1399
+ self.assertEqual(vs.Tuple(vs.Int()).freeze((0,))((1, 2)), (0,))
1400
+
1304
1401
  def test_apply(self):
1305
1402
  self.assertEqual(vs.Tuple(vs.Int()).apply(tuple()), tuple())
1306
1403
  self.assertEqual(vs.Tuple(vs.Int()).apply((1, 1, 1)), (1, 1, 1))
@@ -1533,7 +1630,7 @@ class TupleTest(ValueSpecTest):
1533
1630
  self.assertEqual(v.default, (1,))
1534
1631
 
1535
1632
  with self.assertRaisesRegex(
1536
- TypeError, 'Cannot extend a frozen value spec.'):
1633
+ TypeError, '.* cannot extend a frozen value spec'):
1537
1634
  vs.Tuple(vs.Int()).extend(v)
1538
1635
 
1539
1636
  with self.assertRaisesRegex(
@@ -1756,6 +1853,12 @@ class DictTest(ValueSpecTest):
1756
1853
  'should be a dict of objects.'):
1757
1854
  vs.Dict([('key', 1, 'field 1', 123)])
1758
1855
 
1856
+ def test_instantiation(self):
1857
+ self.assertEqual(vs.Dict()(), {})
1858
+ self.assertEqual(vs.Dict()({'x': 1, 2: 2}), {'x': 1, 2: 2})
1859
+ self.assertEqual(vs.Dict()(x=1), dict(x=1))
1860
+ self.assertEqual(vs.Dict({'a': int, 'b': 1})(a=1), dict(a=1, b=1))
1861
+
1759
1862
  def test_apply(self):
1760
1863
  self.assertEqual(vs.Dict().apply({'a': 1}), {'a': 1})
1761
1864
  self.assertEqual(
@@ -2108,6 +2211,10 @@ class ObjectTest(ValueSpecTest):
2108
2211
  TypeError, '<(type|class) \'object\'> is too general for Object spec.'):
2109
2212
  vs.Object(object)
2110
2213
 
2214
+ def test_instantiation(self):
2215
+ self.assertIsInstance(vs.Object(self.A)(), self.A)
2216
+ self.assertIsInstance(vs.Object(self.B)(1), self.B)
2217
+
2111
2218
  def test_apply(self):
2112
2219
  a = self.A()
2113
2220
  self.assertEqual(vs.Object(self.A).apply(a), a)
@@ -2230,7 +2337,7 @@ class ObjectTest(ValueSpecTest):
2230
2337
  self.assertIs(v.default, b)
2231
2338
 
2232
2339
  with self.assertRaisesRegex(
2233
- TypeError, 'Cannot extend a frozen value spec.'):
2340
+ TypeError, '.* cannot extend a frozen value spec'):
2234
2341
  vs.Object(self.A).extend(v)
2235
2342
 
2236
2343
  with self.assertRaisesRegex(
@@ -2427,6 +2534,10 @@ class CallableTest(ValueSpecTest):
2427
2534
  TypeError, '.* only take 0 positional arguments, while 1 is required'):
2428
2535
  vs.Callable([vs.Int()]).apply(f)
2429
2536
 
2537
+ def test_instantiation(self):
2538
+ with self.assertRaisesRegex(TypeError, '.* cannot be instantiated'):
2539
+ vs.Callable()()
2540
+
2430
2541
  def test_apply_on_callable_object(self):
2431
2542
 
2432
2543
  class CallableObject:
@@ -2652,7 +2763,7 @@ class CallableTest(ValueSpecTest):
2652
2763
  self.assertIs(v.default, f)
2653
2764
 
2654
2765
  with self.assertRaisesRegex(
2655
- TypeError, 'Cannot extend a frozen value spec.'):
2766
+ TypeError, '.* cannot extend a frozen value spec'):
2656
2767
  vs.Callable().extend(v)
2657
2768
 
2658
2769
  with self.assertRaisesRegex(
@@ -2797,6 +2908,9 @@ class TypeTest(ValueSpecTest):
2797
2908
  self.assertNotEqual(
2798
2909
  vs.Type(Exception), vs.Type(Exception, default=ValueError))
2799
2910
 
2911
+ def test_instantiate(self):
2912
+ self.assertIs(vs.Type[str](), str)
2913
+
2800
2914
  def test_apply(self):
2801
2915
  self.assertEqual(vs.Type(Exception).apply(Exception), Exception)
2802
2916
  self.assertEqual(vs.Type(Exception).apply(ValueError), ValueError)
@@ -2904,7 +3018,7 @@ class TypeTest(ValueSpecTest):
2904
3018
  self.assertIs(v.default, e)
2905
3019
 
2906
3020
  with self.assertRaisesRegex(
2907
- TypeError, 'Cannot extend a frozen value spec.'):
3021
+ TypeError, '.* cannot extend a frozen value spec'):
2908
3022
  vs.Type(Exception).extend(v)
2909
3023
 
2910
3024
  with self.assertRaisesRegex(
@@ -3111,6 +3225,12 @@ class UnionTest(ValueSpecTest):
3111
3225
  vs.Union([vs.Callable(), vs.Int()]).get_candidate(vs.Any()),
3112
3226
  vs.Callable())
3113
3227
 
3228
+ def test_instantiate(self):
3229
+ with self.assertRaisesRegex(
3230
+ TypeError, '.* cannot be instantiated'
3231
+ ):
3232
+ vs.Union[int, str]()
3233
+
3114
3234
  def test_apply(self):
3115
3235
  self.assertEqual(vs.Union([vs.Int(), vs.Str()]).apply(1), 1)
3116
3236
  self.assertEqual(
@@ -3261,7 +3381,7 @@ class UnionTest(ValueSpecTest):
3261
3381
  self.assertEqual(v.default, 'foo')
3262
3382
 
3263
3383
  with self.assertRaisesRegex(
3264
- TypeError, 'Cannot extend a frozen value spec.'):
3384
+ TypeError, '.* cannot extend a frozen value spec.'):
3265
3385
  vs.Str().extend(v)
3266
3386
 
3267
3387
  with self.assertRaisesRegex(
@@ -3343,6 +3463,10 @@ class AnyTest(ValueSpecTest):
3343
3463
  self.assertNotEqual(vs.Any(), vs.Int())
3344
3464
  self.assertNotEqual(vs.Any(True), vs.Any())
3345
3465
 
3466
+ def test_instantiate(self):
3467
+ with self.assertRaisesRegex(TypeError, '.* cannot be instantiated'):
3468
+ vs.Any()()
3469
+
3346
3470
  def test_apply(self):
3347
3471
  self.assertEqual(vs.Any().apply(True), True)
3348
3472
  self.assertEqual(vs.Any().apply(1), 1)
@@ -3392,7 +3516,7 @@ class AnyTest(ValueSpecTest):
3392
3516
  self.assertEqual(v.default, 'foo')
3393
3517
 
3394
3518
  with self.assertRaisesRegex(
3395
- TypeError, 'Cannot extend a frozen value spec.'):
3519
+ TypeError, '.* cannot extend a frozen value spec'):
3396
3520
  vs.Any().extend(v)
3397
3521
 
3398
3522
  with self.assertRaisesRegex(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pyglove
3
- Version: 0.4.5.dev202412140808
3
+ Version: 0.4.5.dev202412160810
4
4
  Summary: PyGlove: A library for manipulating Python objects.
5
5
  Home-page: https://github.com/google/pyglove
6
6
  Author: PyGlove Authors
@@ -98,13 +98,13 @@ pyglove/core/symbolic/inferred.py,sha256=jGCKXLkYGDs-iUflR57UWrCrOQIpkpv5kHVyj-J
98
98
  pyglove/core/symbolic/inferred_test.py,sha256=G6uPykONcChvs6vZujXHSWaYfjewLTVBscMqzzKNty0,1270
99
99
  pyglove/core/symbolic/list.py,sha256=63v4Ph0FdkoCDj1FjwcmjUHGZSJLBLxaTKcGg7PdghE,30345
100
100
  pyglove/core/symbolic/list_test.py,sha256=yHYAJhe_EYwtU9p8eDztSXNBjnAGKe0UDN5U6S-xDr8,60627
101
- pyglove/core/symbolic/object.py,sha256=oSb5pNxQDdvrjCFYsWARa8NkQxWIjdCo9lTbETiVQ38,42335
102
- pyglove/core/symbolic/object_test.py,sha256=hM4XD4vCyVL8rcvf0XcpYT1ROq_fLJuPOX49FbwLPOI,93081
101
+ pyglove/core/symbolic/object.py,sha256=VL_Q8KpuwmLEgjagwTBBzBin9gLxQGkM8L48-WW2NIg,42761
102
+ pyglove/core/symbolic/object_test.py,sha256=qiVtQchArGwGt4D0GsL8xVVHA7GQlf-7NplNBLRMBzA,93609
103
103
  pyglove/core/symbolic/origin.py,sha256=5bH1jZvFHY5jwku32vDm8Bj2i-buv-YNuzOOsA5GlSA,6177
104
104
  pyglove/core/symbolic/origin_test.py,sha256=dU_ZGrGDetM_lYVMn3wQO0d367_t_t8eESe3NrKPBNE,3159
105
105
  pyglove/core/symbolic/pure_symbolic.py,sha256=FVq-5Cg5uZe3ybTIrTqTHIEJIpje0oxzV2kKL6UKlsU,3244
106
- pyglove/core/symbolic/ref.py,sha256=Kf2QKRBi4uP7Ql4zL8hh6Le1fUWTo5c0kiGzg06ZpAQ,8204
107
- pyglove/core/symbolic/ref_test.py,sha256=ZQV7qb6LGnLyyYgY1fL86rfMZ6T3uy_leQDAd2Bd5Tc,5890
106
+ pyglove/core/symbolic/ref.py,sha256=4RQcdgP4WBW1HnHeeOriZHL37PCArjUrx6o48Ve-Ilc,8522
107
+ pyglove/core/symbolic/ref_test.py,sha256=0687hClfR5G5_VKuRlwjJGVQ2MC74ADFWklDaZ3aEVI,6294
108
108
  pyglove/core/symbolic/symbolize.py,sha256=ohID9-V8QiFe7OMpPlRomiqUnKBVMpypd8ZuMuHaa4s,6582
109
109
  pyglove/core/symbolic/symbolize_test.py,sha256=o7bRfMhGc6uw2FIH8arE99-bPb3i0YixcHYyiP-QqeQ,6487
110
110
  pyglove/core/tuning/__init__.py,sha256=JtXpjsBto01fLf55hZ1dSx-CEZUyVQeyRP9AMH_hw8c,2229
@@ -119,13 +119,13 @@ pyglove/core/tuning/sample_test.py,sha256=JqwDPy3EPC_VjU9dipk90jj1kovZB3Zb9hAjAl
119
119
  pyglove/core/typing/__init__.py,sha256=Z_jnwUvDQ3wA16a5TiuWbuof9hW0Xm6YoTNwgG4QGqI,14395
120
120
  pyglove/core/typing/annotated.py,sha256=llaajIDj9GK-4kUGJoO4JsHU6ESPOra2SZ-jG6xmsOQ,3203
121
121
  pyglove/core/typing/annotated_test.py,sha256=p1qid3R-jeiOTTxOVq6hXW8XFvn-h1cUzJWISPst2l8,2484
122
- pyglove/core/typing/annotation_conversion.py,sha256=lNqyccuQaGhcxxFm4DkYPcUol2VURWZpgQGQhHfx4nw,8815
123
- pyglove/core/typing/annotation_conversion_test.py,sha256=xbpmpA9sjvDEV2n8TXTJiTQ2V9M-XOmxXzba_Rg_MZU,11483
122
+ pyglove/core/typing/annotation_conversion.py,sha256=rNYOyC0ury-kiDpxRqABz-E3ll7ryq9MwLBE0vzDxyw,9028
123
+ pyglove/core/typing/annotation_conversion_test.py,sha256=tZheqbLWbr76WBIDOplLtY3yznMc4m9u7KCznWEJdEs,11660
124
124
  pyglove/core/typing/callable_ext.py,sha256=1OM770LhT46qjhriKEgyDHp6whAGG0inobPbFUM2J8k,9218
125
125
  pyglove/core/typing/callable_ext_test.py,sha256=TnWKU4_ZjvpbHZFtFHgFvCMDiCos8VmLlODcM_7Xg8M,10156
126
126
  pyglove/core/typing/callable_signature.py,sha256=MAH7isqhxXp8QdMw8jqxqWbsKs2jMh7uJlFIBL0tTQk,27242
127
127
  pyglove/core/typing/callable_signature_test.py,sha256=BYdn0i7nd0ITkACnR1RNtnmpXiE0MfpBrlQ7-yWSxYE,25223
128
- pyglove/core/typing/class_schema.py,sha256=XFGLidRQ_B_x6Va57ml89_zgkEj-4a4bHXSpHPMgZKE,53153
128
+ pyglove/core/typing/class_schema.py,sha256=ls_pg4MgwvV4s5gLnsjqDkiP3O2Fg0sWdjL1NVD-iyE,53273
129
129
  pyglove/core/typing/class_schema_test.py,sha256=1DvJTss20jzjruTnB4CuW6ozO6Hcs6TIN-xsJzEQkCM,29244
130
130
  pyglove/core/typing/custom_typing.py,sha256=w5gdx7JbHmAJfrUKLvonW9rI2gpCwMJo2wdF4yqarLI,2218
131
131
  pyglove/core/typing/inspect.py,sha256=Tp2rYqRvMFvL0NzkJo4p04_hQSF__Eip8RduhEaSFfE,7046
@@ -137,8 +137,8 @@ pyglove/core/typing/type_conversion.py,sha256=mOkp2LP2O9C5k8Q6r-CFwk9P_-oy4u26DI
137
137
  pyglove/core/typing/type_conversion_test.py,sha256=AH7HTOfWcF7cpWi49EG6UmB_xfCsdz7HS1U0Bg8o-PI,5295
138
138
  pyglove/core/typing/typed_missing.py,sha256=5lzkrd-DqJDT8eoW1d8p6mxV3mvy5X78zFon7qEqPIA,2784
139
139
  pyglove/core/typing/typed_missing_test.py,sha256=3k_s0JBYBJ_6xoOuPSMrQzqas6QHD0PHux0T9NlTBZc,2381
140
- pyglove/core/typing/value_specs.py,sha256=V6m4RhCc4yi6H8X1LoptSk5gsa7ko8HgLYDSfVsMBtQ,99874
141
- pyglove/core/typing/value_specs_test.py,sha256=kKwoEmGIXtM5JikhtbhcE4Ds-wSyYAsMTozHVa2Lpbc,122707
140
+ pyglove/core/typing/value_specs.py,sha256=D59ZB8L4HALtljzVkiLG9R2Uxg3bViDyc9Ebsr1fBH8,102276
141
+ pyglove/core/typing/value_specs_test.py,sha256=XY_txldanc5lycz0WgDhtn2XcO3nFo_ymlKLjgN18Mg,127258
142
142
  pyglove/core/views/__init__.py,sha256=gll9ZBRYz4p_-LWOdzSR2a6UTWcJ8nR430trrP0yLCU,967
143
143
  pyglove/core/views/base.py,sha256=Eq94AM5lryQ1IKuQsTSb7ZzX-lp2nhuOnS4ztMnCPIM,26447
144
144
  pyglove/core/views/base_test.py,sha256=UKbr_1TANOAnP7V5ICGF0UEkunfSaHiJ4nXZXhA0SaU,16642
@@ -196,8 +196,8 @@ pyglove/ext/scalars/randoms.py,sha256=LkMIIx7lOq_lvJvVS3BrgWGuWl7Pi91-lA-O8x_gZs
196
196
  pyglove/ext/scalars/randoms_test.py,sha256=nEhiqarg8l_5EOucp59CYrpO2uKxS1pe0hmBdZUzRNM,2000
197
197
  pyglove/ext/scalars/step_wise.py,sha256=IDw3tuTpv0KVh7AN44W43zqm1-E0HWPUlytWOQC9w3Y,3789
198
198
  pyglove/ext/scalars/step_wise_test.py,sha256=TL1vJ19xVx2t5HKuyIzGoogF7N3Rm8YhLE6JF7i0iy8,2540
199
- pyglove-0.4.5.dev202412140808.dist-info/LICENSE,sha256=WNHhf_5RCaeuKWyq_K39vmp9F28LxKsB4SpomwSZ2L0,11357
200
- pyglove-0.4.5.dev202412140808.dist-info/METADATA,sha256=R1KlgVeQR7K4-f-7x29aAwp9fQmUdlmR7Z1P1HIPMxY,6828
201
- pyglove-0.4.5.dev202412140808.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
202
- pyglove-0.4.5.dev202412140808.dist-info/top_level.txt,sha256=wITzJSKcj8GZUkbq-MvUQnFadkiuAv_qv5qQMw0fIow,8
203
- pyglove-0.4.5.dev202412140808.dist-info/RECORD,,
199
+ pyglove-0.4.5.dev202412160810.dist-info/LICENSE,sha256=WNHhf_5RCaeuKWyq_K39vmp9F28LxKsB4SpomwSZ2L0,11357
200
+ pyglove-0.4.5.dev202412160810.dist-info/METADATA,sha256=PkzGA4zFYMds7iJaQIs0dgq7A3yte__HQHsdnWQAEKA,6828
201
+ pyglove-0.4.5.dev202412160810.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
202
+ pyglove-0.4.5.dev202412160810.dist-info/top_level.txt,sha256=wITzJSKcj8GZUkbq-MvUQnFadkiuAv_qv5qQMw0fIow,8
203
+ pyglove-0.4.5.dev202412160810.dist-info/RECORD,,