Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion biosimdb_interface/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
from dotenv import load_dotenv
from flask import Flask

from .form.workflows import WorkflowStore

load_dotenv()
# UPLOAD_FOLDER = "/tmp"

Expand Down Expand Up @@ -45,6 +47,7 @@ def create_app(test_config=None):

# App and Invenio OAuth2 configuration — values loaded from .env
app.config.from_mapping(
WORKFLOW_TTL_SECONDS=int(os.getenv("WORKFLOW_TTL_SECONDS", "14400")),
UPLOAD_FOLDER=os.getenv("UPLOAD_FOLDER", "/tmp"), # App specific
CLIENT_ID=os.getenv("CLIENT_ID", ""),
CLIENT_SECRET=os.getenv("CLIENT_SECRET", ""),
Expand All @@ -55,7 +58,7 @@ def create_app(test_config=None):
API_BASE=os.getenv("API_BASE", ""),
REDIRECT_URI=os.getenv("REDIRECT_URI", ""),
SCOPES=os.getenv("SCOPES", "").strip(),
) # invenio app configs
)

if test_config is None:
# load the instance config, if it exists, when not testing
Expand All @@ -64,6 +67,10 @@ def create_app(test_config=None):
# load the test config if passed in
app.config.from_mapping(test_config)

app.extensions["workflow_store"] = WorkflowStore(
ttl_seconds=app.config["WORKFLOW_TTL_SECONDS"]
)

