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.
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.dev202501132210.dist-info}/METADATA +18 -4
  131. pyglove-0.4.5.dev202501132210.dist-info/RECORD +214 -0
  132. {pyglove-0.4.5.dev20240319.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.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.dev202501132210.dist-info}/LICENSE +0 -0
  145. {pyglove-0.4.5.dev20240319.dist-info → pyglove-0.4.5.dev202501132210.dist-info}/top_level.txt +0 -0
@@ -133,6 +133,7 @@ class MemoryFileSystemTest(unittest.TestCase):
133
133
  self.assertFalse(fs.exists(file1))
134
134
  with fs.open(file1, 'w') as f:
135
135
  f.write('hello')
136
+ f.flush()
136
137
  self.assertTrue(fs.exists(file1))
137
138
  self.assertFalse(fs.isdir(file1))
138
139
 
@@ -224,6 +225,7 @@ class FileIoApiTest(unittest.TestCase):
224
225
  with file_system.open(file1, 'w') as f:
225
226
  self.assertIsInstance(f, file_system.MemoryFile)
226
227
  f.write('foo')
228
+ f.flush()
227
229
  self.assertTrue(file_system.path_exists(file1))
228
230
  self.assertEqual(file_system.readfile(file1), 'foo')
229
231
  file_system.writefile(file1, 'bar')
