Skip to content

Commit fb40f08

Browse files
DiamondJosephdan-fernandesdanielballan
authored
Typed adapters (bluesky#1047)
* fix(devcontainer): Add stage to Docker build for devcontainer * chore(types): Add type hints and hierarchy to Structure types * fix type checking issues on various versions * ClassVar * Numpy types * Add root Adapter type * chore: Type hint and rationalise adapters * Changes to fix pre-commit * Fixes for pre-commit including correct typing of MapAdapter * Fixes for pre-commit * Adopt Zarr expectations of JSON type * Extract Zarr Adapter from array assumptions * Fixes for pre-commit * Retype xarray adapters, MadAdapter * Add types-cachetools to dev requirements * Fixes to pass pre-commit * Remove type vars from non generic classes * Add SQLAdapter.structure_family * Fix treating .structure_family as a property * Make ParquetDatabaseAdapter inherit from Adapter[TableStructure] * Fixes for pre-commit * Make structure_family an attribute, make metadata a callable * Make CSVAdapter inherit directly from Adapter[TableStructure] * Fix structure family pickling issue * Fix hdf5 adapter self referential metadata * Change ZarrGroupAdapter conatiner structure to use list of keys * Change NPYAdapter to inherit directly from Adapter[ArrayStructure] * Remove unused print statement * Remove AnyStructure * Fix typing for backwards compatibility * Fix metadata argument * Add type to ExceAdapter(MadAdapter) generic * Add return type, remove unused type ignore * Fix JPEGAdapter.read_block signature, remove unused type ignore * Fix merge conflict resolution --------- Co-authored-by: Daniel Fernandes <[email protected]> Co-authored-by: Dan Allan <[email protected]>
1 parent d3eae59 commit fb40f08

37 files changed

+449
-534
lines changed

.pre-commit-config.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,4 @@ repos:
3535
hooks:
3636
- id: mypy
3737
args: [--strict]
38+
additional_dependencies: [types-cachetools]

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ dev = [
169169
"sphinx-copybutton",
170170
"sphinx_rtd_theme",
171171
"sphinxcontrib-mermaid",
172+
"types-cachetools",
172173
]
173174
# These are used by the server to read files in the respective formats.
174175
formats = [

tiled/adapters/array.py

Lines changed: 5 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,20 @@
11
import contextlib
2-
from typing import Any, List, Optional, Set, Tuple
2+
from typing import Any, List, Optional, Tuple
33

44
import dask.array
55
import numpy
66
import pandas
77
from numpy.typing import NDArray
88

9+
from tiled.adapters.core import Adapter
10+
911
from ..ndslice import NDSlice
10-
from ..storage import Storage
1112
from ..structures.array import ArrayStructure
1213
from ..structures.core import Spec, StructureFamily
1314
from ..type_aliases import JSON
1415

1516

16-
class ArrayAdapter:
17+
class ArrayAdapter(Adapter[ArrayStructure]):
1718
"""
1819
Wrap an array-like object in an interface that Tiled can serve.
1920
@@ -29,7 +30,6 @@ class ArrayAdapter:
2930
"""
3031

3132
structure_family = StructureFamily.array
32-
supported_storage: Set[type[Storage]] = set()
3333

3434
def __init__(
3535
self,
@@ -39,19 +39,8 @@ def __init__(
3939
metadata: Optional[JSON] = None,
4040
specs: Optional[List[Spec]] = None,
4141
) -> None:
42-
"""
43-
44-
Parameters
45-
----------
46-
array :
47-
structure :
48-
metadata :
49-
specs :
50-
"""
5142
self._array = array
52-
self._structure = structure
53-
self._metadata = metadata or {}
54-
self.specs = specs or []
43+
super().__init__(structure, metadata=metadata, specs=specs)
5544

5645
@classmethod
5746
def from_array(
@@ -64,21 +53,6 @@ def from_array(
6453
metadata: Optional[JSON] = None,
6554
specs: Optional[List[Spec]] = None,
6655
) -> "ArrayAdapter":
67-
"""
68-
69-
Parameters
70-
----------
71-
array :
72-
shape :
73-
chunks :
74-
dims :
75-
metadata :
76-
specs :
77-
78-
Returns
79-
-------
80-
81-
"""
8256
# May be a list of something; convert to array
8357
if not hasattr(array, "__array__"):
8458
array = numpy.asanyarray(array)
@@ -118,26 +92,10 @@ def __repr__(self) -> str:
11892
def dims(self) -> Optional[Tuple[str, ...]]:
11993
return self._structure.dims
12094

121-
def metadata(self) -> JSON:
122-
return self._metadata
123-
124-
def structure(self) -> ArrayStructure:
125-
return self._structure
126-
12795
def read(
12896
self,
12997
slice: NDSlice = NDSlice(...),
13098
) -> NDArray[Any]:
131-
"""
132-
133-
Parameters
134-
----------
135-
slice :
136-
137-
Returns
138-
-------
139-
140-
"""
14199
# _array[...] requires an actual tuple, not just a subclass of tuple
142100
array = self._array[tuple(slice)] if slice else self._array
143101
if isinstance(self._array, dask.array.Array):
@@ -149,17 +107,6 @@ def read_block(
149107
block: Tuple[int, ...],
150108
slice: NDSlice = NDSlice(...),
151109
) -> NDArray[Any]:
152-
"""
153-
154-
Parameters
155-
----------
156-
block :
157-
slice :
158-
159-
Returns
160-
-------
161-
162-
"""
163110
# Slice the whole array to get this block.
164111
slice_, _ = slice_and_shape_from_block_and_chunks(block, self._structure.chunks)
165112
# _array[...] requires an actual tuple, not just a subclass of tuple
@@ -176,14 +123,6 @@ def slice_and_shape_from_block_and_chunks(
176123
) -> tuple[NDSlice, NDSlice]:
177124
"""
178125
Given dask-like chunks and block id, return slice and shape of the block.
179-
Parameters
180-
----------
181-
block :
182-
chunks :
183-
184-
Returns
185-
-------
186-
187126
"""
188127
slice_ = []
189128
shape = []

tiled/adapters/arrow.py

Lines changed: 13 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import copy
2+
from collections.abc import Set
23
from pathlib import Path
34
from typing import Any, Callable, Dict, Iterator, List, Optional, Tuple, Union
45
from urllib.parse import quote_plus
56

67
import pandas
78
import pyarrow
89
import pyarrow.feather as feather
9-
import pyarrow.fs
10+
11+
from tiled.adapters.core import Adapter
1012

1113
from ..catalog.orm import Node
1214
from ..storage import FileStorage, Storage
@@ -19,37 +21,29 @@
1921
from .utils import init_adapter_from_catalog
2022

2123

22-
class ArrowAdapter:
24+
class ArrowAdapter(Adapter[TableStructure]):
2325
"""ArrowAdapter Class"""
2426

25-
structure_family = StructureFamily.table
26-
supported_storage = {FileStorage}
27+
structure_family: StructureFamily = StructureFamily.table
2728

2829
def __init__(
2930
self,
3031
data_uris: List[str],
3132
structure: Optional[TableStructure] = None,
33+
*,
3234
metadata: Optional[JSON] = None,
3335
specs: Optional[List[Spec]] = None,
34-
**kwargs: Optional[Any],
3536
) -> None:
36-
"""
37-
38-
Parameters
39-
----------
40-
data_uris : list of uris where data sits.
41-
structure :
42-
metadata :
43-
specs :
44-
"""
4537
# TODO Store data_uris instead and generalize to non-file schemes.
4638
self._partition_paths = [path_from_uri(uri) for uri in data_uris]
47-
self._metadata = metadata or {}
4839
if structure is None:
4940
table = feather.read_table(self._partition_paths)
5041
structure = TableStructure.from_arrow_table(table)
51-
self._structure = structure
52-
self.specs = list(specs or [])
42+
super().__init__(structure, metadata=metadata, specs=specs)
43+
44+
@classmethod
45+
def supported_storage(cls) -> Set[type[Storage]]:
46+
return {FileStorage}
5347

5448
@classmethod
5549
def from_catalog(
@@ -59,10 +53,7 @@ def from_catalog(
5953
/,
6054
**kwargs: Optional[Any],
6155
) -> "ArrowAdapter":
62-
return init_adapter_from_catalog(cls, data_source, node, **kwargs) # type: ignore
63-
64-
def metadata(self) -> JSON:
65-
return self._metadata
56+
return init_adapter_from_catalog(cls, data_source, node, **kwargs)
6657

6758
@classmethod
6859
def init_storage(
@@ -101,9 +92,6 @@ def init_storage(
10192
data_source.assets.extend(assets)
10293
return data_source
10394

104-
def structure(self) -> TableStructure:
105-
return self._structure
106-
10795
def get(self, key: str) -> Union[ArrayAdapter, None]:
10896
if key not in self.structure().columns:
10997
return None
@@ -153,6 +141,7 @@ def from_single_file(
153141
cls,
154142
data_uri: str,
155143
structure: Optional[TableStructure] = None,
144+
*,
156145
metadata: Optional[JSON] = None,
157146
specs: Optional[List[Spec]] = None,
158147
) -> "ArrowAdapter":

tiled/adapters/awkward.py

Lines changed: 7 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,22 @@
44
import awkward.forms
55
from numpy.typing import NDArray
66

7-
from ..storage import FileStorage
7+
from tiled.adapters.core import Adapter
8+
89
from ..structures.awkward import AwkwardStructure
910
from ..structures.core import Spec, StructureFamily
1011
from ..type_aliases import JSON
1112
from .awkward_directory_container import DirectoryContainer
1213

1314

14-
class AwkwardAdapter:
15-
structure_family = StructureFamily.awkward
16-
supported_storage = {FileStorage}
15+
class AwkwardAdapter(Adapter[AwkwardStructure]):
16+
structure_family: StructureFamily = StructureFamily.awkward
1717

1818
def __init__(
1919
self,
2020
container: DirectoryContainer,
2121
structure: AwkwardStructure,
22+
*,
2223
metadata: Optional[JSON] = None,
2324
specs: Optional[List[Spec]] = None,
2425
) -> None:
@@ -32,14 +33,13 @@ def __init__(
3233
specs :
3334
"""
3435
self.container = container
35-
self._metadata = metadata or {}
36-
self._structure = structure
37-
self.specs = list(specs or [])
36+
super().__init__(structure, metadata=metadata, specs=specs)
3837

3938
@classmethod
4039
def from_array(
4140
cls,
4241
array: NDArray[Any],
42+
*,
4343
metadata: Optional[JSON] = None,
4444
specs: Optional[List[Spec]] = None,
4545
) -> "AwkwardAdapter":
@@ -64,15 +64,6 @@ def from_array(
6464
specs=specs,
6565
)
6666

67-
def metadata(self) -> JSON:
68-
"""
69-
70-
Returns
71-
-------
72-
73-
"""
74-
return self._metadata
75-
7667
def read_buffers(self, form_keys: Optional[List[str]] = None) -> Dict[str, bytes]:
7768
"""
7869
@@ -118,6 +109,3 @@ def write(self, container: DirectoryContainer) -> None:
118109
"""
119110
for form_key, value in container.items():
120111
self.container[form_key] = value
121-
122-
def structure(self) -> AwkwardStructure:
123-
return self._structure

tiled/adapters/awkward_buffers.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"""
44

55
import copy
6+
from collections.abc import Set
67
from pathlib import Path
78
from typing import Any, List, Optional
89
from urllib.parse import quote_plus
@@ -12,7 +13,7 @@
1213
from ..catalog.orm import Node
1314
from ..storage import FileStorage, Storage
1415
from ..structures.awkward import AwkwardStructure
15-
from ..structures.core import Spec, StructureFamily
16+
from ..structures.core import Spec
1617
from ..structures.data_source import Asset, DataSource
1718
from ..type_aliases import JSON
1819
from ..utils import path_from_uri
@@ -22,9 +23,6 @@
2223

2324

2425
class AwkwardBuffersAdapter(AwkwardAdapter):
25-
structure_family = StructureFamily.awkward
26-
supported_storage = {FileStorage}
27-
2826
@classmethod
2927
def init_storage(
3028
cls,
@@ -58,6 +56,7 @@ def __init__(
5856
self,
5957
data_uri: str,
6058
structure: AwkwardStructure,
59+
*,
6160
metadata: Optional[JSON] = None,
6261
specs: Optional[List[Spec]] = None,
6362
):
@@ -73,6 +72,10 @@ def __init__(
7372
specs=specs,
7473
)
7574

75+
@classmethod
76+
def supported_storage(cls) -> Set[type[Storage]]:
77+
return {FileStorage}
78+
7679
@classmethod
7780
def from_catalog(
7881
cls,
@@ -81,4 +84,4 @@ def from_catalog(
8184
/,
8285
**kwargs: Optional[Any],
8386
) -> "AwkwardBuffersAdapter":
84-
return init_adapter_from_catalog(cls, data_source, node, **kwargs) # type: ignore
87+
return init_adapter_from_catalog(cls, data_source, node, **kwargs)

tiled/adapters/container.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from collections.abc import Mapping
2+
from typing import Generic
3+
4+
from tiled.adapters.core import A, Adapter
5+
from tiled.structures.container import ContainerStructure
6+
from tiled.structures.core import StructureFamily
7+
8+
9+
class ContainerAdapter(Adapter[ContainerStructure], Mapping[str, A], Generic[A]):
10+
structure_family: StructureFamily = StructureFamily.container

0 commit comments

Comments
 (0)