@app.context_processor
def inject_base_url():
return {
Expand Down
38 changes: 20 additions & 18 deletions biosimdb_interface/form/extract.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@
import os

from biosim_extractor.metadata.populatemetadata import MetadataPopulator
from flask import jsonify, request, session
from flask import current_app, jsonify, request
from werkzeug.utils import secure_filename

from . import form_bp
from .upload import cache_extracted_files, cleanup_tmpdir
from .utils import make_upload_tmpdir
from .upload import cache_extracted_files
from .workflows import WorkflowNotFound


def extract_files_validate(top_file, traj_file):
Expand Down Expand Up @@ -67,13 +67,19 @@ def extract_metadata():
- ``{"simulation_metadata": ..., "validation_errors": [...]}`` if schema validation fails.
- ``{"error": "..."}`` with status 400 if files are missing, or 500 on unexpected error.
"""
# clear the existing tmpdir from previous extraction
tmpdir = session.get("submission_tmpdir")
if tmpdir:
cleanup_tmpdir(tmpdir)
session.pop("submission_tmpdir", None)
tmpdir = make_upload_tmpdir("biosimdb_submission_")
session["submission_tmpdir"] = tmpdir
workflow_id = request.form.get("workflow_id")
topology = request.files.get("topology")
trajectories = request.files.getlist("trajectory[]")

if not topology or not trajectories:
return jsonify({"error": "Simulation files are missing."}), 400

try:
workflow = current_app.extensions["workflow_store"].reset(workflow_id)
except WorkflowNotFound as exc:
return jsonify({"error": str(exc)}), 400

tmpdir = workflow.tmpdir

try:
topology = request.files.get("topology")
Expand All @@ -96,9 +102,7 @@ def extract_metadata():

result, validation_errors = extract_files_validate(topo_path, traj_files)

if len(validation_errors) > 0:
cleanup_tmpdir(tmpdir)
session.pop("submission_tmpdir", None)
if validation_errors:
return jsonify(
{
"simulation_metadata": result,
Expand All @@ -114,6 +118,7 @@ def extract_metadata():
)

except Exception as e:
current_app.extensions["workflow_store"].delete(workflow_id)
print(f"ERROR: {e}")
import traceback

Expand All @@ -123,9 +128,6 @@ def extract_metadata():

@form_bp.route("/clear_extraction", methods=["POST"])
def clear_extraction():
"""Discard extracted files and reset the pending submission tmpdir."""
tmpdir = session.get("submission_tmpdir")
cleanup_tmpdir(tmpdir)
for key in ("submission_tmpdir", "topo_path", "traj_files"):
session.pop(key, None)
"""Delete only the current tab's extracted workflow files."""
current_app.extensions["workflow_store"].delete(request.form.get("workflow_id"))
return jsonify({"success": True})
81 changes: 29 additions & 52 deletions biosimdb_interface/form/upload.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,18 +36,28 @@ def _load_pending_file_meta(tmpdir):
return json.load(f)


def load_extracted_files(tmpdir):
"""Return topology and trajectory paths cached during extraction."""
with open(_pending_uploads_path(tmpdir)) as f:
saved_files = json.load(f)

topology = saved_files.get("topology", [])
trajectories = saved_files.get("trajectory", [])

return (
topology[0] if topology else None,
trajectories,
)


def cache_extracted_files(tmpdir, saved_files):
"""Persist saved file paths and computed file metadata for later reuse."""
"""Persist extracted file paths and file metadata inside one workflow directory."""
file_meta = files_metadata(saved_files)

with open(_pending_uploads_path(tmpdir), "w") as f:
json.dump(saved_files, f)
_save_pending_file_meta(tmpdir, file_meta)

session["topo_path"] = (
saved_files["topology"][0] if saved_files["topology"] else None
)
session["traj_files"] = saved_files["trajectory"]
return file_meta


Expand Down Expand Up @@ -191,8 +201,10 @@ def _save_request_files(tmpdir):
Returns:
dict[str, list[str]]: Mapping of file role to saved file paths.
"""
topo_path = session.get("topo_path")
traj_files = session.get("traj_files") or []
# topo_path = session.get("topo_path")
# traj_files = session.get("traj_files") or []

topo_path, traj_files = load_extracted_files(tmpdir)

if _paths_are_reusable(tmpdir, topo_path, traj_files):
return {
Expand All @@ -209,10 +221,6 @@ def _save_request_files(tmpdir):
file.save(path)
saved_files.setdefault(role, []).append(path)

if saved_files["topology"]:
session["topo_path"] = saved_files["topology"][0]
session["traj_files"] = saved_files["trajectory"]

return saved_files


Expand Down Expand Up @@ -291,50 +299,19 @@ def cleanup_tmpdir(tmpdir):
shutil.rmtree(tmpdir, ignore_errors=True)


def save_pending_submission(json_form=None):
"""Persist uploaded files and form payload for post-login submission resume.

Saves uploaded request files into a temp directory, writes a manifest of
allowed upload paths, optionally writes simulation_metadata.json, and stores
the form payload as pending_form_data.json.
def save_pending_submission(json_form, tmpdir):
"""Persist validated submission data in the given workflow directory."""
file_meta = _load_pending_file_meta(tmpdir) or []

Args:
json_form (dict | None): Validated BioSim metadata to persist. When
provided, file metadata is attached at json_form["files"] before
writing simulation_metadata.json.

Side Effects:
Writes JSON artifacts under tmpdir.
Sets session["pending_files_dir"].
"""
tmpdir = session.get("submission_tmpdir")
# saved_files, file_meta = _save_files_and_extract_metadata(tmpdir)

topo_path = session.get("topo_path")
traj_files = session.get("traj_files") or []
saved_files = {
"topology": [topo_path] if topo_path else [],
"trajectory": traj_files,
}

file_meta = _load_pending_file_meta(tmpdir)
if file_meta is None:
# Fallback only if cache missing
file_meta = files_metadata(saved_files)
_save_pending_file_meta(tmpdir, file_meta)

# Persist exact user-uploaded paths for later allowlist upload
with open(_pending_uploads_path(tmpdir), "w") as f:
json.dump(saved_files, f)

if json_form is not None:
json_form["files"] = file_meta
json_path = os.path.join(tmpdir, SIM_METADATA_FILENAME)
with open(json_path, "w") as f:
json.dump(json_form, f, indent=2)
json_form["files"] = file_meta
with open(os.path.join(tmpdir, SIM_METADATA_FILENAME), "w") as f:
json.dump(json_form, f, indent=2)

with open(_pending_form_path(tmpdir), "w") as f:
json.dump(request.form.to_dict(flat=False), f)
form_values = request.form.to_dict(flat=False)
form_values.pop("workflow_id", None)

json.dump(form_values, f)


def prepare_for_invenio(form_data, tmpdir):
Expand Down
Loading
Loading