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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. pyglove/core/__init__.py +54 -20
  2. pyglove/core/coding/__init__.py +42 -0
  3. pyglove/core/coding/errors.py +111 -0
  4. pyglove/core/coding/errors_test.py +98 -0
  5. pyglove/core/coding/execution.py +309 -0
  6. pyglove/core/coding/execution_test.py +333 -0
  7. pyglove/core/{object_utils/codegen.py → coding/function_generation.py} +10 -4
  8. pyglove/core/{object_utils/codegen_test.py → coding/function_generation_test.py} +5 -7
  9. pyglove/core/coding/parsing.py +153 -0
  10. pyglove/core/coding/parsing_test.py +150 -0
  11. pyglove/core/coding/permissions.py +100 -0
  12. pyglove/core/coding/permissions_test.py +93 -0
  13. pyglove/core/geno/base.py +54 -41
  14. pyglove/core/geno/base_test.py +2 -4
  15. pyglove/core/geno/categorical.py +37 -28
  16. pyglove/core/geno/custom.py +19 -16
  17. pyglove/core/geno/numerical.py +20 -17
  18. pyglove/core/geno/space.py +4 -5
  19. pyglove/core/hyper/base.py +6 -6
  20. pyglove/core/hyper/categorical.py +94 -55
  21. pyglove/core/hyper/custom.py +7 -7
  22. pyglove/core/hyper/custom_test.py +9 -10
  23. pyglove/core/hyper/derived.py +30 -22
  24. pyglove/core/hyper/derived_test.py +2 -4
  25. pyglove/core/hyper/dynamic_evaluation.py +5 -6
  26. pyglove/core/hyper/evolvable.py +57 -46
  27. pyglove/core/hyper/numerical.py +48 -24
  28. pyglove/core/hyper/numerical_test.py +9 -9
  29. pyglove/core/hyper/object_template.py +58 -46
  30. pyglove/core/io/__init__.py +1 -0
  31. pyglove/core/io/file_system.py +17 -7
  32. pyglove/core/io/file_system_test.py +2 -0
  33. pyglove/core/io/sequence.py +299 -0
  34. pyglove/core/io/sequence_test.py +124 -0
  35. pyglove/core/logging_test.py +0 -2
  36. pyglove/core/patching/object_factory.py +4 -4
  37. pyglove/core/patching/pattern_based.py +4 -4
  38. pyglove/core/patching/rule_based.py +17 -5
  39. pyglove/core/patching/rule_based_test.py +27 -4
  40. pyglove/core/symbolic/__init__.py +2 -7
  41. pyglove/core/symbolic/base.py +320 -183
  42. pyglove/core/symbolic/base_test.py +123 -19
  43. pyglove/core/symbolic/boilerplate.py +7 -13
  44. pyglove/core/symbolic/boilerplate_test.py +25 -23
  45. pyglove/core/symbolic/class_wrapper.py +48 -45
  46. pyglove/core/symbolic/class_wrapper_test.py +2 -2
  47. pyglove/core/symbolic/compounding.py +9 -15
  48. pyglove/core/symbolic/compounding_test.py +2 -4
  49. pyglove/core/symbolic/dict.py +154 -110
  50. pyglove/core/symbolic/dict_test.py +238 -130
  51. pyglove/core/symbolic/diff.py +199 -10
  52. pyglove/core/symbolic/diff_test.py +226 -0
  53. pyglove/core/symbolic/flags.py +1 -1
  54. pyglove/core/symbolic/functor.py +29 -26
  55. pyglove/core/symbolic/functor_test.py +102 -50
  56. pyglove/core/symbolic/inferred.py +2 -2
  57. pyglove/core/symbolic/list.py +81 -50
  58. pyglove/core/symbolic/list_test.py +119 -97
  59. pyglove/core/symbolic/object.py +225 -113
  60. pyglove/core/symbolic/object_test.py +320 -108
  61. pyglove/core/symbolic/origin.py +17 -14
  62. pyglove/core/symbolic/origin_test.py +4 -2
  63. pyglove/core/symbolic/pure_symbolic.py +4 -3
  64. pyglove/core/symbolic/ref.py +108 -21
  65. pyglove/core/symbolic/ref_test.py +93 -0
  66. pyglove/core/symbolic/symbolize_test.py +10 -2
  67. pyglove/core/tuning/local_backend.py +2 -2
  68. pyglove/core/tuning/protocols.py +3 -3
  69. pyglove/core/tuning/sample_test.py +3 -3
  70. pyglove/core/typing/__init__.py +14 -5
  71. pyglove/core/typing/annotation_conversion.py +43 -27
  72. pyglove/core/typing/annotation_conversion_test.py +23 -0
  73. pyglove/core/typing/callable_ext.py +241 -3
  74. pyglove/core/typing/callable_ext_test.py +255 -0
  75. pyglove/core/typing/callable_signature.py +510 -66
  76. pyglove/core/typing/callable_signature_test.py +619 -99
  77. pyglove/core/typing/class_schema.py +229 -154
  78. pyglove/core/typing/class_schema_test.py +149 -95
  79. pyglove/core/typing/custom_typing.py +5 -4
  80. pyglove/core/typing/inspect.py +63 -0
  81. pyglove/core/typing/inspect_test.py +39 -0
  82. pyglove/core/typing/key_specs.py +10 -11
  83. pyglove/core/typing/key_specs_test.py +7 -4
  84. pyglove/core/typing/type_conversion.py +4 -5
  85. pyglove/core/typing/type_conversion_test.py +12 -12
  86. pyglove/core/typing/typed_missing.py +6 -7
  87. pyglove/core/typing/typed_missing_test.py +7 -8
  88. pyglove/core/typing/value_specs.py +604 -362
  89. pyglove/core/typing/value_specs_test.py +328 -90
  90. pyglove/core/utils/__init__.py +164 -0
  91. pyglove/core/{object_utils → utils}/common_traits.py +3 -67
  92. pyglove/core/utils/common_traits_test.py +36 -0
  93. pyglove/core/{object_utils → utils}/docstr_utils.py +23 -0
  94. pyglove/core/{object_utils → utils}/docstr_utils_test.py +36 -4
  95. pyglove/core/{object_utils → utils}/error_utils.py +78 -9
  96. pyglove/core/{object_utils → utils}/error_utils_test.py +61 -5
  97. pyglove/core/utils/formatting.py +464 -0
  98. pyglove/core/utils/formatting_test.py +453 -0
  99. pyglove/core/{object_utils → utils}/hierarchical.py +23 -25
  100. pyglove/core/{object_utils → utils}/hierarchical_test.py +3 -5
  101. pyglove/core/{object_utils → utils}/json_conversion.py +177 -52
  102. pyglove/core/{object_utils → utils}/json_conversion_test.py +97 -16
  103. pyglove/core/{object_utils → utils}/missing.py +3 -3
  104. pyglove/core/{object_utils → utils}/missing_test.py +2 -4
  105. pyglove/core/utils/text_color.py +128 -0
  106. pyglove/core/utils/text_color_test.py +94 -0
  107. pyglove/core/{object_utils → utils}/thread_local_test.py +1 -3
  108. pyglove/core/utils/timing.py +236 -0
  109. pyglove/core/utils/timing_test.py +154 -0
  110. pyglove/core/{object_utils → utils}/value_location.py +275 -6
  111. pyglove/core/utils/value_location_test.py +707 -0
  112. pyglove/core/views/__init__.py +32 -0
  113. pyglove/core/views/base.py +804 -0
  114. pyglove/core/views/base_test.py +580 -0
  115. pyglove/core/views/html/__init__.py +27 -0
  116. pyglove/core/views/html/base.py +547 -0
  117. pyglove/core/views/html/base_test.py +830 -0
  118. pyglove/core/views/html/controls/__init__.py +35 -0
  119. pyglove/core/views/html/controls/base.py +275 -0
  120. pyglove/core/views/html/controls/label.py +207 -0
  121. pyglove/core/views/html/controls/label_test.py +157 -0
  122. pyglove/core/views/html/controls/progress_bar.py +183 -0
  123. pyglove/core/views/html/controls/progress_bar_test.py +97 -0
  124. pyglove/core/views/html/controls/tab.py +320 -0
  125. pyglove/core/views/html/controls/tab_test.py +87 -0
  126. pyglove/core/views/html/controls/tooltip.py +99 -0
  127. pyglove/core/views/html/controls/tooltip_test.py +99 -0
  128. pyglove/core/views/html/tree_view.py +1517 -0
  129. pyglove/core/views/html/tree_view_test.py +1461 -0
  130. {pyglove-0.4.5.dev20240318.dist-info → pyglove-0.4.5.dev202501132210.dist-info}/METADATA +18 -4
  131. pyglove-0.4.5.dev202501132210.dist-info/RECORD +214 -0
  132. {pyglove-0.4.5.dev20240318.dist-info → pyglove-0.4.5.dev202501132210.dist-info}/WHEEL +1 -1
  133. pyglove/core/object_utils/__init__.py +0 -154
  134. pyglove/core/object_utils/common_traits_test.py +0 -82
  135. pyglove/core/object_utils/formatting.py +0 -234
  136. pyglove/core/object_utils/formatting_test.py +0 -223
  137. pyglove/core/object_utils/value_location_test.py +0 -385
  138. pyglove/core/symbolic/schema_utils.py +0 -327
  139. pyglove/core/symbolic/schema_utils_test.py +0 -57
  140. pyglove/core/typing/class_schema_utils.py +0 -202
  141. pyglove/core/typing/class_schema_utils_test.py +0 -194
  142. pyglove-0.4.5.dev20240318.dist-info/RECORD +0 -185
  143. /pyglove/core/{object_utils → utils}/thread_local.py +0 -0
  144. {pyglove-0.4.5.dev20240318.dist-info → pyglove-0.4.5.dev202501132210.dist-info}/LICENSE +0 -0
  145. {pyglove-0.4.5.dev20240318.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 object_utils
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 test_format(self):
68
+ def test_repr(self):
71
69
  self.assertEqual(
72
- str(class_schema.ForwardRef(self._module, 'FieldTest')),
73
- f'ForwardRef(module={self._module.__name__}, name=FieldTest)',
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 test_format(self):
191
- self.assertEqual(
192
- Field('a', vs.Dict([
193
- ('b', vs.Int())
194
- ]), 'this is a very long field.', {
195
- 'm1': 1,
196
- 'm2': 2,
197
- 'm3': 3,
198
- 'm4': 4,
199
- 'm5': 5
200
- }).format(compact=True, verbose=False),
201
- 'Field(key=a, value=Dict({b=Int()}), '
202
- 'description=\'this is a very long ...\', '
203
- 'metadata={...})')
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(object_utils.from_json(f.to_json()), f)
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 test_format(self):
321
+ def test_repr(self):
346
322
  """Tests for Schema.format."""
347
323
  self.assertEqual(
348
- self._create_test_schema().format(compact=True),
349
- 'Schema(a=Int(default=1), b=Bool(default=None, noneable=True), '
350
- 'c=Dict({d=List('
351
- 'Enum(default=0, values=[0, 1, None]), default=[0, 1]), '
352
- 'e=List('
353
- 'Dict({StrKey(regex=\'foo.*\')=Str()})), '
354
- 'f=Object(SimpleObject)}, noneable=True))')
355
-
356
- self.assertEqual(
357
- inspect.cleandoc(self._create_test_schema().format(
358
- compact=False, verbose=False)),
359
- inspect.cleandoc("""Schema(
360
- a = Int(default=1),
361
- b = Bool(default=None, noneable=True),
362
- c = Dict({
363
- d = List(Enum(default=0, values=[0, 1, None]), default=[0, 1]),
364
- e = List(Dict({
365
- StrKey(regex=\'foo.*\') = Str()
366
- })),
367
- f = Object(SimpleObject)
368
- }, noneable=True)
369
- )"""))
370
-
371
- self.assertEqual(
372
- inspect.cleandoc(
373
- # Equal to schema.format(compact=False, verbose=True)
374
- str(self._create_test_schema())),
375
- inspect.cleandoc("""Schema(
376
- # Field a.
377
- a = Int(default=1),
378
-
379
- # Field b.
380
- b = Bool(default=None, noneable=True),
381
-
382
- # Field c.
383
- c = Dict({
384
- # Field d.
385
- d = List(Enum(default=0, values=[0, 1, None]), default=[0, 1]),
386
-
387
- # Field e.
388
- e = List(Dict({
389
- # Mapped values.
390
- StrKey(regex=\'foo.*\') = Str()
391
- })),
392
-
393
- # Field f.
394
- f = Object(SimpleObject)
395
- }, noneable=True)
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 test_Validate(self):
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 = object_utils.from_json(schema.to_json())
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 object_utils
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: object_utils.KeyPath,
37
+ path: utils.KeyPath,
38
38
  value_spec: class_schema.ValueSpec,
39
39
  allow_partial: bool,
40
- child_transform: Optional[Callable[
41
- [object_utils.KeyPath, class_schema.Field, Any], Any]] = None
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
 
@@ -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
@@ -16,7 +16,7 @@
16
16
  import re
17
17
  from typing import Any, Dict, Optional
18
18
 
19
- from pyglove.core import object_utils
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, object_utils.StrKey):
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
- regex_str = object_utils.kvlist_str([
163
- ('regex', object_utils.quote_if_str(
164
- self._regex.pattern if self._regex else None), None)
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 object_utils
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(object_utils.from_json(object_utils.to_json(spec)), spec)
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
- self.assertEqual(str(key), 'a')
39
- self.assertEqual(repr(key), 'a')
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 object_utils
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, object_utils.KeyPath,
139
- object_utils.KeyPath.parse)
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
- object_utils.JSONConvertible.TYPE_CONVERTER = get_json_value_converter
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 object_utils
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(object_utils.KeyPath).apply('a.b.c').keys,
141
- ['a', 'b', 'c'])
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.Str().apply(object_utils.KeyPath.parse('a.b.c')), 'a.b.c')
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(object_utils.KeyPath)(
150
- object_utils.KeyPath.parse('a.b.c')), 'a.b.c')
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 object_utils
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 = object_utils.MISSING_VALUE
22
+ MISSING_VALUE = utils.MISSING_VALUE
23
23
 
24
24
 
25
- class MissingValue(object_utils.MissingValue, object_utils.Formattable):
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 `object_utils.MissingValue` are
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 `object_utils.MissingValue`.
46
+ against `schema.MISSING_VALUE` which is `utils.MissingValue`.
47
47
  Therefore the `__hash__` function returns the same value with
48
- `object_utils.MissingValue`.
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
-