pyglove 0.4.5.dev202410301815__py3-none-any.whl → 0.4.5.dev202410310809__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.

Potentially problematic release.


This version of pyglove might be problematic. Click here for more details.

@@ -116,7 +116,7 @@ class FileSystem(metaclass=abc.ABCMeta):
116
116
  """Removes a directory chain based on a path."""
117
117
 
118
118
 
119
- def _resolve_path(path: Union[str, os.PathLike[str]]) -> str:
119
+ def resolve_path(path: Union[str, os.PathLike[str]]) -> str:
120
120
  if isinstance(path, str):
121
121
  return path
122
122
  elif hasattr(path, '__fspath__'):
@@ -243,7 +243,7 @@ class MemoryFileSystem(FileSystem):
243
243
  self._prefix = prefix
244
244
 
245
245
  def _internal_path(self, path: Union[str, os.PathLike[str]]) -> str:
246
- return '/' + _resolve_path(path).lstrip(self._prefix)
246
+ return '/' + resolve_path(path).lstrip(self._prefix)
247
247
 
248
248
  def _locate(self, path: Union[str, os.PathLike[str]]) -> Any:
249
249
  current = self._root
@@ -287,7 +287,7 @@ class MemoryFileSystem(FileSystem):
287
287
  def _parent_and_name(
288
288
  self, path: Union[str, os.PathLike[str]]
289
289
  ) -> tuple[dict[str, Any], str]:
290
- path = _resolve_path(path)
290
+ path = resolve_path(path)
291
291
  rpos = path.rfind('/')
292
292
  assert rpos >= 0, path
293
293
  name = path[rpos + 1:]
@@ -382,7 +382,7 @@ class _FileSystemRegistry:
382
382
 
383
383
  def get(self, path: Union[str, os.PathLike[str]]) -> FileSystem:
384
384
  """Gets the file system for a path."""
385
- path = _resolve_path(path)
385
+ path = resolve_path(path)
386
386
  for prefix, fs in self._filesystems:
387
387
  if path.startswith(prefix):
388
388
  return fs
@@ -107,7 +107,7 @@ class _SequenceIORegistry(object):
107
107
 
108
108
  def get(self, path: Union[str, os.PathLike[str]]) -> SequenceIO:
109
109
  """Gets the record IO system for a path."""
110
- path = _resolve_path(path)
110
+ path = file_system.resolve_path(path)
111
111
  parts = path.split('.')
112
112
  if parts:
113
113
  extension = parts[-1].lower()
@@ -126,15 +126,6 @@ def add_sequence_io(extension: str, sequence_io: SequenceIO) -> None:
126
126
  _registry.add(extension, sequence_io)
127
127
 
128
128
 
