refactor to allow use with console entry points

This commit is contained in:
Eric Ma
2019-09-05 11:54:34 -04:00
parent 33985614d3
commit 952cb1e8e1
30 changed files with 224 additions and 64 deletions
+26 -18
View File
@@ -1,50 +1,58 @@
# Overview # # Overview
Cellxgene Gateway allows you to use the Cellxgene Server provided by the Chan Zuckerberg Institute (https://github.com/chanzuckerberg/cellxgene) with multiple datasets. It displays an index of available h5ad (anndata) files. When a user clicks on a file name, it launches a Cellxgene Server instance that loads that particular data file and once it is available proxies requests to that server. Cellxgene Gateway allows you to use the Cellxgene Server provided by the Chan Zuckerberg Institute (https://github.com/chanzuckerberg/cellxgene) with multiple datasets. It displays an index of available h5ad (anndata) files. When a user clicks on a file name, it launches a Cellxgene Server instance that loads that particular data file and once it is available proxies requests to that server.
# Running locally # ## Running locally
0. This project requires python 3.6 or higher. Please check your version with 0. This project requires python 3.6 or higher. Please check your version with
python --version ```bash
$ python --version
```
1. Set up a venv with 1. Set up a venv with
```
```bash
python -m venv .cellxgene-gateway python -m venv .cellxgene-gateway
source .cellxgene-gateway/bin/activate source .cellxgene-gateway/bin/activate
``` ```
2. Install requirements with 1. Install requirements with
```
```bash
pip install -r requirements.txt pip install -r requirements.txt
``` ```
3. Prepare a folder with .h5ad files, for example
``` 1. Prepare a folder with .h5ad files, for example
mkdir cellxgene_data
```bash
mkdir ../cellxgene_data
wget https://github.com/chanzuckerberg/cellxgene/raw/master/example-dataset/pbmc3k.h5ad -O ../cellxgene_data/pbmc3k.h5ad wget https://github.com/chanzuckerberg/cellxgene/raw/master/example-dataset/pbmc3k.h5ad -O ../cellxgene_data/pbmc3k.h5ad
``` ```
4. Copy run.sh.example to run.sh: 1. Copy run.sh.example to run.sh:
```
```bash
cp run.sh.example run.sh cp run.sh.example run.sh
``` ```
`run.sh` defines various environment variables: `run.sh` defines various environment variables:
* DEPLOYMENT_ENV - expects 'dev', 'tst' or 'prd' * `DEPLOYMENT_ENV` - expects 'dev', 'tst' or 'prd'
* CELLXGENE_LOCATION - the location of the cellxgene executable, e.g. ~/anaconda2/envs/cellxgene/bin/cellxgene * `CELLXGENE_LOCATION` - the location of the cellxgene executable, e.g. ~/anaconda2/envs/cellxgene/bin/cellxgene
* CELLXGENE_DATA - a directory that can contain subdirectories with .h5ad data files, *without* trailing slash, e.g. /mnt/cellxgene_data * `CELLXGENE_DATA` - a directory that can contain subdirectories with .h5ad data files, *without* trailing slash, e.g. /mnt/cellxgene_data
* GATEWAY_HOST - the hostname and port that the gateway will run on, typically localhost:5005 if running locally * `GATEWAY_HOST` - the hostname and port that the gateway will run on, typically localhost:5005 if running locally
* GATEWAY_PROTOCOL - typically http when running locally, can be https when deployed if the gateway is behind a load balancer or reverse proxy. * `GATEWAY_PROTOCOL` - typically http when running locally, can be https when deployed if the gateway is behind a load balancer or reverse proxy.
The defaults should be fine if you set up a venv and cellxgene_data folder as above. The defaults should be fine if you set up a venv and cellxgene_data folder as above.
5. Finally, execute run.sh: 1. Finally, execute run.sh:
``` ```
source run.sh source run.sh
``` ```
# Customization # # Customization
The current paradigm for customization is to modify files during a build or deployment phase: The current paradigm for customization is to modify files during a build or deployment phase:
+12
View File
@@ -0,0 +1,12 @@
Metadata-Version: 1.1
Name: cellxgene-gateway
Version: 0.1
Summary: Cell-by-gene Gateway
Home-page: http://github.com/Novartis/cellxgene-gateway
Author: Niket Patel, Yohann Potier, Alok Saldanha
Author-email: alok.saldanha@novartis.com
License: MIT
Description: UNKNOWN
Keywords: visualization,genomics
Platform: UNKNOWN
Classifier: Topic :: Scientific/Engineering :: Visualization
+20
View File
@@ -0,0 +1,20 @@
setup.py
cellxgene_gateway/__init__.py
cellxgene_gateway/backend_cache.py
cellxgene_gateway/cache_entry.py
cellxgene_gateway/cellxgene_exception.py
cellxgene_gateway/dir_util.py
cellxgene_gateway/env.py
cellxgene_gateway/extra_scripts.py
cellxgene_gateway/gateway.py
cellxgene_gateway/path_util.py
cellxgene_gateway/process_exception.py
cellxgene_gateway/prune_process_cache.py
cellxgene_gateway/subprocess_backend.py
cellxgene_gateway/util.py
cellxgene_gateway.egg-info/PKG-INFO
cellxgene_gateway.egg-info/SOURCES.txt
cellxgene_gateway.egg-info/dependency_links.txt
cellxgene_gateway.egg-info/entry_points.txt
cellxgene_gateway.egg-info/requires.txt
cellxgene_gateway.egg-info/top_level.txt
@@ -0,0 +1 @@
@@ -0,0 +1,3 @@
[console_scripts]
cellxgene-gateway = cellxgene_gateway.gateway:main
+5
View File
@@ -0,0 +1,5 @@
cellxgene
flask
flask_api
psutil
requests
+1
View File
@@ -0,0 +1 @@
cellxgene_gateway
@@ -6,4 +6,3 @@
# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES # under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
# OR CONDITIONS OF ANY KIND, either express or implied. See the License for # OR CONDITIONS OF ANY KIND, either express or implied. See the License for
# the specific language governing permissions and limitations under the License. # the specific language governing permissions and limitations under the License.
@@ -12,10 +12,10 @@ from threading import Thread
from flask_api import status from flask_api import status
import env from cellxgene_gateway import env
from cache_entry import CacheEntry from cellxgene_gateway.cache_entry import CacheEntry
from cellxgene_exception import CellxgeneException from cellxgene_gateway.cellxgene_exception import CellxgeneException
from subprocess_backend import SubprocessBackend from cellxgene_gateway.subprocess_backend import SubprocessBackend
process_backend = SubprocessBackend() process_backend = SubprocessBackend()
@@ -51,7 +51,8 @@ class BackendCache:
entry = CacheEntry.for_dataset(dataset, file_path, port) entry = CacheEntry.for_dataset(dataset, file_path, port)
background_thread = Thread( background_thread = Thread(
target=process_backend.launch, args=(env.cellxgene_location, scripts, entry) target=process_backend.launch,
args=(env.cellxgene_location, scripts, entry),
) )
background_thread.start() background_thread.start()
@@ -9,9 +9,9 @@
from flask import make_response, request from flask import make_response, request
from requests import get, post, put from requests import get, post, put
import env from cellxgene_gateway import env
from cellxgene_exception import CellxgeneException from cellxgene_gateway.cellxgene_exception import CellxgeneException
from util import current_time_stamp from cellxgene_gateway.util import current_time_stamp
class CacheEntry: class CacheEntry:
@@ -92,14 +92,26 @@ class CacheEntry:
if "content-type" in request.headers: if "content-type" in request.headers:
headers["content-type"] = request.headers["content-type"] headers["content-type"] = request.headers["content-type"]
if request.method in ['GET', 'HEAD', 'OPTIONS']: if request.method in ["GET", "HEAD", "OPTIONS"]:
cellxgene_response = get(cellxgene_basepath + subpath, headers=headers) cellxgene_response = get(
elif request.method == 'PUT': cellxgene_basepath + subpath, headers=headers
cellxgene_response = put(cellxgene_basepath + subpath, headers=headers, data=request.data.decode()) )
elif request.method == 'POST': elif request.method == "PUT":
cellxgene_response = post(cellxgene_basepath + subpath, headers=headers, data=request.data.decode()) cellxgene_response = put(
cellxgene_basepath + subpath,
headers=headers,
data=request.data.decode(),
)
elif request.method == "POST":
cellxgene_response = post(
cellxgene_basepath + subpath,
headers=headers,
data=request.data.decode(),
)
else: else:
raise CellxgeneException(f"Unexpected method {request.method}", 400) raise CellxgeneException(
f"Unexpected method {request.method}", 400
)
content_type = cellxgene_response.headers["content-type"] content_type = cellxgene_response.headers["content-type"]
if "text" in content_type: if "text" in content_type:
cellxgene_content = cellxgene_response.content.decode() cellxgene_content = cellxgene_response.content.decode()
@@ -112,7 +124,7 @@ class CacheEntry:
gateway_response = make_response( gateway_response = make_response(
gateway_content, gateway_content,
cellxgene_response.status_code, cellxgene_response.status_code,
{"Content-Type": content_type } {"Content-Type": content_type},
) )
return gateway_response return gateway_response
@@ -7,6 +7,7 @@
# OR CONDITIONS OF ANY KIND, either express or implied. See the License for # OR CONDITIONS OF ANY KIND, either express or implied. See the License for
# the specific language governing permissions and limitations under the License. # the specific language governing permissions and limitations under the License.
class CellxgeneException(Exception): class CellxgeneException(Exception):
def __init__(self, message, http_status): def __init__(self, message, http_status):
Exception.__init__(self) Exception.__init__(self)
@@ -11,8 +11,8 @@ import os
from flask_api import status from flask_api import status
import env from cellxgene_gateway import env
from cellxgene_exception import CellxgeneException from cellxgene_gateway.cellxgene_exception import CellxgeneException
def is_subdir(full_path, parent_path): def is_subdir(full_path, parent_path):
@@ -31,7 +31,8 @@ def create_dir(parent_path, dir_name):
) )
elif not os.path.exists(parent_path): elif not os.path.exists(parent_path):
raise CellxgeneException( raise CellxgeneException(
"The selected User directory does not exist.", status.HTTP_400_BAD_REQUEST "The selected User directory does not exist.",
status.HTTP_400_BAD_REQUEST,
) )
elif os.path.exists(full_path): elif os.path.exists(full_path):
raise CellxgeneException( raise CellxgeneException(
@@ -74,7 +75,8 @@ def recurse_dir(path):
} }
else: else:
raise CellxgeneException( raise CellxgeneException(
"Given path is neither file nor directory.", status.HTTP_400_BAD_REQUEST "Given path is neither file nor directory.",
status.HTTP_400_BAD_REQUEST,
) )
return [make_entry(x) for x in os.listdir(path)] return [make_entry(x) for x in os.listdir(path)]
+29
View File
@@ -15,3 +15,32 @@ cellxgene_data = os.environ.get("CELLXGENE_DATA")
gateway_host = os.environ.get("GATEWAY_HOST") gateway_host = os.environ.get("GATEWAY_HOST")
gateway_protocol = os.environ.get("GATEWAY_PROTOCOL") gateway_protocol = os.environ.get("GATEWAY_PROTOCOL")
ip = os.environ.get("GATEWAY_IP") ip = os.environ.get("GATEWAY_IP")
env_vars = {
"DEPLOYMENT_ENV": deployment_env,
"CELLXGENE_LOCATION": cellxgene_location,
"CELLXGENE_DATA": cellxgene_data,
"GATEWAY_HOST": gateway_host,
"GATEWAY_PROTOCOL": gateway_protocol,
"GATEWAY_IP": ip,
}
if not all(env_vars.values()):
raise ValueError(
f"""
Please ensure that environment variables are set correctly.
The ones with None below are missing and need to be set.
{env_vars}
Set them at the terminal before running the gateway.
An example is:
export CELLXGENE_LOCATION=~/anaconda/envs/cellxgene-dev/bin/cellxgene
export CELLXGENE_DATA=../cellxgene_data
export DEPLOYMENT_ENV=dev
export GATEWAY_HOST=localhost:5005
export GATEWAY_PROTOCOL=http
export GATEWAY_IP=127.0.0.1
"""
)
@@ -7,6 +7,7 @@
# OR CONDITIONS OF ANY KIND, either express or implied. See the License for # OR CONDITIONS OF ANY KIND, either express or implied. See the License for
# the specific language governing permissions and limitations under the License. # the specific language governing permissions and limitations under the License.
def get_extra_scripts(): def get_extra_scripts():
# can be array of script tags to inject on every page, e.g. for google analytics could be # can be array of script tags to inject on every page, e.g. for google analytics could be
# ['https://www.googletagmanager.com/gtag/js?id=UA-123456-2', # ['https://www.googletagmanager.com/gtag/js?id=UA-123456-2',
+38 -17
View File
@@ -12,19 +12,25 @@ import datetime
import os import os
from threading import Thread from threading import Thread
from flask import Flask, redirect, render_template, request, send_from_directory from flask import (
Flask,
redirect,
render_template,
request,
send_from_directory,
)
from flask_api import status from flask_api import status
from werkzeug import secure_filename from werkzeug import secure_filename
import env from cellxgene_gateway import env
from backend_cache import BackendCache from cellxgene_gateway.backend_cache import BackendCache
from cellxgene_exception import CellxgeneException from cellxgene_gateway.cellxgene_exception import CellxgeneException
from dir_util import create_dir, recurse_dir, render_entries from cellxgene_gateway.dir_util import create_dir, recurse_dir, render_entries
from extra_scripts import get_extra_scripts from cellxgene_gateway.extra_scripts import get_extra_scripts
from path_util import get_dataset, get_file_path from cellxgene_gateway.path_util import get_dataset, get_file_path
from process_exception import ProcessException from cellxgene_gateway.process_exception import ProcessException
from prune_process_cache import PruneProcessCache from cellxgene_gateway.prune_process_cache import PruneProcessCache
from util import current_time_stamp from cellxgene_gateway.util import current_time_stamp
app = Flask(__name__) app = Flask(__name__)
cache = BackendCache() cache = BackendCache()
@@ -38,7 +44,9 @@ def handle_invalid_usage(error):
return ( return (
render_template( render_template(
"cellxgene_error.html", extra_scripts=get_extra_scripts(), message=message "cellxgene_error.html",
extra_scripts=get_extra_scripts(),
message=message,
), ),
error.http_status, error.http_status,
) )
@@ -56,7 +64,9 @@ def handle_invalid_process(error):
return ( return (
render_template( render_template(
"process_error.html", extra_scripts=get_extra_scripts(), message=message "process_error.html",
extra_scripts=get_extra_scripts(),
message=message,
), ),
error.http_status, error.http_status,
) )
@@ -116,7 +126,9 @@ def upload_file():
if "file" in request.files: if "file" in request.files:
f = request.files["file"] f = request.files["file"]
if f and f.filename.endswith(".h5ad"): if f and f.filename.endswith(".h5ad"):
f.save(full_upload_path + "/" + secure_filename(f.filename)) f.save(
full_upload_path + "/" + secure_filename(f.filename)
)
return redirect("/filecrawl.html", code=302) return redirect("/filecrawl.html", code=302)
else: else:
raise CellxgeneException( raise CellxgeneException(
@@ -125,10 +137,13 @@ def upload_file():
) )
else: else:
raise CellxgeneException( raise CellxgeneException(
"A file must be chosen to upload.", status.HTTP_400_BAD_REQUEST "A file must be chosen to upload.",
status.HTTP_400_BAD_REQUEST,
) )
else: else:
raise CellxgeneException("Invalid directory.", status.HTTP_400_BAD_REQUEST) raise CellxgeneException(
"Invalid directory.", status.HTTP_400_BAD_REQUEST
)
return redirect(env.location, code=302) return redirect(env.location, code=302)
@@ -139,7 +154,9 @@ def filecrawl():
entries = recurse_dir(env.cellxgene_data) entries = recurse_dir(env.cellxgene_data)
rendered_html = render_entries(entries) rendered_html = render_entries(entries)
return render_template( return render_template(
"filecrawl.html", extra_scripts=get_extra_scripts(), rendered_html=rendered_html "filecrawl.html",
extra_scripts=get_extra_scripts(),
rendered_html=rendered_html,
) )
@@ -166,8 +183,12 @@ def do_GET(path):
raise ProcessException.from_pid_object(match) raise ProcessException.from_pid_object(match)
if __name__ == "__main__": def main():
background_thread = Thread(target=PruneProcessCache(cache)) background_thread = Thread(target=PruneProcessCache(cache))
background_thread.start() background_thread.start()
app.run(host="0.0.0.0", port=5005, debug=False) app.run(host="0.0.0.0", port=5005, debug=False)
if __name__ == "__main__":
main()
@@ -11,8 +11,8 @@ import os
from flask_api import status from flask_api import status
import env from cellxgene_gateway import env
from cellxgene_exception import CellxgeneException from cellxgene_gateway.cellxgene_exception import CellxgeneException
def get_dataset(path): def get_dataset(path):
@@ -7,6 +7,7 @@
# OR CONDITIONS OF ANY KIND, either express or implied. See the License for # OR CONDITIONS OF ANY KIND, either express or implied. See the License for
# the specific language governing permissions and limitations under the License. # the specific language governing permissions and limitations under the License.
class ProcessException(Exception): class ProcessException(Exception):
def __init__(self, message, stdout, stderr, http_status): def __init__(self, message, stdout, stderr, http_status):
Exception.__init__(self) Exception.__init__(self)
@@ -11,7 +11,7 @@ import time
import psutil import psutil
from util import current_time_stamp from cellxgene_gateway.util import current_time_stamp
class PruneProcessCache: class PruneProcessCache:

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 64 KiB

