pyglove 0.4.5.dev202411132359__py3-none-any.whl → 0.4.5.dev202501250807__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyglove/core/__init__.py +40 -21
- pyglove/core/coding/__init__.py +42 -0
- pyglove/core/coding/errors.py +111 -0
- pyglove/core/coding/errors_test.py +98 -0
- pyglove/core/coding/execution.py +312 -0
- pyglove/core/coding/execution_test.py +333 -0
- pyglove/core/{object_utils/codegen.py → coding/function_generation.py} +10 -4
- pyglove/core/{object_utils/codegen_test.py → coding/function_generation_test.py} +5 -7
- pyglove/core/coding/parsing.py +153 -0
- pyglove/core/coding/parsing_test.py +150 -0
- pyglove/core/coding/permissions.py +100 -0
- pyglove/core/coding/permissions_test.py +93 -0
- pyglove/core/geno/base.py +53 -38
- pyglove/core/geno/base_test.py +2 -4
- pyglove/core/geno/categorical.py +36 -27
- pyglove/core/geno/custom.py +18 -15
- pyglove/core/geno/numerical.py +19 -16
- pyglove/core/geno/space.py +3 -4
- pyglove/core/hyper/base.py +6 -6
- pyglove/core/hyper/categorical.py +91 -52
- pyglove/core/hyper/custom.py +7 -7
- pyglove/core/hyper/custom_test.py +9 -10
- pyglove/core/hyper/derived.py +30 -22
- pyglove/core/hyper/derived_test.py +3 -5
- pyglove/core/hyper/dynamic_evaluation.py +3 -4
- pyglove/core/hyper/evolvable.py +57 -46
- pyglove/core/hyper/numerical.py +48 -24
- pyglove/core/hyper/numerical_test.py +9 -9
- pyglove/core/hyper/object_template.py +58 -46
- pyglove/core/logging_test.py +0 -2
- pyglove/core/patching/object_factory.py +4 -4
- pyglove/core/patching/pattern_based.py +4 -4
- pyglove/core/patching/rule_based.py +4 -3
- pyglove/core/symbolic/__init__.py +4 -0
- pyglove/core/symbolic/base.py +200 -136
- pyglove/core/symbolic/base_test.py +17 -19
- pyglove/core/symbolic/boilerplate.py +4 -5
- pyglove/core/symbolic/class_wrapper.py +10 -14
- pyglove/core/symbolic/class_wrapper_test.py +2 -2
- pyglove/core/symbolic/compounding.py +2 -2
- pyglove/core/symbolic/compounding_test.py +2 -4
- pyglove/core/symbolic/contextual_object.py +288 -0
- pyglove/core/symbolic/contextual_object_test.py +327 -0
- pyglove/core/symbolic/dict.py +115 -87
- pyglove/core/symbolic/dict_test.py +188 -131
- pyglove/core/symbolic/diff.py +12 -12
- pyglove/core/symbolic/flags.py +1 -1
- pyglove/core/symbolic/functor.py +16 -15
- pyglove/core/symbolic/functor_test.py +2 -4
- pyglove/core/symbolic/inferred.py +2 -2
- pyglove/core/symbolic/list.py +70 -47
- pyglove/core/symbolic/list_test.py +117 -98
- pyglove/core/symbolic/object.py +59 -58
- pyglove/core/symbolic/object_test.py +143 -90
- pyglove/core/symbolic/origin.py +5 -7
- pyglove/core/symbolic/pure_symbolic.py +4 -3
- pyglove/core/symbolic/ref.py +33 -16
- pyglove/core/symbolic/ref_test.py +17 -0
- pyglove/core/tuning/local_backend.py +2 -2
- pyglove/core/tuning/protocols.py +3 -3
- pyglove/core/typing/annotation_conversion.py +8 -3
- pyglove/core/typing/annotation_conversion_test.py +8 -0
- pyglove/core/typing/callable_ext.py +11 -13
- pyglove/core/typing/callable_signature.py +22 -19
- pyglove/core/typing/callable_signature_test.py +3 -5
- pyglove/core/typing/class_schema.py +93 -54
- pyglove/core/typing/class_schema_test.py +4 -5
- pyglove/core/typing/custom_typing.py +5 -4
- pyglove/core/typing/key_specs.py +5 -7
- pyglove/core/typing/key_specs_test.py +4 -4
- pyglove/core/typing/type_conversion.py +4 -5
- pyglove/core/typing/type_conversion_test.py +12 -12
- pyglove/core/typing/typed_missing.py +6 -7
- pyglove/core/typing/typed_missing_test.py +7 -8
- pyglove/core/typing/value_specs.py +287 -144
- pyglove/core/typing/value_specs_test.py +148 -25
- pyglove/core/utils/__init__.py +172 -0
- pyglove/core/{object_utils → utils}/common_traits.py +2 -2
- pyglove/core/{object_utils → utils}/common_traits_test.py +1 -3
- pyglove/core/utils/contextual.py +147 -0
- pyglove/core/utils/contextual_test.py +88 -0
- pyglove/core/{object_utils → utils}/docstr_utils_test.py +1 -3
- pyglove/core/{object_utils → utils}/error_utils.py +3 -3
- pyglove/core/{object_utils → utils}/error_utils_test.py +1 -1
- pyglove/core/{object_utils → utils}/formatting.py +1 -1
- pyglove/core/{object_utils → utils}/formatting_test.py +1 -2
- pyglove/core/{object_utils → utils}/hierarchical.py +23 -25
- pyglove/core/{object_utils → utils}/hierarchical_test.py +3 -5
- pyglove/core/{object_utils → utils}/json_conversion.py +1 -1
- pyglove/core/{object_utils → utils}/json_conversion_test.py +1 -3
- pyglove/core/{object_utils → utils}/missing.py +2 -2
- pyglove/core/{object_utils → utils}/missing_test.py +2 -4
- pyglove/core/utils/text_color.py +128 -0
- pyglove/core/utils/text_color_test.py +94 -0
- pyglove/core/{object_utils → utils}/thread_local_test.py +1 -3
- pyglove/core/{object_utils → utils}/timing.py +21 -10
- pyglove/core/{object_utils → utils}/timing_test.py +14 -12
- pyglove/core/{object_utils → utils}/value_location.py +2 -2
- pyglove/core/{object_utils → utils}/value_location_test.py +2 -4
- pyglove/core/views/base.py +25 -29
- pyglove/core/views/html/base.py +15 -16
- pyglove/core/views/html/controls/base.py +46 -9
- pyglove/core/views/html/controls/label.py +13 -2
- pyglove/core/views/html/controls/label_test.py +27 -8
- pyglove/core/views/html/controls/progress_bar.py +3 -5
- pyglove/core/views/html/controls/progress_bar_test.py +2 -2
- pyglove/core/views/html/controls/tab.py +217 -66
- pyglove/core/views/html/controls/tab_test.py +46 -15
- pyglove/core/views/html/tree_view.py +39 -37
- {pyglove-0.4.5.dev202411132359.dist-info → pyglove-0.4.5.dev202501250807.dist-info}/METADATA +17 -3
- pyglove-0.4.5.dev202501250807.dist-info/RECORD +218 -0
- {pyglove-0.4.5.dev202411132359.dist-info → pyglove-0.4.5.dev202501250807.dist-info}/WHEEL +1 -1
- pyglove/core/object_utils/__init__.py +0 -164
- pyglove-0.4.5.dev202411132359.dist-info/RECORD +0 -203
- /pyglove/core/{object_utils → utils}/docstr_utils.py +0 -0
- /pyglove/core/{object_utils → utils}/thread_local.py +0 -0
- {pyglove-0.4.5.dev202411132359.dist-info → pyglove-0.4.5.dev202501250807.dist-info}/LICENSE +0 -0
- {pyglove-0.4.5.dev202411132359.dist-info → pyglove-0.4.5.dev202501250807.dist-info}/top_level.txt +0 -0
@@ -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
|
@@ -42,7 +40,7 @@ from pyglove.core.symbolic.pure_symbolic import PureSymbolic
|
|
42
40
|
from pyglove.core.views.html import tree_view # pylint: disable=unused-import
|
43
41
|
|
44
42
|
|
45
|
-
MISSING_VALUE =
|
43
|
+
MISSING_VALUE = utils.MISSING_VALUE
|
46
44
|
|
47
45
|
|
48
46
|
class ObjectMetaTest(unittest.TestCase):
|
@@ -205,7 +203,7 @@ class ObjectTest(unittest.TestCase):
|
|
205
203
|
])
|
206
204
|
class A(Object):
|
207
205
|
|
208
|
-
@
|
206
|
+
@utils.explicit_method_override
|
209
207
|
def __init__(self, x):
|
210
208
|
super().__init__(int(x))
|
211
209
|
|
@@ -214,7 +212,7 @@ class ObjectTest(unittest.TestCase):
|
|
214
212
|
|
215
213
|
class B(A):
|
216
214
|
|
217
|
-
@
|
215
|
+
@utils.explicit_method_override
|
218
216
|
def __init__(self, x): # pylint: disable=super-init-not-called
|
219
217
|
# Forgot to call super().__init__ will trigger error.
|
220
218
|
self.x = x
|
@@ -324,6 +322,26 @@ class ObjectTest(unittest.TestCase):
|
|
324
322
|
self.assertEqual(e.x, 1)
|
325
323
|
self.assertEqual(e.y, 3)
|
326
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
|
+
|
327
345
|
def test_init_arg_list(self):
|
328
346
|
|
329
347
|
def _update_init_arg_list(cls, init_arg_list):
|
@@ -392,7 +410,7 @@ class ObjectTest(unittest.TestCase):
|
|
392
410
|
_assert_init_arg_list(F, ['x', '*y'], ['x'], [], 'y')
|
393
411
|
|
394
412
|
def test_forward_reference(self):
|
395
|
-
self.assertIs(Foo.
|
413
|
+
self.assertIs(Foo.__schema__.get_field('p').value.cls, Foo)
|
396
414
|
|
397
415
|
def test_update_of_default_values(self):
|
398
416
|
|
@@ -782,8 +800,8 @@ class ObjectTest(unittest.TestCase):
|
|
782
800
|
a = A(A(dict(y=A(1))))
|
783
801
|
self.assertTrue(a.sym_has('x'))
|
784
802
|
self.assertTrue(a.sym_has('x.x'))
|
785
|
-
self.assertTrue(a.sym_has(
|
786
|
-
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')))
|
787
805
|
self.assertFalse(a.sym_has('y')) # `y` is not a symbolic field.
|
788
806
|
|
789
807
|
def test_sym_get(self):
|
@@ -808,10 +826,10 @@ class ObjectTest(unittest.TestCase):
|
|
808
826
|
self.assertIs(a.sym_get('x'), a.x)
|
809
827
|
self.assertIs(a.sym_get('p'), a.sym_getattr('p'))
|
810
828
|
self.assertIs(a.sym_get('x.x'), a.x.x)
|
811
|
-
self.assertIs(a.sym_get(
|
812
|
-
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)
|
813
831
|
self.assertIs(
|
814
|
-
a.sym_get(
|
832
|
+
a.sym_get(utils.KeyPath.parse('x.x.y.p')),
|
815
833
|
a.x.x.y.sym_getattr('p'),
|
816
834
|
)
|
817
835
|
self.assertIsNone(a.sym_get('x.x.y.q', use_inferred=True))
|
@@ -1575,7 +1593,7 @@ class ObjectTest(unittest.TestCase):
|
|
1575
1593
|
self.assertEqual(a.x.x.x.sym_path, 'x.x.x')
|
1576
1594
|
self.assertEqual(a.x.x.x[0].sym_path, 'x.x.x[0]')
|
1577
1595
|
|
1578
|
-
a.sym_setpath(
|
1596
|
+
a.sym_setpath(utils.KeyPath('a'))
|
1579
1597
|
self.assertEqual(a.sym_path, 'a')
|
1580
1598
|
self.assertEqual(a.x.sym_path, 'a.x')
|
1581
1599
|
self.assertEqual(a.x.x.sym_path, 'a.x.x')
|
@@ -1896,6 +1914,32 @@ class InheritanceTest(unittest.TestCase):
|
|
1896
1914
|
self.assertEqual(d.x, 'foo')
|
1897
1915
|
self.assertEqual(d.foo(), 'B')
|
1898
1916
|
|
1917
|
+
def test_multi_inheritance2(self):
|
1918
|
+
|
1919
|
+
class A(Object):
|
1920
|
+
x: typing.Callable[[], Any]
|
1921
|
+
|
1922
|
+
class B(A):
|
1923
|
+
def x(self):
|
1924
|
+
return 1
|
1925
|
+
|
1926
|
+
class C(A):
|
1927
|
+
pass
|
1928
|
+
|
1929
|
+
class D(C, B):
|
1930
|
+
pass
|
1931
|
+
|
1932
|
+
self.assertIs(D.__schema__['x'].default_value, B.x)
|
1933
|
+
|
1934
|
+
class E(A):
|
1935
|
+
def x(self):
|
1936
|
+
return 2
|
1937
|
+
|
1938
|
+
class F(E, B):
|
1939
|
+
pass
|
1940
|
+
|
1941
|
+
self.assertIs(F.__schema__['x'].default_value, E.x)
|
1942
|
+
|
1899
1943
|
|
1900
1944
|
class InitSignatureTest(unittest.TestCase):
|
1901
1945
|
"""Tests for `pg.Object.__init__` signature."""
|
@@ -2055,7 +2099,7 @@ class InitSignatureTest(unittest.TestCase):
|
|
2055
2099
|
class C(B):
|
2056
2100
|
"""Custom __init__."""
|
2057
2101
|
|
2058
|
-
@
|
2102
|
+
@utils.explicit_method_override
|
2059
2103
|
def __init__(self, a, b):
|
2060
2104
|
super().__init__(b, x=a)
|
2061
2105
|
|
@@ -2349,7 +2393,7 @@ class RebindTest(unittest.TestCase):
|
|
2349
2393
|
A(1).rebind({})
|
2350
2394
|
|
2351
2395
|
with self.assertRaisesRegex(
|
2352
|
-
KeyError, 'Key
|
2396
|
+
KeyError, 'Key 1 is not allowed for .*'):
|
2353
2397
|
A(1).rebind({1: 1})
|
2354
2398
|
|
2355
2399
|
with self.assertRaisesRegex(
|
@@ -2430,44 +2474,51 @@ class EventsTest(unittest.TestCase):
|
|
2430
2474
|
[
|
2431
2475
|
# Set default value from outer space (parent List) for field d1.
|
2432
2476
|
{
|
2433
|
-
'd1':
|
2434
|
-
|
2435
|
-
|
2436
|
-
|
2437
|
-
|
2438
|
-
|
2439
|
-
|
2477
|
+
'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
|
+
)
|
2440
2484
|
},
|
2441
2485
|
# Set default value from outer space (parent List) for field d2.
|
2442
2486
|
{
|
2443
|
-
'd2':
|
2444
|
-
|
2445
|
-
|
2446
|
-
|
2447
|
-
|
2448
|
-
|
2449
|
-
|
2450
|
-
}
|
2451
|
-
]
|
2487
|
+
'd2': base.FieldUpdate(
|
2488
|
+
path=utils.KeyPath.parse('a2.b1.c1[0].d2'),
|
2489
|
+
target=sd.a2.b1.c1[0],
|
2490
|
+
field=sd.a2.b1.c1[0].value_spec.schema['d2'],
|
2491
|
+
old_value=MISSING_VALUE,
|
2492
|
+
new_value=True,
|
2493
|
+
)
|
2494
|
+
},
|
2495
|
+
],
|
2496
|
+
)
|
2452
2497
|
|
2453
2498
|
# list get updated after bind with parent structures.
|
2454
|
-
self.assertEqual(
|
2455
|
-
|
2456
|
-
|
2457
|
-
|
2458
|
-
|
2459
|
-
|
2460
|
-
|
2461
|
-
|
2462
|
-
|
2463
|
-
|
2464
|
-
|
2465
|
-
|
2466
|
-
|
2467
|
-
|
2468
|
-
|
2469
|
-
|
2470
|
-
|
2499
|
+
self.assertEqual(
|
2500
|
+
list_updates,
|
2501
|
+
[
|
2502
|
+
{
|
2503
|
+
'[0].d1': base.FieldUpdate(
|
2504
|
+
path=utils.KeyPath.parse('a2.b1.c1[0].d1'),
|
2505
|
+
target=sd.a2.b1.c1[0],
|
2506
|
+
field=sd.a2.b1.c1[0].value_spec.schema['d1'],
|
2507
|
+
old_value=MISSING_VALUE,
|
2508
|
+
new_value='foo',
|
2509
|
+
)
|
2510
|
+
},
|
2511
|
+
{
|
2512
|
+
'[0].d2': base.FieldUpdate(
|
2513
|
+
path=utils.KeyPath.parse('a2.b1.c1[0].d2'),
|
2514
|
+
target=sd.a2.b1.c1[0],
|
2515
|
+
field=sd.a2.b1.c1[0].value_spec.schema['d2'],
|
2516
|
+
old_value=MISSING_VALUE,
|
2517
|
+
new_value=True,
|
2518
|
+
)
|
2519
|
+
},
|
2520
|
+
],
|
2521
|
+
)
|
2471
2522
|
|
2472
2523
|
# There are no updates in root.
|
2473
2524
|
self.assertEqual(root_updates, [])
|
@@ -2490,28 +2541,28 @@ class EventsTest(unittest.TestCase):
|
|
2490
2541
|
root_updates[0],
|
2491
2542
|
{
|
2492
2543
|
'a1': base.FieldUpdate(
|
2493
|
-
path=
|
2544
|
+
path=utils.KeyPath.parse('a1'),
|
2494
2545
|
target=sd,
|
2495
2546
|
field=sd.value_spec.schema['a1'],
|
2496
2547
|
old_value=MISSING_VALUE,
|
2497
2548
|
new_value=1,
|
2498
2549
|
),
|
2499
2550
|
'a2.b1.c1[0].d1': base.FieldUpdate(
|
2500
|
-
path=
|
2551
|
+
path=utils.KeyPath.parse('a2.b1.c1[0].d1'),
|
2501
2552
|
target=sd.a2.b1.c1[0],
|
2502
2553
|
field=sd.a2.b1.c1[0].value_spec.schema['d1'],
|
2503
2554
|
old_value='foo',
|
2504
2555
|
new_value='bar',
|
2505
2556
|
),
|
2506
2557
|
'a2.b1.c1[0].d2': base.FieldUpdate(
|
2507
|
-
path=
|
2558
|
+
path=utils.KeyPath.parse('a2.b1.c1[0].d2'),
|
2508
2559
|
target=sd.a2.b1.c1[0],
|
2509
2560
|
field=sd.a2.b1.c1[0].value_spec.schema['d2'],
|
2510
2561
|
old_value=True,
|
2511
2562
|
new_value=False,
|
2512
2563
|
),
|
2513
2564
|
'a2.b1.c1[0].d3.z': base.FieldUpdate(
|
2514
|
-
path=
|
2565
|
+
path=utils.KeyPath.parse('a2.b1.c1[0].d3.z'),
|
2515
2566
|
target=sd.a2.b1.c1[0].d3,
|
2516
2567
|
field=sd.a2.b1.c1[0].d3.__class__.__schema__['z'],
|
2517
2568
|
old_value=MISSING_VALUE,
|
@@ -2527,21 +2578,21 @@ class EventsTest(unittest.TestCase):
|
|
2527
2578
|
# Root object rebind.
|
2528
2579
|
{
|
2529
2580
|
'[0].d1': base.FieldUpdate(
|
2530
|
-
path=
|
2581
|
+
path=utils.KeyPath.parse('a2.b1.c1[0].d1'),
|
2531
2582
|
target=sd.a2.b1.c1[0],
|
2532
2583
|
field=sd.a2.b1.c1[0].value_spec.schema['d1'],
|
2533
2584
|
old_value='foo',
|
2534
2585
|
new_value='bar',
|
2535
2586
|
),
|
2536
2587
|
'[0].d2': base.FieldUpdate(
|
2537
|
-
path=
|
2588
|
+
path=utils.KeyPath.parse('a2.b1.c1[0].d2'),
|
2538
2589
|
target=sd.a2.b1.c1[0],
|
2539
2590
|
field=sd.a2.b1.c1[0].value_spec.schema['d2'],
|
2540
2591
|
old_value=True,
|
2541
2592
|
new_value=False,
|
2542
2593
|
),
|
2543
2594
|
'[0].d3.z': base.FieldUpdate(
|
2544
|
-
path=
|
2595
|
+
path=utils.KeyPath.parse('a2.b1.c1[0].d3.z'),
|
2545
2596
|
target=sd.a2.b1.c1[0].d3,
|
2546
2597
|
field=sd.a2.b1.c1[0].d3.__class__.__schema__['z'],
|
2547
2598
|
old_value=MISSING_VALUE,
|
@@ -2557,29 +2608,30 @@ class EventsTest(unittest.TestCase):
|
|
2557
2608
|
[
|
2558
2609
|
# Root object rebind.
|
2559
2610
|
{
|
2560
|
-
'd1':
|
2561
|
-
|
2562
|
-
|
2563
|
-
|
2564
|
-
|
2565
|
-
|
2566
|
-
|
2567
|
-
'd2':
|
2568
|
-
|
2569
|
-
|
2570
|
-
|
2571
|
-
|
2572
|
-
|
2573
|
-
|
2574
|
-
'd3.z':
|
2575
|
-
|
2576
|
-
|
2577
|
-
|
2578
|
-
|
2579
|
-
|
2580
|
-
|
2611
|
+
'd1': base.FieldUpdate(
|
2612
|
+
path=utils.KeyPath.parse('a2.b1.c1[0].d1'),
|
2613
|
+
target=sd.a2.b1.c1[0],
|
2614
|
+
field=sd.a2.b1.c1[0].value_spec.schema['d1'],
|
2615
|
+
old_value='foo',
|
2616
|
+
new_value='bar',
|
2617
|
+
),
|
2618
|
+
'd2': base.FieldUpdate(
|
2619
|
+
path=utils.KeyPath.parse('a2.b1.c1[0].d2'),
|
2620
|
+
target=sd.a2.b1.c1[0],
|
2621
|
+
field=sd.a2.b1.c1[0].value_spec.schema['d2'],
|
2622
|
+
old_value=True,
|
2623
|
+
new_value=False,
|
2624
|
+
),
|
2625
|
+
'd3.z': base.FieldUpdate(
|
2626
|
+
path=utils.KeyPath.parse('a2.b1.c1[0].d3.z'),
|
2627
|
+
target=sd.a2.b1.c1[0].d3,
|
2628
|
+
field=sd.a2.b1.c1[0].d3.__class__.__schema__['z'],
|
2629
|
+
old_value=MISSING_VALUE,
|
2630
|
+
new_value='foo',
|
2631
|
+
),
|
2581
2632
|
}
|
2582
|
-
]
|
2633
|
+
],
|
2634
|
+
)
|
2583
2635
|
|
2584
2636
|
def test_on_change_notification_order(self):
|
2585
2637
|
change_order = []
|
@@ -2625,7 +2677,7 @@ class EventsTest(unittest.TestCase):
|
|
2625
2677
|
y.x = A()
|
2626
2678
|
self.assertIs(x.old_parent, y)
|
2627
2679
|
self.assertIsNone(x.new_parent)
|
2628
|
-
self.assertEqual(x.sym_path,
|
2680
|
+
self.assertEqual(x.sym_path, utils.KeyPath())
|
2629
2681
|
|
2630
2682
|
def test_on_path_change(self):
|
2631
2683
|
|
@@ -2636,8 +2688,8 @@ class EventsTest(unittest.TestCase):
|
|
2636
2688
|
self.new_path = new_path
|
2637
2689
|
|
2638
2690
|
x = A()
|
2639
|
-
x.sym_setpath(
|
2640
|
-
self.assertEqual(x.old_path,
|
2691
|
+
x.sym_setpath(utils.KeyPath('a'))
|
2692
|
+
self.assertEqual(x.old_path, utils.KeyPath())
|
2641
2693
|
self.assertEqual(x.new_path, 'a')
|
2642
2694
|
|
2643
2695
|
y = Dict(x=x)
|
@@ -3039,7 +3091,7 @@ class SerializationTest(unittest.TestCase):
|
|
3039
3091
|
|
3040
3092
|
def test_serialization_with_json_convertible(self):
|
3041
3093
|
|
3042
|
-
class Y(
|
3094
|
+
class Y(utils.JSONConvertible):
|
3043
3095
|
|
3044
3096
|
TYPE_NAME = 'Y'
|
3045
3097
|
|
@@ -3059,7 +3111,7 @@ class SerializationTest(unittest.TestCase):
|
|
3059
3111
|
def from_json(cls, json_dict, *args, **kwargs):
|
3060
3112
|
return cls(json_dict.pop('value'))
|
3061
3113
|
|
3062
|
-
|
3114
|
+
utils.JSONConvertible.register(Y.TYPE_NAME, Y)
|
3063
3115
|
|
3064
3116
|
a = self._A(Y(1), y=True)
|
3065
3117
|
self.assertEqual(base.from_json_str(a.to_json_str()), a)
|
@@ -3266,9 +3318,7 @@ class FormatTest(unittest.TestCase):
|
|
3266
3318
|
|
3267
3319
|
def test_compact_python_format(self):
|
3268
3320
|
self.assertEqual(
|
3269
|
-
|
3270
|
-
self._a, compact=True, python_format=True, markdown=True
|
3271
|
-
),
|
3321
|
+
utils.format(self._a, compact=True, python_format=True, markdown=True),
|
3272
3322
|
"`A(x=[A(x=1, y=None), A(x='foo', y={'a': A(x=True, y=1.0)})], "
|
3273
3323
|
'y=MISSING_VALUE)`',
|
3274
3324
|
)
|
@@ -3313,9 +3363,12 @@ class FormatTest(unittest.TestCase):
|
|
3313
3363
|
|
3314
3364
|
def test_noncompact_python_format(self):
|
3315
3365
|
self.assertEqual(
|
3316
|
-
|
3317
|
-
self._a,
|
3318
|
-
|
3366
|
+
utils.format(
|
3367
|
+
self._a,
|
3368
|
+
compact=False,
|
3369
|
+
verbose=False,
|
3370
|
+
python_format=True,
|
3371
|
+
markdown=True,
|
3319
3372
|
),
|
3320
3373
|
inspect.cleandoc("""
|
3321
3374
|
```
|
@@ -3482,10 +3535,10 @@ class FormatTest(unittest.TestCase):
|
|
3482
3535
|
return f() if f is not None else None
|
3483
3536
|
return fn
|
3484
3537
|
|
3485
|
-
with
|
3538
|
+
with utils.str_format(custom_format=_method('_repr_xml_')):
|
3486
3539
|
self.assertEqual(str(Bar(Foo())), 'Bar(\n foo = Foo()\n)')
|
3487
3540
|
|
3488
|
-
with
|
3541
|
+
with utils.str_format(custom_format=_method('_repr_html_')):
|
3489
3542
|
self.assertIn('<html>', str(Bar(Foo())))
|
3490
3543
|
|
3491
3544
|
|
pyglove/core/symbolic/origin.py
CHANGED
@@ -16,11 +16,11 @@
|
|
16
16
|
import traceback
|
17
17
|
from typing import Any, Callable, List, Optional
|
18
18
|
|
19
|
-
from pyglove.core import
|
19
|
+
from pyglove.core import utils
|
20
20
|
from pyglove.core.symbolic import flags
|
21
21
|
|
22
22
|
|
23
|
-
class Origin(
|
23
|
+
class Origin(utils.Formattable):
|
24
24
|
"""Class that represents the origin of a symbolic value.
|
25
25
|
|
26
26
|
Origin is used for debugging the creation chain of a symbolic value, as
|
@@ -158,14 +158,12 @@ class Origin(object_utils.Formattable):
|
|
158
158
|
if isinstance(self._source, (str, type(None))):
|
159
159
|
source_str = self._source
|
160
160
|
else:
|
161
|
-
source_info =
|
161
|
+
source_info = utils.format(
|
162
162
|
self._source, compact, verbose, root_indent + 1, **kwargs
|
163
163
|
)
|
164
|
-
source_str =
|
165
|
-
f'{source_info} at 0x{id(self._source):8x}'
|
166
|
-
)
|
164
|
+
source_str = utils.RawText(f'{source_info} at 0x{id(self._source):8x}')
|
167
165
|
|
168
|
-
return
|
166
|
+
return utils.kvlist_str(
|
169
167
|
[
|
170
168
|
('tag', self._tag, None),
|
171
169
|
('source', source_str, None),
|
@@ -14,8 +14,8 @@
|
|
14
14
|
"""Interfaces for pure symbolic objects."""
|
15
15
|
|
16
16
|
from typing import Any, Callable, Optional, Tuple
|
17
|
-
from pyglove.core import object_utils
|
18
17
|
from pyglove.core import typing as pg_typing
|
18
|
+
from pyglove.core import utils
|
19
19
|
|
20
20
|
|
21
21
|
class PureSymbolic(pg_typing.CustomTyping):
|
@@ -37,11 +37,12 @@ class PureSymbolic(pg_typing.CustomTyping):
|
|
37
37
|
|
38
38
|
def custom_apply(
|
39
39
|
self,
|
40
|
-
path:
|
40
|
+
path: utils.KeyPath,
|
41
41
|
value_spec: pg_typing.ValueSpec,
|
42
42
|
allow_partial: bool,
|
43
43
|
child_transform: Optional[
|
44
|
-
Callable[[
|
44
|
+
Callable[[utils.KeyPath, pg_typing.Field, Any], Any]
|
45
|
+
] = None,
|
45
46
|
) -> Tuple[bool, Any]:
|
46
47
|
"""Custom apply on a value based on its original value spec.
|
47
48
|
|
pyglove/core/symbolic/ref.py
CHANGED
@@ -14,16 +14,30 @@
|
|
14
14
|
"""Symbolic reference."""
|
15
15
|
|
16
16
|
import functools
|
17
|
-
import
|
18
|
-
from typing import Any, Callable, List, Optional, Tuple
|
19
|
-
from pyglove.core import object_utils
|
17
|
+
import typing
|
18
|
+
from typing import Any, Callable, List, Optional, Tuple, Type
|
20
19
|
from pyglove.core import typing as pg_typing
|
20
|
+
from pyglove.core import utils
|
21
21
|
from pyglove.core.symbolic import base
|
22
|
-
from pyglove.core.symbolic
|
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
|
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,11 +96,11 @@ 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, (
|
86
|
-
return
|
87
|
-
return
|
99
|
+
if isinstance(value, (base.Symbolic, list, dict)):
|
100
|
+
return object.__new__(cls)
|
101
|
+
return value
|
88
102
|
|
89
|
-
@
|
103
|
+
@utils.explicit_method_override
|
90
104
|
def __init__(self, value: Any, **kwargs) -> None:
|
91
105
|
super().__init__(**kwargs)
|
92
106
|
if isinstance(value, Ref):
|
@@ -113,12 +127,13 @@ class Ref(Object, base.Inferential, tree_view.HtmlTreeView.Extension):
|
|
113
127
|
|
114
128
|
def custom_apply(
|
115
129
|
self,
|
116
|
-
path:
|
130
|
+
path: utils.KeyPath,
|
117
131
|
value_spec: pg_typing.ValueSpec,
|
118
132
|
allow_partial: bool = False,
|
119
|
-
child_transform: Optional[
|
120
|
-
[
|
121
|
-
|
133
|
+
child_transform: Optional[
|
134
|
+
Callable[[utils.KeyPath, pg_typing.Field, Any], Any]
|
135
|
+
] = None,
|
136
|
+
) -> Tuple[bool, Any]:
|
122
137
|
"""Validate candidates during value_spec binding time."""
|
123
138
|
del child_transform
|
124
139
|
# Check if the field being assigned could accept the referenced value.
|
@@ -152,9 +167,12 @@ class Ref(Object, base.Inferential, tree_view.HtmlTreeView.Extension):
|
|
152
167
|
root_indent: int = 0,
|
153
168
|
**kwargs: Any,
|
154
169
|
) -> str:
|
155
|
-
value_str =
|
170
|
+
value_str = utils.format(
|
156
171
|
self._value,
|
157
|
-
compact=compact,
|
172
|
+
compact=compact,
|
173
|
+
verbose=verbose,
|
174
|
+
root_indent=root_indent + 1,
|
175
|
+
)
|
158
176
|
if compact:
|
159
177
|
return f'{self.__class__.__name__}({value_str})'
|
160
178
|
else:
|
@@ -241,4 +259,3 @@ def deref(value: base.Symbolic, recursive: bool = False) -> Any:
|
|
241
259
|
return v
|
242
260
|
return value.rebind(_deref, raise_on_no_change=False)
|
243
261
|
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()
|
@@ -21,8 +21,8 @@ from typing import Any, Callable, Dict, List, Optional, Sequence
|
|
21
21
|
|
22
22
|
from pyglove.core import geno
|
23
23
|
from pyglove.core import logging
|
24
|
-
from pyglove.core import object_utils
|
25
24
|
from pyglove.core import symbolic
|
25
|
+
from pyglove.core import utils
|
26
26
|
from pyglove.core.tuning import backend
|
27
27
|
from pyglove.core.tuning.early_stopping import EarlyStoppingPolicy
|
28
28
|
from pyglove.core.tuning.protocols import Feedback
|
@@ -278,7 +278,7 @@ class _InMemoryResult(Result):
|
|
278
278
|
('step', self._best_trial.final_measurement.step),
|
279
279
|
('dna', self._best_trial.dna.format(compact=True))
|
280
280
|
])
|
281
|
-
return
|
281
|
+
return utils.format(json_repr, compact, False, root_indent, **kwargs)
|
282
282
|
|
283
283
|
|
284
284
|
@backend.add_backend('in-memory')
|
pyglove/core/tuning/protocols.py
CHANGED
@@ -22,9 +22,9 @@ from typing import Any, Dict, List, Optional, Sequence, Tuple, Type, Union
|
|
22
22
|
|
23
23
|
from pyglove.core import geno
|
24
24
|
from pyglove.core import logging
|
25
|
-
from pyglove.core import object_utils
|
26
25
|
from pyglove.core import symbolic
|
27
26
|
from pyglove.core import typing as pg_typing
|
27
|
+
from pyglove.core import utils
|
28
28
|
|
29
29
|
|
30
30
|
class _DataEntity(symbolic.Object):
|
@@ -116,7 +116,7 @@ class Trial(_DataEntity):
|
|
116
116
|
return tuple(metric_values) if len(metric_values) > 1 else metric_values[0]
|
117
117
|
|
118
118
|
|
119
|
-
class Result(
|
119
|
+
class Result(utils.Formattable):
|
120
120
|
"""Interface for tuning result."""
|
121
121
|
|
122
122
|
@property
|
@@ -416,7 +416,7 @@ class Feedback(metaclass=abc.ABCMeta):
|
|
416
416
|
error_stack = traceback.format_exc()
|
417
417
|
logging.warning('Skipping trial on unhandled exception: %s', error_stack)
|
418
418
|
self.skip(error_stack)
|
419
|
-
return
|
419
|
+
return utils.catch_errors(exceptions, skip_on_exception)
|
420
420
|
|
421
421
|
@contextlib.contextmanager
|
422
422
|
def ignore_race_condition(self):
|