ngio 0.2.7__py3-none-any.whl → 0.3.0a0__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.
@@ -5,17 +5,26 @@ https://fractal-analytics-platform.github.io/fractal-tasks-core/tables/
5
5
  """
6
6
 
7
7
  # Import _type to avoid name conflict with table.type
8
- from builtins import type as _type
9
8
  from collections.abc import Iterable
10
- from typing import Generic, Literal, TypeVar
9
+ from functools import cache
10
+ from typing import Literal
11
11
 
12
12
  import pandas as pd
13
13
  from pydantic import BaseModel
14
14
 
15
15
  from ngio.common import Roi
16
- from ngio.tables._validators import validate_columns
17
- from ngio.tables.backends import BackendMeta, ImplementedTableBackends
18
- from ngio.utils import NgioValueError, ZarrGroupHandler
16
+ from ngio.tables.abstract_table import (
17
+ AbstractBaseTable,
18
+ TableBackendProtocol,
19
+ TabularData,
20
+ )
21
+ from ngio.tables.backends import BackendMeta, convert_to_pandas, normalize_pandas_df
22
+ from ngio.utils import (
23
+ NgioTableValidationError,
24
+ NgioValueError,
25
+ ZarrGroupHandler,
26
+ ngio_logger,
27
+ )
19
28
 
20
29
  REQUIRED_COLUMNS = [
21
30
  "x_micrometer",
@@ -26,6 +35,10 @@ REQUIRED_COLUMNS = [
26
35
  "len_z_micrometer",
27
36
  ]
28
37
 
38
+ #####################
39
+ # Optional columns are not validated at the moment
40
+ # only a warning is raised if non optional columns are present
41
+ #####################
29
42
 
30
43
  ORIGIN_COLUMNS = [
31
44
  "x_micrometer_original",
@@ -34,36 +47,95 @@ ORIGIN_COLUMNS = [
34
47
 
35
48
  TRANSLATION_COLUMNS = ["translation_x", "translation_y", "translation_z"]
36
49
 
37
- OPTIONAL_COLUMNS = ORIGIN_COLUMNS + TRANSLATION_COLUMNS
50
+ PLATE_COLUMNS = ["plate_name", "row", "column", "path", "acquisition"]
38
51
 
52
+ INDEX_COLUMNS = [
53
+ "FieldIndex",
54
+ "label",
55
+ ]
56
+
57
+ OPTIONAL_COLUMNS = ORIGIN_COLUMNS + TRANSLATION_COLUMNS + PLATE_COLUMNS + INDEX_COLUMNS
58
+
59
+
60
+ @cache
61
+ def _check_optional_columns(col_name: str) -> None:
62
+ """Check if the column name is in the optional columns."""
63
+ if col_name not in OPTIONAL_COLUMNS:
64
+ ngio_logger.warning(
65
+ f"Column {col_name} is not in the optional columns. "
66
+ f"Standard optional columns are: {OPTIONAL_COLUMNS}."
67
+ )
39
68
 
40
- def _dataframe_to_rois(dataframe: pd.DataFrame) -> dict[str, Roi]:
69
+
70
+ def _dataframe_to_rois(
71
+ dataframe: pd.DataFrame,
72
+ required_columns: list[str] | None = None,
73
+ ) -> dict[str, Roi]:
41
74
  """Convert a DataFrame to a WorldCooROI object."""
75
+ if required_columns is None:
76
+ required_columns = REQUIRED_COLUMNS
77
+
78
+ # Validate the columns of the DataFrame
79
+ _required_columns = set(dataframe.columns).intersection(set(required_columns))
80
+ if len(_required_columns) != len(required_columns):
81
+ raise NgioTableValidationError(
82
+ f"Could not find required columns: {_required_columns} in the table."
83
+ )
84
+
85
+ extra_columns = set(dataframe.columns).difference(set(required_columns))
86
+
87
+ for col in extra_columns:
88
+ _check_optional_columns(col)
89
+
90
+ extras = {}
91
+
42
92
  rois = {}
43
- for key, row in dataframe.iterrows():
93
+ for row in dataframe.itertuples(index=True):
44
94
  # check if optional columns are present
45
- origin = {col: row.get(col, None) for col in ORIGIN_COLUMNS}
46
- origin = dict(filter(lambda x: x[1] is not None, origin.items()))
47
- translation = {col: row.get(col, None) for col in TRANSLATION_COLUMNS}
48
- translation = dict(filter(lambda x: x[1] is not None, translation.items()))
95
+ if len(extra_columns) > 0:
96
+ extras = {col: getattr(row, col, None) for col in extra_columns}
49
97
 
50
98
  roi = Roi(
51
- name=str(key),
52
- x=row["x_micrometer"],
53
- y=row["y_micrometer"],
54
- z=row["z_micrometer"],
55
- x_length=row["len_x_micrometer"],
56
- y_length=row["len_y_micrometer"],
57
- z_length=row["len_z_micrometer"],
99
+ name=str(row.Index),
100
+ x=row.x_micrometer, # type: ignore
101
+ y=row.y_micrometer, # type: ignore
102
+ z=row.z_micrometer, # type: ignore
103
+ x_length=row.len_x_micrometer, # type: ignore
104
+ y_length=row.len_y_micrometer, # type: ignore
105
+ z_length=row.len_z_micrometer, # type: ignore
58
106
  unit="micrometer", # type: ignore
59
- **origin,
60
- **translation,
107
+ **extras,
61
108
  )
62
109
  rois[roi.name] = roi
63
110
  return rois
64
111
 
65
112
 
66
- def _rois_to_dataframe(rois: dict[str, Roi], index_key: str) -> pd.DataFrame:
113
+ def _table_to_rois(
114
+ table: TabularData,
115
+ index_key: str | None = None,
116
+ index_type: Literal["int", "str"] | None = None,
117
+ required_columns: list[str] | None = None,
118
+ ) -> tuple[pd.DataFrame, dict[str, Roi]]:
119
+ """Convert a table to a dictionary of ROIs.
120
+
121
+ Args:
122
+ table: The table to convert.
123
+ index_key: The column name to use as the index of the DataFrame.
124
+ index_type: The type of the index column in the DataFrame.
125
+ required_columns: The required columns in the DataFrame.
126
+
127
+ Returns:
128
+ A dictionary of ROIs.
129
+ """
130
+ dataframe = convert_to_pandas(
131
+ table,
132
+ index_key=index_key,
133
+ index_type=index_type,
134
+ )
135
+ return dataframe, _dataframe_to_rois(dataframe, required_columns=required_columns)
136
+
137
+
138
+ def _rois_to_dataframe(rois: dict[str, Roi], index_key: str | None) -> pd.DataFrame:
67
139
  """Convert a list of WorldCooROI objects to a DataFrame."""
68
140
  data = []
69
141
  for roi in rois.values():
@@ -78,153 +150,110 @@ def _rois_to_dataframe(rois: dict[str, Roi], index_key: str) -> pd.DataFrame:
78
150
  }
79
151
 
80
152
  extra = roi.model_extra or {}
81
- for col in ORIGIN_COLUMNS:
82
- if col in extra:
83
- row[col] = extra[col]
84
-
85
- for col in TRANSLATION_COLUMNS:
86
- if col in extra:
87
- row[col] = extra[col]
153
+ for col in extra:
154
+ _check_optional_columns(col)
155
+ row[col] = extra[col]
88
156
  data.append(row)
89
157
  dataframe = pd.DataFrame(data)
90
- dataframe = dataframe.set_index(index_key)
158
+ dataframe = normalize_pandas_df(dataframe, index_key=index_key)
91
159
  return dataframe
92
160
 
93
161
 
94
- class RoiTableV1Meta(BackendMeta):
95
- """Metadata for the ROI table."""
96
-
97
- fractal_table_version: Literal["1"] = "1"
98
- type: Literal["roi_table"] = "roi_table"
99
-
100
-
101
- class RegionMeta(BaseModel):
102
- """Metadata for the region."""
103
-
104
- path: str
105
-
106
-
107
- class MaskingRoiTableV1Meta(BackendMeta):
108
- """Metadata for the ROI table."""
109
-
110
- fractal_table_version: Literal["1"] = "1"
111
- type: Literal["masking_roi_table"] = "masking_roi_table"
112
- region: RegionMeta | None = None
113
- instance_key: str = "label"
114
-
115
-
116
- _roi_meta = TypeVar("_roi_meta", RoiTableV1Meta, MaskingRoiTableV1Meta)
117
-
118
-
119
- class _GenericRoiTableV1(Generic[_roi_meta]):
120
- """Class to a non-specific table."""
121
-
122
- _meta: _roi_meta
123
-
162
+ class GenericRoiTableV1(AbstractBaseTable):
124
163
  def __init__(
125
- self, meta: _roi_meta | None = None, rois: Iterable[Roi] | None = None
164
+ self,
165
+ *,
166
+ rois: Iterable[Roi] | None = None,
167
+ meta: BackendMeta,
126
168
  ) -> None:
127
- """Create a new ROI table."""
128
- if meta is None:
129
- raise NgioValueError("Metadata must be provided.")
130
- self._meta = meta
131
- self._table_backend = None
169
+ table = None
132
170
 
133
- self._rois = {}
171
+ self._rois: dict[str, Roi] | None = None
134
172
  if rois is not None:
173
+ self._rois = {}
135
174
  self.add(rois)
175
+ table = _rois_to_dataframe(self._rois, index_key=meta.index_key)
176
+
177
+ super().__init__(table_data=table, meta=meta)
178
+
179
+ def __repr__(self) -> str:
180
+ """Return a string representation of the table."""
181
+ rois = self.rois()
182
+ prop = f"num_rois={len(rois)}"
183
+ class_name = self.__class__.__name__
184
+ return f"{class_name}({prop})"
136
185
 
137
186
  @staticmethod
138
- def type() -> str:
187
+ def table_type() -> str:
139
188
  """Return the type of the table."""
140
- raise NotImplementedError
189
+ return "generic_roi_table"
141
190
 
142
191
  @staticmethod
143
192
  def version() -> Literal["1"]:
144
193
  """Return the version of the fractal table."""
145
194
  return "1"
146
195
 
147
- @staticmethod
148
- def _index_key() -> str:
149
- """Return the index key of the table."""
150
- raise NotImplementedError
151
-
152
- @staticmethod
153
- def _index_type() -> Literal["int", "str"]:
154
- """Return the index type of the table."""
155
- raise NotImplementedError
196
+ @property
197
+ def table_data(self) -> TabularData:
198
+ """Return the table."""
199
+ if self._rois is None:
200
+ return super().table_data
156
201
 
157
- @staticmethod
158
- def _meta_type() -> _type[_roi_meta]:
159
- """Return the metadata type of the table."""
160
- raise NotImplementedError
202
+ if len(self.rois()) > 0:
203
+ self._table_data = _rois_to_dataframe(self._rois, index_key=self.index_key)
204
+ return super().table_data
161
205
 
162
- @property
163
- def backend_name(self) -> str | None:
164
- """Return the name of the backend."""
165
- if self._table_backend is None:
206
+ def set_table_data(
207
+ self, table_data: TabularData | None = None, refresh: bool = False
208
+ ) -> None:
209
+ if table_data is not None:
210
+ if not isinstance(table_data, TabularData):
211
+ raise NgioValueError(
212
+ "The table must be a pandas DataFrame, polars LazyFrame, "
213
+ " or AnnData object."
214
+ )
215
+
216
+ table_data, rois = _table_to_rois(
217
+ table_data,
218
+ index_key=self.index_key,
219
+ index_type=self.index_type,
220
+ required_columns=REQUIRED_COLUMNS,
221
+ )
222
+ self._table_data = table_data
223
+ self._rois = rois
166
224
  return None
167
- return self._table_backend.backend_name()
168
225
 
169
- @classmethod
170
- def _from_handler(
171
- cls, handler: ZarrGroupHandler, backend_name: str | None = None
172
- ) -> "_GenericRoiTableV1":
173
- """Create a new ROI table from a Zarr store."""
174
- meta = cls._meta_type()(**handler.load_attrs())
175
-
176
- if backend_name is None:
177
- backend = ImplementedTableBackends().get_backend(
178
- backend_name=meta.backend,
179
- group_handler=handler,
180
- index_key=cls._index_key(),
181
- index_type=cls._index_type(),
182
- )
183
- else:
184
- backend = ImplementedTableBackends().get_backend(
185
- backend_name=backend_name,
186
- group_handler=handler,
187
- index_key=cls._index_key(),
188
- index_type=cls._index_type(),
189
- )
190
- meta.backend = backend_name
226
+ if self._table_data is not None and not refresh:
227
+ return None
191
228
 
192
- if not backend.implements_pandas:
229
+ if self._table_backend is None:
193
230
  raise NgioValueError(
194
- "The backend does not implement the dataframe protocol."
231
+ "The table does not have a DataFrame in memory nor a backend."
195
232
  )
196
233
 
197
- # This will be implemented in the child classes
198
- table = cls()
199
- table._meta = meta
200
- table._table_backend = backend
201
-
202
- dataframe = backend.load_as_pandas_df()
203
- dataframe = validate_columns(
204
- dataframe,
234
+ table_data, rois = _table_to_rois(
235
+ self._table_backend.load(),
236
+ index_key=self.index_key,
237
+ index_type=self.index_type,
205
238
  required_columns=REQUIRED_COLUMNS,
206
- optional_columns=OPTIONAL_COLUMNS,
207
239
  )
208
- table._rois = _dataframe_to_rois(dataframe)
209
- return table
240
+ self._table_data = table_data
241
+ self._rois = rois
210
242
 
211
- def _set_backend(
212
- self,
213
- handler: ZarrGroupHandler,
214
- backend_name: str | None = None,
215
- ) -> None:
216
- """Set the backend of the table."""
217
- backend = ImplementedTableBackends().get_backend(
218
- backend_name=backend_name,
219
- group_handler=handler,
220
- index_key=self._index_key(),
221
- index_type=self._index_type(),
222
- )
223
- self._meta.backend = backend_name
224
- self._table_backend = backend
243
+ def _check_rois(self) -> None:
244
+ """Load the ROIs from the table.
245
+
246
+ If the ROIs are already loaded, do nothing.
247
+ If the ROIs are not loaded, load them from the table.
248
+ """
249
+ if self._rois is None:
250
+ self._rois = _dataframe_to_rois(self.dataframe)
225
251
 
226
252
  def rois(self) -> list[Roi]:
227
253
  """List all ROIs in the table."""
254
+ self._check_rois()
255
+ if self._rois is None:
256
+ return []
228
257
  return list(self._rois.values())
229
258
 
230
259
  def add(self, roi: Roi | Iterable[Roi], overwrite: bool = False) -> None:
@@ -237,31 +266,49 @@ class _GenericRoiTableV1(Generic[_roi_meta]):
237
266
  if isinstance(roi, Roi):
238
267
  roi = [roi]
239
268
 
269
+ self._check_rois()
270
+ if self._rois is None:
271
+ self._rois = {}
272
+
240
273
  for _roi in roi:
241
274
  if not overwrite and _roi.name in self._rois:
242
275
  raise NgioValueError(f"ROI {_roi.name} already exists in the table.")
243
276
  self._rois[_roi.name] = _roi
244
277
 
245
- def consolidate(self) -> None:
246
- """Write the current state of the table to the Zarr file."""
247
- if self._table_backend is None:
248
- raise NgioValueError(
249
- "No backend set for the table. "
250
- "Please add the table to a OME-Zarr Image before calling consolidate."
251
- )
278
+ def get(self, roi_name: str) -> Roi:
279
+ """Get an ROI from the table."""
280
+ self._check_rois()
281
+ if self._rois is None:
282
+ self._rois = {}
252
283
 
253
- dataframe = _rois_to_dataframe(self._rois, index_key=self._index_key())
254
- dataframe = validate_columns(
255
- dataframe,
284
+ if roi_name not in self._rois:
285
+ raise NgioValueError(f"ROI {roi_name} not found in the table.")
286
+ return self._rois[roi_name]
287
+
288
+ @classmethod
289
+ def from_table_data(
290
+ cls, table_data: TabularData, meta: BackendMeta
291
+ ) -> "GenericRoiTableV1":
292
+ """Create a new ROI table from a table data."""
293
+ _, rois = _table_to_rois(
294
+ table=table_data,
295
+ index_key=meta.index_key,
296
+ index_type=meta.index_type,
256
297
  required_columns=REQUIRED_COLUMNS,
257
- optional_columns=OPTIONAL_COLUMNS,
258
- )
259
- self._table_backend.write(
260
- dataframe, metadata=self._meta.model_dump(exclude_none=True), mode="pandas"
261
298
  )
299
+ return cls(rois=rois.values(), meta=meta)
300
+
301
+
302
+ class RoiTableV1Meta(BackendMeta):
303
+ """Metadata for the ROI table."""
262
304
 
305
+ fractal_table_version: Literal["1"] = "1"
306
+ type: Literal["roi_table"] = "roi_table"
307
+ index_key: str | None = "FieldIndex"
308
+ index_type: Literal["str", "int"] | None = "str"
263
309
 
264
- class RoiTableV1(_GenericRoiTableV1[RoiTableV1Meta]):
310
+
311
+ class RoiTableV1(GenericRoiTableV1):
265
312
  """Class to handle fractal ROI tables.
