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
@@ -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()
|
pyglove/core/logging_test.py
CHANGED
@@ -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
|
-
|
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
|
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
|
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(
|
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(
|
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=
|
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(
|
379
|
-
|
380
|
-
|
381
|
-
pg_typing.
|
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
|