33import tempfile
44from uuid import uuid4
55from pathlib import PurePath , PurePosixPath
6+ import zarr
67
78from .constants import (
89 norm_enum ,
@@ -41,9 +42,11 @@ def __init__(self, **kwargs):
4142 self .out_dir = kwargs ['out_dir' ] if 'out_dir' in kwargs else tempfile .mkdtemp (
4243 )
4344 self .routes = []
44- self .is_remote = False
45+ self .is_remote = False # TODO: change to needs_localhost_serving for clarity
46+ self .is_store = False # TODO: change to needs_store_registration for clarity
4547 self .file_def_creators = []
4648 self .base_dir = None
49+ self .stores = {}
4750 self ._request_init = kwargs ['request_init' ] if 'request_init' in kwargs else None
4851
4952 def __repr__ (self ):
@@ -71,6 +74,20 @@ def get_routes(self):
7174 """
7275 return self .routes
7376
77+ def get_stores (self , base_url ):
78+ """
79+ Obtain the stores that have been created for this wrapper class.
80+
81+ :returns: A dictionary that maps file URLs to Zarr Store objects.
82+ :rtype: dict[str, zarr.Store]
83+ """
84+ relative_stores = self .stores
85+ absolute_stores = {}
86+ for relative_url , store in relative_stores .items ():
87+ absolute_url = base_url + relative_url
88+ absolute_stores [absolute_url ] = store
89+ return absolute_stores
90+
7491 def get_file_defs (self , base_url ):
7592 """
7693 Obtain the file definitions for this wrapper class.
@@ -111,6 +128,30 @@ def get_local_dir_url(self, base_url, dataset_uid, obj_i, local_dir_path, local_
111128 return self ._get_url_simple (base_url , file_path_to_url_path (local_dir_path , prepend_slash = False ))
112129 return self ._get_url (base_url , dataset_uid , obj_i , local_dir_uid )
113130
131+ def register_zarr_store (self , dataset_uid , obj_i , store_or_local_dir_path , local_dir_uid ):
132+ if not self .is_remote and self .is_store :
133+ # Set up `store` and `local_dir_path` variables.
134+ if isinstance (store_or_local_dir_path , str ):
135+ # TODO: use zarr.FSStore if fsspec is installed?
136+ store = zarr .DirectoryStore (store_or_local_dir_path )
137+ local_dir_path = store_or_local_dir_path
138+ else :
139+ # TODO: check that store_or_local_dir_path is a zarr.Store or StoreLike?
140+ store = store_or_local_dir_path
141+ # A store instance was passed directly, so there is no local directory path.
142+ # Instead we just make one up using _get_route_str but it could be any string.
143+ local_dir_path = self ._get_route_str (dataset_uid , obj_i , local_dir_uid )
144+
145+ # Register the store on the same route path
146+ # that will be used for the "url" field in the file definition.
147+ if self .base_dir is None :
148+ route_path = self ._get_route_str (dataset_uid , obj_i , local_dir_uid )
149+ else :
150+ route_path = file_path_to_url_path (local_dir_path )
151+ local_dir_path = join (self .base_dir , local_dir_path )
152+
153+ self .stores [route_path ] = store
154+
114155 def get_local_dir_route (self , dataset_uid , obj_i , local_dir_path , local_dir_uid ):
115156 """
116157 Obtain the Mount for some local directory
@@ -896,12 +937,14 @@ def image_file_def_creator(base_url):
896937
897938
898939class AnnDataWrapper (AbstractWrapper ):
899- def __init__ (self , adata_path = None , adata_url = None , obs_feature_matrix_path = None , feature_filter_path = None , initial_feature_filter_path = None , obs_set_paths = None , obs_set_names = None , obs_locations_path = None , obs_segmentations_path = None , obs_embedding_paths = None , obs_embedding_names = None , obs_embedding_dims = None , obs_spots_path = None , obs_points_path = None , feature_labels_path = None , obs_labels_path = None , convert_to_dense = True , coordination_values = None , obs_labels_paths = None , obs_labels_names = None , ** kwargs ):
940+ def __init__ (self , adata_path = None , adata_url = None , adata_store = None , obs_feature_matrix_path = None , feature_filter_path = None , initial_feature_filter_path = None , obs_set_paths = None , obs_set_names = None , obs_locations_path = None , obs_segmentations_path = None , obs_embedding_paths = None , obs_embedding_names = None , obs_embedding_dims = None , obs_spots_path = None , obs_points_path = None , feature_labels_path = None , obs_labels_path = None , convert_to_dense = True , coordination_values = None , obs_labels_paths = None , obs_labels_names = None , ** kwargs ):
900941 """
901942 Wrap an AnnData object by creating an instance of the ``AnnDataWrapper`` class.
902943
903944 :param str adata_path: A path to an AnnData object written to a Zarr store containing single-cell experiment data.
904945 :param str adata_url: A remote url pointing to a zarr-backed AnnData store.
946+ :param adata_store: A path to pass to zarr.FSStore, or an existing store instance.
947+ :type adata_store: str or zarr.Storage
905948 :param str obs_feature_matrix_path: Location of the expression (cell x gene) matrix, like `X` or `obsm/highly_variable_genes_subset`
906949 :param str feature_filter_path: A string like `var/highly_variable` used in conjunction with `obs_feature_matrix_path` if obs_feature_matrix_path points to a subset of `X` of the full `var` list.
907950 :param str initial_feature_filter_path: A string like `var/highly_variable` used in conjunction with `obs_feature_matrix_path` if obs_feature_matrix_path points to a subset of `X` of the full `var` list.
@@ -927,18 +970,30 @@ def __init__(self, adata_path=None, adata_url=None, obs_feature_matrix_path=None
927970 self ._repr = make_repr (locals ())
928971 self ._adata_path = adata_path
929972 self ._adata_url = adata_url
930- if adata_url is not None and (adata_path is not None ):
973+ self ._adata_store = adata_store
974+
975+ num_inputs = sum ([1 for x in [adata_path , adata_url , adata_store ] if x is not None ])
976+ if num_inputs > 1 :
931977 raise ValueError (
932- "Did not expect adata_url to be provided with adata_path " )
933- if adata_url is None and ( adata_path is None ) :
978+ "Expected only one of adata_path, adata_url, or adata_store to be provided" )
979+ if num_inputs == 0 :
934980 raise ValueError (
935- "Expected either adata_url or adata_path to be provided" )
981+ "Expected one of adata_path, adata_url, or adata_store to be provided" )
982+
936983 if adata_path is not None :
937984 self .is_remote = False
985+ self .is_store = False
938986 self .zarr_folder = 'anndata.zarr'
939- else :
987+ elif adata_url is not None :
940988 self .is_remote = True
989+ self .is_store = False
941990 self .zarr_folder = None
991+ else :
992+ # Store case
993+ self .is_remote = False
994+ self .is_store = True
995+ self .zarr_folder = None
996+
942997 self .local_dir_uid = make_unique_filename (".adata.zarr" )
943998 self ._expression_matrix = obs_feature_matrix_path
944999 self ._cell_set_obs_names = obs_set_names
@@ -978,6 +1033,9 @@ def convert_and_save(self, dataset_uid, obj_i, base_dir=None):
9781033 def make_anndata_routes (self , dataset_uid , obj_i ):
9791034 if self .is_remote :
9801035 return []
1036+ elif self .is_store :
1037+ self .register_zarr_store (dataset_uid , obj_i , self ._adata_store , self .local_dir_uid )
1038+ return []
9811039 else :
9821040 return self .get_local_dir_route (dataset_uid , obj_i , self ._adata_path , self .local_dir_uid )
9831041
0 commit comments