mirror of
https://github.com/Novartis/cellxgene-gateway.git
synced 2026-09-27 08:38:12 +08:00
Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0eed8bcb30 | ||
|
|
0f79e1a010 | ||
|
|
d5008893f7 |
@@ -186,6 +186,21 @@ pip install isort flake8 black
|
||||
isort -rc . # rc means recursive, and was deprecated in dev version of isort
|
||||
black .
|
||||
```
|
||||
## Dependency management
|
||||
|
||||
We use the following files for dependency management:
|
||||
|
||||
* environment.yml - specifies a conda environment sufficient to run the packages
|
||||
* setup.cfg - lists static dependency information, both minimal and extra dependencies
|
||||
* setup.py - dynamically generated package information
|
||||
* requirements.txt - simple wrapper invoking setup.py
|
||||
|
||||
For more details, see the folloiwng links:
|
||||
|
||||
* https://towardsdatascience.com/setuptools-python-571e7d5500f2
|
||||
* https://towardsdatascience.com/requirements-vs-setuptools-python-ae3ee66e28af
|
||||
|
||||
|
||||
|
||||
# Getting Help
|
||||
|
||||
|
||||
@@ -26,8 +26,8 @@ def url(endpoint, descriptor, source_name):
|
||||
|
||||
|
||||
def view_url(descriptor, source_name):
|
||||
return url("do_view", descriptor, source_name)
|
||||
return url("gateway_blueprint.do_view", descriptor, source_name)
|
||||
|
||||
|
||||
def relaunch_url(descriptor, source_name):
|
||||
return url("do_relaunch", descriptor, source_name)
|
||||
return url("gateway_blueprint.do_relaunch", descriptor, source_name)
|
||||
|
||||
+24
-269
@@ -1,45 +1,15 @@
|
||||
# Copyright 2019 Novartis Institutes for BioMedical Research Inc. Licensed
|
||||
# under the Apache License, Version 2.0 (the "License"); you may not use
|
||||
# this file except in compliance with the License. You may obtain a copy
|
||||
# of the License at http://www.apache.org/licenses/LICENSE-2.0. Unless
|
||||
# required by applicable law or agreed to in writing, software distributed
|
||||
# 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
|
||||
# the specific language governing permissions and limitations under the License.
|
||||
# import BaseHTTPServer
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import urllib.parse
|
||||
from threading import Lock, Thread
|
||||
|
||||
from flask import (
|
||||
Flask,
|
||||
make_response,
|
||||
redirect,
|
||||
render_template,
|
||||
request,
|
||||
send_from_directory,
|
||||
url_for,
|
||||
)
|
||||
import typer
|
||||
from flask import Flask
|
||||
from werkzeug.middleware.proxy_fix import ProxyFix
|
||||
|
||||
from cellxgene_gateway import env, flask_util
|
||||
from cellxgene_gateway.backend_cache import BackendCache
|
||||
from cellxgene_gateway.cache_entry import CacheEntryStatus
|
||||
from cellxgene_gateway.cache_key import CacheKey
|
||||
from cellxgene_gateway.cellxgene_exception import CellxgeneException
|
||||
from cellxgene_gateway.extra_scripts import get_extra_scripts
|
||||
from cellxgene_gateway.filecrawl import render_item_source
|
||||
from cellxgene_gateway.process_exception import ProcessException
|
||||
from cellxgene_gateway.prune_process_cache import PruneProcessCache
|
||||
from cellxgene_gateway import env, flask_util, gateway_blueprint
|
||||
from cellxgene_gateway.util import current_time_stamp
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
item_sources = []
|
||||
default_item_source = None
|
||||
|
||||
|
||||
def _force_https(app):
|
||||
def wrapper(environ, start_response):
|
||||
@@ -50,14 +20,6 @@ def _force_https(app):
|
||||
return wrapper
|
||||
|
||||
|
||||
def set_no_cache(resp):
|
||||
resp.headers["Cache-Control"] = "no-cache, no-store, must-revalidate"
|
||||
resp.headers["Pragma"] = "no-cache"
|
||||
resp.headers["Expires"] = "0"
|
||||
resp.headers["Cache-Control"] = "public, max-age=0"
|
||||
return resp
|
||||
|
||||
|
||||
app.wsgi_app = _force_https(app.wsgi_app)
|
||||
if (
|
||||
env.proxy_fix_for > 0
|
||||
@@ -75,229 +37,8 @@ if (
|
||||
x_prefix=env.proxy_fix_prefix,
|
||||
)
|
||||
|
||||
cache = BackendCache()
|
||||
|
||||
|
||||
@app.errorhandler(CellxgeneException)
|
||||
def handle_invalid_usage(error):
|
||||
|
||||
message = f"{error.http_status} Error : {error.message}"
|
||||
|
||||
return (
|
||||
render_template(
|
||||
"cellxgene_error.html",
|
||||
extra_scripts=get_extra_scripts(),
|
||||
message=message,
|
||||
),
|
||||
error.http_status,
|
||||
)
|
||||
|
||||
|
||||
@app.errorhandler(ProcessException)
|
||||
def handle_invalid_process(error):
|
||||
|
||||
message = []
|
||||
|
||||
message.append(error.message)
|
||||
message.append(f"{error.http_status} Error.")
|
||||
message.append(f"Stdout: {error.stdout}")
|
||||
message.append(f"Stderr: {error.stderr}")
|
||||
|
||||
return (
|
||||
render_template(
|
||||
"process_error.html",
|
||||
extra_scripts=get_extra_scripts(),
|
||||
message=error.message,
|
||||
http_status=error.http_status,
|
||||
stdout=error.stdout,
|
||||
stderr=error.stderr,
|
||||
relaunch_url=error.key.relaunch_url(),
|
||||
annotation_file=error.key.annotation_descriptor,
|
||||
),
|
||||
error.http_status,
|
||||
)
|
||||
|
||||
|
||||
@app.route("/favicon.ico")
|
||||
def favicon():
|
||||
return send_from_directory(
|
||||
os.path.join(app.root_path, "static"),
|
||||
"nibr.ico",
|
||||
mimetype="image/vnd.microsof.icon",
|
||||
)
|
||||
|
||||
|
||||
@app.route("/")
|
||||
def index():
|
||||
return render_template(
|
||||
"index.html",
|
||||
ip=env.ip,
|
||||
cellxgene_data=env.cellxgene_data,
|
||||
extra_scripts=get_extra_scripts(),
|
||||
)
|
||||
|
||||
|
||||
@app.route("/filecrawl.html")
|
||||
@app.route("/filecrawl/<path:path>")
|
||||
def filecrawl(path=None):
|
||||
source_name = request.args.get("source")
|
||||
sources = (
|
||||
filter(
|
||||
lambda x: x.name == urllib.parse.unquote_plus(source_name),
|
||||
item_sources,
|
||||
)
|
||||
if source_name
|
||||
else item_sources
|
||||
)
|
||||
# loop all data sources --
|
||||
rendered_sources = [
|
||||
render_item_source(item_source, path) for item_source in sources
|
||||
] # will we need to make this async in the page???
|
||||
rendered_html = "\n".join(rendered_sources)
|
||||
|
||||
resp = make_response(
|
||||
render_template(
|
||||
"filecrawl.html",
|
||||
extra_scripts=get_extra_scripts(),
|
||||
rendered_html=rendered_html,
|
||||
path=path,
|
||||
)
|
||||
)
|
||||
set_no_cache(resp)
|
||||
return resp
|
||||
|
||||
|
||||
entry_lock = Lock()
|
||||
|
||||
|
||||
def matching_source(source_name):
|
||||
if source_name is None:
|
||||
source_name = default_item_source.name
|
||||
matching = [i for i in item_sources if i.name == source_name]
|
||||
if len(matching) != 1:
|
||||
raise Exception(f"Could not find matching item source {source_name}")
|
||||
source = matching[0]
|
||||
return source
|
||||
|
||||
|
||||
@app.route(
|
||||
"/source/<path:source_name>/view/<path:path>",
|
||||
methods=["GET", "PUT", "POST"],
|
||||
)
|
||||
@app.route("/view/<path:path>", methods=["GET", "PUT", "POST"])
|
||||
def do_view(path, source_name=None):
|
||||
source = matching_source(source_name)
|
||||
match = cache.check_path(source, path)
|
||||
|
||||
if match is None:
|
||||
lookup = source.lookup(path)
|
||||
if lookup is None:
|
||||
raise CellxgeneException(
|
||||
f"Could not find item for path {path} in source {source.name}",
|
||||
404,
|
||||
)
|
||||
key = CacheKey.for_lookup(source, lookup)
|
||||
print(
|
||||
f"view path={path}, source_name={source_name}, dataset={key.file_path}, annotation_file= {key.annotation_file_path}, key={key.descriptor}, source={key.source_name}"
|
||||
)
|
||||
with entry_lock:
|
||||
match = cache.check_entry(key)
|
||||
if match is None:
|
||||
uascripts = get_extra_scripts()
|
||||
match = cache.create_entry(key, uascripts)
|
||||
|
||||
match.timestamp = current_time_stamp()
|
||||
|
||||
if (
|
||||
match.status == CacheEntryStatus.loaded
|
||||
or match.status == CacheEntryStatus.loading
|
||||
):
|
||||
if source.is_authorized(match.key.descriptor):
|
||||
return match.serve_content(path)
|
||||
else:
|
||||
raise CellxgeneException("User not authorized to access this data", 403)
|
||||
elif match.status == CacheEntryStatus.error:
|
||||
raise ProcessException.from_cache_entry(match)
|
||||
|
||||
|
||||
@app.route("/cache_status", methods=["GET"])
|
||||
def do_GET_status():
|
||||
return render_template(
|
||||
"cache_status.html",
|
||||
entry_list=cache.entry_list,
|
||||
extra_scripts=get_extra_scripts(),
|
||||
)
|
||||
|
||||
|
||||
@app.route("/cache_status.json", methods=["GET"])
|
||||
def do_GET_status_json():
|
||||
return json.dumps(
|
||||
{
|
||||
"launchtime": app.launchtime,
|
||||
"entry_list": [
|
||||
{
|
||||
"dataset": entry.key.dataset,
|
||||
"annotation_file": entry.key.annotation_file,
|
||||
"launchtime": entry.launchtime,
|
||||
"last_access": entry.timestamp,
|
||||
"status": entry.status,
|
||||
}
|
||||
for entry in cache.entry_list
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@app.route("/relaunch/<path:path>", methods=["GET"])
|
||||
def do_relaunch(path):
|
||||
source_name = request.args.get("source_name") or default_item_source.name
|
||||
source = matching_source(source_name)
|
||||
key = CacheKey.for_lookup(source, source.lookup(path))
|
||||
match = cache.check_entry(key)
|
||||
if not match is None:
|
||||
match.terminate()
|
||||
return redirect(
|
||||
key.view_url,
|
||||
code=302,
|
||||
)
|
||||
|
||||
|
||||
@app.route("/terminate/<path:path>", methods=["GET"])
|
||||
def do_terminate(path):
|
||||
source_name = request.args.get("source_name") or default_item_source.name
|
||||
source = matching_source(source_name)
|
||||
key = CacheKey.for_lookup(source, source.lookup(path))
|
||||
match = cache.check_entry(key)
|
||||
if not match is None:
|
||||
match.terminate()
|
||||
return redirect(url_for("do_GET_status"), code=302)
|
||||
|
||||
|
||||
@app.route("/metadata/ip_address", methods=["GET"])
|
||||
def ip_address():
|
||||
resp = make_response(env.ip)
|
||||
return set_no_cache(resp)
|
||||
|
||||
|
||||
def launch():
|
||||
env.validate()
|
||||
if not item_sources or not len(item_sources):
|
||||
raise Exception("No data sources specified for Cellxgene Gateway")
|
||||
|
||||
global default_item_source
|
||||
if default_item_source is None:
|
||||
default_item_source = item_sources[0]
|
||||
|
||||
pruner = PruneProcessCache(cache)
|
||||
|
||||
background_thread = Thread(target=pruner)
|
||||
background_thread.start()
|
||||
|
||||
app.launchtime = current_time_stamp()
|
||||
app.run(host="0.0.0.0", port=env.gateway_port, debug=False)
|
||||
|
||||
|
||||
def main():
|
||||
def main(prometheus: bool = False):
|
||||
logging.basicConfig(
|
||||
level=env.log_level,
|
||||
format="%(asctime)s:%(name)s:%(levelname)s:%(message)s",
|
||||
@@ -308,19 +49,33 @@ def main():
|
||||
if cellxgene_bucket is not None:
|
||||
from cellxgene_gateway.items.s3.s3item_source import S3ItemSource
|
||||
|
||||
item_sources.append(S3ItemSource(cellxgene_bucket, name="s3"))
|
||||
gateway_blueprint.item_sources.append(S3ItemSource(cellxgene_bucket, name="s3"))
|
||||
default_item_source = "s3"
|
||||
if cellxgene_data is not None:
|
||||
from cellxgene_gateway.items.file.fileitem_source import FileItemSource
|
||||
|
||||
item_sources.append(FileItemSource(cellxgene_data, name="local"))
|
||||
gateway_blueprint.item_sources.append(
|
||||
FileItemSource(cellxgene_data, name="local")
|
||||
)
|
||||
default_item_source = "local"
|
||||
if len(item_sources) == 0:
|
||||
if len(gateway_blueprint.item_sources) == 0:
|
||||
raise Exception("Please specify CELLXGENE_DATA or CELLXGENE_BUCKET")
|
||||
flask_util.include_source_in_url = len(item_sources) > 1
|
||||
flask_util.include_source_in_url = len(gateway_blueprint.item_sources) > 1
|
||||
|
||||
launch()
|
||||
if prometheus:
|
||||
from cellxgene_gateway.prometheus import add_metrics
|
||||
|
||||
add_metrics(app)
|
||||
app.register_blueprint(gateway_blueprint.gateway_blueprint)
|
||||
gateway_blueprint.launch()
|
||||
|
||||
app.launchtime = current_time_stamp()
|
||||
app.run(host="0.0.0.0", port=env.gateway_port, debug=False)
|
||||
|
||||
|
||||
def run():
|
||||
typer.run(main)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
run()
|
||||
|
||||
@@ -0,0 +1,266 @@
|
||||
# Copyright 2019 Novartis Institutes for BioMedical Research Inc. Licensed
|
||||
# under the Apache License, Version 2.0 (the "License"); you may not use
|
||||
# this file except in compliance with the License. You may obtain a copy
|
||||
# of the License at http://www.apache.org/licenses/LICENSE-2.0. Unless
|
||||
# required by applicable law or agreed to in writing, software distributed
|
||||
# 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
|
||||
# the specific language governing permissions and limitations under the License.
|
||||
# import BaseHTTPServer
|
||||
import json
|
||||
import os
|
||||
import urllib.parse
|
||||
from threading import Lock, Thread
|
||||
|
||||
from flask import (
|
||||
Blueprint,
|
||||
current_app,
|
||||
make_response,
|
||||
redirect,
|
||||
render_template,
|
||||
request,
|
||||
send_from_directory,
|
||||
url_for,
|
||||
)
|
||||
|
||||
from cellxgene_gateway import env
|
||||
from cellxgene_gateway.backend_cache import BackendCache
|
||||
from cellxgene_gateway.cache_entry import CacheEntryStatus
|
||||
from cellxgene_gateway.cache_key import CacheKey
|
||||
from cellxgene_gateway.cellxgene_exception import CellxgeneException
|
||||
from cellxgene_gateway.extra_scripts import get_extra_scripts
|
||||
from cellxgene_gateway.filecrawl import render_item_source
|
||||
from cellxgene_gateway.process_exception import ProcessException
|
||||
from cellxgene_gateway.prune_process_cache import PruneProcessCache
|
||||
from cellxgene_gateway.util import current_time_stamp
|
||||
|
||||
gateway_blueprint = Blueprint("gateway_blueprint", __name__)
|
||||
item_sources = []
|
||||
default_item_source = None
|
||||
|
||||
|
||||
def set_no_cache(resp):
|
||||
resp.headers["Cache-Control"] = "no-cache, no-store, must-revalidate"
|
||||
resp.headers["Pragma"] = "no-cache"
|
||||
resp.headers["Expires"] = "0"
|
||||
resp.headers["Cache-Control"] = "public, max-age=0"
|
||||
return resp
|
||||
|
||||
|
||||
cache = BackendCache()
|
||||
|
||||
|
||||
@gateway_blueprint.errorhandler(CellxgeneException)
|
||||
def handle_invalid_usage(error):
|
||||
|
||||
message = f"{error.http_status} Error : {error.message}"
|
||||
|
||||
return (
|
||||
render_template(
|
||||
"cellxgene_error.html",
|
||||
extra_scripts=get_extra_scripts(),
|
||||
message=message,
|
||||
),
|
||||
error.http_status,
|
||||
)
|
||||
|
||||
|
||||
@gateway_blueprint.errorhandler(ProcessException)
|
||||
def handle_invalid_process(error):
|
||||
|
||||
message = []
|
||||
|
||||
message.append(error.message)
|
||||
message.append(f"{error.http_status} Error.")
|
||||
message.append(f"Stdout: {error.stdout}")
|
||||
message.append(f"Stderr: {error.stderr}")
|
||||
|
||||
return (
|
||||
render_template(
|
||||
"process_error.html",
|
||||
extra_scripts=get_extra_scripts(),
|
||||
message=error.message,
|
||||
http_status=error.http_status,
|
||||
stdout=error.stdout,
|
||||
stderr=error.stderr,
|
||||
relaunch_url=error.key.relaunch_url(),
|
||||
annotation_file=error.key.annotation_descriptor,
|
||||
),
|
||||
error.http_status,
|
||||
)
|
||||
|
||||
|
||||
@gateway_blueprint.route("/favicon.ico")
|
||||
def favicon():
|
||||
return send_from_directory(
|
||||
os.path.join(current_app.root_path, "static"),
|
||||
"nibr.ico",
|
||||
mimetype="image/vnd.microsof.icon",
|
||||
)
|
||||
|
||||
|
||||
@gateway_blueprint.route("/")
|
||||
def index():
|
||||
return render_template(
|
||||
"index.html",
|
||||
ip=env.ip,
|
||||
cellxgene_data=env.cellxgene_data,
|
||||
extra_scripts=get_extra_scripts(),
|
||||
)
|
||||
|
||||
|
||||
@gateway_blueprint.route("/filecrawl.html")
|
||||
@gateway_blueprint.route("/filecrawl/<path:path>")
|
||||
def filecrawl(path=None):
|
||||
source_name = request.args.get("source")
|
||||
sources = (
|
||||
filter(
|
||||
lambda x: x.name == urllib.parse.unquote_plus(source_name),
|
||||
item_sources,
|
||||
)
|
||||
if source_name
|
||||
else item_sources
|
||||
)
|
||||
# loop all data sources --
|
||||
rendered_sources = [
|
||||
render_item_source(item_source, path) for item_source in sources
|
||||
] # will we need to make this async in the page???
|
||||
rendered_html = "\n".join(rendered_sources)
|
||||
|
||||
resp = make_response(
|
||||
render_template(
|
||||
"filecrawl.html",
|
||||
extra_scripts=get_extra_scripts(),
|
||||
rendered_html=rendered_html,
|
||||
path=path,
|
||||
)
|
||||
)
|
||||
set_no_cache(resp)
|
||||
return resp
|
||||
|
||||
|
||||
entry_lock = Lock()
|
||||
|
||||
|
||||
def matching_source(source_name):
|
||||
if source_name is None:
|
||||
source_name = default_item_source.name
|
||||
matching = [i for i in item_sources if i.name == source_name]
|
||||
if len(matching) != 1:
|
||||
raise Exception(f"Could not find matching item source {source_name}")
|
||||
source = matching[0]
|
||||
return source
|
||||
|
||||
|
||||
@gateway_blueprint.route(
|
||||
"/source/<path:source_name>/view/<path:path>",
|
||||
methods=["GET", "PUT", "POST"],
|
||||
)
|
||||
@gateway_blueprint.route("/view/<path:path>", methods=["GET", "PUT", "POST"])
|
||||
def do_view(path, source_name=None):
|
||||
source = matching_source(source_name)
|
||||
match = cache.check_path(source, path)
|
||||
|
||||
if match is None:
|
||||
lookup = source.lookup(path)
|
||||
if lookup is None:
|
||||
raise CellxgeneException(
|
||||
f"Could not find item for path {path} in source {source.name}",
|
||||
404,
|
||||
)
|
||||
key = CacheKey.for_lookup(source, lookup)
|
||||
print(
|
||||
f"view path={path}, source_name={source_name}, dataset={key.file_path}, annotation_file= {key.annotation_file_path}, key={key.descriptor}, source={key.source_name}"
|
||||
)
|
||||
with entry_lock:
|
||||
match = cache.check_entry(key)
|
||||
if match is None:
|
||||
uascripts = get_extra_scripts()
|
||||
match = cache.create_entry(key, uascripts)
|
||||
|
||||
match.timestamp = current_time_stamp()
|
||||
|
||||
if (
|
||||
match.status == CacheEntryStatus.loaded
|
||||
or match.status == CacheEntryStatus.loading
|
||||
):
|
||||
if source.is_authorized(match.key.descriptor):
|
||||
return match.serve_content(path)
|
||||
else:
|
||||
raise CellxgeneException("User not authorized to access this data", 403)
|
||||
elif match.status == CacheEntryStatus.error:
|
||||
raise ProcessException.from_cache_entry(match)
|
||||
|
||||
|
||||
@gateway_blueprint.route("/cache_status", methods=["GET"])
|
||||
def do_GET_status():
|
||||
return render_template(
|
||||
"cache_status.html",
|
||||
entry_list=cache.entry_list,
|
||||
extra_scripts=get_extra_scripts(),
|
||||
)
|
||||
|
||||
|
||||
@gateway_blueprint.route("/cache_status.json", methods=["GET"])
|
||||
def do_GET_status_json():
|
||||
return json.dumps(
|
||||
{
|
||||
"launchtime": current_app.launchtime,
|
||||
"entry_list": [
|
||||
{
|
||||
"dataset": entry.key.dataset,
|
||||
"annotation_file": entry.key.annotation_file,
|
||||
"launchtime": entry.launchtime,
|
||||
"last_access": entry.timestamp,
|
||||
"status": entry.status,
|
||||
}
|
||||
for entry in cache.entry_list
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@gateway_blueprint.route("/relaunch/<path:path>", methods=["GET"])
|
||||
def do_relaunch(path):
|
||||
source_name = request.args.get("source_name") or default_item_source.name
|
||||
source = matching_source(source_name)
|
||||
key = CacheKey.for_lookup(source, source.lookup(path))
|
||||
match = cache.check_entry(key)
|
||||
if not match is None:
|
||||
match.terminate()
|
||||
return redirect(
|
||||
key.view_url,
|
||||
code=302,
|
||||
)
|
||||
|
||||
|
||||
@gateway_blueprint.route("/terminate/<path:path>", methods=["GET"])
|
||||
def do_terminate(path):
|
||||
source_name = request.args.get("source_name") or default_item_source.name
|
||||
source = matching_source(source_name)
|
||||
key = CacheKey.for_lookup(source, source.lookup(path))
|
||||
match = cache.check_entry(key)
|
||||
if not match is None:
|
||||
match.terminate()
|
||||
return redirect(url_for("gateway_blueprint.do_GET_status"), code=302)
|
||||
|
||||
|
||||
@gateway_blueprint.route("/metadata/ip_address", methods=["GET"])
|
||||
def ip_address():
|
||||
resp = make_response(env.ip)
|
||||
return set_no_cache(resp)
|
||||
|
||||
|
||||
def launch():
|
||||
env.validate()
|
||||
if not item_sources or not len(item_sources):
|
||||
raise Exception("No data sources specified for Cellxgene Gateway")
|
||||
|
||||
global default_item_source
|
||||
if default_item_source is None:
|
||||
default_item_source = item_sources[0]
|
||||
|
||||
pruner = PruneProcessCache(cache)
|
||||
|
||||
background_thread = Thread(target=pruner)
|
||||
background_thread.start()
|
||||
@@ -0,0 +1,9 @@
|
||||
from prometheus_flask_exporter import PrometheusMetrics
|
||||
|
||||
from cellxgene_gateway import __version__
|
||||
|
||||
|
||||
def add_metrics(app):
|
||||
metrics = PrometheusMetrics(app)
|
||||
metrics.info("app_info", "Application info", version=__version__)
|
||||
return metrics
|
||||
@@ -61,7 +61,7 @@
|
||||
<td>
|
||||
{% if entry.status.name == 'loaded' %}
|
||||
<a
|
||||
href="{{ url_for('do_terminate', path=entry.key.descriptor, source_name=entry.key.source_name) }}">
|
||||
href="{{ url_for('gateway_blueprint.do_terminate', path=entry.key.descriptor, source_name=entry.key.source_name) }}">
|
||||
terminate </a>
|
||||
{% endif %}
|
||||
</td>
|
||||
|
||||
@@ -28,11 +28,11 @@
|
||||
|
||||
<h4>{{ message }}</h4>
|
||||
|
||||
<a href="{{ url_for('filecrawl') }}">
|
||||
<a href="{{ url_for('gateway_blueprint.filecrawl') }}">
|
||||
Please click here to be redirected to the file directory.
|
||||
</a>
|
||||
<br>
|
||||
<a href="{{ url_for('index') }}">
|
||||
<a href="{{ url_for('gateway_blueprint.index') }}">
|
||||
Please click here to return to the homepage.
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -36,10 +36,10 @@
|
||||
Navigation:
|
||||
<ul>
|
||||
{% if path %}
|
||||
<li><a href="{{ url_for('filecrawl') }}">top level</a></li>
|
||||
<li><a href="{{ url_for('gateway_blueprint.filecrawl') }}">top level</a></li>
|
||||
{% else %}
|
||||
{% endif %}
|
||||
<li><a href="{{ url_for('index') }}">homepage</a></li>
|
||||
<li><a href="{{ url_for('gateway_blueprint.index') }}">homepage</a></li>
|
||||
</ul>
|
||||
</p>
|
||||
<script>
|
||||
|
||||
@@ -35,12 +35,12 @@
|
||||
Links:
|
||||
</h1>
|
||||
<div class="list-group" style="width:50%;padding-left:65px">
|
||||
<a href="{{ url_for('filecrawl') }}" class="list-group-item list-group-item-action">
|
||||
<a href="{{ url_for('gateway_blueprint.filecrawl') }}" class="list-group-item list-group-item-action">
|
||||
<u>File Crawler: Allows you to view all uploaded data.</u></a>
|
||||
|
||||
</div>
|
||||
<div class="list-group" style="width:50%;padding-left:65px">
|
||||
<a href="{{ url_for('do_GET_status') }}" class="list-group-item list-group-item-action">
|
||||
<a href="{{ url_for('gateway_blueprint.do_GET_status') }}" class="list-group-item list-group-item-action">
|
||||
<u>Cache Status: view status of launched cellxgene servers.</u></a>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -35,11 +35,11 @@
|
||||
The page will refresh shortly.
|
||||
</p>
|
||||
|
||||
<a href="{{ url_for('filecrawl') }}">
|
||||
<a href="{{ url_for('gateway_blueprint.filecrawl') }}">
|
||||
Please click here to be redirected to the file directory.
|
||||
</a>
|
||||
<br>
|
||||
<a href="{{ url_for('index') }}">
|
||||
<a href="{{ url_for('gateway_blueprint.index') }}">
|
||||
Please click here to return to the homepage.
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -39,10 +39,10 @@
|
||||
<li><a href="{{ relaunch_url }}">
|
||||
Attempt to relaunch the cellxgene server.
|
||||
</a></li>
|
||||
<li><a href="{{ url_for('filecrawl') }}">
|
||||
<li><a href="{{ url_for('gateway_blueprint.filecrawl') }}">
|
||||
Return to the file directory.
|
||||
</a></li>
|
||||
<li><a href="{{ url_for('index') }}">
|
||||
<li><a href="{{ url_for('gateway_blueprint.index') }}">
|
||||
Return to the homepage.
|
||||
</a></li>
|
||||
</ul>
|
||||
|
||||
@@ -7,6 +7,7 @@ dependencies:
|
||||
- flask
|
||||
- psutil
|
||||
- black
|
||||
- typer
|
||||
- twine
|
||||
- isort
|
||||
- coverage
|
||||
@@ -16,3 +17,4 @@ dependencies:
|
||||
- flask-api
|
||||
- werkzeug
|
||||
- cellxgene
|
||||
- prometheus-flask-exporter
|
||||
|
||||
+9
-6
@@ -1,6 +1,9 @@
|
||||
cellxgene
|
||||
flask
|
||||
flask-api
|
||||
werkzeug
|
||||
psutil
|
||||
requests
|
||||
# requirements.txt
|
||||
#
|
||||
# installs dependencies from ./setup.py, and the package itself,
|
||||
# in editable mode
|
||||
-e .[prometheus]
|
||||
|
||||
# (the -e above is optional). you could also just install the package
|
||||
# normally with just the line below (after uncommenting)
|
||||
# .
|
||||
|
||||
@@ -1,2 +1,30 @@
|
||||
[metadata]
|
||||
description-file = README.md
|
||||
description_file = README.md
|
||||
description = "Cellxgene Gateway"
|
||||
author = "Niket Patel, Yohann Potier, Alok Saldanha"
|
||||
author_email = "alok.saldanha@novartis.com"
|
||||
long_description_content_type="text/markdown"
|
||||
license = "MIT"
|
||||
keywords ="visualization, genomics"
|
||||
url = "http://github.com/Novartis/cellxgene-gateway"
|
||||
python_requires = ">=3.6"
|
||||
classifier =
|
||||
"Topic :: Scientific/Engineering :: Visualization"
|
||||
|
||||
[options]
|
||||
install_requires =
|
||||
cellxgene
|
||||
flask
|
||||
flask-api
|
||||
werkzeug
|
||||
psutil
|
||||
requests
|
||||
typer
|
||||
|
||||
[options.extras_require]
|
||||
prometheus =
|
||||
prometheus-flask-exporter
|
||||
|
||||
[entry_points]
|
||||
console_scripts =
|
||||
cellxgene-gateway = cellxgene_gateway.cli:run
|
||||
|
||||
@@ -4,9 +4,6 @@ import sys
|
||||
|
||||
from setuptools import find_packages, setup
|
||||
|
||||
if sys.version_info < (3, 6):
|
||||
sys.exit("Sorry, Python < 3.6 is not supported")
|
||||
|
||||
|
||||
def read(rel_path):
|
||||
here = os.path.abspath(os.path.dirname(__file__))
|
||||
@@ -23,33 +20,15 @@ def get_version(rel_path):
|
||||
raise RuntimeError("Unable to find version string.")
|
||||
|
||||
|
||||
def parse_requirements():
|
||||
reqs = []
|
||||
with open("requirements.txt", "r") as f:
|
||||
for line in f.readlines():
|
||||
reqs.append(line.strip("\n"))
|
||||
return reqs
|
||||
|
||||
|
||||
with open("README.md", "r") as fh:
|
||||
long_description = fh.read()
|
||||
|
||||
install_reqs = parse_requirements()
|
||||
|
||||
setup(
|
||||
# mandatory
|
||||
name="cellxgene-gateway",
|
||||
# mandatory
|
||||
version=get_version("cellxgene_gateway/__init__.py"),
|
||||
# mandatory
|
||||
author="Niket Patel, Yohann Potier, Alok Saldanha",
|
||||
author_email="alok.saldanha@novartis.com",
|
||||
description=("Cellxgene Gateway"),
|
||||
long_description=long_description,
|
||||
long_description_content_type="text/markdown",
|
||||
license="MIT",
|
||||
keywords="visualization, genomics",
|
||||
url="http://github.com/Novartis/cellxgene-gateway",
|
||||
name="cellxgene-gateway",
|
||||
packages=find_packages(),
|
||||
package_data={
|
||||
"cellxgene_gateway": [
|
||||
@@ -60,10 +39,4 @@ setup(
|
||||
]
|
||||
},
|
||||
data_files=[("", ["README.md", "LICENSE"])],
|
||||
install_requires=install_reqs,
|
||||
entry_points={
|
||||
"console_scripts": ["cellxgene-gateway=cellxgene_gateway.gateway:main"]
|
||||
},
|
||||
classifiers=["Topic :: Scientific/Engineering :: Visualization"],
|
||||
python_requires=">=3.6",
|
||||
)
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import unittest
|
||||
from unittest.mock import MagicMock, Mock, patch
|
||||
|
||||
from cellxgene_gateway.gateway import app
|
||||
from flask import Flask
|
||||
|
||||
from cellxgene_gateway.gateway_blueprint import gateway_blueprint
|
||||
from cellxgene_gateway.items.item import ItemType
|
||||
from cellxgene_gateway.items.s3.s3item import S3Item
|
||||
from cellxgene_gateway.items.s3.s3item_source import S3ItemSource
|
||||
@@ -82,6 +84,8 @@ class TestScanDirectory(unittest.TestCase):
|
||||
|
||||
s3func.return_value = S3Mock
|
||||
source = S3ItemSource("my-bucket")
|
||||
app = Flask(__name__)
|
||||
app.register_blueprint(gateway_blueprint)
|
||||
with app.test_request_context(query_string="refresh=true") as test_context:
|
||||
tree = source.scan_directory()
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ from flask import Flask
|
||||
from cellxgene_gateway import flask_util
|
||||
from cellxgene_gateway.cache_entry import CacheEntry, CacheEntryStatus
|
||||
from cellxgene_gateway.cache_key import CacheKey
|
||||
from cellxgene_gateway.gateway import app
|
||||
from cellxgene_gateway.gateway_blueprint import gateway_blueprint
|
||||
from cellxgene_gateway.items.file.fileitem import FileItem
|
||||
from cellxgene_gateway.items.file.fileitem_source import FileItemSource
|
||||
from cellxgene_gateway.items.item import ItemType
|
||||
@@ -18,7 +18,8 @@ key = CacheKey(
|
||||
|
||||
class TestRenderEntry(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.app = app
|
||||
self.app = Flask(__name__)
|
||||
self.app.register_blueprint(gateway_blueprint)
|
||||
self.app_context = self.app.test_request_context()
|
||||
self.app_context.push()
|
||||
self.client = self.app.test_client()
|
||||
|
||||
Reference in New Issue
Block a user