@@ -0,0 +1,299 @@
1
+ # Copyright 2024 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
+ """Pluggable record IO."""
15
+
16
+ import abc
17
+ import collections
18
+ import os
19
+ from typing import Any, Callable, Iterator, Optional, Union
20
+
21
+ from pyglove.core.io import file_system
22
+
23
+
24
+ class Sequence(metaclass=abc.ABCMeta):
25
+ """Interface for a sequence of records."""
26
+
27
+ def __init__(
28
+ self,
29
+ serializer: Optional[Callable[[Any], Union[bytes, str]]] = None,
30
+ deserializer: Optional[Callable[[Union[bytes, str]], Any]] = None
31
+ ):
32
+ self._serializer = serializer
33
+ self._deserializer = deserializer
34
+
35
+ def add(self, record: Any) -> None:
36
+ """Adds a record to the reader."""
37
+ if self._serializer:
38
+ record = self._serializer(record)
39
+ if not isinstance(record, (str, bytes)):
40
+ raise ValueError(
41
+ f'Cannot write record with type {type(record)}. '
42
+ 'Did you forget to pass a serializer?'
43
+ )
44
+ self._add(record)
45
+
46
+ @abc.abstractmethod
47
+ def _add(self, record: Union[bytes, str]) -> None:
48
+ """Adds a raw record to the reader."""
49
+
50
+ @abc.abstractmethod
51
+ def __len__(self):
52
+ """Gets the number of records in the reader."""
53
+
54
+ def __iter__(self) -> Iterator[Any]:
55
+ """Iterates over the records in the reader."""
56
+ for record in self._iter():
57
+ if self._deserializer:
58
+ yield self._deserializer(record)
59
+ else:
60
+ yield record
61
+
62
+ @abc.abstractmethod
63
+ def _iter(self) -> Iterator[Union[bytes, str]]:
64
+ """Iterates over the raw records in the reader."""
65
+
66
+ @abc.abstractmethod
67
+ def close(self) -> None:
68
+ """Closes the reader."""
69
+
70
+ @abc.abstractmethod
71
+ def flush(self) -> None:
72
+ """Flushes the read records to the storage."""
73
+
74
+ def __enter__(self):
75
+ return self
76
+
77
+ def __exit__(self, exc_type, exc_value, traceback):
78
+ del exc_type, exc_value, traceback
79
+ self.close()
80
+
81
+
82
+ class SequenceIO(metaclass=abc.ABCMeta):
83
+ """Interface for a record IO system."""
84
+
85
+ @abc.abstractmethod
86
+ def open(
87
+ self,
88
+ path: Union[str, os.PathLike[str]],
89
+ mode: str,
90
+ *,
91
+ serializer: Optional[Callable[[Any], Union[bytes, str]]],
92
+ deserializer: Optional[Callable[[Union[bytes, str]], Any]],
93
+ **kwargs
94
+ ) -> Sequence:
95
+ """Opens a sequence for reading or writing."""
96
+
97
+
98
+ class _SequenceIORegistry(object):
99
+ """Registry for record IO systems."""
100
+
101
+ def __init__(self):
102
+ self._registry = {}
103
+
104
+ def add(self, extension: str, sequence_io: SequenceIO) -> None:
105
+ """Adds a record IO system with a prefix."""
106
+ self._registry[extension] = sequence_io
107
+
108
+ def get(self, path: Union[str, os.PathLike[str]]) -> SequenceIO:
109
+ """Gets the record IO system for a path."""
110
+ path = file_system.resolve_path(path)
111
+ parts = path.split('.')
112
+ if parts:
113
+ extension = parts[-1].lower()
114
+ if '@' in extension:
115
+ extension = extension.split('@')[0]
116
+ if extension in self._registry:
117
+ return self._registry[extension]
118
+ return LineSequenceIO()
119
+
120
+
121
+ _registry = _SequenceIORegistry()
122
+
123
+
124
+ def add_sequence_io(extension: str, sequence_io: SequenceIO) -> None:
125
+ """Adds a record IO system with a prefix."""
126
+ _registry.add(extension, sequence_io)
127
+
128
+
129
+ def open_sequence(
130
+ path: Union[str, os.PathLike[str]],
131
+ mode: str = 'r',
132
+ *,
133
+ serializer: Optional[
134
+ Callable[[Any], Union[bytes, str]]
135
+ ] = None,
136
+ deserializer: Optional[
137
+ Callable[[Union[bytes, str]], Any]
138
+ ] = None,
139
+ make_dirs_if_not_exist: bool = True,
140
+ ) -> Sequence:
141
+ """Open sequence for reading or writing.
142
+
143
+ Args:
144
+ path: The path to the sequence.
145
+ mode: The mode of the sequence.
146
+ serializer: (Optional) A serializer function for converting a structured
147
+ object to a string or bytes.
148
+ deserializer: (Optional) A deserializer function for converting a string or
149
+ bytes to a structured object.
150
+ make_dirs_if_not_exist: (Optional) Whether to create the directories
151
+ if they do not exist. Applicable when opening in write or append mode.
152
+
153
+ Returns:
154
+ A sequence for reading or writing.
155
+ """
156
+ if 'w' in mode or 'a' in mode:
157
+ parent_dir = os.path.dirname(path)
158
+ if make_dirs_if_not_exist:
159
+ file_system.mkdirs(parent_dir, exist_ok=True)
160
+ return _registry.get(path).open(
161
+ path, mode, serializer=serializer, deserializer=deserializer
162
+ )
163
+
164
+
165
+ class MemorySequence(Sequence):
166
+ """An in-memory sequence."""
167
+
168
+ def __init__(
169
+ self,
170
+ path: str,
171
+ mode: str,
172
+ records: list[Union[str, bytes]],
173
+ serializer: Optional[Callable[[Any], Union[bytes, str]]],
174
+ deserializer: Optional[Callable[[Union[bytes, str]], Any]]
175
+ ):
176
+ super().__init__(serializer, deserializer)
177
+ self._path = path
178
+ self._mode = mode
179
+ self._records = records
180
+ self._closed = False
181
+
182
+ def _add(self, record: Union[str, bytes]) -> None:
183
+ if 'w' not in self._mode and 'a' not in self._mode:
184
+ raise ValueError(
185
+ f'Cannot write record {record!r} to memory sequence {self._path!r} '
186
+ f'with mode {self._mode!r}.'
187
+ )
188
+ if self._closed:
189
+ raise ValueError(
190
+ f'Cannot write record {record!r} to a closed writer for '
191
+ f'{self._path!r}.'
192
+ )
193
+ self._records.append(record)
194
+
195
+ def __len__(self):
196
+ return len(self._records)
197
+
198
+ def _iter(self):
199
+ if 'r' not in self._mode:
200
+ raise ValueError(
201
+ f'Cannot read memory sequence {self._path!r} with '
202
+ f'mode {self._mode!r}.'
203
+ )
204
+ if self._closed:
205
+ raise ValueError(
206
+ f'Cannot iterate over a closed sequence reader {self._path!r}.'
207
+ )
208
+ return iter(self._records)
209
+
210
+ def flush(self):
211
+ pass
212
+
213
+ def close(self) -> None:
214
+ self._closed = True
215
+
216
+
217
+ class MemorySequenceIO(SequenceIO):
218
+ """Memory-based record IO."""
219
+
220
+ def __init__(self):
221
+ super().__init__()
222
+ self._root = collections.defaultdict(list)
223
+
224
+ def open(
225
+ self,
226
+ path: Union[str, os.PathLike[str]],
227
+ mode: str,
228
+ *,
229
+ serializer: Optional[Callable[[Any], Union[bytes, str]]],
230
+ deserializer: Optional[Callable[[Union[bytes, str]], Any]],
231
+ **kwargs
232
+ ) -> Sequence:
233
+ """Opens a reader for a sequence."""
234
+ del kwargs
235
+ if 'w' in mode:
236
+ self._root[path] = []
237
+ return MemorySequence(
238
+ path, mode, self._root[path],
239
+ serializer=serializer, deserializer=deserializer
240
+ )
241
+
242
+
243
+ add_sequence_io('mem', MemorySequenceIO())
244
+
245
+
246
+ class LineSequence(Sequence):
247
+ """A new-line broken sequence."""
248
+
249
+ def __init__(
250
+ self,
251
+ file: file_system.File,
252
+ serializer: Optional[Callable[[Any], Union[bytes, str]]],
253
+ deserializer: Optional[Callable[[Union[bytes, str]], Any]],
254
+ ) -> None:
255
+ super().__init__(serializer, deserializer)
256
+ self._file = file
257
+
258
+ def __len__(self):
259
+ raise NotImplementedError(
260
+ '__len__ is not supported for LineSequence. '
261
+ 'Use `len(list(iter(sequence)))` instead.'
262
+ )
263
+
264
+ def _iter(self):
265
+ while True:
266
+ line = self._file.readline()
267
+ if not line:
268
+ break
269
+ yield line.rstrip('\n')
270
+
271
+ def _add(self, record: Union[str, bytes]) -> None:
272
+ self._file.write(record.rstrip('\n'))
273
+ self._file.write('\n')
274
+
275
+ def flush(self) -> None:
276
+ self._file.flush()
277
+
278
+ def close(self) -> None:
279
+ self._file.close()
280
+
281
+
282
+ class LineSequenceIO(SequenceIO):
283
+ """Line-based record IO."""
284
+
285
+ def open(
286
+ self,
287
+ path: Union[str, os.PathLike[str]],
288
+ mode: str,
289
+ *,
290
+ serializer: Optional[Callable[[Any], Union[bytes, str]]],
291
+ deserializer: Optional[Callable[[Union[bytes, str]], Any]],
292
+ **kwargs
293
+ ) -> Sequence:
294
+ """Opens a reader for a sequence."""
295
+ del kwargs
296
+ return LineSequence(
297
+ file_system.open(path, mode), serializer, deserializer
298
+ )
299
+
@@ -0,0 +1,124 @@
1
+ # Copyright 2024 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
+
15
+ import os
16
+ import tempfile
17
+ import unittest
18
+ from pyglove.core.io import sequence as sequence_io
19
+ # We need to import this module to register the default parser/serializer.
20
+ import pyglove.core.symbolic as pg_symbolic
21
+
22
+
23
+ class LineSequenceIOTest(unittest.TestCase):
24
+
25
+ def test_read_write(self):
26
+ tmp_dir = tempfile.gettempdir()
27
+ file1 = os.path.join(tmp_dir, 'abc', 'file1')
28
+ with pg_symbolic.open_jsonl(file1, 'w') as f:
29
+ self.assertIsInstance(f, sequence_io.LineSequence)
30
+ f.add(1)
31
+ f.add(' foo')
32
+ f.add(' bar ')
33
+ f.flush()
34
+ self.assertTrue(os.path.exists(file1))
35
+
36
+ f.add('baz\n')
37
+ f.add(dict(x=1))
38
+
39
+ self.assertTrue(os.path.exists(file1))
40
+ with pg_symbolic.open_jsonl(file1, 'r') as f:
41
+ self.assertIsInstance(f, sequence_io.LineSequence)
42
+ self.assertEqual(list(iter(f)), [1, ' foo', ' bar ', 'baz\n', dict(x=1)])
43
+
44
+ with pg_symbolic.open_jsonl(file1, 'a') as f:
45
+ f.add('qux')
46
+
47
+ with pg_symbolic.open_jsonl(file1, 'r') as f:
48
+ with self.assertRaisesRegex(
49
+ NotImplementedError, '__len__ is not supported'
50
+ ):
51
+ _ = len(f)
52
+ self.assertEqual(
53
+ list(iter(f)), [1, ' foo', ' bar ', 'baz\n', dict(x=1), 'qux']
54
+ )
55
+
56
+ def test_read_write_with_raw_texts(self):
57
+ tmp_dir = tempfile.gettempdir()
58
+ file2 = os.path.join(tmp_dir, 'file2')
59
+ with sequence_io.open_sequence(file2, 'w') as f:
60
+ self.assertIsInstance(f, sequence_io.LineSequence)
61
+ with self.assertRaisesRegex(
62
+ ValueError, 'Cannot write record with type'
63
+ ):
64
+ f.add(1)
65
+ f.add('foo\nbar\n')
66
+
67
+ with sequence_io.open_sequence(file2, 'r') as f:
68
+ with self.assertRaisesRegex(
69
+ ValueError, 'not writable'
70
+ ):
71
+ f.add('baz')
72
+ self.assertEqual(list(iter(f)), ['foo', 'bar'])
73
+
74
+
75
+ class MemorySequenceIOTest(unittest.TestCase):
76
+
77
+ def test_read_write(self):
78
+ with sequence_io.open_sequence('/file1.mem@123', 'w') as f:
79
+ self.assertIsInstance(f, sequence_io.MemorySequence)
80
+ f.add(' foo')
81
+ f.add(' bar ')
82
+ f.flush()
83
+ f.add('baz')
84
+ with self.assertRaisesRegex(
85
+ ValueError, 'Cannot write record with type'
86
+ ):
87
+ f.add(1)
88
+ with self.assertRaisesRegex(
89
+ ValueError, 'Cannot read memory sequence'
90
+ ):
91
+ next(iter(f))
92
+
93
+ with self.assertRaisesRegex(
94
+ ValueError, 'Cannot write record .* to a closed writer'
95
+ ):
96
+ f.add('qux')
97
+
98
+ with sequence_io.open_sequence('/file1.mem@123', 'a') as f:
99
+ self.assertIsInstance(f, sequence_io.MemorySequence)
100
+ f.add('qux')
101
+
102
+ with sequence_io.open_sequence('/file1.mem@123') as f:
103
+ self.assertIsInstance(f, sequence_io.MemorySequence)
104
+ self.assertEqual(len(f), 4)
105
+ self.assertEqual(list(f), [' foo', ' bar ', 'baz', 'qux'])
106
+ with self.assertRaisesRegex(
107
+ ValueError, 'Cannot write record .* to memory sequence'
108
+ ):
109
+ f.add('abc')
110
+
111
+ with self.assertRaisesRegex(
112
+ ValueError, 'Cannot iterate over a closed sequence reader'
113
+ ):
114
+ next(iter(f))
115
+
116
+ with sequence_io.open_sequence('/file1.mem@123', 'w') as f:
117
+ f.add('abc')
118
+
119
+ with sequence_io.open_sequence('/file1.mem@123', 'r') as f:
120
+ self.assertEqual(list(iter(f)), ['abc'])
121
+
122
+
123
+ if __name__ == '__main__':
124
+ unittest.main()
@@ -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_utils."""
15
-
16
14
  import io
17
15
  import logging
18
16
  import unittest
@@ -14,8 +14,8 @@
14
14
  """Object factory based on patchers."""
15
15
 
16
16
  from typing import Any, Callable, Dict, Optional, Type, Union
17
- from pyglove.core import object_utils
18
17
  from pyglove.core import symbolic
18
+ from pyglove.core import utils
19
19
  from pyglove.core.patching import rule_based
20
20
 
21
21
 
@@ -88,7 +88,7 @@ def ObjectFactory( # pylint: disable=invalid-name
88
88
  # Step 3: Patch with additional parameter override dict if available.
89
89
  if params_override:
90
90
  value = value.rebind(
91
- object_utils.flatten(from_maybe_serialized(params_override, dict)),
92
- raise_on_no_change=False)
91
+ utils.flatten(from_maybe_serialized(params_override, dict)),
92
+ raise_on_no_change=False,
93
+ )
93
94
  return value
94
-
@@ -15,8 +15,8 @@
15
15
 
16
16
  import re
17
17
  from typing import Any, Callable, Optional, Tuple, Type, Union
18
- from pyglove.core import object_utils
19
18
  from pyglove.core import symbolic
19
+ from pyglove.core import utils
20
20
 
21
21
 
22
22
  def patch_on_key(
@@ -214,11 +214,11 @@ def patch_on_member(
214
214
 
215
215
  def _conditional_patch(
216
216
  src: symbolic.Symbolic,
217
- condition: Callable[
218
- [object_utils.KeyPath, Any, symbolic.Symbolic], bool],
217
+ condition: Callable[[utils.KeyPath, Any, symbolic.Symbolic], bool],
219
218
  value: Any = None,
220
219
  value_fn: Optional[Callable[[Any], Any]] = None,
221
- skip_notification: Optional[bool] = None) -> Any:
220
+ skip_notification: Optional[bool] = None,
221
+ ) -> Any:
222
222
  """Recursive patch values on condition.