129
- def _resolve_path(path: Union[str, os.PathLike[str]]) -> str:
130
- if isinstance(path, str):
131
- return path
132
- elif hasattr(path, '__fspath__'):
133
- return path.__fspath__()
134
- else:
135
- raise ValueError(f'Unsupported path: {path!r}.')
136
-
137
-
138
129
  def open_sequence(
139
130
  path: Union[str, os.PathLike[str]],
140
131
  mode: str = 'r',
@@ -145,6 +136,7 @@ def open_sequence(
145
136
  deserializer: Optional[
146
137
  Callable[[Union[bytes, str]], Any]
147
138
  ] = None,
139
+ make_dirs_if_not_exist: bool = True,
148
140
  ) -> Sequence:
149
141
  """Open sequence for reading or writing.
150
142
 
@@ -155,10 +147,16 @@ def open_sequence(
155
147
  object to a string or bytes.
156
148
  deserializer: (Optional) A deserializer function for converting a string or
157
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.
158
152
 
159
153
  Returns:
160
154
  A sequence for reading or writing.
161
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)
162
160
  return _registry.get(path).open(
163
161
  path, mode, serializer=serializer, deserializer=deserializer
164
162
  )
@@ -13,7 +13,6 @@
13
13
  # limitations under the License.
14
14
 
15
15
  import os
16
- import pathlib
17
16
  import tempfile
18
17
  import unittest
19
18
  from pyglove.core.io import sequence as sequence_io
@@ -25,7 +24,7 @@ class LineSequenceIOTest(unittest.TestCase):
25
24
 
26
25
  def test_read_write(self):
27
26
  tmp_dir = tempfile.gettempdir()
28
- file1 = os.path.join(tmp_dir, 'file1')
27
+ file1 = os.path.join(tmp_dir, 'abc', 'file1')
29
28
  with pg_symbolic.open_jsonl(file1, 'w') as f:
30
29
  self.assertIsInstance(f, sequence_io.LineSequence)
31
30
  f.add(1)
@@ -73,19 +72,7 @@ class LineSequenceIOTest(unittest.TestCase):
73
72
  self.assertEqual(list(iter(f)), ['foo', 'bar'])
74
73
 
75
74
 
76
- class MemoryRecordIOIOTest(unittest.TestCase):
77
-
78
- def test_resolve_path(self):
79
- self.assertEqual(
80
- sequence_io._resolve_path('/file1.mem@123'),
81
- '/file1.mem@123'
82
- )
83
- self.assertEqual(
84
- sequence_io._resolve_path(pathlib.Path('/file1.mem@123.txt')),
85
- '/file1.mem@123.txt'
86
- )
87
- with self.assertRaisesRegex(ValueError, 'Unsupported path'):
88
- sequence_io._resolve_path(1)
75
+ class MemorySequenceIOTest(unittest.TestCase):
89
76
 
90
77
  def test_read_write(self):
91
78
  with sequence_io.open_sequence('/file1.mem@123', 'w') as f:
@@ -30,19 +30,19 @@ class ErrorInfo(json_conversion.JSONConvertible, formatting.Formattable):
30
30
  """Serializable error information.
31
31
 
32
32
  Attributes:
33
- type: The type path of the error. For example,
33
+ tag: A path of the error types in the exception chain. For example,
34
34
  `ValueError.ZeroDivisionError` means the error is a `ZeroDivisionError`
35
35
  raised at the first place and then reraised as a `ValueError`.
36
36
  description: The description of the error.
37
37
  stacktrace: The stacktrace of the error.
38
38
  """
39
39
 
40
- type: str
40
+ tag: str
41
41
  description: str
42
42
  stacktrace: str
43
43
 
44
44
  @classmethod
45
- def _get_type(cls, error: BaseException):
45
+ def _compute_tag(cls, error: BaseException):
46
46
  error_types = []
47
47
  while error is not None:
48
48
  error_types.append(error.__class__.__name__)
@@ -53,7 +53,7 @@ class ErrorInfo(json_conversion.JSONConvertible, formatting.Formattable):
53
53
  def from_exception(cls, error: BaseException) -> 'ErrorInfo':
54
54
  """Creates an error info from an exception."""
55
55
  return cls(
56
- type=cls._get_type(error),
56
+ tag=cls._compute_tag(error),
57
57
  description=str(error),
58
58
  stacktrace=''.join(
59
59
  traceback.format_exception(*sys.exc_info())
@@ -63,7 +63,7 @@ class ErrorInfo(json_conversion.JSONConvertible, formatting.Formattable):
63
63
  def to_json(self, **kwargs) -> Dict[str, Any]:
64
64
  return self.to_json_dict(
65
65
  fields=dict(
66
- type=(self.type, None),
66
+ tag=(self.tag, None),
67
67
  description=(self.description, None),
68
68
  stacktrace=(self.stacktrace, None),
69
69
  ),
@@ -74,7 +74,7 @@ class ErrorInfo(json_conversion.JSONConvertible, formatting.Formattable):
74
74
  def format(self, *args, **kwargs) -> str:
75
75
  return formatting.kvlist_str(
76
76
  [
77
- ('type', self.type, None),
77
+ ('tag', self.tag, None),
78
78
  ('description', self.description, None),
79
79
  ('stacktrace', self.stacktrace, None),
80
80
  ],
@@ -36,13 +36,13 @@ class ErrorInfoTest(unittest.TestCase):
36
36
  except ValueError as e:
37
37
  error_info = error_utils.ErrorInfo.from_exception(e)
38
38
  self.assertIsNotNone(error_info)
39
- self.assertEqual(error_info.type, 'ValueError.ZeroDivisionError')
39
+ self.assertEqual(error_info.tag, 'ValueError.ZeroDivisionError')
40
40
  self.assertEqual(error_info.description, 'Bad call to `foo`')
41
41
  self.assertIn('Traceback (most recent call last)', error_info.stacktrace)
42
42
 
43
43
  def test_to_json(self):
44
44
  error_info = error_utils.ErrorInfo(
45
- type='ValueError.ZeroDivisionError',
45
+ tag='ValueError.ZeroDivisionError',
46
46
  description='Bad call to `foo`',
47
47
  stacktrace='Traceback (most recent call last)',
48
48
  )
@@ -53,7 +53,7 @@ class ErrorInfoTest(unittest.TestCase):
53
53
 
54
54
  def test_format(self):
55
55
  error_info = error_utils.ErrorInfo(
56
- type='ValueError.ZeroDivisionError',
56
+ tag='ValueError.ZeroDivisionError',
57
57
  description='Bad call to `foo`',
58
58
  stacktrace='Traceback (most recent call last)',
59
59
  )
@@ -62,7 +62,7 @@ class ErrorInfoTest(unittest.TestCase):
62
62
  inspect.cleandoc(
63
63
  """
64
64
  ErrorInfo(
65
- type='ValueError.ZeroDivisionError',
65
+ tag='ValueError.ZeroDivisionError',
66
66
  description='Bad call to `foo`',
67
67
  stacktrace='Traceback (most recent call last)'
68
68
  )
@@ -13,10 +13,12 @@
13
13
  # limitations under the License.
14
14
  """Utilities for timing."""
15
15
 
16
+ import collections
16
17
  import dataclasses
17
18
  import time
18
19
  from typing import Any, Dict, List, Optional
19
20
 
21
+ from pyglove.core.object_utils import error_utils
20
22
  from pyglove.core.object_utils import json_conversion
21
23
  from pyglove.core.object_utils import thread_local
22
24
 
@@ -30,7 +32,7 @@ class TimeIt:
30
32
  name: str
31
33
  elapse: float = 0.0
32
34
  has_ended: bool = True
33
- error: Optional[Exception] = None
35
+ error: Optional[error_utils.ErrorInfo] = None
34
36
 
35
37
  @property
36
38
  def has_started(self) -> bool:
@@ -66,6 +68,9 @@ class TimeIt:
66
68
  num_ended: int = 0
67
69
  num_failed: int = 0
68
70
  avg_duration: float = 0.0
71
+ error_tags: Dict[str, int] = dataclasses.field(
72
+ default_factory=lambda: collections.defaultdict(int)
73
+ )
69
74
 
70
75
  def update(self, status: 'TimeIt.Status'):
71
76
  self.avg_duration = (
@@ -77,6 +82,8 @@ class TimeIt:
77
82
  self.num_ended += 1
78
83
  if status.has_error:
79
84
  self.num_failed += 1
85
+ assert status.error is not None
86
+ self.error_tags[status.error.tag] += 1
80
87
 
81
88
  def to_json(self, **kwargs) -> Dict[str, Any]:
82
89
  return self.to_json_dict(
@@ -85,6 +92,7 @@ class TimeIt:
85
92
  num_ended=(self.num_ended, 0),
86
93
  num_failed=(self.num_failed, 0),
87
94
  avg_duration=(self.avg_duration, 0.0),
95
+ error_tags=(self.error_tags, {}),
88
96
  ),
89
97
  exclude_default=True,
90
98
  **kwargs,
@@ -113,13 +121,13 @@ class TimeIt:
113
121
  **kwargs,
114
122
  )
115
123
 
116
- def __init__(self, name: str):
117
- self._name = name
118
- self._start_time = None
119
- self._end_time = None
120
- self._child_contexts = {}
121
- self._error = None
122
- self._parent = None
124
+ def __init__(self, name: str = ''):
125
+ self._name: str = name
126
+ self._start_time: Optional[float] = None
127
+ self._end_time: Optional[float] = None
128
+ self._child_contexts: Dict[str, TimeIt] = {}
129
+ self._error: Optional[error_utils.ErrorInfo] = None
130
+ self._parent: Optional[TimeIt] = None
123
131
 
124
132
  @property
125
133
  def name(self) -> str:
@@ -145,7 +153,9 @@ class TimeIt:
145
153
  """Ends timing."""
146
154
  if not self.has_ended:
147
155
  self._end_time = time.time()
148
- self._error = error
156
+ self._error = (
157
+ None if error is None else error_utils.ErrorInfo.from_exception(error)
158
+ )
149
159
  return True
150
160
  return False
151
161
 
@@ -170,7 +180,7 @@ class TimeIt:
170
180
  return self._end_time
171
181
 
172
182
  @property
173
- def error(self) -> Optional[BaseException]:
183
+ def error(self) -> Optional[error_utils.ErrorInfo]:
174
184
  """Returns error."""
175
185
  return self._error
176
186
 
@@ -199,7 +209,8 @@ class TimeIt:
199
209
  for child in self._child_contexts.values():
200
210
  child_result = child.status()
201
211
  for k, v in child_result.items():
202
- result[f'{self.name}.{k}'] = v
212
+ key = f'{self.name}.{k}' if self.name else k
213
+ result[key] = v
203
214
  return result
204
215
 
205
216
  def __enter__(self):
@@ -220,6 +231,6 @@ class TimeIt:
220
231
  thread_local.thread_local_set('__timing_context__', self._parent)
221
232
 
222
233
 
223
- def timeit(name: str) -> TimeIt:
234
+ def timeit(name: str = '') -> TimeIt:
224
235
  """Context manager to time a block of code."""
225
236
  return TimeIt(name)
@@ -100,8 +100,8 @@ class TimeItTest(unittest.TestCase):
100
100
  r = t.status()
101
101
  self.assertTrue(r['node'].has_error)
102
102
  self.assertTrue(t.has_error)
103
- self.assertIsInstance(t.error, ValueError)
104
- self.assertIsInstance(r['node'].error, ValueError)
103
+ self.assertTrue(t.error.tag.startswith('ValueError'))
104
+ self.assertTrue(r['node'].error.tag.startswith('ValueError'))
105
105
  self.assertTrue(r['node.child'].has_error)
106
106
  self.assertTrue(t1.has_error)
107
107
  self.assertTrue(r['node.child.grandchild'].has_error)
@@ -111,7 +111,7 @@ class TimeItTest(unittest.TestCase):
111
111
  summary = timing.TimeIt.StatusSummary()
112
112
  self.assertFalse(summary)
113
113
  for i in range(10):
114
- with timing.timeit('node') as t:
114
+ with timing.timeit() as t:
115
115
  time.sleep(0.1)
116
116
  with timing.timeit('child'):
117
117
  time.sleep(0.1)
@@ -126,7 +126,7 @@ class TimeItTest(unittest.TestCase):
126
126
  self.assertTrue(summary)
127
127
  self.assertEqual(
128
128
  list(summary.breakdown.keys()),
129
- ['node', 'node.child', 'node.child.grandchild']
129
+ ['', 'child', 'child.grandchild']
130
130
  )
131
131
  self.assertEqual(
132
132
  [x.num_started for x in summary.breakdown.values()],
@@ -140,6 +140,10 @@ class TimeItTest(unittest.TestCase):
140
140
  [x.num_failed for x in summary.breakdown.values()],
141
141
  [0, 0, 2]
142
142
  )
143
+ self.assertEqual(
144
+ summary.breakdown['child.grandchild'].error_tags,
145
+ {'ValueError': 2},
146
+ )
143
147
  # Test serialization.
144
148
  json_dict = summary.to_json()
145
149
  summary2 = timing.TimeIt.StatusSummary.from_json(json_dict)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pyglove
3
- Version: 0.4.5.dev202410301815
3
+ Version: 0.4.5.dev202410310809
4
4
  Summary: PyGlove: A library for manipulating Python objects.
5
5
  Home-page: https://github.com/google/pyglove
6
6
  Author: PyGlove Authors
@@ -43,10 +43,10 @@ pyglove/core/hyper/numerical_test.py,sha256=_xZe0qe3wJdrQL4F8pgt0zf04zpbc_9c_5va
43
43
  pyglove/core/hyper/object_template.py,sha256=x6aBe-6ueo3CsFJ2yhbXbcdJKfId6wm_KsgkHHbpSoY,22325
44
44
  pyglove/core/hyper/object_template_test.py,sha256=TEFX7LIqUvdCdJILnK_gP5xIgNJKzRnioUF0CGVBzcY,9105
45
45
  pyglove/core/io/__init__.py,sha256=4ZT1a595DqQuLTNYc2JP_eCp_KesXvHmKRkr777bzpg,785
46
- pyglove/core/io/file_system.py,sha256=KR1riWNBVZpkgIcqPUhR5xqjCN5Lhp937x7Ubjpv-jE,13283
46
+ pyglove/core/io/file_system.py,sha256=ZfeQeZ1HF1WjV1REC9wCaut5Wkdu_FjxPzulUju7Z_E,13279
47
47
  pyglove/core/io/file_system_test.py,sha256=FX0ySuh_Xcg1RO68do7ikD4pvslKUzfSpwZ6P4wIP7c,8691
48
- pyglove/core/io/sequence.py,sha256=Kir8JM8lxjrlKr1gs3hvhCB8F08PPz4K8-aDI_US9Vo,8042
49
- pyglove/core/io/sequence_test.py,sha256=iqDR-MPm64VbUPKFYocVKH91Y6Z6tP1TZB0PzCn92tE,4265
48
+ pyglove/core/io/sequence.py,sha256=7QWMGXPtJzHyGPgqkT3yJ01FxKJ4mP4lF5HRDiIHNbQ,8165
49
+ pyglove/core/io/sequence_test.py,sha256=6tmnS7frBuDR8ussT5jugeh23TDsPDrDnbfxHh81Gy4,3891
50
50
  pyglove/core/object_utils/__init__.py,sha256=bFyt-_uNzpOjJGDfYFTRP1kV6rL4du9xGx_OaFMcIxI,8758
51
51
  pyglove/core/object_utils/codegen.py,sha256=HQlpjz42oh_2lrGSflwuAcEneE9Bv8pgPahQzvJm1KI,1568
52
52
  pyglove/core/object_utils/codegen_test.py,sha256=ThPVyE805plNvM8vwlDxMrcjV-GGdDgbBLSqSOpCvJ0,2166
@@ -54,8 +54,8 @@ pyglove/core/object_utils/common_traits.py,sha256=9wfWvcq3uuVgL8mQexgJmWdF2aaQ-w
54
54
  pyglove/core/object_utils/common_traits_test.py,sha256=wIeVsF3kon9K0Kbblcaib9hBJeZ76LyGZDD-5A1BD9w,1182
55
55
  pyglove/core/object_utils/docstr_utils.py,sha256=5BY40kXozPKVGOB0eN8jy1P5_GHIzqFJ9FXAu_kzxaw,5119
56
56
  pyglove/core/object_utils/docstr_utils_test.py,sha256=1NIsXXpp87HGC8LB7vx1KEMVVrmeGi733Xpuf6yTUq0,4287
57
- pyglove/core/object_utils/error_utils.py,sha256=wnGX_aR4TdbCylvUOh-x6PPYzGSJBnfyX9MANpk03u8,5780
58
- pyglove/core/object_utils/error_utils_test.py,sha256=vlFkR41jNLzUnwqF6IXy7HmXhpLlwOvUvlVMoMSOitk,4175
57
+ pyglove/core/object_utils/error_utils.py,sha256=5mcD61La1tTYmmKodsWfnt8R-yV3yfBfbW4o5rZzCTg,5801
58
+ pyglove/core/object_utils/error_utils_test.py,sha256=rnX4HYsZaMphWPm4ppgzCQ49LAXfJJ9KDTiDz4f9Ahc,4171
59
59
  pyglove/core/object_utils/formatting.py,sha256=cXGWJd6zleS9QiEfY3w4fqWfFYgi_QF8S2IapTOzAj8,14994
60
60
  pyglove/core/object_utils/formatting_test.py,sha256=QGbpUS8u70NoF8-3v5IYmPWEsML3ZbIwkQpUMwQdTkQ,13589
61
61
  pyglove/core/object_utils/hierarchical.py,sha256=za9sA6XnlUOKUHMKNwQPNSOK9E3wDqXFQB0AND2Yghw,19754
@@ -66,8 +66,8 @@ pyglove/core/object_utils/missing.py,sha256=0liMs9iEyQxxu6UohdJ5hEM246e9Nu05qp0h
66
66
  pyglove/core/object_utils/missing_test.py,sha256=B36p-vqUvAnXWMszAj9GOPBN0_8cq7vVF61AkcsZ9qU,1396
67
67
  pyglove/core/object_utils/thread_local.py,sha256=i-CnyY3VREtLfAj4_JndBnsKuQLIgwG29ma8dAyRxbI,4839
68
68
  pyglove/core/object_utils/thread_local_test.py,sha256=EvU1-TF7KqpLQxxBvHd7dxtuY22YUQSIwQ0UcR-NORA,6816
69
- pyglove/core/object_utils/timing.py,sha256=PeSN59g8vQDpenPgsWf4oYP45c0UDSpnFYDKhrtcSBc,6665
70
- pyglove/core/object_utils/timing_test.py,sha256=Ye4qBrNl5ix2C_K-qT5TxKHui4Kvwmea3uUTx8t26qk,4828
69
+ pyglove/core/object_utils/timing.py,sha256=ihOdcZaJo96wpe4U-bXEu-H1PZMWeWOaU-nSW9DFStY,7262
70
+ pyglove/core/object_utils/timing_test.py,sha256=jym-jV55kRujhgoxmDUUIXuqRLDnBGYiNKN3lUMbnfg,4943
71
71
  pyglove/core/object_utils/value_location.py,sha256=lSFQNTazY2M6_nRLmMbouqZAcZSiOLZQnmQPMD2FDMs,26770
72
72
  pyglove/core/object_utils/value_location_test.py,sha256=wCZYqWf_tq3l4ZM6dnkAs1obmq8LfYawkns2NKos9kk,21406
73
73
  pyglove/core/patching/__init__.py,sha256=C1Q1cWPV74YL3eXbzGvc-8aPw1DR8EK6lRhQYDCwHek,2059
@@ -186,8 +186,8 @@ pyglove/ext/scalars/randoms.py,sha256=LkMIIx7lOq_lvJvVS3BrgWGuWl7Pi91-lA-O8x_gZs
186
186
  pyglove/ext/scalars/randoms_test.py,sha256=nEhiqarg8l_5EOucp59CYrpO2uKxS1pe0hmBdZUzRNM,2000
187
187
  pyglove/ext/scalars/step_wise.py,sha256=IDw3tuTpv0KVh7AN44W43zqm1-E0HWPUlytWOQC9w3Y,3789
188
188
  pyglove/ext/scalars/step_wise_test.py,sha256=TL1vJ19xVx2t5HKuyIzGoogF7N3Rm8YhLE6JF7i0iy8,2540
189
- pyglove-0.4.5.dev202410301815.dist-info/LICENSE,sha256=WNHhf_5RCaeuKWyq_K39vmp9F28LxKsB4SpomwSZ2L0,11357
190
- pyglove-0.4.5.dev202410301815.dist-info/METADATA,sha256=jIeDOiGyeIig84equ3ZG2EjO4s1his-CfD1PjMETKpU,6666
191
- pyglove-0.4.5.dev202410301815.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
192
- pyglove-0.4.5.dev202410301815.dist-info/top_level.txt,sha256=wITzJSKcj8GZUkbq-MvUQnFadkiuAv_qv5qQMw0fIow,8
193
- pyglove-0.4.5.dev202410301815.dist-info/RECORD,,
189
+ pyglove-0.4.5.dev202410310809.dist-info/LICENSE,sha256=WNHhf_5RCaeuKWyq_K39vmp9F28LxKsB4SpomwSZ2L0,11357
190
+ pyglove-0.4.5.dev202410310809.dist-info/METADATA,sha256=HtMHbHQS4WSDH7njjSK3TS_F8CDrrhyGpPFI5v2Jphs,6666
191
+ pyglove-0.4.5.dev202410310809.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
192
+ pyglove-0.4.5.dev202410310809.dist-info/top_level.txt,sha256=wITzJSKcj8GZUkbq-MvUQnFadkiuAv_qv5qQMw0fIow,8
193
+ pyglove-0.4.5.dev202410310809.dist-info/RECORD,,