66
77import numpy as np
88import zarr
9+ import datajoint as dj
10+ from datajoint import DataJointError
11+ from datajoint .builtin_codecs import SchemaCodec
912
10- try :
11- import datajoint as dj
12- from datajoint import DataJointError
13- from datajoint .builtin_codecs import SchemaCodec
14- except ImportError as e :
15- raise ImportError (
16- "datajoint>=2.0.0a22 is required. Install with: pip install 'datajoint>=2.0.0a22'"
17- ) from e
18-
19-
20- class ZarrCodec (SchemaCodec ):
13+ class ZarrArrayCodec (SchemaCodec ):
2114 """
2215 Store numpy arrays in Zarr format with schema-addressed paths.
2316
@@ -78,7 +71,6 @@ class Recording(dj.Manual):
7871 """
7972
8073 name = "zarr"
81- CODEC_VERSION = "1.0" # Data format version for backward compatibility
8274
8375 def validate (self , value : Any ) -> None :
8476 """
@@ -94,16 +86,16 @@ def validate(self, value: Any) -> None:
9486 DataJointError
9587 If value is not a numpy array or has object dtype.
9688 """
97- if not isinstance (value , np .ndarray ):
98- raise DataJointError (
99- f"<zarr> requires numpy.ndarray , got { type (value ).__name__ } "
89+ if not isinstance (value , np .ndarray | zarr . Array ):
90+ raise TypeError (
91+ f"<zarr> requires a Numpy array or Zarr array , got { type (value ).__name__ } "
10092 )
10193 if value .dtype == object :
10294 raise DataJointError ("<zarr> does not support object dtype arrays" )
10395
10496 def encode (
10597 self ,
106- value : np .ndarray ,
98+ value : np .ndarray | zarr . Array ,
10799 * ,
108100 key : dict | None = None ,
109101 store_name : str | None = None ,
@@ -130,40 +122,35 @@ def encode(
130122 DataJointError
131123 If encoding fails.
132124 """
133- try :
134- # Extract context from key
135- schema , table , field , primary_key = self ._extract_context (key )
136-
137- # Build schema-addressed path
138- path , _token = self ._build_path (
139- schema , table , field , primary_key , ext = ".zarr" , store_name = store_name
140- )
141-
142- # Get storage backend
143- backend = self ._get_backend (store_name )
144-
145- # Get fsspec mapper for direct Zarr write
146- store_map = backend .get_fsmap (path )
147-
148- # Write array to Zarr format
149- zarr .save_array (store_map , value )
150-
151- # Store version metadata in Zarr attributes
152- z = zarr .open (store_map , mode = "r+" )
153- z .attrs ["codec_version" ] = self .CODEC_VERSION
154- z .attrs ["codec_name" ] = self .name
155-
156- # Return metadata for database storage
157- return {
158- "path" : path ,
159- "store" : store_name ,
160- "codec_version" : self .CODEC_VERSION ,
161- "shape" : list (value .shape ),
162- "dtype" : str (value .dtype ),
163- }
164-
165- except Exception as e :
166- raise DataJointError (f"Failed to encode Zarr array: { e } " ) from e
125+ # import here to avoid circular import
126+ from dj_zarr_codecs import __version__
127+ # Extract context from key
128+ schema , table , field , primary_key = self ._extract_context (key )
129+
130+ # Build schema-addressed path
131+ path , _token = self ._build_path (
132+ schema , table , field , primary_key , ext = ".zarr" , store_name = store_name
133+ )
134+
135+ # Get storage backend
136+ backend = self ._get_backend (store_name )
137+
138+ # Get fsspec mapper for direct Zarr write
139+ store_map = backend .get_fsmap (path )
140+
141+ zarr .create_array (store = store_map , data = value , write_data = True )
142+
143+ # Return metadata for database storage (stored as JSON column)
144+ return {
145+ "path" : path ,
146+ "store" : store_name ,
147+ "shape" : list (value .shape ),
148+ "dtype" : str (value .dtype ),
149+ "provenance" : {
150+ "datajoint-python" : dj .__version__ ,
151+ "dj-zarr-codecs" : __version__ ,
152+ },
153+ }
167154
168155 def decode (self , stored : dict , * , key : dict | None = None ) -> zarr .Array :
169156 """
@@ -187,30 +174,11 @@ def decode(self, stored: dict, *, key: dict | None = None) -> zarr.Array:
187174 DataJointError
188175 If decoding fails.
189176 """
190- try :
191- # Get storage backend
192- backend = self ._get_backend (stored .get ("store" ))
177+ # Get storage backend
178+ backend = self ._get_backend (stored .get ("store" ))
193179
194- # Get fsspec mapper for Zarr path
195- store_map = backend .get_fsmap (stored ["path" ])
196-
197- # Open Zarr array (read-only)
198- z = zarr .open (store_map , mode = "r" )
199-
200- # Check codec version for backward compatibility
201- # Priority: Zarr attrs > DB metadata > default "1.0"
202- version = z .attrs .get (
203- "codec_version" , stored .get ("codec_version" , "1.0" )
204- )
180+ # Get fsspec mapper for Zarr path
181+ store_map = backend .get_fsmap (stored ["path" ])
205182
206- # All v1.x versions are compatible
207- if version .startswith ("1." ):
208- return z
209- else :
210- raise DataJointError (
211- f"Unsupported zarr codec version: { version } . "
212- f"Upgrade dj-zarr-codecs or migrate data."
213- )
214-
215- except Exception as e :
216- raise DataJointError (f"Failed to decode Zarr array: { e } " ) from e
183+ # Open Zarr array (read-only)
184+ return zarr .open (store_map , mode = "r" )
0 commit comments