266
313
 
267
314
  To know more about the ROI table format, please refer to the
@@ -269,43 +316,57 @@ class RoiTableV1(_GenericRoiTableV1[RoiTableV1Meta]):
269
316
  https://fractal-analytics-platform.github.io/fractal-tasks-core/tables/
270
317
  """
271
318
 
272
- def __init__(self, rois: Iterable[Roi] | None = None) -> None:
319
+ def __init__(
320
+ self, rois: Iterable[Roi] | None = None, *, meta: RoiTableV1Meta | None = None
321
+ ) -> None:
273
322
  """Create a new ROI table."""
274
- super().__init__(RoiTableV1Meta(), rois)
323
+ if meta is None:
324
+ meta = RoiTableV1Meta()
275
325
 
276
- def __repr__(self) -> str:
277
- """Return a string representation of the table."""
278
- prop = f"num_rois={len(self._rois)}"
279
- return f"RoiTableV1({prop})"
326
+ if meta.index_key is None:
327
+ meta.index_key = "FieldIndex"
328
+
329
+ if meta.index_type is None:
330
+ meta.index_type = "str"
331
+ super().__init__(meta=meta, rois=rois)
332
+
333
+ @classmethod
334
+ def from_handler(
335
+ cls,
336
+ handler: ZarrGroupHandler,
337
+ backend: str | TableBackendProtocol | None = None,
338
+ ) -> "RoiTableV1":
339
+ table = cls._from_handler(
340
+ handler=handler,
341
+ backend=backend,
342
+ meta_model=RoiTableV1Meta,
343
+ )
344
+ return table
280
345
 
281
346
  @staticmethod
282
- def type() -> Literal["roi_table"]:
347
+ def table_type() -> Literal["roi_table"]:
283
348
  """Return the type of the table."""
284
349
  return "roi_table"
285
350
 
286
- @staticmethod
287
- def _index_key() -> str:
288
- """Return the index key of the table."""
289
- return "FieldIndex"
290
351
 
291
- @staticmethod
292
- def _index_type() -> Literal["int", "str"]:
293
- """Return the index type of the table."""
294
- return "str"
352
+ class RegionMeta(BaseModel):
353
+ """Metadata for the region."""
295
354
 
296
- @staticmethod
297
- def _meta_type() -> _type[RoiTableV1Meta]:
298
- """Return the metadata type of the table."""
299
- return RoiTableV1Meta
355
+ path: str
300
356
 
301
- def get(self, roi_name: str) -> Roi:
302
- """Get an ROI from the table."""
303
- if roi_name not in self._rois:
304
- raise NgioValueError(f"ROI {roi_name} not found in the table.")
305
- return self._rois[roi_name]
357
+
358
+ class MaskingRoiTableV1Meta(BackendMeta):
359
+ """Metadata for the ROI table."""
360
+
361
+ fractal_table_version: Literal["1"] = "1"
362
+ type: Literal["masking_roi_table"] = "masking_roi_table"
363
+ region: RegionMeta | None = None
364
+ instance_key: str = "label"
365
+ index_key: str | None = "label"
366
+ index_type: Literal["int", "str"] | None = "int"
306
367
 
307
368
 
308
- class MaskingRoiTableV1(_GenericRoiTableV1[MaskingRoiTableV1Meta]):
369
+ class MaskingRoiTableV1(GenericRoiTableV1):
309
370
  """Class to handle fractal ROI tables.
