|
|
import io, uuid, time, os |
|
|
import flask |
|
|
from flask_socketio import SocketIO, emit, join_room, leave_room |
|
|
import eventlet |
|
|
from core.mtdna_backend import * |
|
|
from werkzeug.middleware.proxy_fix import ProxyFix |
|
|
import mimetypes |
|
|
|
|
|
|
|
|
|
|
|
eventlet.monkey_patch() |
|
|
|
|
|
print("CWD:", os.getcwd()) |
|
|
print("templates/ exists:", os.path.isdir("templates"), "->", os.listdir("templates") if os.path.isdir("templates") else "missing") |
|
|
print("static/ exists:", os.path.isdir("static"), "->", os.listdir("static")[:10] if os.path.isdir("static") else "missing") |
|
|
|
|
|
|
|
|
isvip = True |
|
|
|
|
|
mimetypes.add_type("text/css", ".css") |
|
|
|
|
|
BASE_DIR = os.path.dirname(os.path.abspath(__file__)) |
|
|
app = flask.Flask(__name__, static_folder=os.path.join(BASE_DIR, "static"), static_url_path="/static", template_folder=os.path.join(BASE_DIR, "templates")) |
|
|
app.config["DEBUG"] = True |
|
|
app.config["SECRET_KEY"] = "dev-key" |
|
|
|
|
|
|
|
|
app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_prefix=1) |
|
|
|
|
|
|
|
|
socketio = SocketIO(app, async_mode="eventlet", cors_allowed_origins="*") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
CANCEL_FLAGS = {} |
|
|
JOBS = {} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
print(app.url_map) |
|
|
|
|
|
|
|
|
@app.before_request |
|
|
def _log(): |
|
|
if flask.request.path.startswith('/static/') or flask.request.path == '/favicon.ico' or flask.request.path.startswith('/socket.io'): |
|
|
return |
|
|
print("REQ:", flask.request.method, flask.request.path) |
|
|
|
|
|
|
|
|
@app.route("/") |
|
|
def home(): |
|
|
return flask.render_template("Home.html", isvip=isvip) |
|
|
|
|
|
|
|
|
@app.route("/submit", methods=["POST"]) |
|
|
def submit(): |
|
|
raw_text = flask.request.form.get("raw_text", "").strip() |
|
|
file_upload = flask.request.files.get("file_upload") |
|
|
user_email = flask.request.form.get("user_email", "").strip() |
|
|
|
|
|
if file_upload and getattr(file_upload, "filename", ""): |
|
|
data = file_upload.read() |
|
|
buf = io.BytesIO(data); buf.name = file_upload.filename |
|
|
file_upload = buf |
|
|
|
|
|
accs, error = extract_accessions_from_input(file=file_upload, raw_text=raw_text) |
|
|
|
|
|
job_id = uuid.uuid4().hex[:8] |
|
|
CANCEL_FLAGS[job_id] = False |
|
|
|
|
|
|
|
|
user_hash = hash_user_id(user_email) |
|
|
user_usage, max_allowed = increment_usage(user_hash, 0) |
|
|
remaining_trials = max(max_allowed - user_usage, 0) |
|
|
total_queries = max(0, min(len(accs), max_allowed - user_usage)) |
|
|
|
|
|
|
|
|
accs = accs[:total_queries] |
|
|
|
|
|
|
|
|
JOBS[job_id] = {"accs": accs, |
|
|
"user": { |
|
|
"user_hash": user_hash, |
|
|
"user_usage": user_usage, |
|
|
"max_allowed": max_allowed, |
|
|
"total_queries": total_queries, |
|
|
"remaining_trials": remaining_trials |
|
|
}, |
|
|
"started": False} |
|
|
|
|
|
return flask.redirect(flask.url_for("output", job_id=job_id), code=303) |
|
|
|
|
|
@app.route("/output/") |
|
|
def output_root(): |
|
|
return flask.redirect(flask.url_for("home")) |
|
|
|
|
|
|
|
|
@app.route("/output/<job_id>") |
|
|
def output(job_id): |
|
|
started_at = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) |
|
|
total_queries = JOBS[job_id]['user']['total_queries'] |
|
|
return flask.render_template( |
|
|
"Output.html", |
|
|
job_id=job_id, |
|
|
started_at=started_at, |
|
|
total_queries=total_queries, |
|
|
isvip=isvip, |
|
|
ws_url="", |
|
|
) |
|
|
|
|
|
|
|
|
def run_job(job_id, accessions): |
|
|
total_queries = JOBS[job_id]['user']['total_queries'] |
|
|
user_hash = JOBS[job_id]['user']['user_hash'] |
|
|
room = job_id |
|
|
|
|
|
|
|
|
def send_log(msg): socketio.emit("log", {"msg": msg}, room=room) |
|
|
def send_row(row): socketio.emit("row", row, room=room) |
|
|
|
|
|
|
|
|
try: |
|
|
socketio.emit("status", {"state": "started", "total": len(accessions)}, room=room) |
|
|
start_time = time.perf_counter() |
|
|
send_log(f"Job {job_id} started. {total_queries} accession(s).") |
|
|
|
|
|
outs = [] |
|
|
for i, acc in enumerate(accessions, 1): |
|
|
if CANCEL_FLAGS.get(job_id): |
|
|
send_log("Cancellation requested. Stopping…") |
|
|
socketio.emit("status", {"state": "cancelled"}, room=room) |
|
|
increment_usage(user_hash, i - 1) |
|
|
return |
|
|
|
|
|
t0 = time.perf_counter() |
|
|
out = summarize_results(acc) |
|
|
dt = time.perf_counter() - t0 |
|
|
|
|
|
|
|
|
if not out: |
|
|
out = {} |
|
|
elif isinstance(out, list): |
|
|
|
|
|
if out and isinstance(out[0], dict): |
|
|
out = out[0] |
|
|
elif out and isinstance(out[0], list): |
|
|
|
|
|
keys = [ |
|
|
"Sample ID", "Predicted Country", "Country Explanation", |
|
|
"Predicted Sample Type", "Sample Type Explanation", |
|
|
"Sources", "Time cost" |
|
|
] |
|
|
row0 = out[0] |
|
|
out = {k: (row0[idx] if idx < len(row0) else "") for idx, k in enumerate(keys)} |
|
|
else: |
|
|
out = {} |
|
|
elif not isinstance(out, dict): |
|
|
out = {} |
|
|
|
|
|
|
|
|
sample_id = out.get("Sample ID") or str(acc) |
|
|
predicted_country = out.get("Predicted Country", "unknown") |
|
|
country_explanation = out.get("Country Explanation", "unknown") |
|
|
predicted_sample_type = out.get("Predicted Sample Type", "unknown") |
|
|
sample_type_explanation = out.get("Sample Type Explanation", "unknown") |
|
|
sources = out.get("Sources", "No Links") |
|
|
time_cost = out.get("Time cost") or f"{dt:.2f}s" |
|
|
|
|
|
send = { |
|
|
"idx": i, |
|
|
"sample_id": sample_id, |
|
|
"predicted_country": predicted_country, |
|
|
"country_explanation": country_explanation, |
|
|
"predicted_sample_type": predicted_sample_type, |
|
|
"sample_type_explanation": sample_type_explanation, |
|
|
"sources": sources, |
|
|
"time_cost": time_cost, |
|
|
} |
|
|
|
|
|
socketio.emit("row", send, room=room) |
|
|
socketio.sleep(0) |
|
|
send_log(f"Processed {acc} in {dt:.2f}s") |
|
|
|
|
|
total_dt = time.perf_counter() - start_time |
|
|
|
|
|
|
|
|
increment_usage(user_hash, total_queries) |
|
|
|
|
|
remaining_trials = JOBS[job_id]['user']['remaining_trials'] |
|
|
|
|
|
socketio.emit("status", {"state": "finished", "elapsed": f"{total_dt:.2f}s"}, room=room) |
|
|
send_log(f"Job finished successfully. Number of trials left is") |
|
|
except Exception as e: |
|
|
send_log(f"ERROR: {e}") |
|
|
socketio.emit("status", {"state": "error", "message": str(e)}, room=room) |
|
|
finally: |
|
|
CANCEL_FLAGS.pop(job_id, None) |
|
|
JOBS.pop(job_id, None) |
|
|
|
|
|
|
|
|
@socketio.on("connect") |
|
|
def on_connect(): |
|
|
emit("connected", {"ok": True}) |
|
|
|
|
|
@socketio.on("join") |
|
|
def on_join(data): |
|
|
job_id = data.get("job_id") |
|
|
if job_id: |
|
|
join_room(job_id) |
|
|
emit("joined", {"room": job_id}) |
|
|
|
|
|
|
|
|
job = JOBS.get(job_id) |
|
|
if job and not job["started"]: |
|
|
job["started"] = True |
|
|
total = len(job["accs"]) |
|
|
|
|
|
socketio.emit("status", {"state": "queued", "total": total}, room=job_id) |
|
|
socketio.start_background_task(run_job, job_id, job["accs"]) |
|
|
|
|
|
@socketio.on("leave") |
|
|
def on_leave(data): |
|
|
job_id = data.get("job_id") |
|
|
if job_id: |
|
|
leave_room(job_id) |
|
|
|
|
|
@socketio.on("cancel") |
|
|
def on_cancel(data): |
|
|
job_id = data.get("job_id") |
|
|
if job_id in CANCEL_FLAGS: |
|
|
CANCEL_FLAGS[job_id] = True |
|
|
emit("status", {"state": "cancelling"}, room=job_id) |
|
|
|
|
|
@app.route("/about") |
|
|
def about(): |
|
|
return flask.render_template("About.html", isvip=isvip) |
|
|
|
|
|
@app.route("/pricing") |
|
|
def pricing(): |
|
|
return flask.render_template("Pricing.html", isvip=isvip) |
|
|
|
|
|
@app.route("/contact") |
|
|
def contact(): |
|
|
return flask.render_template("Contact.html", isvip=isvip) |
|
|
|
|
|
if __name__ == "__main__": |
|
|
port = int(os.environ.get("PORT", 7860)) |
|
|
socketio.run(app, host="0.0.0.0", port=port) |