gene sets summary route (#2099)

* gene sets summary route

* lint

* clarify return type

* style
This commit is contained in:
Bruce Martin
2021-03-10 16:02:05 -08:00
committed by GitHub
parent 1d3d9237e7
commit 31e0326ded
10 changed files with 228 additions and 38 deletions
+7
View File
@@ -155,6 +155,12 @@ class GenesetsAPI(Resource):
return common_rest.genesets_put(request, data_adaptor) return common_rest.genesets_put(request, data_adaptor)
class GenesetSummaryAPI(Resource):
@rest_get_data_adaptor
def get(self, data_adaptor):
return common_rest.geneset_summary_get(request, data_adaptor)
def get_api_base_resources(bp_base): def get_api_base_resources(bp_base):
"""Add resources that are accessed from the api url""" """Add resources that are accessed from the api url"""
api = Api(bp_base) api = Api(bp_base)
@@ -181,6 +187,7 @@ def get_api_dataroot_resources(bp_dataroot):
add_resource(AnnotationsVarAPI, "/annotations/var") add_resource(AnnotationsVarAPI, "/annotations/var")
add_resource(DataVarAPI, "/data/var") add_resource(DataVarAPI, "/data/var")
add_resource(GenesetsAPI, "/genesets") add_resource(GenesetsAPI, "/genesets")
add_resource(GenesetSummaryAPI, "/geneset_summary")
# Display routes # Display routes
add_resource(ColorsAPI, "/colors") add_resource(ColorsAPI, "/colors")
# Computation routes # Computation routes
@@ -110,7 +110,7 @@ class Annotations(metaclass=ABCMeta):
from io import StringIO from io import StringIO
import csv import csv
if type(genesets) == dict: if isinstance(genesets, dict):
genesets = genesets.values() genesets = genesets.values()
with StringIO() as sio: with StringIO() as sio:
@@ -26,10 +26,14 @@ class AnnotationsLocalFile(Annotations):
self.label_lock = threading.RLock() self.label_lock = threading.RLock()
self.gene_sets_lock = threading.RLock() self.gene_sets_lock = threading.RLock()
# cache the most recent annotations. # cache the most recent cell labels/annotations.
self.last_fname = None self.last_label_fname = None
self.last_labels = None self.last_labels = None
# cache the most recent gene sets.
self.last_geneset_fname = None
self.last_geneset = None
# txn ID - used to de-dup geneset writes # txn ID - used to de-dup geneset writes
self.last_geneset_tid = 0 self.last_geneset_tid = 0
@@ -63,14 +67,14 @@ class AnnotationsLocalFile(Annotations):
with self.label_lock: with self.label_lock:
if fname is not None and os.path.exists(fname) and os.path.getsize(fname) > 0: if fname is not None and os.path.exists(fname) and os.path.getsize(fname) > 0:
# returned the cached labels if possible, otherwise read them from the file # returned the cached labels if possible, otherwise read them from the file
if fname == self.last_fname: if fname == self.last_label_fname:
return self.last_labels return self.last_labels
else: else:
labels = pd.read_csv( labels = pd.read_csv(
fname, dtype="category", index_col=0, header=0, comment="#", keep_default_na=False fname, dtype="category", index_col=0, header=0, comment="#", keep_default_na=False
) )
# update the cache # update the cache
self.last_fname = fname self.last_label_fname = fname
self.last_labels = labels self.last_labels = labels
return labels return labels
else: else:
@@ -102,7 +106,7 @@ class AnnotationsLocalFile(Annotations):
open(fname, "w").close() open(fname, "w").close()
# update the cache # update the cache
self.last_fname = fname self.last_label_fname = fname
self.last_labels = df self.last_labels = df
def read_gene_sets(self, data_adaptor, context=None): def read_gene_sets(self, data_adaptor, context=None):
@@ -116,8 +120,19 @@ class AnnotationsLocalFile(Annotations):
with self.gene_sets_lock: with self.gene_sets_lock:
tid = self.last_geneset_tid # inside the critical section tid = self.last_geneset_tid # inside the critical section
if fname is not None and os.path.exists(fname) and os.path.getsize(fname) > 0: if fname is not None and os.path.exists(fname) and os.path.getsize(fname) > 0:
with open(fname, newline="") as f: # return the cached genesets if possible, otherwise read from file and validate them
gene_sets = read_gene_set_tidycsv(f, context) if fname == self.last_geneset_fname:
gene_sets = self.last_geneset
else:
with open(fname, newline="") as f:
gene_sets = read_gene_set_tidycsv(f, context)
# validate
gene_sets = data_adaptor.check_new_gene_sets(gene_sets, context)
# update cache
self.last_geneset_fname = fname
self.last_geneset = gene_sets
return (gene_sets, tid) return (gene_sets, tid)
@@ -127,6 +142,9 @@ class AnnotationsLocalFile(Annotations):
if type(tid) != int or tid < 0: if type(tid) != int or tid < 0:
raise ValueError("tid must be a positive integer") raise ValueError("tid must be a positive integer")
# may raise
gene_sets = data_adaptor.check_new_gene_sets(gene_sets)
with self.gene_sets_lock: with self.gene_sets_lock:
# skip if the request is stale # skip if the request is stale
if tid is not None: if tid is not None:
@@ -149,6 +167,10 @@ class AnnotationsLocalFile(Annotations):
f.write(header) f.write(header)
f.write(self.gene_sets_to_csv(gene_sets)) f.write(self.gene_sets_to_csv(gene_sets))
# update the cache
self.last_geneset_fname = fname
self.last_geneset = gene_sets if type(gene_sets) == dict else {g["geneset_name"]: g for g in gene_sets}
def _get_userdata_idhash(self, data_adaptor): def _get_userdata_idhash(self, data_adaptor):
""" """
Return a short hash that weakly identifies the user and dataset. Return a short hash that weakly identifies the user and dataset.
+5 -5
View File
@@ -33,9 +33,9 @@ class DatasetConfig(BaseConfig):
"obo_location" "obo_location"
] ]
self.user_annotations__gene_sets__readonly = default_config["user_annotations"]["gene_sets"]["readonly"] self.user_annotations__gene_sets__readonly = default_config["user_annotations"]["gene_sets"]["readonly"]
self.user_annotations__local_file_csv__gene_sets_file = default_config["user_annotations"]["local_file_csv"][ self.user_annotations__local_file_csv__gene_sets_file = default_config["user_annotations"][
"gene_sets_file" "local_file_csv"
] ]["gene_sets_file"]
self.embeddings__names = default_config["embeddings"]["names"] self.embeddings__names = default_config["embeddings"]["names"]
self.embeddings__enable_reembedding = default_config["embeddings"]["enable_reembedding"] self.embeddings__enable_reembedding = default_config["embeddings"]["enable_reembedding"]
@@ -169,10 +169,10 @@ class DatasetConfig(BaseConfig):
if server_config.single_dataset__datapath: if server_config.single_dataset__datapath:
data_adaptor = self.get_data_adaptor() data_adaptor = self.get_data_adaptor()
if self.user_annotations__local_file_csv__file: if self.user_annotations__local_file_csv__file:
data_adaptor.check_new_labels(self.user_annotations.read_labels(data_adaptor)) self.user_annotations.read_labels(data_adaptor)
if self.user_annotations__local_file_csv__gene_sets_file: if self.user_annotations__local_file_csv__gene_sets_file:
try: try:
data_adaptor.check_new_gene_sets(self.user_annotations.read_gene_sets(data_adaptor, context), context) self.user_annotations.read_gene_sets(data_adaptor, context)
except (ValueError, AnnotationsError, KeyError) as e: except (ValueError, AnnotationsError, KeyError) as e:
raise ConfigurationError(f"Unable to read genesets CSV file: {str(e)}") from e raise ConfigurationError(f"Unable to read genesets CSV file: {str(e)}") from e
+2 -1
View File
@@ -55,4 +55,5 @@ define_exception("OntologyLoadFailure", "Raised when reading the ontology file f
define_exception("ConfigurationError", "Raised when checking configuration errors") define_exception("ConfigurationError", "Raised when checking configuration errors")
define_exception("PrepareError", "Raised when data is misprepared") define_exception("PrepareError", "Raised when data is misprepared")
define_exception("SecretKeyRetrievalError", "Raised when get_secret_key from AWS fails") define_exception("SecretKeyRetrievalError", "Raised when get_secret_key from AWS fails")
define_exception("ObsoleteRequest", "Raised when the request is no longer valid.") define_exception("ObsoleteRequest", "Raised when the request is no longer valid.")
define_exception("UnsupportedSummaryMethod", "Raised when a gene set summary method is unknown or unsupported.")
+33 -3
View File
@@ -19,6 +19,7 @@ from local_server.common.errors import (
ColorFormatException, ColorFormatException,
AnnotationsError, AnnotationsError,
ObsoleteRequest, ObsoleteRequest,
UnsupportedSummaryMethod,
) )
import json import json
@@ -336,7 +337,7 @@ def genesets_get(request, data_adaptor):
try: try:
annotations = data_adaptor.dataset_config.user_annotations annotations = data_adaptor.dataset_config.user_annotations
(genesets, tid) = data_adaptor.check_new_gene_sets(annotations.read_gene_sets(data_adaptor)) (genesets, tid) = annotations.read_gene_sets(data_adaptor)
if preferred_mimetype == "text/csv": if preferred_mimetype == "text/csv":
return make_response( return make_response(
@@ -373,10 +374,39 @@ def genesets_put(request, data_adaptor):
if genesets is None: if genesets is None:
abort(HTTPStatus.BAD_REQUEST) abort(HTTPStatus.BAD_REQUEST)
(gs, _) = data_adaptor.check_new_gene_sets((genesets, tid)) annotations.write_gene_sets(genesets, tid, data_adaptor)
annotations.write_gene_sets(gs, tid, data_adaptor)
return make_response(jsonify({"status": "OK"}), HTTPStatus.OK) return make_response(jsonify({"status": "OK"}), HTTPStatus.OK)
except (ValueError, DisabledFeatureError, KeyError) as e: except (ValueError, DisabledFeatureError, KeyError) as e:
return abort_and_log(HTTPStatus.BAD_REQUEST, str(e), include_exc_info=True) return abort_and_log(HTTPStatus.BAD_REQUEST, str(e), include_exc_info=True)
except (ObsoleteRequest, TypeError) as e: except (ObsoleteRequest, TypeError) as e:
return abort(HTTPStatus.NOT_FOUND, description=str(e)) return abort(HTTPStatus.NOT_FOUND, description=str(e))
def geneset_summary_get(request, data_adaptor):
preferred_mimetype = request.accept_mimetypes.best_match(["application/octet-stream"])
if preferred_mimetype != "application/octet-stream":
return abort(HTTPStatus.NOT_ACCEPTABLE)
geneset_name = request.args.get("geneset_name", default=None)
summary_method = request.args.get("method", default="mean")
request_tid = request.args.get("tid", default=None)
try:
annotations = data_adaptor.dataset_config.user_annotations
(genesets, tid) = annotations.read_gene_sets(data_adaptor)
if request_tid is not None and int(request_tid) != tid:
return abort(HTTPStatus.NOT_FOUND, "Obsolete TID")
if geneset_name is None or geneset_name not in genesets:
return abort(HTTPStatus.BAD_REQUEST, "Gene set name not found.")
genes = [g["gene_symbol"] for g in genesets.get(geneset_name)["genes"]]
return make_response(
data_adaptor.get_gene_set_summary(geneset_name, genes, summary_method),
HTTPStatus.OK,
{"Content-Type": "application/octet-stream"},
)
except (ValueError) as e:
return abort(HTTPStatus.NOT_FOUND, description=str(e))
except (UnsupportedSummaryMethod) as e:
return abort(HTTPStatus.BAD_REQUEST, description=str(e))
+28 -1
View File
@@ -5,6 +5,7 @@ import anndata
import numpy as np import numpy as np
from packaging import version from packaging import version
from pandas.core.dtypes.dtypes import CategoricalDtype from pandas.core.dtypes.dtypes import CategoricalDtype
import pandas as pd
from scipy import sparse from scipy import sparse
from server_timing import Timing as ServerTiming from server_timing import Timing as ServerTiming
@@ -12,7 +13,7 @@ import local_server.compute.diffexp_generic as diffexp_generic
from local_server.common.colors import convert_anndata_category_colors_to_cxg_category_colors from local_server.common.colors import convert_anndata_category_colors_to_cxg_category_colors
from local_server.common.constants import Axis, MAX_LAYOUTS from local_server.common.constants import Axis, MAX_LAYOUTS
from local_server.common.corpora import corpora_get_props_from_anndata from local_server.common.corpora import corpora_get_props_from_anndata
from local_server.common.errors import PrepareError, DatasetAccessError, FilterError from local_server.common.errors import PrepareError, DatasetAccessError, FilterError, UnsupportedSummaryMethod
from local_server.common.utils.type_conversion_utils import get_schema_type_hint_of_array from local_server.common.utils.type_conversion_utils import get_schema_type_hint_of_array
from local_server.compute.scanpy import scanpy_umap from local_server.compute.scanpy import scanpy_umap
from local_server.data_common.data_adaptor import DataAdaptor from local_server.data_common.data_adaptor import DataAdaptor
@@ -367,3 +368,29 @@ class AnndataAdaptor(DataAdaptor):
def get_var_keys(self): def get_var_keys(self):
# return list of keys # return list of keys
return self.data.var.keys().to_list() return self.data.var.keys().to_list()
def get_gene_set_summary(self, geneset_name, genes, method):
if method != "mean":
raise UnsupportedSummaryMethod("Unknown gene set summary method.")
var_index = self.parameters.get("var_names")
obs_selector, var_selector = self._filter_to_mask(
{
"var": {
"annotation_value": [
{
"name": var_index,
"values": genes,
}
]
}
}
)
X = self.get_X_array(obs_selector, var_selector)
if sparse.issparse(X):
mean = X.mean(axis=1)
else:
mean = X.mean(axis=1, keepdims=True)
col_idx = pd.Index([geneset_name])
return encode_matrix_fbs(mean, col_idx=col_idx, row_idx=None)
+22 -15
View File
@@ -17,7 +17,7 @@ class DataAdaptor(metaclass=ABCMeta):
"""Base class for loading and accessing matrix data""" """Base class for loading and accessing matrix data"""
def __init__(self, data_locator, app_config, dataset_config=None): def __init__(self, data_locator, app_config, dataset_config=None):
if type(app_config) != AppConfig: if not isinstance(app_config, AppConfig):
raise TypeError("config expected to be of type AppConfig") raise TypeError("config expected to be of type AppConfig")
# location to the dataset # location to the dataset
@@ -157,7 +157,7 @@ class DataAdaptor(metaclass=ABCMeta):
def _index_filter_to_mask(self, filter, count): def _index_filter_to_mask(self, filter, count):
mask = np.zeros((count,), dtype=np.bool) mask = np.zeros((count,), dtype=np.bool)
for i in filter: for i in filter:
if type(i) == list: if isinstance(i, list):
mask[i[0] : i[1]] = True mask[i[0] : i[1]] = True
else: else:
mask[i] = True mask[i] = True
@@ -262,15 +262,17 @@ class DataAdaptor(metaclass=ABCMeta):
return labels_df return labels_df
def check_new_gene_sets(self, args, context=None): def check_new_gene_sets(self, genesets, context=None):
""" """
Check validity of gene sets, return if correct, else raise error. Check validity of gene sets, return if correct, else raise error.
May also modify the gene set for conditions that should be resolved, May also modify the gene set for conditions that should be resolved,
but which do not warrant a hard error. but which do not warrant a hard error.
Argument 'args' must be a tuple containing (genesets, tid). Genesets Argument genesets may be either the REST OTA format (list of dicts) or the internal
may be either the REST OTA format (list of dicts) or the internal format format (dict of dicts, keyed by the geneset name).
(dict of dicts, keyed by the geneset name).
Will return a modified genesets (eg, remove dups) of the same type as the
provided argument. Ie, dict->dict, list->list
Rules: Rules:
0. all geneset names must be unique. 0. all geneset names must be unique.
@@ -283,17 +285,16 @@ class DataAdaptor(metaclass=ABCMeta):
will generate a warning and the symbol removed. will generate a warning and the symbol removed.
3. Duplicate gene symbols are silently de-duped. 3. Duplicate gene symbols are silently de-duped.
""" """
(genesets, tid) = args
messagefn = context["messagefn"] if context else (lambda x: None) messagefn = context["messagefn"] if context else (lambda x: None)
# accept genesets args as either the internal (dict) or REST (list) format, # accept genesets args as either the internal (dict) or REST (list) format,
# as they are identical except for the dict being keyed by geneset_name. # as they are identical except for the dict being keyed by geneset_name.
if type(genesets) not in (dict, list): if not isinstance(genesets, dict) and not isinstance(genesets, list):
raise ValueError("Genesets must be either dict or list.") raise ValueError("Genesets must be either dict or list.")
genesets = genesets if type(genesets) == list else genesets.values() genesets_iterable = genesets if isinstance(genesets, list) else genesets.values()
# 0. check for uniqueness of geneset names # 0. check for uniqueness of geneset names
geneset_names = [gs["geneset_name"] for gs in genesets] geneset_names = [gs["geneset_name"] for gs in genesets_iterable]
if len(set(geneset_names)) != len(geneset_names): if len(set(geneset_names)) != len(geneset_names):
raise KeyError("All geneset names must be unique.") raise KeyError("All geneset names must be unique.")
@@ -316,18 +317,19 @@ class DataAdaptor(metaclass=ABCMeta):
# 2. & 3. check for duplicate gene symbols, and those not present in the dataset. They will # 2. & 3. check for duplicate gene symbols, and those not present in the dataset. They will
# generate a warning and be removed. # generate a warning and be removed.
var_names = set(self.query_var_array(self.parameters.get("var_names"))) var_names = set(self.query_var_array(self.parameters.get("var_names")))
for geneset in genesets: for geneset in genesets_iterable:
if type(geneset) != dict: if not isinstance(geneset, dict):
raise ValueError("Each geneset must be a dict.") raise ValueError("Each geneset must be a dict.")
geneset_name = geneset["geneset_name"] geneset_name = geneset["geneset_name"]
genes = geneset["genes"] genes = geneset["genes"]
if type(genes) != list: if not isinstance(genes, list):
raise ValueError("Geneset genes field must be a list") raise ValueError("Geneset genes field must be a list")
geneset.setdefault("geneset_description", "")
gene_symbol_already_seen = set() gene_symbol_already_seen = set()
new_genes = [] new_genes = []
for gene in genes: for gene in genes:
gene_symbol = gene["gene_symbol"] gene_symbol = gene["gene_symbol"]
if type(gene_symbol) != str or len(gene_symbol) == 0: if not isinstance(gene_symbol, str) or len(gene_symbol) == 0:
raise ValueError("Gene symbol must be non-null string.") raise ValueError("Gene symbol must be non-null string.")
if gene_symbol in gene_symbol_already_seen: if gene_symbol in gene_symbol_already_seen:
# duplicate check # duplicate check
@@ -345,11 +347,12 @@ class DataAdaptor(metaclass=ABCMeta):
continue continue
gene_symbol_already_seen.add(gene_symbol) gene_symbol_already_seen.add(gene_symbol)
gene.setdefault("gene_description", "")
new_genes.append(gene) new_genes.append(gene)
geneset["genes"] = new_genes geneset["genes"] = new_genes
return args return genesets
def data_frame_to_fbs_matrix(self, filter, axis): def data_frame_to_fbs_matrix(self, filter, axis):
""" """
@@ -479,3 +482,7 @@ class DataAdaptor(metaclass=ABCMeta):
except RuntimeError: except RuntimeError:
lastmod = None lastmod = None
return lastmod return lastmod
@abstractmethod
def get_gene_set_summary(self, geneset_name, genes, method):
pass
+4
View File
@@ -10,3 +10,7 @@ second gene set,,SIK1
third gene set,,NO_SUCH_GENE third gene set,,NO_SUCH_GENE
fourth_gene_set,fourth description,,gene intentionally missing fourth_gene_set,fourth description,,gene intentionally missing
fifth_dataset,,, fifth_dataset,,,
summary test,,ACD,
summary test,,AATF,
summary test,,F5,
summary test,,PIGU,
1 # Test fixture
10 third gene set,,NO_SUCH_GENE
11 fourth_gene_set,fourth description,,gene intentionally missing
12 fifth_dataset,,,
13 summary test,,ACD,
14 summary test,,AATF,
15 summary test,,F5,
16 summary test,,PIGU,
+97 -5
View File
@@ -509,6 +509,16 @@ class EndPointsAnnDataGenesets(unittest.TestCase, EndPoints):
{"genes": [], "geneset_description": "", "geneset_name": "third gene set"}, {"genes": [], "geneset_description": "", "geneset_name": "third gene set"},
{"genes": [], "geneset_description": "fourth description", "geneset_name": "fourth_gene_set"}, {"genes": [], "geneset_description": "fourth description", "geneset_name": "fourth_gene_set"},
{"genes": [], "geneset_description": "", "geneset_name": "fifth_dataset"}, {"genes": [], "geneset_description": "", "geneset_name": "fifth_dataset"},
{
"genes": [
{"gene_description": "", "gene_symbol": "ACD"},
{"gene_description": "", "gene_symbol": "AATF"},
{"gene_description": "", "gene_symbol": "F5"},
{"gene_description": "", "gene_symbol": "PIGU"},
],
"geneset_description": "",
"geneset_name": "summary test",
},
], ],
"tid": 0, "tid": 0,
}, },
@@ -531,6 +541,10 @@ second gene set,,SIK1,\r
third gene set,,,\r third gene set,,,\r
fourth_gene_set,fourth description,,\r fourth_gene_set,fourth description,,\r
fifth_dataset,,,\r fifth_dataset,,,\r
summary test,,ACD,\r
summary test,,AATF,\r
summary test,,F5,\r
summary test,,PIGU,\r
""", """,
) )
@@ -677,8 +691,86 @@ fifth_dataset,,,\r
original_data, original_data,
) )
""" def test_get_geneset_summary(self):
TODO once we have some code to support it: endpoint = "geneset_summary?geneset_name=summary%20test&method=mean"
1. GET genesets_summary url = f"{self.URL_BASE}{endpoint}"
2. genesets_summary obeys tid header = {"Accept": "application/octet-stream"}
""" result = self.session.get(url, headers=header)
self.assertEqual(result.status_code, HTTPStatus.OK)
self.assertEqual(result.headers["Content-Type"], "application/octet-stream")
df = decode_fbs.decode_matrix_FBS(result.content)
self.assertEqual(df["n_rows"], 2638)
self.assertEqual(df["n_cols"], 1)
self.assertEqual(df["col_idx"], ["summary test"])
self.assertAlmostEqual(df["columns"][0][0], -0.19863907)
def test_get_geneset_summary_default_method(self):
endpoint = "geneset_summary?geneset_name=summary%20test"
url = f"{self.URL_BASE}{endpoint}"
header = {"Accept": "application/octet-stream"}
result = self.session.get(url, headers=header)
self.assertEqual(result.status_code, HTTPStatus.OK)
self.assertEqual(result.headers["Content-Type"], "application/octet-stream")
df = decode_fbs.decode_matrix_FBS(result.content)
self.assertEqual(df["n_rows"], 2638)
self.assertEqual(df["n_cols"], 1)
self.assertEqual(df["col_idx"], ["summary test"])
self.assertAlmostEqual(df["columns"][0][0], -0.19863907)
def test_get_geneset_summary_check_tid(self):
# get the TID
result = self.session.get(f"{self.URL_BASE}genesets", headers={"Accept": "application/json"})
self.assertEqual(result.status_code, HTTPStatus.OK)
tid = result.json()["tid"]
# current tid
endpoint = f"geneset_summary?geneset_name=summary%20test&tid={tid}"
result = self.session.get(f"{self.URL_BASE}{endpoint}", headers={"Accept": "application/octet-stream"})
self.assertEqual(result.status_code, HTTPStatus.OK)
# future tid
endpoint = f"geneset_summary?geneset_name=summary%20test&tid={tid+1}"
result = self.session.get(f"{self.URL_BASE}{endpoint}", headers={"Accept": "application/octet-stream"})
self.assertEqual(result.status_code, HTTPStatus.NOT_FOUND)
# past tid
endpoint = f"geneset_summary?geneset_name=summary%20test&tid={tid-1}"
result = self.session.get(f"{self.URL_BASE}{endpoint}", headers={"Accept": "application/octet-stream"})
self.assertEqual(result.status_code, HTTPStatus.NOT_FOUND)
# No tid - ie, skip check
endpoint = "geneset_summary?geneset_name=summary%20test"
result = self.session.get(f"{self.URL_BASE}{endpoint}", headers={"Accept": "application/octet-stream"})
self.assertEqual(result.status_code, HTTPStatus.OK)
def test_get_geneset_summary_edge_cases(self):
# attempt to summarize _all_ genesets, including edge cases with zero or one gene
result = self.session.get(f"{self.URL_BASE}genesets", headers={"Accept": "application/json"})
self.assertEqual(result.status_code, HTTPStatus.OK)
geneset_names = [gs["geneset_name"] for gs in result.json()["genesets"]]
for gs in geneset_names:
endpoint = f"geneset_summary?geneset_name={gs}"
result = self.session.get(f"{self.URL_BASE}{endpoint}", headers={"Accept": "application/octet-stream"})
self.assertEqual(result.status_code, HTTPStatus.OK)
self.assertEqual(result.headers["Content-Type"], "application/octet-stream")
df = decode_fbs.decode_matrix_FBS(result.content)
self.assertEqual(df["n_rows"], 2638)
self.assertEqual(df["n_cols"], 1)
self.assertEqual(df["col_idx"], [gs])
def test_get_geneset_error_handling(self):
# no geneset
endpoint = "geneset_summary"
result = self.session.get(f"{self.URL_BASE}{endpoint}", headers={"Accept": "application/octet-stream"})
self.assertEqual(result.status_code, HTTPStatus.BAD_REQUEST)
# unknown geneset
endpoint = "geneset_summary?geneset_name=NO_SUCH_GENE_SET"
result = self.session.get(f"{self.URL_BASE}{endpoint}", headers={"Accept": "application/octet-stream"})
self.assertEqual(result.status_code, HTTPStatus.BAD_REQUEST)
# unknown method
endpoint = "geneset_summary?geneset_name=summary%20test&method=NO_SUCH_METHOD"
result = self.session.get(f"{self.URL_BASE}{endpoint}", headers={"Accept": "application/octet-stream"})
self.assertEqual(result.status_code, HTTPStatus.BAD_REQUEST)