223
223
 
224
224
  Args:
@@ -16,9 +16,9 @@
16
16
  import re
17
17
  import typing
18
18
  from typing import Any, Callable, Dict, List, Optional, Tuple, Type, Union
19
- from pyglove.core import object_utils
20
19
  from pyglove.core import symbolic
21
20
  from pyglove.core import typing as pg_typing
21
+ from pyglove.core import utils
22
22
 
23
23
 
24
24
  class Patcher(symbolic.Functor):
@@ -181,7 +181,9 @@ _PATCHER_REGISTRY = _PatcherRegistry()
181
181
 
182
182
  def patcher(
183
183
  args: Optional[List[Tuple[str, pg_typing.ValueSpec]]] = None,
184
- name: Optional[str] = None) -> Any:
184
+ name: Optional[str] = None,
185
+ auto_typing: bool = False,
186
+ auto_doc: bool = False) -> Any:
185
187
  """Decorate a function into a Patcher and register it.
186
188
 
187
189
  A patcher function is defined as:
@@ -209,11 +211,20 @@ def patcher(
209
211
  args: A list of (arg_name, arg_value_spec) to schematize patcher arguments.
210
212
  name: String to be used as patcher name in URI. If None, function name will
211
213
  be used as patcher name.
214
+ auto_typing: If True, automatically inference the typing from the
215
+ function signature.
216
+ auto_doc: If True, use the docstring of the function as the docstring of
217
+ the patcher.
212
218
 
213
219
  Returns:
214
220
  A decorator that converts a function into a Patcher subclass.
215
221
  """