310
371
 
311
372
  To know more about the ROI table format, please refer to the
@@ -316,54 +377,73 @@ class MaskingRoiTableV1(_GenericRoiTableV1[MaskingRoiTableV1Meta]):
316
377
  def __init__(
317
378
  self,
318
379
  rois: Iterable[Roi] | None = None,
380
+ *,
319
381
  reference_label: str | None = None,
382
+ meta: MaskingRoiTableV1Meta | None = None,
320
383
  ) -> None:
321
384
  """Create a new ROI table."""
322
- meta = MaskingRoiTableV1Meta()
385
+ if meta is None:
386
+ meta = MaskingRoiTableV1Meta()
387
+
323
388
  if reference_label is not None:
324
389
  meta.region = RegionMeta(path=reference_label)
325
- super().__init__(meta, rois)
390
+
391
+ if meta.index_key is None:
392
+ meta.index_key = "label"
393
+
394
+ if meta.index_type is None:
395
+ meta.index_type = "int"
396
+ meta.instance_key = meta.index_key
397
+ super().__init__(meta=meta, rois=rois)
326
398
 
327
399
  def __repr__(self) -> str:
328
400
  """Return a string representation of the table."""
329
- prop = f"num_rois={len(self._rois)}"
401
+ rois = self.rois()
330
402
  if self.reference_label is not None:
