mirror of
https://github.com/chanzuckerberg/cellxgene.git
synced 2026-10-08 07:18:12 +08:00
Use DataLocator consistently on backend (#1208)
* allow DataLocator to accept another locator as init param * migrate to DataLocator * migrate to DataLocator * lint * migrate to DataLocator * add check for erroroneous use of remote path and annotations * lint * revert default data location - now back go CWD * remove unused import
This commit is contained in:
@@ -26,10 +26,18 @@ class DataLocator:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, uri_or_path):
|
def __init__(self, uri_or_path):
|
||||||
self.uri_or_path = uri_or_path
|
if isinstance(uri_or_path, DataLocator):
|
||||||
self.protocol, self.path = DataLocator._get_protocol_and_path(uri_or_path)
|
locator = uri_or_path
|
||||||
# work-around for LocalFileSystem not treating file: and None as the same scheme/protocol
|
self.uri_or_path = locator.uri_or_path
|
||||||
self.cname = self.path if self.protocol == "file" else self.uri_or_path
|
self.protocol = locator.protocol
|
||||||
|
self.path = locator.path
|
||||||
|
self.cname = locator.cname
|
||||||
|
else:
|
||||||
|
self.uri_or_path = uri_or_path
|
||||||
|
self.protocol, self.path = DataLocator._get_protocol_and_path(uri_or_path)
|
||||||
|
# work-around for LocalFileSystem not treating file: and None as the same scheme/protocol
|
||||||
|
self.cname = self.path if self.protocol == "file" else self.uri_or_path
|
||||||
|
|
||||||
# will throw RuntimeError if the protocol is unsupported
|
# will throw RuntimeError if the protocol is unsupported
|
||||||
self.fs = fsspec.filesystem(self.protocol)
|
self.fs = fsspec.filesystem(self.protocol)
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ from server.data_common.fbs.matrix import encode_matrix_fbs
|
|||||||
from server.common.utils import series_to_schema
|
from server.common.utils import series_to_schema
|
||||||
from server.common.constants import Axis, MAX_LAYOUTS
|
from server.common.constants import Axis, MAX_LAYOUTS
|
||||||
from server.common.errors import PrepareError, DatasetAccessError, FilterError
|
from server.common.errors import PrepareError, DatasetAccessError, FilterError
|
||||||
from server.common.data_locator import DataLocator
|
|
||||||
from server.compute.scanpy import scanpy_umap
|
from server.compute.scanpy import scanpy_umap
|
||||||
|
|
||||||
anndata_version = version.parse(str(anndata.__version__)).release
|
anndata_version = version.parse(str(anndata.__version__)).release
|
||||||
@@ -38,31 +37,31 @@ class AnndataAdaptor(DataAdaptor):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def pre_load_validation(location):
|
def pre_load_validation(data_locator):
|
||||||
data_locator = DataLocator(location)
|
|
||||||
if data_locator.islocal():
|
if data_locator.islocal():
|
||||||
# if data locator is local, apply file system conventions and other "cheap"
|
# if data locator is local, apply file system conventions and other "cheap"
|
||||||
# validation checks. If a URI, defer until we actually fetch the data and
|
# validation checks. If a URI, defer until we actually fetch the data and
|
||||||
# try to read it. Many of these tests don't make sense for URIs (eg, extension-
|
# try to read it. Many of these tests don't make sense for URIs (eg, extension-
|
||||||
# based typing).
|
# based typing).
|
||||||
if not data_locator.exists():
|
if not data_locator.exists():
|
||||||
raise DatasetAccessError(f"{location} does not exist")
|
raise DatasetAccessError(f"{data_locator.uri_or_path} does not exist")
|
||||||
if not data_locator.isfile():
|
if not data_locator.isfile():
|
||||||
raise DatasetAccessError(f"{location} is not a file")
|
raise DatasetAccessError(f"{data_locator.uri_or_path} is not a file")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def file_size(location):
|
def file_size(data_locator):
|
||||||
data_locator = DataLocator(location)
|
|
||||||
return data_locator.size() if data_locator.islocal() else 0
|
return data_locator.size() if data_locator.islocal() else 0
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def open(location, config):
|
def open(data_locator, config):
|
||||||
data_locator = DataLocator(location)
|
|
||||||
return AnndataAdaptor(data_locator, config)
|
return AnndataAdaptor(data_locator, config)
|
||||||
|
|
||||||
def get_location(self):
|
def get_location(self):
|
||||||
return self.data_locator.uri_or_path
|
return self.data_locator.uri_or_path
|
||||||
|
|
||||||
|
def get_data_locator(self):
|
||||||
|
return self.data_locator
|
||||||
|
|
||||||
def get_name(self):
|
def get_name(self):
|
||||||
return "cellxgene anndata adaptor version"
|
return "cellxgene anndata adaptor version"
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ from server.common.errors import FilterError, JSONEncodingValueError
|
|||||||
from server.compute.diffexp import diffexp_ttest
|
from server.compute.diffexp import diffexp_ttest
|
||||||
from server.common.utils import jsonify_numpy
|
from server.common.utils import jsonify_numpy
|
||||||
from server.common.app_config import AppFeature, AppConfig
|
from server.common.app_config import AppFeature, AppConfig
|
||||||
from server.common.data_locator import DataLocator
|
|
||||||
|
|
||||||
|
|
||||||
class DataAdaptor(metaclass=ABCMeta):
|
class DataAdaptor(metaclass=ABCMeta):
|
||||||
@@ -32,17 +31,17 @@ class DataAdaptor(metaclass=ABCMeta):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def pre_load_validation(location):
|
def pre_load_validation(data_locator):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def open(location, config):
|
def open(data_locator, config):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def file_size(location):
|
def file_size(data_locator):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
@@ -105,6 +104,10 @@ class DataAdaptor(metaclass=ABCMeta):
|
|||||||
def get_location(self):
|
def get_location(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def get_data_locator(self):
|
||||||
|
pass
|
||||||
|
|
||||||
def get_about(self):
|
def get_about(self):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -345,8 +348,7 @@ class DataAdaptor(metaclass=ABCMeta):
|
|||||||
|
|
||||||
def get_last_mod_time(self):
|
def get_last_mod_time(self):
|
||||||
try:
|
try:
|
||||||
data_locator = DataLocator(self.get_location())
|
lastmod = self.get_data_locator().lastmodtime()
|
||||||
lastmod = data_locator.lastmodtime()
|
|
||||||
except RuntimeError:
|
except RuntimeError:
|
||||||
lastmod = None
|
lastmod = None
|
||||||
return lastmod
|
return lastmod
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import threading
|
|||||||
import time
|
import time
|
||||||
from server.data_common.rwlock import RWLock
|
from server.data_common.rwlock import RWLock
|
||||||
from server.common.errors import DatasetAccessError
|
from server.common.errors import DatasetAccessError
|
||||||
|
from server.common.data_locator import DataLocator
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
|
|
||||||
|
|
||||||
@@ -136,7 +137,8 @@ class MatrixDataType(Enum):
|
|||||||
|
|
||||||
class MatrixDataLoader(object):
|
class MatrixDataLoader(object):
|
||||||
def __init__(self, location, etype=None):
|
def __init__(self, location, etype=None):
|
||||||
self.location = location
|
""" location can be a string or DataLocator """
|
||||||
|
self.location = DataLocator(location)
|
||||||
if etype is None:
|
if etype is None:
|
||||||
self.etype = self.matrix_data_type()
|
self.etype = self.matrix_data_type()
|
||||||
else:
|
else:
|
||||||
@@ -152,9 +154,9 @@ class MatrixDataLoader(object):
|
|||||||
self.matrix_type = CxgAdaptor
|
self.matrix_type = CxgAdaptor
|
||||||
|
|
||||||
def matrix_data_type(self):
|
def matrix_data_type(self):
|
||||||
if self.location.endswith(".h5ad"):
|
if self.location.path.endswith(".h5ad"):
|
||||||
return MatrixDataType.H5AD
|
return MatrixDataType.H5AD
|
||||||
elif ".cxg" in self.location:
|
elif ".cxg" in self.location.path:
|
||||||
return MatrixDataType.CXG
|
return MatrixDataType.CXG
|
||||||
else:
|
else:
|
||||||
return MatrixDataType.UNKNOWN
|
return MatrixDataType.UNKNOWN
|
||||||
|
|||||||
@@ -18,13 +18,13 @@ class CxgAdaptor(DataAdaptor):
|
|||||||
# TODO: The tiledb context parameters should be a configuration option
|
# TODO: The tiledb context parameters should be a configuration option
|
||||||
tiledb_ctx = tiledb.Ctx({"sm.tile_cache_size": 8 * 1024 * 1024 * 1024, "sm.num_reader_threads": 32})
|
tiledb_ctx = tiledb.Ctx({"sm.tile_cache_size": 8 * 1024 * 1024 * 1024, "sm.num_reader_threads": 32})
|
||||||
|
|
||||||
def __init__(self, location, config=None):
|
def __init__(self, data_locator, config=None):
|
||||||
super().__init__(config)
|
super().__init__(config)
|
||||||
self.url = location
|
|
||||||
self.arrays = {}
|
self.arrays = {}
|
||||||
self.lock = threading.Lock()
|
self.lock = threading.Lock()
|
||||||
|
|
||||||
self.url = location
|
self.data_locator = data_locator
|
||||||
|
self.url = data_locator.uri_or_path
|
||||||
if self.url[-1] != "/":
|
if self.url[-1] != "/":
|
||||||
self.url += "/"
|
self.url += "/"
|
||||||
|
|
||||||
@@ -37,17 +37,18 @@ class CxgAdaptor(DataAdaptor):
|
|||||||
self.arrays.clear()
|
self.arrays.clear()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def pre_load_validation(location):
|
def pre_load_validation(data_locator):
|
||||||
|
location = data_locator.uri_or_path
|
||||||
if not CxgAdaptor.isvalid(location):
|
if not CxgAdaptor.isvalid(location):
|
||||||
raise DatasetAccessError(f"cxg matrix is not valid: {location}")
|
raise DatasetAccessError(f"cxg matrix is not valid: {location}")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def file_size(location):
|
def file_size(data_locator):
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def open(location, args):
|
def open(data_locator, args):
|
||||||
return CxgAdaptor(location, args)
|
return CxgAdaptor(data_locator, args)
|
||||||
|
|
||||||
def get_about(self):
|
def get_about(self):
|
||||||
return self.about if self.about else super().get_about()
|
return self.about if self.about else super().get_about()
|
||||||
@@ -58,6 +59,9 @@ class CxgAdaptor(DataAdaptor):
|
|||||||
def get_location(self):
|
def get_location(self):
|
||||||
return self.url
|
return self.url
|
||||||
|
|
||||||
|
def get_data_locator(self):
|
||||||
|
return self.data_locator
|
||||||
|
|
||||||
def get_name(self):
|
def get_name(self):
|
||||||
return "cellxgene cxg adaptor version"
|
return "cellxgene cxg adaptor version"
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -45,7 +45,8 @@ try:
|
|||||||
obs_names=None,
|
obs_names=None,
|
||||||
var_names=None,
|
var_names=None,
|
||||||
anndata_backed=False,
|
anndata_backed=False,
|
||||||
disable_diffexp=False)
|
disable_diffexp=False,
|
||||||
|
)
|
||||||
|
|
||||||
matrix_data_cache_manager = MatrixDataCacheManager()
|
matrix_data_cache_manager = MatrixDataCacheManager()
|
||||||
annotations = None
|
annotations = None
|
||||||
|
|||||||
Reference in New Issue
Block a user