216
- functor_decorator = symbolic.functor(args, base_class=Patcher)
222
+ functor_decorator = symbolic.functor(
223
+ args,
224
+ base_class=Patcher,
225
+ auto_typing=auto_typing,
226
+ auto_doc=auto_doc
227
+ )
217
228
  def _decorator(fn):
218
229
  """Returns decorated Patcher class."""
219
230
  cls = functor_decorator(fn)
@@ -339,7 +350,7 @@ def from_uri(uri: str) -> Patcher:
339
350
  name, args, kwargs = parse_uri(uri)
340
351
  patcher_cls = typing.cast(Type[Any], _PATCHER_REGISTRY.get(name))
341
352
  args, kwargs = parse_args(patcher_cls.__signature__, args, kwargs)
342
- return patcher_cls(object_utils.MISSING_VALUE, *args, **kwargs)
353
+ return patcher_cls(utils.MISSING_VALUE, *args, **kwargs)
343
354
 
344
355
 
345
356
  def parse_uri(uri: str) -> Tuple[str, List[str], Dict[str, str]]:
@@ -456,7 +467,8 @@ def parse_arg(patcher_id: str, arg_name: str,
456
467
  f'{value_spec!r} cannot be used for Patcher argument.\n'
457
468
  f'Consider to treat this argument as string and parse it yourself.')