331
- prop += f", reference_label={self.reference_label}"
403
+ prop = f"num_rois={len(rois)}, reference_label={self.reference_label}"
404
+ else:
405
+ prop = f"num_rois={len(rois)}"
332
406
  return f"MaskingRoiTableV1({prop})"
333
407
 
408
+ @classmethod
409
+ def from_handler(
410
+ cls,
411
+ handler: ZarrGroupHandler,
412
+ backend: str | TableBackendProtocol | None = None,
413
+ ) -> "MaskingRoiTableV1":
414
+ table = cls._from_handler(
415
+ handler=handler,
416
+ backend=backend,
417
+ meta_model=MaskingRoiTableV1Meta,
418
+ )
419
+ return table
420
+
334
421
  @staticmethod
335
- def type() -> Literal["masking_roi_table"]:
422
+ def table_type() -> Literal["masking_roi_table"]:
336
423
  """Return the type of the table."""
337
424
  return "masking_roi_table"
338
425
 
339
- @staticmethod
340
- def _index_key() -> str:
341
- """Return the index key of the table."""
342
- return "label"
343
-
344
- @staticmethod
345
- def _index_type() -> Literal["int", "str"]:
346
- """Return the index type of the table."""
347
- return "int"
348
-
349
- @staticmethod
350
- def _meta_type() -> _type[MaskingRoiTableV1Meta]:
351
- """Return the metadata type of the table."""
352
- return MaskingRoiTableV1Meta
426
+ @property
427
+ def meta(self) -> MaskingRoiTableV1Meta:
428
+ """Return the metadata of the table."""
429
+ if not isinstance(self._meta, MaskingRoiTableV1Meta):
430
+ raise NgioValueError(
431
+ "The metadata of the table is not of type MaskingRoiTableV1Meta."
432
+ )
433
+ return self._meta
353
434
 
