pyglove 0.4.5.dev20240319__py3-none-any.whl → 0.4.5.dev202501140808__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.dev20240319.dist-info → pyglove-0.4.5.dev202501140808.dist-info}/METADATA +18 -4
  131. pyglove-0.4.5.dev202501140808.dist-info/RECORD +214 -0
  132. {pyglove-0.4.5.dev20240319.dist-info → pyglove-0.4.5.dev202501140808.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.dev20240319.dist-info/RECORD +0 -185
  143. /pyglove/core/{object_utils → utils}/thread_local.py +0 -0
  144. {pyglove-0.4.5.dev20240319.dist-info → pyglove-0.4.5.dev202501140808.dist-info}/LICENSE +0 -0
  145. {pyglove-0.4.5.dev20240319.dist-info → pyglove-0.4.5.dev202501140808.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,100 @@
1
+ # Copyright 2025 The PyGlove Authors
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ """Python code permissions."""
15
+
16
+ import contextlib
17
+ import enum
18
+ from typing import Optional
19
+
20
+ from pyglove.core import utils
21
+
22
+
23
+ class CodePermission(enum.Flag):
24
+ """Permissions for code execution."""
25
+
26
+ # Allows assignment.
27
+ ASSIGN = enum.auto()
28
+
29
+ # Allows conditions.
30
+ CONDITION = enum.auto()
31
+
32
+ # Allows loops.
33
+ LOOP = enum.auto()
34
+
35
+ # Call functions or methods.
36
+ CALL = enum.auto()
37
+
38
+ # Allows exception.
39
+ EXCEPTION = enum.auto()
40
+
41
+ # Allows class definitions.
42
+ CLASS_DEFINITION = enum.auto()
43
+
44
+ # Allows function definitions.
45
+ FUNCTION_DEFINITION = enum.auto()
46
+
47
+ # Allows import.
48
+ IMPORT = enum.auto()
49
+
50
+ @classmethod
51
+ @property
52
+ def BASIC(cls) -> 'CodePermission': # pylint: disable=invalid-name
53
+ """Returns basic permissions."""
54
+ return CodePermission.ASSIGN | CodePermission.CALL
55
+
56
+ @classmethod
57
+ @property
58
+ def ALL(cls) -> 'CodePermission': # pylint: disable=invalid-name
59
+ """Returns all permissions."""
60
+ return (
61
+ CodePermission.BASIC | CodePermission.CONDITION | CodePermission.LOOP |
62
+ CodePermission.EXCEPTION | CodePermission.CLASS_DEFINITION |
63
+ CodePermission.FUNCTION_DEFINITION | CodePermission.IMPORT)
64
+
65
+
66
+ _TLS_CODE_RUN_PERMISSION = '__code_run_permission__'
67
+
68
+
69
+ @contextlib.contextmanager
70
+ def permission(perm: CodePermission):
71
+ """Context manager for controling the permission for code execution.
72
+
73
+ When the `permission` context manager is nested, the outtermost permission
74
+ will be used. This design allows users to control permission at the top level.
75
+
76
+ Args:
77
+ perm: Code execution permission.
78
+
79
+ Yields:
80
+ Actual permission applied.
81
+ """
82
+
83
+ outter_perm = utils.thread_local_get(_TLS_CODE_RUN_PERMISSION, None)
84
+
85
+ # Use the top-level permission as the actual permission
86
+ if outter_perm is not None:
87
+ perm = outter_perm
88
+
89
+ utils.thread_local_set(_TLS_CODE_RUN_PERMISSION, perm)
90
+
91
+ try:
92
+ yield perm
93
+ finally:
94
+ if outter_perm is None:
95
+ utils.thread_local_del(_TLS_CODE_RUN_PERMISSION)
96
+
97
+
98
+ def get_permission() -> Optional[CodePermission]:
99
+ """Gets the current permission for code execution."""
100
+ return utils.thread_local_get(_TLS_CODE_RUN_PERMISSION, None)
@@ -0,0 +1,93 @@
1
+ # Copyright 2025 The PyGlove Authors
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ import unittest
15
+ from pyglove.core.coding import permissions
16
+
17
+
18
+ class CodePermissionTest(unittest.TestCase):
19
+
20
+ def assert_set(
21
+ self,
22
+ permission: permissions.CodePermission,
23
+ flag: permissions.CodePermission,
24
+ ):
25
+ self.assertEqual(permission & flag, flag)
26
+
27
+ def assert_not_set(
28
+ self,
29
+ permission: permissions.CodePermission,
30
+ flag: permissions.CodePermission,
31
+ ):
32
+ self.assertFalse(permission & flag)
33
+
34
+ def test_basic(self):
35
+ self.assert_set(
36
+ permissions.CodePermission.BASIC, permissions.CodePermission.ASSIGN
37
+ )
38
+ self.assert_set(
39
+ permissions.CodePermission.BASIC, permissions.CodePermission.CALL
40
+ )
41
+ self.assert_set(
42
+ permissions.CodePermission.BASIC, permissions.CodePermission.CALL
43
+ )
44
+
45
+ def test_all(self):
46
+ self.assert_set(
47
+ permissions.CodePermission.ALL, permissions.CodePermission.BASIC
48
+ )
49
+ self.assert_set(
50
+ permissions.CodePermission.ALL, permissions.CodePermission.CONDITION
51
+ )
52
+ self.assert_set(
53
+ permissions.CodePermission.ALL, permissions.CodePermission.LOOP
54
+ )
55
+ self.assert_set(
56
+ permissions.CodePermission.ALL, permissions.CodePermission.EXCEPTION
57
+ )
58
+ self.assert_set(
59
+ permissions.CodePermission.ALL,
60
+ permissions.CodePermission.CLASS_DEFINITION,
61
+ )
62
+ self.assert_set(
63
+ permissions.CodePermission.ALL,
64
+ permissions.CodePermission.FUNCTION_DEFINITION,
65
+ )
66
+ self.assert_set(
67
+ permissions.CodePermission.ALL, permissions.CodePermission.IMPORT
68
+ )
69
+
70
+ def test_xor(self):
71
+ self.assert_not_set(
72
+ permissions.CodePermission.ALL ^ permissions.CodePermission.BASIC,
73
+ permissions.CodePermission.BASIC,
74
+ )
75
+ self.assert_set(
76
+ permissions.CodePermission.ALL ^ permissions.CodePermission.BASIC,
77
+ permissions.CodePermission.CONDITION,
78
+ )
79
+
80
+ def test_permission_control(self):
81
+ self.assertIsNone(permissions.get_permission())
82
+ with permissions.permission(permissions.CodePermission.BASIC):
83
+ self.assertEqual(
84
+ permissions.get_permission(), permissions.CodePermission.BASIC
85
+ )
86
+ with permissions.permission(permissions.CodePermission.ALL):
87
+ self.assertEqual(
88
+ permissions.get_permission(), permissions.CodePermission.BASIC
89
+ )
90
+
91
+
92
+ if __name__ == '__main__':
93
+ unittest.main()
pyglove/core/geno/base.py CHANGED
@@ -19,9 +19,9 @@ import random
19
19
  import types
20
20
  from typing import Any, Callable, Dict, List, Optional, Tuple, Union
21
21
 
22
- from pyglove.core import object_utils
23
22
  from pyglove.core import symbolic
24
23
  from pyglove.core import typing as pg_typing
24
+ from pyglove.core import utils
25
25
 
26
26
 
27
27
  class AttributeDict(dict):
@@ -37,12 +37,20 @@ class AttributeDict(dict):
37
37
 
38
38
 
39
39
  @symbolic.members([
40
- ('location',
41
- pg_typing.Object(object_utils.KeyPath, default=object_utils.KeyPath()),
42
- ('KeyPath of associated genetic encoder relative to parent object '
43
- 'template. This allows DNA generator to apply rule based on locations.')),
44
- ('hints',
45
- pg_typing.Any(default=None), 'Hints for DNA generator to consume.')
40
+ (
41
+ 'location',
42
+ pg_typing.Object(utils.KeyPath, default=utils.KeyPath()),
43
+ (
44
+ 'KeyPath of associated genetic encoder relative to parent object'
45
+ ' template. This allows DNA generator to apply rule based on'
46
+ ' locations.'
47
+ ),
48
+ ),
49
+ (
50
+ 'hints',
51
+ pg_typing.Any(default=None),
52
+ 'Hints for DNA generator to consume.',
53
+ ),
46
54
  ])
47
55
  class DNASpec(symbolic.Object):
48
56
  """Base class for DNA specifications (genotypes).
@@ -175,7 +183,7 @@ class DNASpec(symbolic.Object):
175
183
  """Returns all decision points in their declaration order."""
176
184
 
177
185
  @property
178
- def decision_ids(self) -> List[object_utils.KeyPath]:
186
+ def decision_ids(self) -> List[utils.KeyPath]:
179
187
  """Returns decision IDs."""
180
188
  return list(self._decision_point_by_id.keys())
181
189
 
@@ -286,7 +294,7 @@ class DNASpec(symbolic.Object):
286
294
  return self.parent_spec if self.is_space else self.parent_spec.parent_choice
287
295
 
288
296
  @property
289
- def id(self) -> object_utils.KeyPath:
297
+ def id(self) -> utils.KeyPath:
290
298
  """Returns a path of locations from the root as the ID for current node."""
291
299
  if self._id is None:
292
300
  parent = self.parent_spec
@@ -295,18 +303,20 @@ class DNASpec(symbolic.Object):
295
303
  elif self.is_space:
296
304
  assert parent.is_categorical, parent
297
305
  assert self.index is not None
298
- self._id = object_utils.KeyPath(
299
- ConditionalKey(self.index, len(parent.candidates)),
300
- parent.id) + self.location
306
+ self._id = (
307
+ utils.KeyPath(
308
+ ConditionalKey(self.index, len(parent.candidates)), parent.id
309
+ )
310
+ + self.location
311
+ )
301
312
  else:
302
313
  # Float() or a multi-choice spec of a parent Choice.
303
314
  self._id = parent.id + self.location
304
315
  return self._id
305
316
 
306
- def get(self,
307
- name_or_id: Union[object_utils.KeyPath, str],
308
- default: Any = None
309
- ) -> Union['DecisionPoint', List['DecisionPoint']]:
317
+ def get(
318
+ self, name_or_id: Union[utils.KeyPath, str], default: Any = None
319
+ ) -> Union['DecisionPoint', List['DecisionPoint']]:
310
320
  """Get decision point(s) by name or ID."""
311
321
  try:
312
322
  return self[name_or_id]
@@ -314,9 +324,8 @@ class DNASpec(symbolic.Object):
314
324
  return default
315
325
 
316
326
  def __getitem__(
317
- self,
318
- name_or_id: Union[object_utils.KeyPath, str]
319
- ) -> Union['DecisionPoint', List['DecisionPoint']]:
327
+ self, name_or_id: Union[utils.KeyPath, str]
328
+ ) -> Union['DecisionPoint', List['DecisionPoint']]:
320
329
  """Get decision point(s) by name or ID ."""
321
330
  v = self._named_decision_points.get(name_or_id, None)
322
331
  if v is None:
@@ -475,7 +484,7 @@ class DNA(symbolic.Object):
475
484
  # Allow assignment on symbolic attributes.
476
485
  allow_symbolic_assignment = True
477
486
 
478
- @object_utils.explicit_method_override
487
+ @utils.explicit_method_override
479
488
  def __init__(
480
489
  self,
481
490
  value: Union[None, int, float, str, List[Any], Tuple[Any]] = None,
@@ -727,7 +736,7 @@ class DNA(symbolic.Object):
727
736
  return self._decision_by_id_cache
728
737
 
729
738
  @property
730
- def decision_ids(self) -> List[object_utils.KeyPath]:
739
+ def decision_ids(self) -> List[utils.KeyPath]:
731
740
  """Returns decision IDs."""
732
741
  self._ensure_dna_spec()
733
742
  return self._spec.decision_ids
@@ -1249,9 +1258,11 @@ class DNA(symbolic.Object):
1249
1258
  return dna
1250
1259
 
1251
1260
  def to_numbers(
1252
- self, flatten: bool = True,
1253
- ) -> Union[List[Union[int, float, str]],
1254
- object_utils.Nestable[Union[int, float, str]]]:
1261
+ self,
1262
+ flatten: bool = True,
1263
+ ) -> Union[
1264
+ List[Union[int, float, str]], utils.Nestable[Union[int, float, str]]
1265
+ ]:
1255
1266
  """Returns a (maybe) nested structure of numbers as decisions.
1256
1267
 
1257
1268
  Args:
@@ -1338,7 +1349,7 @@ class DNA(symbolic.Object):
1338
1349
  f'Location: {dna_spec.location.path}.')
1339
1350
  children = []
1340
1351
  for i, choice in enumerate(decision):
1341
- choice_location = object_utils.KeyPath(i, dna_spec.location)
1352
+ choice_location = utils.KeyPath(i, dna_spec.location)
1342
1353
  if not isinstance(choice, int):
1343
1354
  raise ValueError(
1344
1355
  f'Choice value should be int. Encountered: {choice}, '
@@ -1410,7 +1421,7 @@ class DNA(symbolic.Object):
1410
1421
 
1411
1422
  if type_info:
1412
1423
  json_value = {
1413
- object_utils.JSONConvertible.TYPE_NAME_KEY: (
1424
+ utils.JSONConvertible.TYPE_NAME_KEY: (
1414
1425
  self.__class__.__serialization_key__
1415
1426
  ),
1416
1427
  'format': 'compact',
@@ -1435,7 +1446,8 @@ class DNA(symbolic.Object):
1435
1446
  json_value: Dict[str, Any],
1436
1447
  *,
1437
1448
  allow_partial: bool = False,
1438
- root_path: Optional[object_utils.KeyPath] = None) -> 'DNA':
1449
+ root_path: Optional[utils.KeyPath] = None,
1450
+ ) -> 'DNA':
1439
1451
  """Class method that load a DNA from a JSON value.
1440
1452
 
1441
1453
  Args:
@@ -1472,8 +1484,8 @@ class DNA(symbolic.Object):
1472
1484
  return not self.children
1473
1485
 
1474
1486
  def __getitem__(
1475
- self, key: Union[int, slice, str, object_utils.KeyPath, 'DecisionPoint']
1476
- ) -> Union[None, 'DNA', List[Optional['DNA']]]:
1487
+ self, key: Union[int, slice, str, utils.KeyPath, 'DecisionPoint']
1488
+ ) -> Union[None, 'DNA', List[Optional['DNA']]]:
1477
1489
  """Get an immediate child DNA or DNA in the sub-tree.
1478
1490
 
1479
1491
  Args:
@@ -1504,10 +1516,11 @@ class DNA(symbolic.Object):
1504
1516
  v = self._decision_by_id[key]
1505
1517
  return v
1506
1518
 
1507
- def get(self,
1508
- key: Union[int, slice, str, object_utils.KeyPath, 'DecisionPoint'],
1509
- default: Any = None
1510
- ) -> Union[Any, None, 'DNA', List[Optional['DNA']]]:
1519
+ def get(
1520
+ self,
1521
+ key: Union[int, slice, str, utils.KeyPath, 'DecisionPoint'],
1522
+ default: Any = None,
1523
+ ) -> Union[Any, None, 'DNA', List[Optional['DNA']]]:
1511
1524
  """Get an immediate child DNA or DNA in the sub-tree."""
1512
1525
  try:
1513
1526
  return self[key]
@@ -1529,8 +1542,9 @@ class DNA(symbolic.Object):
1529
1542
  return True
1530
1543
  else:
1531
1544
  raise ValueError(
1532
- f'DNA.__contains__ does not accept '
1533
- f'{object_utils.quote_if_str(dna_or_value)!r}.')
1545
+ 'DNA.__contains__ does not accept '
1546
+ f'{utils.quote_if_str(dna_or_value)!r}.'
1547
+ )
1534
1548
  return False
1535
1549
 
1536
1550
  def __hash__(self):
@@ -1599,21 +1613,20 @@ class DNA(symbolic.Object):
1599
1613
  verbose: bool = True,
1600
1614
  root_indent: int = 0,
1601
1615
  *,
1602
- markdown: bool = False,
1603
1616
  list_wrap_threshold: int = 80,
1604
1617
  as_dict: bool = False,
1605
1618
  **kwargs,
1606
1619
  ):