458
469
  return value_spec.apply(
459
- arg, root_path=object_utils.KeyPath.parse(f'{patcher_id}.{arg_name}'))
470
+ arg, root_path=utils.KeyPath.parse(f'{patcher_id}.{arg_name}')
471
+ )
460
472
 
461
473
 
462
474
  def parse_list(string: str,
@@ -48,6 +48,18 @@ class PatcherTest(unittest.TestCase):
48
48
  TypeError, 'The 1st argument of .* must be a symbolic type'):
49
49
  set_value1(k='a').patch(1) # pylint: disable=not-callable, no-value-for-parameter
50
50
 
51
+ def test_patcher_with_auto_typing(self):
52
+ @rule_based.patcher(auto_typing=True)
53
+ def set_value2(unused_src, k: str, v: int):
54
+ return {
55
+ k: v
56
+ }
57
+ self.assert_patch_equal(symbolic.Dict(), set_value2(k='a', v=1), {'a': 1}) # pylint: disable=no-value-for-parameter
58
+ self.assert_patch_equal(symbolic.Dict(), 'set_value2?a&1', {'a': 1})
59
+ self.assert_patch_equal(symbolic.Dict(), 'set_value2?a&v=1', {'a': 1})
60
+ self.assert_patch_equal(symbolic.Dict(), 'set_value2?k=a&v=1', {'a': 1})
61
+ self.assertIn('set_value2', rule_based.patcher_names())
62
+
51
63
  def test_patcher_with_typing(self):
