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.
- pyglove/core/__init__.py +54 -20
- pyglove/core/coding/__init__.py +42 -0
- pyglove/core/coding/errors.py +111 -0
- pyglove/core/coding/errors_test.py +98 -0
- pyglove/core/coding/execution.py +309 -0
- pyglove/core/coding/execution_test.py +333 -0
- pyglove/core/{object_utils/codegen.py → coding/function_generation.py} +10 -4
- pyglove/core/{object_utils/codegen_test.py → coding/function_generation_test.py} +5 -7
- pyglove/core/coding/parsing.py +153 -0
- pyglove/core/coding/parsing_test.py +150 -0
- pyglove/core/coding/permissions.py +100 -0
- pyglove/core/coding/permissions_test.py +93 -0
- pyglove/core/geno/base.py +54 -41
- pyglove/core/geno/base_test.py +2 -4
- pyglove/core/geno/categorical.py +37 -28
- pyglove/core/geno/custom.py +19 -16
- pyglove/core/geno/numerical.py +20 -17
- pyglove/core/geno/space.py +4 -5
- pyglove/core/hyper/base.py +6 -6
- pyglove/core/hyper/categorical.py +94 -55
- pyglove/core/hyper/custom.py +7 -7
- pyglove/core/hyper/custom_test.py +9 -10
- pyglove/core/hyper/derived.py +30 -22
- pyglove/core/hyper/derived_test.py +2 -4
- pyglove/core/hyper/dynamic_evaluation.py +5 -6
- pyglove/core/hyper/evolvable.py +57 -46
- pyglove/core/hyper/numerical.py +48 -24
- pyglove/core/hyper/numerical_test.py +9 -9
- pyglove/core/hyper/object_template.py +58 -46
- pyglove/core/io/__init__.py +1 -0
- pyglove/core/io/file_system.py +17 -7
- pyglove/core/io/file_system_test.py +2 -0
- pyglove/core/io/sequence.py +299 -0
- pyglove/core/io/sequence_test.py +124 -0
- pyglove/core/logging_test.py +0 -2
- pyglove/core/patching/object_factory.py +4 -4
- pyglove/core/patching/pattern_based.py +4 -4
- pyglove/core/patching/rule_based.py +17 -5
- pyglove/core/patching/rule_based_test.py +27 -4
- pyglove/core/symbolic/__init__.py +2 -7
- pyglove/core/symbolic/base.py +320 -183
- pyglove/core/symbolic/base_test.py +123 -19
- pyglove/core/symbolic/boilerplate.py +7 -13
- pyglove/core/symbolic/boilerplate_test.py +25 -23
- pyglove/core/symbolic/class_wrapper.py +48 -45
- pyglove/core/symbolic/class_wrapper_test.py +2 -2
- pyglove/core/symbolic/compounding.py +9 -15
- pyglove/core/symbolic/compounding_test.py +2 -4
- pyglove/core/symbolic/dict.py +154 -110
- pyglove/core/symbolic/dict_test.py +238 -130
- pyglove/core/symbolic/diff.py +199 -10
- pyglove/core/symbolic/diff_test.py +226 -0
- pyglove/core/symbolic/flags.py +1 -1
- pyglove/core/symbolic/functor.py +29 -26
- pyglove/core/symbolic/functor_test.py +102 -50
- pyglove/core/symbolic/inferred.py +2 -2
- pyglove/core/symbolic/list.py +81 -50
- pyglove/core/symbolic/list_test.py +119 -97
- pyglove/core/symbolic/object.py +225 -113
- pyglove/core/symbolic/object_test.py +320 -108
- pyglove/core/symbolic/origin.py +17 -14
- pyglove/core/symbolic/origin_test.py +4 -2
- pyglove/core/symbolic/pure_symbolic.py +4 -3
- pyglove/core/symbolic/ref.py +108 -21
- pyglove/core/symbolic/ref_test.py +93 -0
- pyglove/core/symbolic/symbolize_test.py +10 -2
- pyglove/core/tuning/local_backend.py +2 -2
- pyglove/core/tuning/protocols.py +3 -3
- pyglove/core/tuning/sample_test.py +3 -3
- pyglove/core/typing/__init__.py +14 -5
- pyglove/core/typing/annotation_conversion.py +43 -27
- pyglove/core/typing/annotation_conversion_test.py +23 -0
- pyglove/core/typing/callable_ext.py +241 -3
- pyglove/core/typing/callable_ext_test.py +255 -0
- pyglove/core/typing/callable_signature.py +510 -66
- pyglove/core/typing/callable_signature_test.py +619 -99
- pyglove/core/typing/class_schema.py +229 -154
- pyglove/core/typing/class_schema_test.py +149 -95
- pyglove/core/typing/custom_typing.py +5 -4
- pyglove/core/typing/inspect.py +63 -0
- pyglove/core/typing/inspect_test.py +39 -0
- pyglove/core/typing/key_specs.py +10 -11
- pyglove/core/typing/key_specs_test.py +7 -4
- pyglove/core/typing/type_conversion.py +4 -5
- pyglove/core/typing/type_conversion_test.py +12 -12
- pyglove/core/typing/typed_missing.py +6 -7
- pyglove/core/typing/typed_missing_test.py +7 -8
- pyglove/core/typing/value_specs.py +604 -362
- pyglove/core/typing/value_specs_test.py +328 -90
- pyglove/core/utils/__init__.py +164 -0
- pyglove/core/{object_utils → utils}/common_traits.py +3 -67
- pyglove/core/utils/common_traits_test.py +36 -0
- pyglove/core/{object_utils → utils}/docstr_utils.py +23 -0
- pyglove/core/{object_utils → utils}/docstr_utils_test.py +36 -4
- pyglove/core/{object_utils → utils}/error_utils.py +78 -9
- pyglove/core/{object_utils → utils}/error_utils_test.py +61 -5
- pyglove/core/utils/formatting.py +464 -0
- pyglove/core/utils/formatting_test.py +453 -0
- pyglove/core/{object_utils → utils}/hierarchical.py +23 -25
- pyglove/core/{object_utils → utils}/hierarchical_test.py +3 -5
- pyglove/core/{object_utils → utils}/json_conversion.py +177 -52
- pyglove/core/{object_utils → utils}/json_conversion_test.py +97 -16
- pyglove/core/{object_utils → utils}/missing.py +3 -3
- pyglove/core/{object_utils → utils}/missing_test.py +2 -4
- pyglove/core/utils/text_color.py +128 -0
- pyglove/core/utils/text_color_test.py +94 -0
- pyglove/core/{object_utils → utils}/thread_local_test.py +1 -3
- pyglove/core/utils/timing.py +236 -0
- pyglove/core/utils/timing_test.py +154 -0
- pyglove/core/{object_utils → utils}/value_location.py +275 -6
- pyglove/core/utils/value_location_test.py +707 -0
- pyglove/core/views/__init__.py +32 -0
- pyglove/core/views/base.py +804 -0
- pyglove/core/views/base_test.py +580 -0
- pyglove/core/views/html/__init__.py +27 -0
- pyglove/core/views/html/base.py +547 -0
- pyglove/core/views/html/base_test.py +830 -0
- pyglove/core/views/html/controls/__init__.py +35 -0
- pyglove/core/views/html/controls/base.py +275 -0
- pyglove/core/views/html/controls/label.py +207 -0
- pyglove/core/views/html/controls/label_test.py +157 -0
- pyglove/core/views/html/controls/progress_bar.py +183 -0
- pyglove/core/views/html/controls/progress_bar_test.py +97 -0
- pyglove/core/views/html/controls/tab.py +320 -0
- pyglove/core/views/html/controls/tab_test.py +87 -0
- pyglove/core/views/html/controls/tooltip.py +99 -0
- pyglove/core/views/html/controls/tooltip_test.py +99 -0
- pyglove/core/views/html/tree_view.py +1517 -0
- pyglove/core/views/html/tree_view_test.py +1461 -0
- {pyglove-0.4.5.dev20240319.dist-info → pyglove-0.4.5.dev202501132210.dist-info}/METADATA +18 -4
- pyglove-0.4.5.dev202501132210.dist-info/RECORD +214 -0
- {pyglove-0.4.5.dev20240319.dist-info → pyglove-0.4.5.dev202501132210.dist-info}/WHEEL +1 -1
- pyglove/core/object_utils/__init__.py +0 -154
- pyglove/core/object_utils/common_traits_test.py +0 -82
- pyglove/core/object_utils/formatting.py +0 -234
- pyglove/core/object_utils/formatting_test.py +0 -223
- pyglove/core/object_utils/value_location_test.py +0 -385
- pyglove/core/symbolic/schema_utils.py +0 -327
- pyglove/core/symbolic/schema_utils_test.py +0 -57
- pyglove/core/typing/class_schema_utils.py +0 -202
- pyglove/core/typing/class_schema_utils_test.py +0 -194
- pyglove-0.4.5.dev20240319.dist-info/RECORD +0 -185
- /pyglove/core/{object_utils → utils}/thread_local.py +0 -0
- {pyglove-0.4.5.dev20240319.dist-info → pyglove-0.4.5.dev202501132210.dist-info}/LICENSE +0 -0
- {pyglove-0.4.5.dev20240319.dist-info → pyglove-0.4.5.dev202501132210.dist-info}/top_level.txt +0 -0
@@ -11,8 +11,6 @@
|
|
11
11
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
12
|
# See the License for the specific language governing permissions and
|
13
13
|
# limitations under the License.
|
14
|
-
"""Tests for pyglove.Object."""
|
15
|
-
|
16
14
|
import copy
|
17
15
|
import inspect
|
18
16
|
import io
|
@@ -23,8 +21,8 @@ import typing
|
|
23
21
|
from typing import Any
|
24
22
|
import unittest
|
25
23
|
|
26
|
-
from pyglove.core import object_utils
|
27
24
|
from pyglove.core import typing as pg_typing
|
25
|
+
from pyglove.core import utils
|
28
26
|
from pyglove.core.symbolic import base
|
29
27
|
from pyglove.core.symbolic import flags
|
30
28
|
from pyglove.core.symbolic import inferred
|
@@ -39,9 +37,10 @@ from pyglove.core.symbolic.object import use_init_args as pg_use_init_args
|
|
39
37
|
from pyglove.core.symbolic.origin import Origin
|
40
38
|
from pyglove.core.symbolic.pure_symbolic import NonDeterministic
|
41
39
|
from pyglove.core.symbolic.pure_symbolic import PureSymbolic
|
40
|
+
from pyglove.core.views.html import tree_view # pylint: disable=unused-import
|
42
41
|
|
43
42
|
|
44
|
-
MISSING_VALUE =
|
43
|
+
MISSING_VALUE = utils.MISSING_VALUE
|
45
44
|
|
46
45
|
|
47
46
|
class ObjectMetaTest(unittest.TestCase):
|
@@ -69,7 +68,7 @@ class ObjectMetaTest(unittest.TestCase):
|
|
69
68
|
pass
|
70
69
|
|
71
70
|
@pg_members([
|
72
|
-
('args', pg_typing.List(pg_typing.Str())),
|
71
|
+
('args', pg_typing.List(pg_typing.Str(), default=[])),
|
73
72
|
], init_arg_list=['x', 'y', 'z', '*args'])
|
74
73
|
class C(B):
|
75
74
|
pass
|
@@ -87,7 +86,7 @@ class ObjectMetaTest(unittest.TestCase):
|
|
87
86
|
('z', pg_typing.List(pg_typing.Int(min_value=1))),
|
88
87
|
('p', pg_typing.Bool().freeze(True)),
|
89
88
|
('q', pg_typing.Bool(default=True)),
|
90
|
-
('args', pg_typing.List(pg_typing.Str())),
|
89
|
+
('args', pg_typing.List(pg_typing.Str(), default=[])),
|
91
90
|
]),
|
92
91
|
)
|
93
92
|
|
@@ -100,16 +99,14 @@ class ObjectMetaTest(unittest.TestCase):
|
|
100
99
|
('z', pg_typing.List(pg_typing.Int(min_value=1))),
|
101
100
|
('p', pg_typing.Bool().freeze(True)),
|
102
101
|
('q', pg_typing.Bool(default=True)),
|
103
|
-
('args', pg_typing.List(pg_typing.Str())),
|
104
|
-
])
|
102
|
+
('args', pg_typing.List(pg_typing.Str(), default=[])),
|
103
|
+
])
|
104
|
+
)
|
105
105
|
|
106
106
|
def test_init_arg_list(self):
|
107
|
-
self.assertEqual(
|
108
|
-
|
109
|
-
self.assertEqual(
|
110
|
-
self._B.init_arg_list, ['x', 'y', 'z', 'p', 'q'])
|
111
|
-
self.assertEqual(
|
112
|
-
self._C.init_arg_list, ['x', 'y', 'z', '*args'])
|
107
|
+
self.assertEqual(self._A.init_arg_list, ['x', 'y', 'z', 'p'])
|
108
|
+
self.assertEqual(self._B.init_arg_list, ['x', 'y', 'z', 'q'])
|
109
|
+
self.assertEqual(self._C.init_arg_list, ['x', 'y', 'z', '*args'])
|
113
110
|
|
114
111
|
def test_serialization_key(self):
|
115
112
|
self.assertEqual(self._A.__serialization_key__, self._A.__type_name__)
|
@@ -206,7 +203,7 @@ class ObjectTest(unittest.TestCase):
|
|
206
203
|
])
|
207
204
|
class A(Object):
|
208
205
|
|
209
|
-
@
|
206
|
+
@utils.explicit_method_override
|
210
207
|
def __init__(self, x):
|
211
208
|
super().__init__(int(x))
|
212
209
|
|
@@ -215,7 +212,7 @@ class ObjectTest(unittest.TestCase):
|
|
215
212
|
|
216
213
|
class B(A):
|
217
214
|
|
218
|
-
@
|
215
|
+
@utils.explicit_method_override
|
219
216
|
def __init__(self, x): # pylint: disable=super-init-not-called
|
220
217
|
# Forgot to call super().__init__ will trigger error.
|
221
218
|
self.x = x
|
@@ -325,8 +322,95 @@ class ObjectTest(unittest.TestCase):
|
|
325
322
|
self.assertEqual(e.x, 1)
|
326
323
|
self.assertEqual(e.y, 3)
|
327
324
|
|
325
|
+
class F(Object):
|
326
|
+
x: typing.Literal[1, 'a']
|
327
|
+
|
328
|
+
class G(F):
|
329
|
+
x: typing.Final[int] = 1
|
330
|
+
y: typing.ClassVar[int] = 2
|
331
|
+
|
332
|
+
self.assertEqual(G().x, 1)
|
333
|
+
self.assertEqual(G.y, 2)
|
334
|
+
|
335
|
+
with self.assertRaisesRegex(
|
336
|
+
ValueError, 'Frozen field is not assignable'):
|
337
|
+
G(x=2)
|
338
|
+
|
339
|
+
with self.assertRaisesRegex(
|
340
|
+
TypeError, 'Field x is marked as final but has no default value'):
|
341
|
+
|
342
|
+
class H(Object): # pylint: disable=unused-variable
|
343
|
+
x: typing.Final[int] # pylint: disable=invalid-name
|
344
|
+
|
345
|
+
def test_init_arg_list(self):
|
346
|
+
|
347
|
+
def _update_init_arg_list(cls, init_arg_list):
|
348
|
+
cls.__schema__.metadata['init_arg_list'] = init_arg_list
|
349
|
+
cls.apply_schema()
|
350
|
+
|
351
|
+
def _assert_init_arg_list(
|
352
|
+
cls,
|
353
|
+
updated_init_arg_list: typing.Optional[typing.List[str]],
|
354
|
+
arg_names: typing.List[str],
|
355
|
+
kwonly_names: typing.List[str],
|
356
|
+
vararg_name: typing.Optional[str] = None
|
357
|
+
):
|
358
|
+
_update_init_arg_list(cls, updated_init_arg_list)
|
359
|
+
signature = pg_typing.signature(cls)
|
360
|
+
self.assertEqual(
|
361
|
+
[v.name for v in signature.args],
|
362
|
+
arg_names
|
363
|
+
)
|
364
|
+
self.assertEqual(
|
365
|
+
[v.name for v in signature.kwonlyargs],
|
366
|
+
kwonly_names
|
367
|
+
)
|
368
|
+
self.assertEqual(getattr(signature.varargs, 'name', None), vararg_name)
|
369
|
+
|
370
|
+
class A(Object):
|
371
|
+
x: int
|
372
|
+
y: str
|
373
|
+
|
374
|
+
_update_init_arg_list(A, ['x'])
|
375
|
+
|
376
|
+
class B(A):
|
377
|
+
pass
|
378
|
+
|
379
|
+
_update_init_arg_list(B, None)
|
380
|
+
self.assertEqual(B.init_arg_list, ['x'])
|
381
|
+
|
382
|
+
# Case 2: base has init_arg_list, child adds new fields.
|
383
|
+
class C(A):
|
384
|
+
z: str
|
385
|
+
|
386
|
+
_update_init_arg_list(C, None)
|
387
|
+
self.assertEqual(C.init_arg_list, ['x', 'y', 'z'])
|
388
|
+
|
389
|
+
# Case 3: base has no init_arg_list, child automatically figured it out.
|
390
|
+
class D(Object):
|
391
|
+
x: int
|
392
|
+
y: str
|
393
|
+
|
394
|
+
_update_init_arg_list(D, None)
|
395
|
+
|
396
|
+
class E(D):
|
397
|
+
pass
|
398
|
+
|
399
|
+
_update_init_arg_list(E, None)
|
400
|
+
self.assertEqual(E.init_arg_list, ['x', 'y'])
|
401
|
+
|
402
|
+
class F(Object):
|
403
|
+
x: int
|
404
|
+
y: typing.List[int]
|
405
|
+
|
406
|
+
_assert_init_arg_list(F, [], [], ['x', 'y'])
|
407
|
+
_assert_init_arg_list(F, ['x'], ['x'], ['y'])
|
408
|
+
_assert_init_arg_list(F, ['x', 'y'], ['x', 'y'], [])
|
409
|
+
_assert_init_arg_list(F, ['y', 'x'], ['y', 'x'], [])
|
410
|
+
_assert_init_arg_list(F, ['x', '*y'], ['x'], [], 'y')
|
411
|
+
|
328
412
|
def test_forward_reference(self):
|
329
|
-
self.assertIs(Foo.
|
413
|
+
self.assertIs(Foo.__schema__.get_field('p').value.cls, Foo)
|
330
414
|
|
331
415
|
def test_update_of_default_values(self):
|
332
416
|
|
@@ -370,6 +454,9 @@ class ObjectTest(unittest.TestCase):
|
|
370
454
|
def y(self):
|
371
455
|
return self.sym_init_args.y * 2
|
372
456
|
|
457
|
+
self.assertTrue(H.__schema__.fields['x'].frozen)
|
458
|
+
self.assertFalse(H.__schema__.fields['y'].frozen)
|
459
|
+
|
373
460
|
h = H(y=1)
|
374
461
|
self.assertEqual(h.x(1), 3)
|
375
462
|
self.assertEqual(h.y(), 2)
|
@@ -485,6 +572,28 @@ class ObjectTest(unittest.TestCase):
|
|
485
572
|
a.inspect(where=lambda v: v == 1, file=s)
|
486
573
|
self.assertEqual(s.getvalue(), '{\n \'x[0].x\': 1\n}\n')
|
487
574
|
|
575
|
+
def test_clone(self):
|
576
|
+
class X:
|
577
|
+
pass
|
578
|
+
|
579
|
+
@pg_members([
|
580
|
+
('x', pg_typing.Any()),
|
581
|
+
])
|
582
|
+
class A(Object):
|
583
|
+
pass
|
584
|
+
|
585
|
+
a = [dict(y=A([dict(), A(X())]))]
|
586
|
+
a2 = base.clone(a)
|
587
|
+
self.assertEqual(a, a2)
|
588
|
+
|
589
|
+
# Containers and symbolic objects are deeply copied.
|
590
|
+
self.assertIsNot(a, a2)
|
591
|
+
self.assertIsNot(a[0], a2[0])
|
592
|
+
self.assertIsNot(a[0]['y'].x[0], a2[0]['y'].x[0])
|
593
|
+
self.assertIsNot(a[0]['y'].x[1], a2[0]['y'].x[1])
|
594
|
+
# Regualr objects are shallowly copied.
|
595
|
+
self.assertIs(a[0]['y'].x[1].x, a2[0]['y'].x[1].x)
|
596
|
+
|
488
597
|
def test_copy(self):
|
489
598
|
|
490
599
|
class X:
|
@@ -691,8 +800,8 @@ class ObjectTest(unittest.TestCase):
|
|
691
800
|
a = A(A(dict(y=A(1))))
|
692
801
|
self.assertTrue(a.sym_has('x'))
|
693
802
|
self.assertTrue(a.sym_has('x.x'))
|
694
|
-
self.assertTrue(a.sym_has(
|
695
|
-
self.assertTrue(a.sym_has(
|
803
|
+
self.assertTrue(a.sym_has(utils.KeyPath.parse('x.x.y')))
|
804
|
+
self.assertTrue(a.sym_has(utils.KeyPath.parse('x.x.y.x')))
|
696
805
|
self.assertFalse(a.sym_has('y')) # `y` is not a symbolic field.
|
697
806
|
|
698
807
|
def test_sym_get(self):
|
@@ -717,10 +826,10 @@ class ObjectTest(unittest.TestCase):
|
|
717
826
|
self.assertIs(a.sym_get('x'), a.x)
|
718
827
|
self.assertIs(a.sym_get('p'), a.sym_getattr('p'))
|
719
828
|
self.assertIs(a.sym_get('x.x'), a.x.x)
|
720
|
-
self.assertIs(a.sym_get(
|
721
|
-
self.assertIs(a.sym_get(
|
829
|
+
self.assertIs(a.sym_get(utils.KeyPath.parse('x.x.y')), a.x.x.y)
|
830
|
+
self.assertIs(a.sym_get(utils.KeyPath.parse('x.x.y.x')), a.x.x.y.x)
|
722
831
|
self.assertIs(
|
723
|
-
a.sym_get(
|
832
|
+
a.sym_get(utils.KeyPath.parse('x.x.y.p')),
|
724
833
|
a.x.x.y.sym_getattr('p'),
|
725
834
|
)
|
726
835
|
self.assertIsNone(a.sym_get('x.x.y.q', use_inferred=True))
|
@@ -1014,8 +1123,6 @@ class ObjectTest(unittest.TestCase):
|
|
1014
1123
|
|
1015
1124
|
# Origin is not tracked by default.
|
1016
1125
|
a = builder_of_builder(1)
|
1017
|
-
a1 = a() # a1 is a `builder`.
|
1018
|
-
a2 = a() # a2 is an `A`.
|
1019
1126
|
a3 = a.clone()
|
1020
1127
|
a4 = a3.clone(deep=True)
|
1021
1128
|
self.assertIsNone(a4.sym_origin)
|
@@ -1486,7 +1593,7 @@ class ObjectTest(unittest.TestCase):
|
|
1486
1593
|
self.assertEqual(a.x.x.x.sym_path, 'x.x.x')
|
1487
1594
|
self.assertEqual(a.x.x.x[0].sym_path, 'x.x.x[0]')
|
1488
1595
|
|
1489
|
-
a.sym_setpath(
|
1596
|
+
a.sym_setpath(utils.KeyPath('a'))
|
1490
1597
|
self.assertEqual(a.sym_path, 'a')
|
1491
1598
|
self.assertEqual(a.x.sym_path, 'a.x')
|
1492
1599
|
self.assertEqual(a.x.x.sym_path, 'a.x.x')
|
@@ -1745,13 +1852,67 @@ class MembersTest(unittest.TestCase):
|
|
1745
1852
|
json_dict['_type'] = key
|
1746
1853
|
self.assertEqual(base.from_json(json_dict), A(1))
|
1747
1854
|
|
1748
|
-
def test_bad_cases(self):
|
1749
1855
|
|
1750
|
-
|
1856
|
+
class InheritanceTest(unittest.TestCase):
|
1857
|
+
"""Tests for `pg.Object` inheritance."""
|
1751
1858
|
|
1752
|
-
|
1753
|
-
|
1754
|
-
|
1859
|
+
def test_single_inheritance(self):
|
1860
|
+
|
1861
|
+
class A(Object):
|
1862
|
+
x: Any
|
1863
|
+
|
1864
|
+
class B(A):
|
1865
|
+
y: str
|
1866
|
+
|
1867
|
+
self.assertEqual(list(B.__schema__.keys()), ['x', 'y'])
|
1868
|
+
|
1869
|
+
class C(B):
|
1870
|
+
# Be more specific about x and y's type.
|
1871
|
+
x: int
|
1872
|
+
y: typing.Literal['a', 'b']
|
1873
|
+
|
1874
|
+
self.assertEqual(list(C.__schema__.keys()), ['x', 'y'])
|
1875
|
+
self.assertEqual(C.__schema__['x'].value, pg_typing.Int())
|
1876
|
+
self.assertEqual(
|
1877
|
+
C.__schema__['y'].value,
|
1878
|
+
pg_typing.Enum(pg_typing.MISSING_VALUE, ['a', 'b'])
|
1879
|
+
)
|
1880
|
+
|
1881
|
+
def test_bad_inheritance(self):
|
1882
|
+
|
1883
|
+
class A(Object):
|
1884
|
+
x: int
|
1885
|
+
|
1886
|
+
with self.assertRaisesRegex(TypeError, 'incompatible type'):
|
1887
|
+
|
1888
|
+
class B(A): # pylint: disable=unused-variable
|
1889
|
+
x: str
|
1890
|
+
|
1891
|
+
def test_multi_inheritance(self):
|
1892
|
+
|
1893
|
+
class A(Object):
|
1894
|
+
x: str
|
1895
|
+
|
1896
|
+
class B(A):
|
1897
|
+
x = 'foo'
|
1898
|
+
|
1899
|
+
def foo(self):
|
1900
|
+
return 'B'
|
1901
|
+
|
1902
|
+
class C(A):
|
1903
|
+
y: int
|
1904
|
+
|
1905
|
+
def bar(self):
|
1906
|
+
return 'C'
|
1907
|
+
|
1908
|
+
class D(B, C):
|
1909
|
+
pass
|
1910
|
+
|
1911
|
+
self.assertEqual(list(D.__schema__.keys()), ['x', 'y'])
|
1912
|
+
self.assertEqual(D.__schema__['x'].default_value, 'foo')
|
1913
|
+
d = D(y=2)
|
1914
|
+
self.assertEqual(d.x, 'foo')
|
1915
|
+
self.assertEqual(d.foo(), 'B')
|
1755
1916
|
|
1756
1917
|
|
1757
1918
|
class InitSignatureTest(unittest.TestCase):
|
@@ -1912,7 +2073,7 @@ class InitSignatureTest(unittest.TestCase):
|
|
1912
2073
|
class C(B):
|
1913
2074
|
"""Custom __init__."""
|
1914
2075
|
|
1915
|
-
@
|
2076
|
+
@utils.explicit_method_override
|
1916
2077
|
def __init__(self, a, b):
|
1917
2078
|
super().__init__(b, x=a)
|
1918
2079
|
|
@@ -2206,7 +2367,7 @@ class RebindTest(unittest.TestCase):
|
|
2206
2367
|
A(1).rebind({})
|
2207
2368
|
|
2208
2369
|
with self.assertRaisesRegex(
|
2209
|
-
KeyError, 'Key
|
2370
|
+
KeyError, 'Key 1 is not allowed for .*'):
|
2210
2371
|
A(1).rebind({1: 1})
|
2211
2372
|
|
2212
2373
|
with self.assertRaisesRegex(
|
@@ -2287,44 +2448,51 @@ class EventsTest(unittest.TestCase):
|
|
2287
2448
|
[
|
2288
2449
|
# Set default value from outer space (parent List) for field d1.
|
2289
2450
|
{
|
2290
|
-
'd1':
|
2291
|
-
|
2292
|
-
|
2293
|
-
|
2294
|
-
|
2295
|
-
|
2296
|
-
|
2451
|
+
'd1': base.FieldUpdate(
|
2452
|
+
path=utils.KeyPath.parse('a2.b1.c1[0].d1'),
|
2453
|
+
target=sd.a2.b1.c1[0],
|
2454
|
+
field=sd.a2.b1.c1[0].value_spec.schema['d1'],
|
2455
|
+
old_value=MISSING_VALUE,
|
2456
|
+
new_value='foo',
|
2457
|
+
)
|
2297
2458
|
},
|
2298
2459
|
# Set default value from outer space (parent List) for field d2.
|
2299
2460
|
{
|
2300
|
-
'd2':
|
2301
|
-
|
2302
|
-
|
2303
|
-
|
2304
|
-
|
2305
|
-
|
2306
|
-
|
2307
|
-
}
|
2308
|
-
]
|
2461
|
+
'd2': base.FieldUpdate(
|
2462
|
+
path=utils.KeyPath.parse('a2.b1.c1[0].d2'),
|
2463
|
+
target=sd.a2.b1.c1[0],
|
2464
|
+
field=sd.a2.b1.c1[0].value_spec.schema['d2'],
|
2465
|
+
old_value=MISSING_VALUE,
|
2466
|
+
new_value=True,
|
2467
|
+
)
|
2468
|
+
},
|
2469
|
+
],
|
2470
|
+
)
|
2309
2471
|
|
2310
2472
|
# list get updated after bind with parent structures.
|
2311
|
-
self.assertEqual(
|
2312
|
-
|
2313
|
-
|
2314
|
-
|
2315
|
-
|
2316
|
-
|
2317
|
-
|
2318
|
-
|
2319
|
-
|
2320
|
-
|
2321
|
-
|
2322
|
-
|
2323
|
-
|
2324
|
-
|
2325
|
-
|
2326
|
-
|
2327
|
-
|
2473
|
+
self.assertEqual(
|
2474
|
+
list_updates,
|
2475
|
+
[
|
2476
|
+
{
|
2477
|
+
'[0].d1': base.FieldUpdate(
|
2478
|
+
path=utils.KeyPath.parse('a2.b1.c1[0].d1'),
|
2479
|
+
target=sd.a2.b1.c1[0],
|
2480
|
+
field=sd.a2.b1.c1[0].value_spec.schema['d1'],
|
2481
|
+
old_value=MISSING_VALUE,
|
2482
|
+
new_value='foo',
|
2483
|
+
)
|
2484
|
+
},
|
2485
|
+
{
|
2486
|
+
'[0].d2': base.FieldUpdate(
|
2487
|
+
path=utils.KeyPath.parse('a2.b1.c1[0].d2'),
|
2488
|
+
target=sd.a2.b1.c1[0],
|
2489
|
+
field=sd.a2.b1.c1[0].value_spec.schema['d2'],
|
2490
|
+
old_value=MISSING_VALUE,
|
2491
|
+
new_value=True,
|
2492
|
+
)
|
2493
|
+
},
|
2494
|
+
],
|
2495
|
+
)
|
2328
2496
|
|
2329
2497
|
# There are no updates in root.
|
2330
2498
|
self.assertEqual(root_updates, [])
|
@@ -2347,28 +2515,28 @@ class EventsTest(unittest.TestCase):
|
|
2347
2515
|
root_updates[0],
|
2348
2516
|
{
|
2349
2517
|
'a1': base.FieldUpdate(
|
2350
|
-
path=
|
2518
|
+
path=utils.KeyPath.parse('a1'),
|
2351
2519
|
target=sd,
|
2352
2520
|
field=sd.value_spec.schema['a1'],
|
2353
2521
|
old_value=MISSING_VALUE,
|
2354
2522
|
new_value=1,
|
2355
2523
|
),
|
2356
2524
|
'a2.b1.c1[0].d1': base.FieldUpdate(
|
2357
|
-
path=
|
2525
|
+
path=utils.KeyPath.parse('a2.b1.c1[0].d1'),
|
2358
2526
|
target=sd.a2.b1.c1[0],
|
2359
2527
|
field=sd.a2.b1.c1[0].value_spec.schema['d1'],
|
2360
2528
|
old_value='foo',
|
2361
2529
|
new_value='bar',
|
2362
2530
|
),
|
2363
2531
|
'a2.b1.c1[0].d2': base.FieldUpdate(
|
2364
|
-
path=
|
2532
|
+
path=utils.KeyPath.parse('a2.b1.c1[0].d2'),
|
2365
2533
|
target=sd.a2.b1.c1[0],
|
2366
2534
|
field=sd.a2.b1.c1[0].value_spec.schema['d2'],
|
2367
2535
|
old_value=True,
|
2368
2536
|
new_value=False,
|
2369
2537
|
),
|
2370
2538
|
'a2.b1.c1[0].d3.z': base.FieldUpdate(
|
2371
|
-
path=
|
2539
|
+
path=utils.KeyPath.parse('a2.b1.c1[0].d3.z'),
|
2372
2540
|
target=sd.a2.b1.c1[0].d3,
|
2373
2541
|
field=sd.a2.b1.c1[0].d3.__class__.__schema__['z'],
|
2374
2542
|
old_value=MISSING_VALUE,
|
@@ -2384,21 +2552,21 @@ class EventsTest(unittest.TestCase):
|
|
2384
2552
|
# Root object rebind.
|
2385
2553
|
{
|
2386
2554
|
'[0].d1': base.FieldUpdate(
|
2387
|
-
path=
|
2555
|
+
path=utils.KeyPath.parse('a2.b1.c1[0].d1'),
|
2388
2556
|
target=sd.a2.b1.c1[0],
|
2389
2557
|
field=sd.a2.b1.c1[0].value_spec.schema['d1'],
|
2390
2558
|
old_value='foo',
|
2391
2559
|
new_value='bar',
|
2392
2560
|
),
|
2393
2561
|
'[0].d2': base.FieldUpdate(
|
2394
|
-
path=
|
2562
|
+
path=utils.KeyPath.parse('a2.b1.c1[0].d2'),
|
2395
2563
|
target=sd.a2.b1.c1[0],
|
2396
2564
|
field=sd.a2.b1.c1[0].value_spec.schema['d2'],
|
2397
2565
|
old_value=True,
|
2398
2566
|
new_value=False,
|
2399
2567
|
),
|
2400
2568
|
'[0].d3.z': base.FieldUpdate(
|
2401
|
-
path=
|
2569
|
+
path=utils.KeyPath.parse('a2.b1.c1[0].d3.z'),
|
2402
2570
|
target=sd.a2.b1.c1[0].d3,
|
2403
2571
|
field=sd.a2.b1.c1[0].d3.__class__.__schema__['z'],
|
2404
2572
|
old_value=MISSING_VALUE,
|
@@ -2414,29 +2582,30 @@ class EventsTest(unittest.TestCase):
|
|
2414
2582
|
[
|
2415
2583
|
# Root object rebind.
|
2416
2584
|
{
|
2417
|
-
'd1':
|
2418
|
-
|
2419
|
-
|
2420
|
-
|
2421
|
-
|
2422
|
-
|
2423
|
-
|
2424
|
-
'd2':
|
2425
|
-
|
2426
|
-
|
2427
|
-
|
2428
|
-
|
2429
|
-
|
2430
|
-
|
2431
|
-
'd3.z':
|
2432
|
-
|
2433
|
-
|
2434
|
-
|
2435
|
-
|
2436
|
-
|
2437
|
-
|
2585
|
+
'd1': base.FieldUpdate(
|
2586
|
+
path=utils.KeyPath.parse('a2.b1.c1[0].d1'),
|
2587
|
+
target=sd.a2.b1.c1[0],
|
2588
|
+
field=sd.a2.b1.c1[0].value_spec.schema['d1'],
|
2589
|
+
old_value='foo',
|
2590
|
+
new_value='bar',
|
2591
|
+
),
|
2592
|
+
'd2': base.FieldUpdate(
|
2593
|
+
path=utils.KeyPath.parse('a2.b1.c1[0].d2'),
|
2594
|
+
target=sd.a2.b1.c1[0],
|
2595
|
+
field=sd.a2.b1.c1[0].value_spec.schema['d2'],
|
2596
|
+
old_value=True,
|
2597
|
+
new_value=False,
|
2598
|
+
),
|
2599
|
+
'd3.z': base.FieldUpdate(
|
2600
|
+
path=utils.KeyPath.parse('a2.b1.c1[0].d3.z'),
|
2601
|
+
target=sd.a2.b1.c1[0].d3,
|
2602
|
+
field=sd.a2.b1.c1[0].d3.__class__.__schema__['z'],
|
2603
|
+
old_value=MISSING_VALUE,
|
2604
|
+
new_value='foo',
|
2605
|
+
),
|
2438
2606
|
}
|
2439
|
-
]
|
2607
|
+
],
|
2608
|
+
)
|
2440
2609
|
|
2441
2610
|
def test_on_change_notification_order(self):
|
2442
2611
|
change_order = []
|
@@ -2482,7 +2651,7 @@ class EventsTest(unittest.TestCase):
|
|
2482
2651
|
y.x = A()
|
2483
2652
|
self.assertIs(x.old_parent, y)
|
2484
2653
|
self.assertIsNone(x.new_parent)
|
2485
|
-
self.assertEqual(x.sym_path,
|
2654
|
+
self.assertEqual(x.sym_path, utils.KeyPath())
|
2486
2655
|
|
2487
2656
|
def test_on_path_change(self):
|
2488
2657
|
|
@@ -2493,8 +2662,8 @@ class EventsTest(unittest.TestCase):
|
|
2493
2662
|
self.new_path = new_path
|
2494
2663
|
|
2495
2664
|
x = A()
|
2496
|
-
x.sym_setpath(
|
2497
|
-
self.assertEqual(x.old_path,
|
2665
|
+
x.sym_setpath(utils.KeyPath('a'))
|
2666
|
+
self.assertEqual(x.old_path, utils.KeyPath())
|
2498
2667
|
self.assertEqual(x.new_path, 'a')
|
2499
2668
|
|
2500
2669
|
y = Dict(x=x)
|
@@ -2896,7 +3065,7 @@ class SerializationTest(unittest.TestCase):
|
|
2896
3065
|
|
2897
3066
|
def test_serialization_with_json_convertible(self):
|
2898
3067
|
|
2899
|
-
class Y(
|
3068
|
+
class Y(utils.JSONConvertible):
|
2900
3069
|
|
2901
3070
|
TYPE_NAME = 'Y'
|
2902
3071
|
|
@@ -2916,7 +3085,7 @@ class SerializationTest(unittest.TestCase):
|
|
2916
3085
|
def from_json(cls, json_dict, *args, **kwargs):
|
2917
3086
|
return cls(json_dict.pop('value'))
|
2918
3087
|
|
2919
|
-
|
3088
|
+
utils.JSONConvertible.register(Y.TYPE_NAME, Y)
|
2920
3089
|
|
2921
3090
|
a = self._A(Y(1), y=True)
|
2922
3091
|
self.assertEqual(base.from_json_str(a.to_json_str()), a)
|
@@ -2935,18 +3104,38 @@ class SerializationTest(unittest.TestCase):
|
|
2935
3104
|
Q.partial(P.partial()).to_json_str(), allow_partial=True),
|
2936
3105
|
Q.partial(P.partial()))
|
2937
3106
|
|
2938
|
-
def
|
3107
|
+
def test_serialization_with_auto_dict(self):
|
2939
3108
|
|
2940
3109
|
class P(Object):
|
3110
|
+
auto_register = False
|
2941
3111
|
x: int
|
2942
3112
|
|
2943
3113
|
class Q(Object):
|
3114
|
+
auto_register = False
|
2944
3115
|
p: P
|
2945
3116
|
y: str
|
2946
3117
|
|
2947
3118
|
self.assertEqual(
|
2948
|
-
|
2949
|
-
{
|
3119
|
+
Q(P(1), y='foo').to_json(),
|
3120
|
+
{
|
3121
|
+
'p': {
|
3122
|
+
'_type': P.__type_name__,
|
3123
|
+
'x': 1
|
3124
|
+
},
|
3125
|
+
'y': 'foo',
|
3126
|
+
'_type': Q.__type_name__,
|
3127
|
+
}
|
3128
|
+
)
|
3129
|
+
self.assertEqual(
|
3130
|
+
base.from_json_str(Q(P(1), y='foo').to_json_str(), auto_dict=True),
|
3131
|
+
{
|
3132
|
+
'p': {
|
3133
|
+
'type_name': P.__type_name__,
|
3134
|
+
'x': 1
|
3135
|
+
},
|
3136
|
+
'y': 'foo',
|
3137
|
+
'type_name': Q.__type_name__,
|
3138
|
+
}
|
2950
3139
|
)
|
2951
3140
|
|
2952
3141
|
def test_serialization_with_converter(self):
|
@@ -2986,9 +3175,7 @@ class SerializationTest(unittest.TestCase):
|
|
2986
3175
|
ValueError, 'Cannot encode opaque object .* with pickle'):
|
2987
3176
|
base.to_json(self._A(w=Z(), y=True))
|
2988
3177
|
|
2989
|
-
with self.assertRaisesRegex(
|
2990
|
-
TypeError,
|
2991
|
-
'Type name \'.*\' is not registered with a .* subclass'):
|
3178
|
+
with self.assertRaisesRegex(TypeError, 'Cannot load class .*'):
|
2992
3179
|
base.from_json_str('{"_type": "pyglove.core.symbolic.object_test.NotExisted", "a": 1}')
|
2993
3180
|
|
2994
3181
|
def test_default_load_save_handler(self):
|
@@ -3105,7 +3292,7 @@ class FormatTest(unittest.TestCase):
|
|
3105
3292
|
|
3106
3293
|
def test_compact_python_format(self):
|
3107
3294
|
self.assertEqual(
|
3108
|
-
self._a
|
3295
|
+
utils.format(self._a, compact=True, python_format=True, markdown=True),
|
3109
3296
|
"`A(x=[A(x=1, y=None), A(x='foo', y={'a': A(x=True, y=1.0)})], "
|
3110
3297
|
'y=MISSING_VALUE)`',
|
3111
3298
|
)
|
@@ -3150,8 +3337,12 @@ class FormatTest(unittest.TestCase):
|
|
3150
3337
|
|
3151
3338
|
def test_noncompact_python_format(self):
|
3152
3339
|
self.assertEqual(
|
3153
|
-
|
3154
|
-
|
3340
|
+
utils.format(
|
3341
|
+
self._a,
|
3342
|
+
compact=False,
|
3343
|
+
verbose=False,
|
3344
|
+
python_format=True,
|
3345
|
+
markdown=True,
|
3155
3346
|
),
|
3156
3347
|
inspect.cleandoc("""
|
3157
3348
|
```
|
@@ -3303,6 +3494,27 @@ class FormatTest(unittest.TestCase):
|
|
3303
3494
|
]
|
3304
3495
|
)"""))
|
3305
3496
|
|
3497
|
+
def test_custom_format(self):
|
3498
|
+
|
3499
|
+
class Foo(Object): # pylint: disable=redefined-outer-name]
|
3500
|
+
pass
|
3501
|
+
|
3502
|
+
class Bar(Object):
|
3503
|
+
foo: Foo
|
3504
|
+
|
3505
|
+
def _method(attr_name):
|
3506
|
+
def fn(v, root_indent):
|
3507
|
+
del root_indent
|
3508
|
+
f = getattr(v, attr_name, None)
|
3509
|
+
return f() if f is not None else None
|
3510
|
+
return fn
|
3511
|
+
|
3512
|
+
with utils.str_format(custom_format=_method('_repr_xml_')):
|
3513
|
+
self.assertEqual(str(Bar(Foo())), 'Bar(\n foo = Foo()\n)')
|
3514
|
+
|
3515
|
+
with utils.str_format(custom_format=_method('_repr_html_')):
|
3516
|
+
self.assertIn('<html>', str(Bar(Foo())))
|
3517
|
+
|
3306
3518
|
|
3307
3519
|
class Foo(Object):
|
3308
3520
|
x: typing.List[typing.Dict[str, int]] = [dict(x=1)]
|