1607
1620
  """Customize format method for DNA for more compact representation."""
1608
1621
  if as_dict and self.spec:
1609
- details = object_utils.format(
1622
+ details = utils.format(
1610
1623
  self.to_dict(value_type='choice_and_literal'),
1611
1624
  False,
1612
1625
  verbose,
1613
1626
  root_indent,
1614
- **kwargs)
1627
+ **kwargs,
1628
+ )
1615
1629
  s = f'DNA({details})'
1616
- compact = False
1617
1630
  else:
1618
1631
  if 'list_wrap_threshold' not in kwargs:
1619
1632
  kwargs['list_wrap_threshold'] = list_wrap_threshold
@@ -1623,7 +1636,7 @@ class DNA(symbolic.Object):
1623
1636
  elif self.is_leaf:
1624
1637
  s = f'DNA({self.value!r})'
1625
1638
  else:
1626
- rep = object_utils.format(
1639
+ rep = utils.format(
1627
1640
  self.to_json(compact=True, type_info=False),
1628
1641
  compact,
1629
1642
  verbose,
@@ -1636,7 +1649,7 @@ class DNA(symbolic.Object):
1636
1649
  s = f'DNA{rep}'
1637
1650
  else:
1638
1651
  s = f'DNA({rep})'
1639
- return object_utils.maybe_markdown_quote(s, markdown)
1652
+ return s
1640
1653
 
1641
1654
  def parameters(
1642
1655
  self, use_literal_values: bool = False) -> Dict[str, str]:
@@ -11,12 +11,10 @@
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.geno.DNA."""
15
-
16
14
  import unittest
17
15
 
18
- from pyglove.core import object_utils
19
16
  from pyglove.core import symbolic
17
+ from pyglove.core import utils
20
18
  from pyglove.core.geno.base import ConditionalKey
21
19
  from pyglove.core.geno.base import DNA
22
20
  from pyglove.core.geno.categorical import manyof
@@ -1144,7 +1142,7 @@ class ConditionalKeyTest(unittest.TestCase):
1144
1142
  self.assertEqual(key.num_choices, 5)
1145
1143
 
1146
1144
  def test_to_str(self):
1147
- key = object_utils.KeyPath(['a', ConditionalKey(1, 5), 'b'])
1145
+ key = utils.KeyPath(['a', ConditionalKey(1, 5), 'b'])
1148
1146
  self.assertEqual(str(key), 'a[=1/5].b')
1149
1147
 
1150
1148
 
@@ -18,10 +18,9 @@ import re
18
18
  import types
19
19
  from typing import Any, List, Optional, Union
20
20
 
21
- from pyglove.core import object_utils
22
21
  from pyglove.core import symbolic
23
22
  from pyglove.core import typing as pg_typing
24
-
23
+ from pyglove.core import utils
25
24
  from pyglove.core.geno.base import DecisionPoint
26
25
  from pyglove.core.geno.base import DNA
27
26
  from pyglove.core.geno.base import DNASpec
@@ -139,14 +138,15 @@ class Choices(DecisionPoint):
139
138
  for i in range(self.num_choices):
140
139
  subchoice_spec = Choices(
141
140
  subchoice_index=i,
142
- location=object_utils.KeyPath(i),
141
+ location=utils.KeyPath(i),
143
142
  num_choices=1,
144
143
  candidates=self.candidates,
145
144
  literal_values=self.literal_values,
146
145
  distinct=self.distinct,
147
146
  sorted=self.sorted,
148
147
  name=self.name,
149
- hints=self.hints)
148
+ hints=self.hints,
149
+ )
150
150
  self._decision_points.extend(subchoice_spec.decision_points)
151
151
  subchoice_specs.append(subchoice_spec)
152
152
  self._subchoice_specs = symbolic.List(subchoice_specs)
@@ -158,7 +158,8 @@ class Choices(DecisionPoint):
158
158
  self._decision_points.extend(c.decision_points)
159
159
 
160
160
  def _update_children_paths(
161
- self, old_path: object_utils.KeyPath, new_path: object_utils.KeyPath):
161
+ self, old_path: utils.KeyPath, new_path: utils.KeyPath
162
+ ):
162
163
  """Trigger path change for subchoices so their IDs can be invalidated."""
163
164
  super()._update_children_paths(old_path, new_path)
164
165
  if self._subchoice_specs:
@@ -337,7 +338,7 @@ class Choices(DecisionPoint):
337
338
  f'DNA child values should be sorted. '
338
339
  f'Encountered: {sub_dna_values}, Location: {self.location.path}.')
339
340
  for i, sub_dna in enumerate(dna):
340
- sub_location = object_utils.KeyPath(i, self.location)
341
+ sub_location = utils.KeyPath(i, self.location)
341
342
  if not isinstance(sub_dna.value, int):
342
343
  raise ValueError(
343
344
  f'Choice value should be int. Encountered: {sub_dna.value}, '
@@ -598,16 +599,21 @@ class Choices(DecisionPoint):
598
599
  s.append(_indent(f'({i}): {value_str}\n', root_indent + 1))
599
600
  s.append(_indent(']', root_indent))
600
601
  if show_id:
601
- kvlist = [('id', object_utils.quote_if_str(str(self.id)), '\'\'')]
602
+ kvlist = [('id', str(self.id), '\'\'')]
602
603
  else:
603
604
  kvlist = []
604
- additionl_properties = object_utils.kvlist_str(kvlist + [
605
- ('name', object_utils.quote_if_str(self.name), None),
606
- ('distinct', self.distinct, True),
607
- ('sorted', self.sorted, False),
608
- ('hints', object_utils.quote_if_str(self.hints), None),
609
- ('subchoice_index', self.subchoice_index, None)
610
- ], compact=False, root_indent=root_indent)
605
+ additionl_properties = utils.kvlist_str(
606
+ kvlist
607
+ + [
608
+ ('name', self.name, None),
609
+ ('distinct', self.distinct, True),
610
+ ('sorted', self.sorted, False),
611
+ ('hints', self.hints, None),
612
+ ('subchoice_index', self.subchoice_index, None),
613
+ ],
614
+ compact=False,
615
+ root_indent=root_indent,
616
+ )
611
617
  if additionl_properties:
612
618
  s.append(', ')
613
619
  s.append(additionl_properties)
@@ -615,14 +621,16 @@ class Choices(DecisionPoint):
615
621
  return ''.join(s)
616
622
 
617
623
 
618
- def manyof(num_choices: int,
619
- candidates: List[DNASpec],
620
- distinct: bool = True,
621
- sorted: bool = False, # pylint: disable=redefined-builtin
622
- literal_values: Optional[List[Union[str, int, float]]] = None,
623
- hints: Any = None,
624
- location: Union[str, object_utils.KeyPath] = object_utils.KeyPath(),
625
- name: Optional[str] = None) -> Choices:
624
+ def manyof(
625
+ num_choices: int,
626
+ candidates: List[DNASpec],
627
+ distinct: bool = True,
628
+ sorted: bool = False, # pylint: disable=redefined-builtin
629
+ literal_values: Optional[List[Union[str, int, float]]] = None,
630
+ hints: Any = None,
631
+ location: Union[str, utils.KeyPath] = utils.KeyPath(),
632
+ name: Optional[str] = None,
633
+ ) -> Choices:
626
634
  """Returns a multi-choice specification.
627
635
 
628
636
  It creates the genotype for :func:`pyglove.manyof`.
@@ -674,11 +682,13 @@ def manyof(num_choices: int,
674
682
  hints=hints, location=location, name=name)
675
683
 
676
684
 
677
- def oneof(candidates: List[DNASpec],
678
- literal_values: Optional[List[Union[str, int, float]]] = None,
679
- hints: Any = None,
680
- location: Union[str, object_utils.KeyPath] = object_utils.KeyPath(),
681
- name: Optional[str] = None) -> Choices:
685
+ def oneof(
686
+ candidates: List[DNASpec],
687
+ literal_values: Optional[List[Union[str, int, float]]] = None,
688
+ hints: Any = None,
689
+ location: Union[str, utils.KeyPath] = utils.KeyPath(),
690
+ name: Optional[str] = None,
691
+ ) -> Choices:
682
692
  """Returns a single choice specification.
683
693
 
684
694
  It creates the genotype for :func:`pyglove.oneof`.
@@ -716,4 +726,3 @@ def oneof(candidates: List[DNASpec],
716
726
  """
717
727
  return manyof(1, candidates, literal_values=literal_values,
718
728
  hints=hints, location=location, name=name)
719
-
@@ -17,10 +17,9 @@ import random
17
17
  import types
18
18
  from typing import Any, Callable, List, Optional, Union
19
19
 
20
- from pyglove.core import object_utils
21
20
  from pyglove.core import symbolic
22
21
  from pyglove.core import typing as pg_typing
23
-
22
+ from pyglove.core import utils
24
23
  from pyglove.core.geno.base import DecisionPoint
25
24
  from pyglove.core.geno.base import DNA
26
25
 
@@ -125,7 +124,7 @@ class CustomDecisionPoint(DecisionPoint):
125
124
  f'CustomDecisionPoint expects string type DNA. '
126
125
  f'Encountered: {dna!r}, Location: {self.location.path}.')
127
126
 
128
- def sym_jsonify(self, **kwargs: Any) -> object_utils.JSONValueType:
127
+ def sym_jsonify(self, **kwargs: Any) -> utils.JSONValueType:
129
128
  """Overrides sym_jsonify to exclude non-serializable fields."""
130
129
  exclude_keys = kwargs.pop('exclude_keys', [])
131
130
  exclude_keys.extend(['random_dna_fn', 'next_dna_fn'])
@@ -150,24 +149,28 @@ class CustomDecisionPoint(DecisionPoint):
150
149
  if not compact:
151
150
  return super().format(compact, verbose, root_indent, **kwargs)
152
151
  if show_id:
153
- kvlist = [('id', object_utils.quote_if_str(str(self.id)), '\'\'')]
152
+ kvlist = [('id', str(self.id), '\'\'')]
154
153
  else:
155
154
  kvlist = []
156
- details = object_utils.kvlist_str(kvlist + [
157
- ('hyper_type', object_utils.quote_if_str(self.hyper_type), None),
158
- ('name', object_utils.quote_if_str(self.name), None),
159
- ('hints', object_utils.quote_if_str(self.hints), None),
160
- ])
155
+ details = utils.kvlist_str(
156
+ kvlist
157
+ + [
158
+ ('hyper_type', self.hyper_type, None),
159
+ ('name', self.name, None),
160
+ ('hints', self.hints, None),
161
+ ]
162
+ )
161
163
  return f'{self.__class__.__name__}({details})'
162
164
 
163
165
 
164
- def custom(hyper_type: Optional[str] = None,
165
- next_dna_fn: Optional[
166
- Callable[[Optional[DNA]], Optional[DNA]]] = None,
167
- random_dna_fn: Optional[Callable[[Any], DNA]] = None,
168
- hints: Any = None,
169
- location: object_utils.KeyPath = object_utils.KeyPath(),
170
- name: Optional[str] = None) -> CustomDecisionPoint:
166
+ def custom(
167
+ hyper_type: Optional[str] = None,
168
+ next_dna_fn: Optional[Callable[[Optional[DNA]], Optional[DNA]]] = None,
169
+ random_dna_fn: Optional[Callable[[Any], DNA]] = None,
170
+ hints: Any = None,
171
+ location: utils.KeyPath = utils.KeyPath(),
172
+ name: Optional[str] = None,
173
+ ) -> CustomDecisionPoint:
171
174
  """Returns a custom decision point.
172
175
 
173
176
  It creates the genotype for subclasses of :func:`pyglove.hyper.CustomHyper`.