52
64
  @rule_based.patcher([
53
65
  ('k', pg_typing.Str()),
@@ -375,10 +387,21 @@ class PatcherHelpersTest(unittest.TestCase):
375
387
  signature = pg_typing.Signature(
376
388
  pg_typing.CallableType.FUNCTION, 'foo', '__main__',
377
389
  [
378
- pg_typing.Argument('src', pg_typing.Any()),
379
- pg_typing.Argument('x', pg_typing.Int()),
380
- pg_typing.Argument('y', pg_typing.List(
381
- pg_typing.Float(min_value=0.0, max_value=1.0)))
390
+ pg_typing.Argument(
391
+ 'src',
392
+ pg_typing.Argument.Kind.POSITIONAL_OR_KEYWORD,
393
+ pg_typing.Any()
394
+ ),
395
+ pg_typing.Argument(
396
+ 'x',
397
+ pg_typing.Argument.Kind.POSITIONAL_OR_KEYWORD,
398
+ pg_typing.Int()
399
+ ),
400
+ pg_typing.Argument(
401
+ 'y',
402
+ pg_typing.Argument.Kind.POSITIONAL_OR_KEYWORD,
403
+ pg_typing.List(pg_typing.Float(min_value=0.0, max_value=1.0))
404
+ )
382
405
  ])
383
406
  args, kwargs = rule_based.parse_args(signature, ['0'], {'y': '0.1:0.5'})
384
407
  self.assertEqual(args, [0])
@@ -95,6 +95,7 @@ from pyglove.core.symbolic.inferred import ValueFromParentChain
95
95
  # Reference type.
96
96
  from pyglove.core.symbolic.ref import Ref
97
97
  from pyglove.core.symbolic.ref import maybe_ref
98
+ from pyglove.core.symbolic.ref import deref
98
99
 
99
100
  # Symbolic operations.
100
101
  from pyglove.core.symbolic.base import traverse
@@ -119,6 +120,7 @@ from pyglove.core.symbolic.base import to_json
119
120
  from pyglove.core.symbolic.base import to_json_str
120
121
  from pyglove.core.symbolic.base import load
121
122
  from pyglove.core.symbolic.base import save
123
+ from pyglove.core.symbolic.base import open_jsonl
122
124
 
123
125
  # Interfaces for pure symbolic objects.
124
126
  from pyglove.core.symbolic.pure_symbolic import PureSymbolic
@@ -140,11 +142,4 @@ from pyglove.core.symbolic.list import mark_as_insertion
140
142
  # Error types.
141
143
  from pyglove.core.symbolic.base import WritePermissionError
142
144
 
143
- # TODO(daiyip): internal dependencies, remove later.
144
- from pyglove.core.symbolic.schema_utils import formalize_schema
145
- from pyglove.core.symbolic.schema_utils import augment_schema
146
- from pyglove.core.symbolic.schema_utils import function_schema
147
- from pyglove.core.symbolic.schema_utils import update_schema
148
-
149
-
150
145
  # pylint: enable=g-bad-import-order