genesets route for local server (#2079)

* first cut at GET /genesets route

* update existing tests to match code changes

* more GET /genesets and initial tests

* add missing test fixture

* geneset validation accepts OTA format

* genesets route: better error handling, more tests

* lint
This commit is contained in:
Bruce Martin
2021-02-26 17:53:07 -08:00
committed by GitHub
parent 09466a5c32
commit f3a3820ffa
18 changed files with 813 additions and 82 deletions
@@ -44,6 +44,9 @@ def get_client_config(app_config, data_adaptor):
"annotations": False,
"annotations_file": None,
"annotations_dir": None,
"annotations_genesets": True, # feature flag
"annotations_genesets_readonly": dataset_config.user_annotations__genesets__readonly,
"annotations_genesets_summary_methods": ["mean"],
"annotations_cell_ontology_enabled": False,
"annotations_cell_ontology_obopath": None,
"annotations_cell_ontology_terms": None,
+62 -31
View File
@@ -3,7 +3,7 @@ from os.path import splitext, isdir
from local_server.common.annotations.local_file_csv import AnnotationsLocalFile
from local_server.common.config.base_config import BaseConfig
from local_server.common.errors import ConfigurationError, OntologyLoadFailure
from local_server.common.errors import ConfigurationError, OntologyLoadFailure, AnnotationsError
from local_server.compute.scanpy import get_scanpy_module
from local_server.data_common.matrix_loader import MatrixDataLoader
@@ -32,6 +32,10 @@ class DatasetConfig(BaseConfig):
self.user_annotations__ontology__obo_location = default_config["user_annotations"]["ontology"][
"obo_location"
]
self.user_annotations__genesets__readonly = default_config["user_annotations"]["genesets"]["readonly"]
self.user_annotations__local_file_csv__genesets_file = default_config["user_annotations"]["local_file_csv"][
"genesets_file"
]
self.embeddings__names = default_config["embeddings"]["names"]
self.embeddings__enable_reembedding = default_config["embeddings"]["enable_reembedding"]
@@ -56,9 +60,7 @@ class DatasetConfig(BaseConfig):
def get_data_adaptor(self):
server_config = self.app_config.server_config
if not server_config.data_adaptor:
matrix_data_loader = MatrixDataLoader(
server_config.single_dataset__datapath, app_config=self.app_config
)
matrix_data_loader = MatrixDataLoader(server_config.single_dataset__datapath, app_config=self.app_config)
server_config.data_adaptor = matrix_data_loader.open(self.app_config)
return server_config.data_adaptor
@@ -96,11 +98,16 @@ class DatasetConfig(BaseConfig):
self.validate_correct_type_of_configuration_attribute(
"user_annotations__local_file_csv__file", (type(None), str)
)
self.validate_correct_type_of_configuration_attribute(
"user_annotations__local_file_csv__genesets_file", (type(None), str)
)
self.validate_correct_type_of_configuration_attribute("user_annotations__ontology__enable", bool)
self.validate_correct_type_of_configuration_attribute(
"user_annotations__ontology__obo_location", (type(None), str)
)
if self.user_annotations__enable:
self.validate_correct_type_of_configuration_attribute("user_annotations__genesets__readonly", bool)
if self.user_annotations__enable or not self.user_annotations__genesets__readonly:
server_config = self.app_config.server_config
if not self.app__authentication_enable:
raise ConfigurationError("user annotations requires authentication to be enabled")
@@ -108,59 +115,84 @@ class DatasetConfig(BaseConfig):
auth_type = server_config.authentication__type
raise ConfigurationError(f"authentication method {auth_type} is not compatible with user annotations")
if self.user_annotations__type == "local_file_csv":
self.handle_local_file_csv_annotations()
else:
raise ConfigurationError('The only annotation type support is "local_file_csv"')
# Must always have an annotations instance to support genesets. User annotation (cell labels) are optional
# as are writable gene sets
if self.user_annotations__type == "local_file_csv":
self.handle_local_file_csv_annotations(context)
else:
raise ConfigurationError('The only annotation type support is "local_file_csv"')
if self.user_annotations__enable:
if self.user_annotations__ontology__enable or self.user_annotations__ontology__obo_location:
try:
self.user_annotations.load_ontology(self.user_annotations__ontology__obo_location)
except OntologyLoadFailure as e:
raise ConfigurationError("Unable to load ontology terms\n" + str(e))
else:
self.check_annotation_config_vars_not_set(context)
def handle_local_file_csv_annotations(self):
self.check_annotation_config_vars_not_set(context)
def handle_local_file_csv_annotations(self, context):
dirname = self.user_annotations__local_file_csv__directory
filename = self.user_annotations__local_file_csv__file
if filename is not None and dirname is not None:
raise ConfigurationError("'annotations-file' and 'annotations-dir' may not be used together.")
genesets_filename = self.user_annotations__local_file_csv__genesets_file
if dirname is not None and (filename is not None or genesets_filename is not None):
raise ConfigurationError(
"'user-generated-data-dir' may not be used with annotations-file' or 'genesets-file'."
)
if filename is not None:
lf_name, lf_ext = splitext(filename)
if lf_ext and lf_ext != ".csv":
raise ConfigurationError(f"annotation file type must be .csv: {filename}")
if genesets_filename is not None:
lf_name, lf_ext = splitext(genesets_filename)
if lf_ext and lf_ext != ".csv":
raise ConfigurationError(f"genesets file type must be .csv: {genesets_filename}")
if dirname is not None and not isdir(dirname):
try:
os.mkdir(dirname)
except OSError:
raise ConfigurationError("Unable to create directory specified by --annotations-dir")
raise ConfigurationError("Unable to create directory specified by --user-generated-data-dir")
self.user_annotations = AnnotationsLocalFile(dirname, filename)
anno_config = {
"user-annotations": self.user_annotations__enable,
"genesets-save": not self.user_annotations__genesets__readonly,
}
self.user_annotations = AnnotationsLocalFile(anno_config, dirname, filename, genesets_filename)
# if the user has specified a fixed label file, go ahead and validate it
# so that we can remove errors early in the process.
server_config = self.app_config.server_config
if server_config.single_dataset__datapath and self.user_annotations__local_file_csv__file:
if server_config.single_dataset__datapath:
data_adaptor = self.get_data_adaptor()
data_adaptor.check_new_labels(self.user_annotations.read_labels(data_adaptor))
if self.user_annotations__local_file_csv__file:
data_adaptor.check_new_labels(self.user_annotations.read_labels(data_adaptor))
if self.user_annotations__local_file_csv__genesets_file:
try:
data_adaptor.check_new_genesets(self.user_annotations.read_genesets(data_adaptor, context), context)
except (ValueError, AnnotationsError, KeyError) as e:
raise ConfigurationError(f"Unable to read genesets CSV file: {str(e)}") from e
def check_annotation_config_vars_not_set(self, context):
if self.user_annotations__type is not None:
dirname = self.user_annotations__local_file_csv__directory
filename = self.user_annotations__local_file_csv__file
if filename is not None:
context["messagefn"]("Warning: --annotations-file ignored as annotations are disabled.")
if dirname is not None:
context["messagefn"]("Warning: --annotations-dir ignored as annotations are disabled.")
if self.user_annotations__ontology__enable:
context["messagefn"]("Warning: --experimental-annotations-ontology ignored as annotations are disabled.")
if self.user_annotations__ontology__obo_location is not None:
context["messagefn"](
"Warning: --experimental-annotations-ontology-obo ignored as annotations are disabled."
)
if not self.user_annotations__enable:
if filename is not None:
context["messagefn"]("Warning: --annotations-file ignored as annotations are disabled.")
if self.user_annotations__ontology__enable:
context["messagefn"](
"Warning: --experimental-annotations-ontology ignored as annotations are disabled."
)
if self.user_annotations__ontology__obo_location is not None:
context["messagefn"](
"Warning: --experimental-annotations-ontology-obo ignored as annotations are disabled."
)
if dirname is not None:
context["messagefn"]("Warning: --user-generated-data-dir ignored as annotations are disabled.")
def handle_embeddings(self):
self.validate_correct_type_of_configuration_attribute("embeddings__names", list)
@@ -186,6 +218,5 @@ class DatasetConfig(BaseConfig):
data_adaptor = self.get_data_adaptor()
if self.diffexp__enable and data_adaptor.parameters.get("diffexp_may_be_slow", False):
context["messagefn"](
"CAUTION: due to the size of your dataset, "
"running differential expression may take longer or fail."
"CAUTION: due to the size of your dataset, " "running differential expression may take longer or fail."
)