354
435
  @property
355
436
  def reference_label(self) -> str | None:
356
437
  """Return the reference label."""
357
- path = self._meta.region
438
+ path = self.meta.region
358
439
  if path is None:
359
440
  return None
441
+
360
442
  path = path.path
361
443
  path = path.split("/")[-1]
362
444
  return path
363
445
 
364
- def get(self, label: int) -> Roi:
446
+ def get(self, label: int | str) -> Roi: # type: ignore
365
447
  """Get an ROI from the table."""
366
- _label = str(label)
367
- if _label not in self._rois:
368
- raise KeyError(f"ROI {_label} not found in the table.")
369
- return self._rois[_label]
448
+ roi_name = str(label)
449
+ return super().get(roi_name)
@@ -1,4 +1,7 @@
1
1
  import fsspec.implementations.http
2
+ from aiohttp import ClientResponseError
3
+
4
+ from ngio.utils import NgioValueError
2
5
 
3
6
 
4
7
  def fractal_fsspec_store(
@@ -9,5 +12,31 @@ def fractal_fsspec_store(
9
12
  if fractal_token is not None:
10
13
  client_kwargs["headers"] = {"Authorization": f"Bearer {fractal_token}"}
11
14
  fs = fsspec.implementations.http.HTTPFileSystem(client_kwargs=client_kwargs)
15
+
12
16
  store = fs.get_mapper(url)
17
+
18
+ possible_keys = [".zgroup", ".zarray"]
19
+ for key in possible_keys:
20
+ try:
21
+ value = store.get(key)
22
+ if value is not None:
23
+ break
24
+ except ClientResponseError as e:
25
+ if e.status == 401 and fractal_token is None:
26
+ raise NgioValueError(
27
+ "No auto token is provided. You need a valid "
28
+ f"'fractal_token' to access: {url}."
29
+ ) from e
30
+ elif e.status == 401 and fractal_token is not None:
31
+ raise NgioValueError(
32
+ f"The 'fractal_token' provided is invalid for: {url}."
33
+ ) from e
34
+ else:
35
+ raise e
36
+ else:
37
+ raise NgioValueError(
38
+ f"Store {url} can not be read. Possible problems are: \n"
39
+ "- The url does not exist. \n"
40
+ f"- The url is not a valid .zarr. \n"
41
+ )
13
42
  return store
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ngio
3
- Version: 0.2.7
3
+ Version: 0.3.0a0
4
4
  Summary: Next Generation file format IO
5
- Project-URL: homepage, https://github.com/lorenzocerrone/ngio
6
- Project-URL: repository, https://github.com/lorenzocerrone/ngio
5
+ Project-URL: homepage, https://github.com/fractal-analytics-platform/ngio
6
+ Project-URL: repository, https://github.com/fractal-analytics-platform/ngio
7
7
  Author-email: Lorenzo Cerrone <lorenzo.cerrone@uzh.ch>
8
8
  License: BSD-3-Clause
9
9
  License-File: LICENSE