@@ -12,7 +12,7 @@ import subprocess
from flask_api import status from flask_api import status
from process_exception import ProcessException from cellxgene_gateway.process_exception import ProcessException
class SubprocessBackend: class SubprocessBackend:
@@ -39,7 +39,6 @@
<u>File Crawler: Allows you to view all uploaded data.</u></a> <u>File Crawler: Allows you to view all uploaded data.</u></a>
</div> </div>
<br> <br>
<h1 style="padding-left:35px"> <h1 style="padding-left:35px">
+11
View File
@@ -0,0 +1,11 @@
name: cellxgene-dev
channels:
- conda-forge
dependencies:
- python=3.7
- requests
- flask
- psutil
- pip:
- flask-api
- cellxgene
-1
View File
@@ -3,4 +3,3 @@ flask
flask_api flask_api
psutil psutil
requests requests
+34
View File
@@ -0,0 +1,34 @@
import os
from setuptools import setup
def parse_requirements():
reqs = []
with open("requirements.txt", "r") as f:
for l in f.readlines():
reqs.append(l.strip("\n"))
return reqs
install_reqs = parse_requirements()
setup(
# mandatory
name="cellxgene-gateway",
# mandatory
version="0.1",
# mandatory
author="Niket Patel, Yohann Potier, Alok Saldanha",
author_email="alok.saldanha@novartis.com",
description=("Cell-by-gene Gateway"),
license="MIT",
keywords="visualization, genomics",
url="http://github.com/Novartis/cellxgene-gateway",
packages=["cellxgene_gateway"],
package_data={"": ["README.md", "LICENSE.txt"]},
install_requires=install_reqs,
entry_points={
"console_scripts": ["cellxgene-gateway=cellxgene_gateway.gateway:main"]
},
classifiers=["Topic :: Scientific/Engineering :: Visualization"],
)