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,15 +11,13 @@
|
|
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.core.typing.class_schema."""
|
15
|
-
|
16
14
|
import copy
|
17
15
|
import inspect
|
18
16
|
import sys
|
19
17
|
from typing import Optional, Union, List
|
20
18
|
import unittest
|
21
19
|
|
22
|
-
from pyglove.core import
|
20
|
+
from pyglove.core import utils
|
23
21
|
from pyglove.core.typing import annotation_conversion # pylint: disable=unused-import
|
24
22
|
from pyglove.core.typing import class_schema
|
25
23
|
from pyglove.core.typing import custom_typing
|
@@ -67,10 +65,10 @@ class ForwardRefTest(unittest.TestCase):
|
|
67
65
|
with self.assertRaisesRegex(TypeError, '.* is not a class'):
|
68
66
|
_ = class_schema.ForwardRef(self._module, 'unittest').cls
|
69
67
|
|
70
|
-
def
|
68
|
+
def test_repr(self):
|
71
69
|
self.assertEqual(
|
72
|
-
|
73
|
-
f'ForwardRef(module
|
70
|
+
repr(class_schema.ForwardRef(self._module, 'FieldTest')),
|
71
|
+
f'ForwardRef(module=\'{self._module.__name__}\', name=\'FieldTest\')',
|
74
72
|
)
|
75
73
|
|
76
74
|
def test_eq_ne(self):
|
@@ -187,46 +185,24 @@ class FieldTest(unittest.TestCase):
|
|
187
185
|
'b': 1,
|
188
186
|
}, allow_partial=False)
|
189
187
|
|
190
|
-
def
|
191
|
-
self.assertEqual(
|
192
|
-
|
193
|
-
(
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
'
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
self.assertEqual(
|
206
|
-
Field('a', vs.Dict([
|
207
|
-
('b', vs.Int())
|
208
|
-
]), 'this is a very long field.', {
|
209
|
-
'm1': 1,
|
210
|
-
'm2': 2,
|
211
|
-
'm3': 3,
|
212
|
-
'm4': 4,
|
213
|
-
'm5': 5
|
214
|
-
}).format(compact=True, verbose=True),
|
215
|
-
'Field(key=a, value=Dict({b=Int()}), '
|
216
|
-
'description=\'this is a very long field.\', '
|
217
|
-
'metadata={\'m1\': 1, \'m2\': 2, \'m3\': 3, \'m4\': 4, \'m5\': 5})')
|
218
|
-
|
219
|
-
self.assertEqual(
|
220
|
-
Field('a', vs.Dict([
|
221
|
-
('b', vs.Int())
|
222
|
-
]), 'field a').format(compact=False, verbose=False),
|
223
|
-
'Field(key=a, value=Dict({\n'
|
224
|
-
' b = Int()\n'
|
225
|
-
' }), description=\'field a\')')
|
188
|
+
def test_repr(self):
|
189
|
+
self.assertEqual(
|
190
|
+
repr(
|
191
|
+
Field(
|
192
|
+
'a', vs.Dict([('b', vs.Int())]), 'this is a very long field.',
|
193
|
+
{'m1': 1, 'm2': 2, 'm3': 3, 'm4': 4, 'm5': 5}
|
194
|
+
)
|
195
|
+
),
|
196
|
+
(
|
197
|
+
'Field(key=a, value=Dict(fields=[Field(key=b, '
|
198
|
+
'value=Int())]), description=\'this is a very long field.\', '
|
199
|
+
'metadata={\'m1\': 1, \'m2\': 2, \'m3\': 3, \'m4\': 4, \'m5\': 5})'
|
200
|
+
)
|
201
|
+
)
|
226
202
|
|
227
203
|
def test_json_conversion(self):
|
228
204
|
def assert_json_conversion(f):
|
229
|
-
self.assertEqual(
|
205
|
+
self.assertEqual(utils.from_json(f.to_json()), f)
|
230
206
|
|
231
207
|
assert_json_conversion(Field('a', vs.Int()))
|
232
208
|
assert_json_conversion(Field('a', vs.Int(), 'description'))
|
@@ -342,58 +318,131 @@ class SchemaTest(unittest.TestCase):
|
|
342
318
|
def test_eq(self):
|
343
319
|
self.assertEqual(self._create_test_schema(), self._create_test_schema())
|
344
320
|
|
345
|
-
def
|
321
|
+
def test_repr(self):
|
346
322
|
"""Tests for Schema.format."""
|
347
323
|
self.assertEqual(
|
348
|
-
self._create_test_schema()
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
324
|
+
repr(self._create_test_schema()),
|
325
|
+
(
|
326
|
+
"Schema(fields=[Field(key=a, value=Int(default=1), description="
|
327
|
+
"'Field a.'), Field(key=b, value=Bool(default=None, noneable="
|
328
|
+
"True), description='Field b.'), Field(key=c, value=Dict("
|
329
|
+
"fields=[Field(key=d, value=List(Enum(default=0, values=[0, 1, "
|
330
|
+
"None]), default=[0, 1]), description='Field d.'), Field(key=e, "
|
331
|
+
"value=List(Dict(fields=[Field(key=StrKey(regex='foo.*'), "
|
332
|
+
"value=Str(), description='Mapped values.')])), description='Field"
|
333
|
+
" e.'), Field(key=f, value=Object(SimpleObject), description="
|
334
|
+
"'Field f.')], noneable=True), description='Field c.')], "
|
335
|
+
"allow_nonconst_keys=False, metadata={'init_arg_list': []})"
|
336
|
+
)
|
337
|
+
)
|
338
|
+
|
339
|
+
def test_str(self):
|
340
|
+
self.maxDiff = None
|
341
|
+
self.assertEqual(
|
342
|
+
str(self._create_test_schema()),
|
343
|
+
inspect.cleandoc("""
|
344
|
+
Schema(
|
345
|
+
fields=[
|
346
|
+
Field(
|
347
|
+
key=a,
|
348
|
+
value=Int(
|
349
|
+
default=1
|
350
|
+
),
|
351
|
+
description='Field a.'
|
352
|
+
),
|
353
|
+
Field(
|
354
|
+
key=b,
|
355
|
+
value=Bool(
|
356
|
+
default=None,
|
357
|
+
noneable=True
|
358
|
+
),
|
359
|
+
description='Field b.'
|
360
|
+
),
|
361
|
+
Field(
|
362
|
+
key=c,
|
363
|
+
value=Dict(
|
364
|
+
fields=[
|
365
|
+
Field(
|
366
|
+
key=d,
|
367
|
+
value=List(
|
368
|
+
Enum(
|
369
|
+
default=0,
|
370
|
+
values=[0, 1, None]
|
371
|
+
),
|
372
|
+
default=[0, 1]
|
373
|
+
),
|
374
|
+
description='Field d.'
|
375
|
+
),
|
376
|
+
Field(
|
377
|
+
key=e,
|
378
|
+
value=List(
|
379
|
+
Dict(
|
380
|
+
fields=[
|
381
|
+
Field(
|
382
|
+
key=StrKey(regex='foo.*'),
|
383
|
+
value=Str(),
|
384
|
+
description='Mapped values.'
|
385
|
+
)
|
386
|
+
]
|
387
|
+
)
|
388
|
+
),
|
389
|
+
description='Field e.'
|
390
|
+
),
|
391
|
+
Field(
|
392
|
+
key=f,
|
393
|
+
value=Object(
|
394
|
+
SimpleObject
|
395
|
+
),
|
396
|
+
description='Field f.'
|
397
|
+
)
|
398
|
+
],
|
399
|
+
noneable=True
|
400
|
+
),
|
401
|
+
description='Field c.'
|
402
|
+
)
|
403
|
+
],
|
404
|
+
allow_nonconst_keys=False,
|
405
|
+
metadata={
|
406
|
+
'init_arg_list': []
|
407
|
+
}
|
408
|
+
)""")
|
409
|
+
)
|
410
|
+
|
411
|
+
def test_merge(self):
|
412
|
+
"""Tests for Schema.merge."""
|
413
|
+
self.assertEqual(
|
414
|
+
Schema.merge([
|
415
|
+
class_schema.create_schema([
|
416
|
+
('a', vs.Int()),
|
417
|
+
('b', vs.Bool().noneable()),
|
418
|
+
]),
|
419
|
+
class_schema.create_schema([
|
420
|
+
('a', vs.Str()),
|
421
|
+
('c', vs.Float()),
|
422
|
+
]),
|
423
|
+
]),
|
424
|
+
class_schema.create_schema([
|
425
|
+
('a', vs.Int()),
|
426
|
+
('b', vs.Bool().noneable()),
|
427
|
+
('c', vs.Float()),
|
428
|
+
]),
|
429
|
+
)
|
430
|
+
self.assertEqual(
|
431
|
+
Schema.merge([
|
432
|
+
class_schema.create_schema([
|
433
|
+
('a', vs.Int()),
|
434
|
+
(ks.StrKey(), vs.Str().noneable()),
|
435
|
+
], allow_nonconst_keys=True),
|
436
|
+
class_schema.create_schema([
|
437
|
+
('c', vs.Float()),
|
438
|
+
]),
|
439
|
+
]),
|
440
|
+
class_schema.create_schema([
|
441
|
+
('a', vs.Int()),
|
442
|
+
('c', vs.Float()),
|
443
|
+
(ks.StrKey(), vs.Str().noneable()),
|
444
|
+
], allow_nonconst_keys=True),
|
445
|
+
)
|
397
446
|
|
398
447
|
def test_extend(self):
|
399
448
|
"""Tests for Schema.extend."""
|
@@ -541,7 +590,7 @@ class SchemaTest(unittest.TestCase):
|
|
541
590
|
], base_schema_list=[s], allow_nonconst_keys=True)
|
542
591
|
self.assertEqual(s.dynamic_field, Field(ks.StrKey(), vs.Str()))
|
543
592
|
|
544
|
-
def
|
593
|
+
def test_validate(self):
|
545
594
|
# Validate fully specified fields.
|
546
595
|
self._create_test_schema().validate({
|
547
596
|
'a': 1,
|
@@ -771,7 +820,7 @@ class SchemaTest(unittest.TestCase):
|
|
771
820
|
schema = self._create_test_schema()
|
772
821
|
schema.set_description('Foo')
|
773
822
|
schema.set_name('Bar')
|
774
|
-
schema_copy =
|
823
|
+
schema_copy = utils.from_json(schema.to_json())
|
775
824
|
|
776
825
|
# This compares fields only
|
777
826
|
self.assertEqual(schema_copy, schema)
|
@@ -856,6 +905,11 @@ class CreateSchemaTest(unittest.TestCase):
|
|
856
905
|
):
|
857
906
|
class_schema.create_schema([], metadata=1)
|
858
907
|
|
908
|
+
with self.assertRaisesRegex(
|
909
|
+
TypeError, 'Schema definition should be a dict.*a list.'
|
910
|
+
):
|
911
|
+
class_schema.create_schema(1, metadata=1)
|
912
|
+
|
859
913
|
with self.assertRaisesRegex(
|
860
914
|
TypeError, 'Field definition should be tuples with 2 to 4 elements.'
|
861
915
|
):
|
@@ -16,7 +16,7 @@
|
|
16
16
|
import abc
|
17
17
|
from typing import Any, Callable, Optional, Tuple
|
18
18
|
|
19
|
-
from pyglove.core import
|
19
|
+
from pyglove.core import utils
|
20
20
|
from pyglove.core.typing import class_schema
|
21
21
|
|
22
22
|
|
@@ -34,11 +34,12 @@ class CustomTyping(metaclass=abc.ABCMeta):
|
|
34
34
|
@abc.abstractmethod
|
35
35
|
def custom_apply(
|
36
36
|
self,
|
37
|
-
path:
|
37
|
+
path: utils.KeyPath,
|
38
38
|
value_spec: class_schema.ValueSpec,
|
39
39
|
allow_partial: bool,
|
40
|
-
child_transform: Optional[
|
41
|
-
[
|
40
|
+
child_transform: Optional[
|
41
|
+
Callable[[utils.KeyPath, class_schema.Field, Any], Any]
|
42
|
+
] = None,
|
42
43
|
) -> Tuple[bool, Any]:
|
43
44
|
"""Custom apply on a value based on its original value spec.
|
44
45
|
|
pyglove/core/typing/inspect.py
CHANGED
@@ -14,6 +14,7 @@
|
|
14
14
|
"""Utility module for inspecting generics types."""
|
15
15
|
|
16
16
|
import inspect
|
17
|
+
import sys
|
17
18
|
import typing
|
18
19
|
from typing import Any, Callable, Optional, Tuple, Type, Union
|
19
20
|
|
@@ -116,6 +117,68 @@ def get_type_args(
|
|
116
117
|
return ()
|
117
118
|
|
118
119
|
|
120
|
+
def get_outer_class(
|
121
|
+
cls: Type[Any],
|
122
|
+
base_cls: Union[Type[Any], Tuple[Type[Any], ...], None] = None,
|
123
|
+
immediate: bool = False,
|
124
|
+
) -> Optional[Type[Any]]:
|
125
|
+
"""Returns the outer class.
|
126
|
+
|
127
|
+
Example::
|
128
|
+
|
129
|
+
class A:
|
130
|
+
pass
|
131
|
+
|
132
|
+
class A1:
|
133
|
+
class B:
|
134
|
+
class C:
|
135
|
+
...
|
136
|
+
|
137
|
+
pg.typing.outer_class(B) is A1
|
138
|
+
pg.typing.outer_class(C) is B
|
139
|
+
pg.typing.outer_class(C, base_cls=A) is None
|
140
|
+
pg.typing.outer_class(C, base_cls=A1) is None
|
141
|
+
|
142
|
+
Args:
|
143
|
+
cls: The class to get the outer class for.
|
144
|
+
base_cls: The base class of the outer class. If provided, an outer class
|
145
|
+
that is not a subclass of `base_cls` will be returned as None.
|
146
|
+
immediate: Whether to return the immediate outer class or a class in the
|
147
|
+
nesting hierarchy that is a subclass of `base_cls`. Applicable when
|
148
|
+
`base_cls` is not None.
|
149
|
+
|
150
|
+
Returns:
|
151
|
+
The outer class of `cls`. None if cannot find one or the outer class is
|
152
|
+
not a subclass of `base_cls`.
|
153
|
+
"""
|
154
|
+
if '<locals>' in cls.__qualname__:
|
155
|
+
raise ValueError(
|
156
|
+
'Cannot find the outer class for locally defined class '
|
157
|
+
f'{cls.__qualname__!r}'
|
158
|
+
)
|
159
|
+
|
160
|
+
names = cls.__qualname__.split('.')
|
161
|
+
if len(names) < 2:
|
162
|
+
return None
|
163
|
+
|
164
|
+
parent = sys.modules[cls.__module__]
|
165
|
+
symbols = []
|
166
|
+
for name in names[:-1]:
|
167
|
+
symbol = getattr(parent, name, None)
|
168
|
+
if symbol is None:
|
169
|
+
return None
|
170
|
+
assert inspect.isclass(symbol), symbol
|
171
|
+
symbols.append(symbol)
|
172
|
+
parent = symbol
|
173
|
+
|
174
|
+
for symbol in reversed(symbols):
|
175
|
+
if immediate:
|
176
|
+
return symbol if not base_cls or issubclass(symbol, base_cls) else None
|
177
|
+
if not base_cls or issubclass(symbol, base_cls):
|
178
|
+
return symbol
|
179
|
+
return None
|
180
|
+
|
181
|
+
|
119
182
|
def callable_eq(
|
120
183
|
x: Optional[Callable[..., Any]], y: Optional[Callable[..., Any]]
|
121
184
|
) -> bool:
|
@@ -16,8 +16,10 @@
|
|
16
16
|
from typing import Any, Generic, TypeVar
|
17
17
|
import unittest
|
18
18
|
|
19
|
+
from pyglove.core.typing import callable_signature
|
19
20
|
from pyglove.core.typing import inspect
|
20
21
|
|
22
|
+
|
21
23
|
XType = TypeVar('XType')
|
22
24
|
YType = TypeVar('YType')
|
23
25
|
|
@@ -50,6 +52,16 @@ class D(C):
|
|
50
52
|
pass
|
51
53
|
|
52
54
|
|
55
|
+
class AA:
|
56
|
+
pass
|
57
|
+
|
58
|
+
|
59
|
+
class AA1(AA):
|
60
|
+
class BB1:
|
61
|
+
class CC1:
|
62
|
+
pass
|
63
|
+
|
64
|
+
|
53
65
|
class InspectTest(unittest.TestCase):
|
54
66
|
|
55
67
|
def test_issubclass(self):
|
@@ -141,6 +153,33 @@ class InspectTest(unittest.TestCase):
|
|
141
153
|
self.assertEqual(inspect.get_type_args(C, A), (str, int))
|
142
154
|
self.assertEqual(inspect.get_type_args(C, B), (Str,))
|
143
155
|
|
156
|
+
def test_outer_class(self):
|
157
|
+
class Foo:
|
158
|
+
pass
|
159
|
+
|
160
|
+
with self.assertRaisesRegex(ValueError, '.* locally defined class'):
|
161
|
+
inspect.get_outer_class(Foo)
|
162
|
+
|
163
|
+
self.assertIsNone(inspect.get_outer_class(AA))
|
164
|
+
self.assertIs(inspect.get_outer_class(AA1.BB1), AA1)
|
165
|
+
self.assertIs(inspect.get_outer_class(AA1.BB1, AA), AA1)
|
166
|
+
self.assertIs(inspect.get_outer_class(AA1.BB1, A), None)
|
167
|
+
self.assertIs(inspect.get_outer_class(AA1.BB1.CC1), AA1.BB1)
|
168
|
+
self.assertIsNone(
|
169
|
+
inspect.get_outer_class(AA1.BB1.CC1, base_cls=AA, immediate=True)
|
170
|
+
)
|
171
|
+
self.assertIs(inspect.get_outer_class(AA1.BB1.CC1, AA), AA1)
|
172
|
+
self.assertIs(
|
173
|
+
inspect.get_outer_class(callable_signature.Argument.Kind),
|
174
|
+
callable_signature.Argument
|
175
|
+
)
|
176
|
+
|
177
|
+
class Bar:
|
178
|
+
pass
|
179
|
+
|
180
|
+
Bar.__qualname__ = 'NonExist.Bar'
|
181
|
+
self.assertIsNone(inspect.get_outer_class(Bar))
|
182
|
+
|
144
183
|
def test_callable_eq(self):
|
145
184
|
def foo(unused_x):
|
146
185
|
pass
|
pyglove/core/typing/key_specs.py
CHANGED
@@ -16,7 +16,7 @@
|
|
16
16
|
import re
|
17
17
|
from typing import Any, Dict, Optional
|
18
18
|
|
19
|
-
from pyglove.core import
|
19
|
+
from pyglove.core import utils
|
20
20
|
from pyglove.core.typing.class_schema import KeySpec
|
21
21
|
|
22
22
|
|
@@ -29,16 +29,16 @@ class KeySpecBase(KeySpec):
|
|
29
29
|
raise KeyError(f'{self} cannot extend {base} for keys are different.')
|
30
30
|
return self
|
31
31
|
|
32
|
-
def __repr__(self) -> str:
|
33
|
-
"""Operator repr."""
|
34
|
-
return self.__str__()
|
35
|
-
|
36
32
|
def __ne__(self, other: Any) -> bool:
|
37
33
|
"""Operator !=."""
|
38
34
|
return not self.__eq__(other)
|
39
35
|
|
36
|
+
def __str_kwargs__(self) -> Dict[str, Any]:
|
37
|
+
"""Returns the string representation of this key spec."""
|
38
|
+
return {}
|
39
|
+
|
40
40
|
|
41
|
-
class ConstStrKey(KeySpecBase,
|
41
|
+
class ConstStrKey(KeySpecBase, utils.StrKey):
|
42
42
|
"""Class that represents a constant string key.
|
43
43
|
|
44
44
|
Example::
|
@@ -159,11 +159,10 @@ class StrKey(NonConstKey):
|
|
159
159
|
|
160
160
|
def format(self, **kwargs):
|
161
161
|
"""Format this object."""
|
162
|
-
|
163
|
-
('regex',
|
164
|
-
|
165
|
-
|
166
|
-
return f'StrKey({regex_str})'
|
162
|
+
return utils.kvlist_str(
|
163
|
+
[('regex', getattr(self._regex, 'pattern', None), None)],
|
164
|
+
label=self.__class__.__name__,
|
165
|
+
)
|
167
166
|
|
168
167
|
def to_json(self, **kwargs: Any) -> Dict[str, Any]:
|
169
168
|
regex = self._regex.pattern if self._regex is not None else None
|
@@ -14,7 +14,7 @@
|
|
14
14
|
"""Tests for pyglove.core.typing.key_specs."""
|
15
15
|
|
16
16
|
import unittest
|
17
|
-
from pyglove.core import
|
17
|
+
from pyglove.core import utils
|
18
18
|
from pyglove.core.typing import key_specs as ks
|
19
19
|
|
20
20
|
|
@@ -22,7 +22,7 @@ class KeySpecTest(unittest.TestCase):
|
|
22
22
|
"""Base class for KeySpec tests."""
|
23
23
|
|
24
24
|
def assert_json_conversion(self, spec: ks.KeySpec):
|
25
|
-
self.assertEqual(
|
25
|
+
self.assertEqual(utils.from_json(utils.to_json(spec)), spec)
|
26
26
|
|
27
27
|
|
28
28
|
class ConstStrKeyTest(KeySpecTest):
|
@@ -35,8 +35,10 @@ class ConstStrKeyTest(KeySpecTest):
|
|
35
35
|
self.assertEqual(key.text, 'a')
|
36
36
|
self.assertNotEqual(key, 'b')
|
37
37
|
self.assertIn(key, {'a': 1})
|
38
|
-
|
39
|
-
|
38
|
+
with utils.str_format(markdown=True):
|
39
|
+
self.assertEqual(str(key), 'a')
|
40
|
+
with utils.str_format(markdown=True):
|
41
|
+
self.assertEqual(repr(key), 'a')
|
40
42
|
self.assertTrue(key.match('a'))
|
41
43
|
self.assertFalse(key.match('b'))
|
42
44
|
|
@@ -67,6 +69,7 @@ class StrKeyTest(KeySpecTest):
|
|
67
69
|
|
68
70
|
def test_match_with_regex(self):
|
69
71
|
key = ks.StrKey('a.*')
|
72
|
+
self.assertEqual(repr(key), 'StrKey(regex=\'a.*\')')
|
70
73
|
self.assertTrue(key.match('a1'))
|
71
74
|
self.assertTrue(key.match('a'))
|
72
75
|
self.assertFalse(key.match('b'))
|
@@ -17,7 +17,7 @@ import calendar
|
|
17
17
|
import datetime
|
18
18
|
from typing import Any, Callable, Optional, Tuple, Type, Union
|
19
19
|
|
20
|
-
from pyglove.core import
|
20
|
+
from pyglove.core import utils
|
21
21
|
from pyglove.core.typing import inspect as pg_inspect
|
22
22
|
|
23
23
|
|
@@ -135,10 +135,9 @@ def _register_builtin_converters():
|
|
135
135
|
lambda x: calendar.timegm(x.timetuple()))
|
136
136
|
|
137
137
|
# string <=> KeyPath.
|
138
|
-
register_converter(str,
|
139
|
-
|
140
|
-
register_converter(object_utils.KeyPath, str, lambda x: x.path)
|
138
|
+
register_converter(str, utils.KeyPath, utils.KeyPath.parse)
|
139
|
+
register_converter(utils.KeyPath, str, lambda x: x.path)
|
141
140
|
|
142
141
|
|
143
142
|
_register_builtin_converters()
|
144
|
-
|
143
|
+
utils.JSONConvertible.TYPE_CONVERTER = get_json_value_converter
|
@@ -11,14 +11,12 @@
|
|
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.core.typing.type_conversion."""
|
15
|
-
|
16
14
|
import calendar
|
17
15
|
import datetime
|
18
16
|
import typing
|
19
17
|
import unittest
|
20
18
|
|
21
|
-
from pyglove.core import
|
19
|
+
from pyglove.core import utils
|
22
20
|
from pyglove.core.typing import annotation_conversion # pylint: disable=unused-import
|
23
21
|
from pyglove.core.typing import type_conversion
|
24
22
|
from pyglove.core.typing import value_specs as vs
|
@@ -137,17 +135,19 @@ class BuiltInConversionsTest(unittest.TestCase):
|
|
137
135
|
def test_keypath_to_str(self):
|
138
136
|
"""Test built-in converter between string and KeyPath."""
|
139
137
|
self.assertEqual(
|
140
|
-
vs.Object(
|
141
|
-
|
142
|
-
self.assertEqual(
|
143
|
-
vs.Union([vs.Object(object_utils.KeyPath), vs.Int()]).apply(
|
144
|
-
'a.b.c').keys,
|
145
|
-
['a', 'b', 'c'])
|
138
|
+
vs.Object(utils.KeyPath).apply('a.b.c').keys, ['a', 'b', 'c']
|
139
|
+
)
|
146
140
|
self.assertEqual(
|
147
|
-
vs.
|
141
|
+
vs.Union([vs.Object(utils.KeyPath), vs.Int()]).apply('a.b.c').keys,
|
142
|
+
['a', 'b', 'c'],
|
143
|
+
)
|
144
|
+
self.assertEqual(vs.Str().apply(utils.KeyPath.parse('a.b.c')), 'a.b.c')
|
148
145
|
self.assertEqual(
|
149
|
-
type_conversion.get_json_value_converter(
|
150
|
-
|
146
|
+
type_conversion.get_json_value_converter(utils.KeyPath)(
|
147
|
+
utils.KeyPath.parse('a.b.c')
|
148
|
+
),
|
149
|
+
'a.b.c',
|
150
|
+
)
|
151
151
|
|
152
152
|
|
153
153
|
if __name__ == '__main__':
|
@@ -14,15 +14,15 @@
|
|
14
14
|
"""Typed value placeholders."""
|
15
15
|
|
16
16
|
from typing import Any
|
17
|
-
from pyglove.core import
|
17
|
+
from pyglove.core import utils
|
18
18
|
from pyglove.core.typing import class_schema
|
19
19
|
|
20
20
|
|
21
21
|
# Non-typed missing value.
|
22
|
-
MISSING_VALUE =
|
22
|
+
MISSING_VALUE = utils.MISSING_VALUE
|
23
23
|
|
24
24
|
|
25
|
-
class MissingValue(
|
25
|
+
class MissingValue(utils.MissingValue, utils.Formattable):
|
26
26
|
"""Class represents missing value **for a specific value spec**."""
|
27
27
|
|
28
28
|
def __init__(self, value_spec: class_schema.ValueSpec):
|
@@ -37,15 +37,15 @@ class MissingValue(object_utils.MissingValue, object_utils.Formattable):
|
|
37
37
|
def __eq__(self, other: Any) -> bool:
|
38
38
|
"""Operator ==.
|
39
39
|
|
40
|
-
NOTE: `MissingValue(value_spec) and `
|
40
|
+
NOTE: `MissingValue(value_spec) and `utils.MissingValue` are
|
41
41
|
considered equal, but `MissingValue(value_spec1)` and
|
42
42
|
`MissingValue(value_spec2)` are considered different. That being said,
|
43
43
|
the 'eq' operation is not transitive.
|
44
44
|
|
45
45
|
However in practice this is not a problem, since user always compare
|
46
|
-
against `schema.MISSING_VALUE` which is `
|
46
|
+
against `schema.MISSING_VALUE` which is `utils.MissingValue`.
|
47
47
|
Therefore the `__hash__` function returns the same value with
|
48
|
-
`
|
48
|
+
`utils.MissingValue`.
|
49
49
|
|
50
50
|
Args:
|
51
51
|
other: the value to compare against.
|
@@ -80,4 +80,3 @@ class MissingValue(object_utils.MissingValue, object_utils.Formattable):
|
|
80
80
|
def __deepcopy__(self, memo):
|
81
81
|
"""Avoid deep copy by copying value_spec by reference."""
|
82
82
|
return MissingValue(self.value_spec)
|
83
|
-
|