polystore 0.1.2__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.
- polystore/__init__.py +80 -0
- polystore/atomic.py +249 -0
- polystore/backend_registry.py +179 -0
- polystore/base.py +318 -0
- polystore/disk.py +783 -0
- polystore/exceptions.py +55 -0
- polystore/filemanager.py +770 -0
- polystore/formats.py +79 -0
- polystore/lazy_imports.py +127 -0
- polystore/memory.py +654 -0
- polystore/registry.py +9 -0
- polystore/streaming.py +241 -0
- polystore/utils.py +42 -0
- polystore/zarr.py +1266 -0
- polystore-0.1.2.dist-info/METADATA +249 -0
- polystore-0.1.2.dist-info/RECORD +19 -0
- polystore-0.1.2.dist-info/WHEEL +5 -0
- polystore-0.1.2.dist-info/licenses/LICENSE +22 -0
- polystore-0.1.2.dist-info/top_level.txt +1 -0
polystore/base.py
ADDED
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Abstract base classes for storage backends.
|
|
3
|
+
|
|
4
|
+
This module defines the fundamental interfaces for storage backends,
|
|
5
|
+
independent of specific implementations. It establishes the contract
|
|
6
|
+
that all storage backends must fulfill.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
from abc import ABC, abstractmethod
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any, Dict, List, Optional, Set, Union
|
|
13
|
+
|
|
14
|
+
from .exceptions import StorageResolutionError
|
|
15
|
+
from .registry import AutoRegisterMeta
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class DataSink(ABC):
|
|
21
|
+
"""
|
|
22
|
+
Abstract base class for data destinations.
|
|
23
|
+
|
|
24
|
+
Defines the minimal interface for sending data to any destination,
|
|
25
|
+
whether storage, streaming, or other data handling systems.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
@abstractmethod
|
|
29
|
+
def save(self, data: Any, identifier: Union[str, Path], **kwargs) -> None:
|
|
30
|
+
"""
|
|
31
|
+
Send data to the destination.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
data: The data to send
|
|
35
|
+
identifier: Unique identifier for the data (path-like for compatibility)
|
|
36
|
+
**kwargs: Backend-specific arguments
|
|
37
|
+
|
|
38
|
+
Raises:
|
|
39
|
+
TypeError: If identifier is not a valid type
|
|
40
|
+
ValueError: If data cannot be sent to destination
|
|
41
|
+
"""
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
@abstractmethod
|
|
45
|
+
def save_batch(
|
|
46
|
+
self, data_list: List[Any], identifiers: List[Union[str, Path]], **kwargs
|
|
47
|
+
) -> None:
|
|
48
|
+
"""
|
|
49
|
+
Send multiple data objects to the destination in a single operation.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
data_list: List of data objects to send
|
|
53
|
+
identifiers: List of unique identifiers (must match length of data_list)
|
|
54
|
+
**kwargs: Backend-specific arguments
|
|
55
|
+
|
|
56
|
+
Raises:
|
|
57
|
+
ValueError: If data_list and identifiers have different lengths
|
|
58
|
+
TypeError: If any identifier is not a valid type
|
|
59
|
+
ValueError: If any data cannot be sent to destination
|
|
60
|
+
"""
|
|
61
|
+
pass
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class DataSource(ABC):
|
|
65
|
+
"""
|
|
66
|
+
Abstract base class for read-only data sources.
|
|
67
|
+
|
|
68
|
+
Defines the minimal interface for loading data from any source,
|
|
69
|
+
whether filesystem, virtual workspace, remote storage, or databases.
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
@abstractmethod
|
|
73
|
+
def load(self, file_path: Union[str, Path], **kwargs) -> Any:
|
|
74
|
+
"""
|
|
75
|
+
Load data from a file path.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
file_path: Path to the file to load
|
|
79
|
+
**kwargs: Backend-specific arguments
|
|
80
|
+
|
|
81
|
+
Raises:
|
|
82
|
+
FileNotFoundError: If the file does not exist
|
|
83
|
+
TypeError: If file_path is not a valid type
|
|
84
|
+
ValueError: If the data cannot be loaded
|
|
85
|
+
"""
|
|
86
|
+
pass
|
|
87
|
+
|
|
88
|
+
@abstractmethod
|
|
89
|
+
def load_batch(self, file_paths: List[Union[str, Path]], **kwargs) -> List[Any]:
|
|
90
|
+
"""
|
|
91
|
+
Load multiple files in a single batch operation.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
file_paths: List of file paths to load
|
|
95
|
+
**kwargs: Backend-specific arguments
|
|
96
|
+
|
|
97
|
+
Raises:
|
|
98
|
+
FileNotFoundError: If any file does not exist
|
|
99
|
+
TypeError: If any file_path is not a valid type
|
|
100
|
+
ValueError: If any data cannot be loaded
|
|
101
|
+
"""
|
|
102
|
+
pass
|
|
103
|
+
|
|
104
|
+
@abstractmethod
|
|
105
|
+
def list_files(
|
|
106
|
+
self,
|
|
107
|
+
directory: Union[str, Path],
|
|
108
|
+
pattern: Optional[str] = None,
|
|
109
|
+
extensions: Optional[Set[str]] = None,
|
|
110
|
+
recursive: bool = False,
|
|
111
|
+
**kwargs,
|
|
112
|
+
) -> List[str]:
|
|
113
|
+
"""
|
|
114
|
+
List files in a directory.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
directory: Directory to list files from
|
|
118
|
+
pattern: Optional glob pattern to filter files
|
|
119
|
+
extensions: Optional set of file extensions to filter (e.g., {'.tif', '.png'})
|
|
120
|
+
recursive: Whether to search recursively
|
|
121
|
+
**kwargs: Backend-specific arguments
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
List of file paths (absolute or relative depending on backend)
|
|
125
|
+
"""
|
|
126
|
+
pass
|
|
127
|
+
|
|
128
|
+
@abstractmethod
|
|
129
|
+
def exists(self, path: Union[str, Path]) -> bool:
|
|
130
|
+
"""Check if a path exists."""
|
|
131
|
+
pass
|
|
132
|
+
|
|
133
|
+
@abstractmethod
|
|
134
|
+
def is_file(self, path: Union[str, Path]) -> bool:
|
|
135
|
+
"""Check if a path is a file."""
|
|
136
|
+
pass
|
|
137
|
+
|
|
138
|
+
@abstractmethod
|
|
139
|
+
def is_dir(self, path: Union[str, Path]) -> bool:
|
|
140
|
+
"""Check if a path is a directory."""
|
|
141
|
+
pass
|
|
142
|
+
|
|
143
|
+
@abstractmethod
|
|
144
|
+
def list_dir(self, path: Union[str, Path]) -> List[str]:
|
|
145
|
+
"""List immediate entries in a directory (names only)."""
|
|
146
|
+
pass
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
class VirtualBackend(DataSink):
|
|
150
|
+
"""
|
|
151
|
+
Abstract base for backends that provide virtual filesystem semantics.
|
|
152
|
+
|
|
153
|
+
Virtual backends generate file listings on-demand without real filesystem operations.
|
|
154
|
+
Examples: OMERO (generates filenames from plate structure), S3 (lists objects), HTTP APIs.
|
|
155
|
+
"""
|
|
156
|
+
|
|
157
|
+
@abstractmethod
|
|
158
|
+
def load(self, file_path: Union[str, Path], **kwargs) -> Any:
|
|
159
|
+
"""
|
|
160
|
+
Load data from virtual path.
|
|
161
|
+
|
|
162
|
+
Args:
|
|
163
|
+
file_path: Virtual path to load
|
|
164
|
+
**kwargs: Backend-specific context
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
The loaded data
|
|
168
|
+
|
|
169
|
+
Raises:
|
|
170
|
+
FileNotFoundError: If the virtual path does not exist
|
|
171
|
+
TypeError: If required kwargs are missing
|
|
172
|
+
ValueError: If the data cannot be loaded
|
|
173
|
+
"""
|
|
174
|
+
pass
|
|
175
|
+
|
|
176
|
+
@abstractmethod
|
|
177
|
+
def load_batch(self, file_paths: List[Union[str, Path]], **kwargs) -> List[Any]:
|
|
178
|
+
"""
|
|
179
|
+
Load multiple virtual paths in a single batch operation.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
file_paths: List of virtual paths to load
|
|
183
|
+
**kwargs: Backend-specific context
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
List of loaded data objects in the same order as file_paths
|
|
187
|
+
|
|
188
|
+
Raises:
|
|
189
|
+
FileNotFoundError: If any virtual path does not exist
|
|
190
|
+
TypeError: If required kwargs are missing
|
|
191
|
+
ValueError: If any data cannot be loaded
|
|
192
|
+
"""
|
|
193
|
+
pass
|
|
194
|
+
|
|
195
|
+
@abstractmethod
|
|
196
|
+
def list_files(
|
|
197
|
+
self,
|
|
198
|
+
directory: Union[str, Path],
|
|
199
|
+
pattern: Optional[str] = None,
|
|
200
|
+
extensions: Optional[Set[str]] = None,
|
|
201
|
+
recursive: bool = False,
|
|
202
|
+
**kwargs,
|
|
203
|
+
) -> List[str]:
|
|
204
|
+
"""
|
|
205
|
+
Generate virtual file listing.
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
directory: Virtual directory path
|
|
209
|
+
pattern: Optional file pattern filter
|
|
210
|
+
extensions: Optional set of file extensions to filter
|
|
211
|
+
recursive: Whether to list recursively
|
|
212
|
+
**kwargs: Backend-specific context
|
|
213
|
+
|
|
214
|
+
Returns:
|
|
215
|
+
List of virtual filenames
|
|
216
|
+
|
|
217
|
+
Raises:
|
|
218
|
+
TypeError: If required kwargs are missing
|
|
219
|
+
ValueError: If directory is invalid
|
|
220
|
+
"""
|
|
221
|
+
pass
|
|
222
|
+
|
|
223
|
+
@property
|
|
224
|
+
def requires_filesystem_validation(self) -> bool:
|
|
225
|
+
"""
|
|
226
|
+
Whether this backend requires filesystem validation.
|
|
227
|
+
|
|
228
|
+
Virtual backends return False - they don't have real filesystem paths.
|
|
229
|
+
Real backends return True - they need path validation.
|
|
230
|
+
|
|
231
|
+
Returns:
|
|
232
|
+
False for virtual backends
|
|
233
|
+
"""
|
|
234
|
+
return False
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
class BackendBase(metaclass=AutoRegisterMeta):
|
|
238
|
+
"""
|
|
239
|
+
Base class for all storage backends (read-only and read-write).
|
|
240
|
+
|
|
241
|
+
Defines the registry and common interface for backend discovery.
|
|
242
|
+
Concrete backends should inherit from StorageBackend or ReadOnlyBackend.
|
|
243
|
+
"""
|
|
244
|
+
|
|
245
|
+
__registry_key__ = "_backend_type"
|
|
246
|
+
|
|
247
|
+
@property
|
|
248
|
+
@abstractmethod
|
|
249
|
+
def requires_filesystem_validation(self) -> bool:
|
|
250
|
+
"""Whether this backend requires filesystem validation."""
|
|
251
|
+
pass
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
class ReadOnlyBackend(BackendBase, DataSource):
|
|
255
|
+
"""
|
|
256
|
+
Abstract base class for read-only storage backends with auto-registration.
|
|
257
|
+
|
|
258
|
+
Use this for backends that only need to read data (virtual workspaces,
|
|
259
|
+
read-only mounts, archive viewers, etc.).
|
|
260
|
+
"""
|
|
261
|
+
|
|
262
|
+
@property
|
|
263
|
+
def requires_filesystem_validation(self) -> bool:
|
|
264
|
+
"""
|
|
265
|
+
Whether this backend requires filesystem validation.
|
|
266
|
+
|
|
267
|
+
Returns:
|
|
268
|
+
False for virtual/remote backends, True for local filesystem
|
|
269
|
+
"""
|
|
270
|
+
return False
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
class StorageBackend(BackendBase, DataSource, DataSink):
|
|
274
|
+
"""
|
|
275
|
+
Abstract base class for read-write storage backends.
|
|
276
|
+
|
|
277
|
+
Extends DataSource (read) and DataSink (write) with file system operations
|
|
278
|
+
for backends that provide persistent storage with file-like semantics.
|
|
279
|
+
|
|
280
|
+
Concrete implementations are automatically registered via AutoRegisterMeta.
|
|
281
|
+
"""
|
|
282
|
+
|
|
283
|
+
@property
|
|
284
|
+
def requires_filesystem_validation(self) -> bool:
|
|
285
|
+
"""
|
|
286
|
+
Whether this backend requires filesystem validation.
|
|
287
|
+
|
|
288
|
+
Returns:
|
|
289
|
+
True for real filesystem backends (default for StorageBackend)
|
|
290
|
+
"""
|
|
291
|
+
return True
|
|
292
|
+
|
|
293
|
+
def exists(self, path: Union[str, Path]) -> bool:
|
|
294
|
+
"""
|
|
295
|
+
Check if a path exists (is a valid file or directory).
|
|
296
|
+
|
|
297
|
+
Args:
|
|
298
|
+
path: Path to check
|
|
299
|
+
|
|
300
|
+
Returns:
|
|
301
|
+
bool: True if path resolves to a real object
|
|
302
|
+
"""
|
|
303
|
+
try:
|
|
304
|
+
return self.is_file(path)
|
|
305
|
+
except (FileNotFoundError, NotADirectoryError, StorageResolutionError):
|
|
306
|
+
pass
|
|
307
|
+
except IsADirectoryError:
|
|
308
|
+
# Path exists but is a directory
|
|
309
|
+
try:
|
|
310
|
+
return self.is_dir(path)
|
|
311
|
+
except (FileNotFoundError, NotADirectoryError, StorageResolutionError):
|
|
312
|
+
return False
|
|
313
|
+
|
|
314
|
+
# If is_file failed for other reasons, try is_dir
|
|
315
|
+
try:
|
|
316
|
+
return self.is_dir(path)
|
|
317
|
+
except (FileNotFoundError, NotADirectoryError, StorageResolutionError):
|
|
318
|
+
return False
|