amina-cli 0.4.1__tar.gz → 0.4.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {amina_cli-0.4.1 → amina_cli-0.4.3}/PKG-INFO +1 -1
- {amina_cli-0.4.1 → amina_cli-0.4.3}/pyproject.toml +1 -1
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/__init__.py +1 -1
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/jobs_cmd.py +95 -20
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/__init__.py +17 -4
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/storage.py +28 -12
- {amina_cli-0.4.1 → amina_cli-0.4.3}/.gitignore +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/LICENSE +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/README.md +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/auth.py +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/client.py +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/__init__.py +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/auth_cmd.py +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/run_cmd.py +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/analysis/__init__.py +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/analysis/docs/hydrophobicity.yaml +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/analysis/docs/mmseqs2_cluster.yaml +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/analysis/docs/residue_accessibility.yaml +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/analysis/docs/rmsd.yaml +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/analysis/docs/sasa.yaml +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/analysis/docs/simple_rmsd.yaml +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/analysis/docs/surface_charge.yaml +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/analysis/docs/usalign.yaml +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/analysis/hydrophobicity.py +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/analysis/mmseqs2_cluster.py +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/analysis/residue_accessibility.py +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/analysis/rmsd.py +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/analysis/sasa.py +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/analysis/simple_rmsd.py +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/analysis/surface_charge.py +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/analysis/usalign.py +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/design/__init__.py +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/design/docs/esm_if1.yaml +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/design/docs/protein_mc.yaml +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/design/docs/proteinmpnn.yaml +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/design/docs/rfdiffusion.yaml +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/design/esm_if1.py +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/design/protein_mc.py +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/design/proteinmpnn.py +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/design/rfdiffusion.py +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/display.py +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/doccard.py +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/folding/__init__.py +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/folding/boltz2.py +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/folding/docs/boltz2.yaml +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/folding/docs/esmfold.yaml +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/folding/docs/openfold3.yaml +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/folding/docs/protenix.yaml +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/folding/esmfold.py +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/folding/openfold3.py +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/folding/protenix.py +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/interactions/__init__.py +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/interactions/autodock_vina.py +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/interactions/diffdock.py +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/interactions/dockq.py +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/interactions/docs/autodock_vina.yaml +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/interactions/docs/diffdock.yaml +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/interactions/docs/dockq.yaml +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/interactions/docs/emngly.yaml +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/interactions/docs/glycosylation_ensemble.yaml +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/interactions/docs/interface_identifier.yaml +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/interactions/docs/isoglyp.yaml +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/interactions/docs/lmngly.yaml +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/interactions/docs/p2rank.yaml +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/interactions/docs/pesto.yaml +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/interactions/emngly.py +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/interactions/glycosylation_ensemble.py +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/interactions/interface_identifier.py +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/interactions/isoglyp.py +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/interactions/lmngly.py +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/interactions/p2rank.py +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/interactions/pesto.py +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/properties/__init__.py +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/properties/aminosol.py +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/properties/docs/aminosol.yaml +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/properties/docs/esm1v.yaml +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/properties/docs/esm2_embedding.yaml +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/properties/esm1v.py +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/properties/esm2_embedding.py +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/utilities/__init__.py +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/utilities/activesite_verifier.py +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/utilities/chain_select.py +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/utilities/distance_calculator.py +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/utilities/docs/activesite_verifier.yaml +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/utilities/docs/chain_select.yaml +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/utilities/docs/distance_calculator.yaml +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/utilities/docs/maxit_convert.yaml +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/utilities/docs/mol_size_calculator.yaml +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/utilities/docs/obabel_convert.yaml +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/utilities/docs/pdb_bfactor_overwrite.yaml +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/utilities/docs/pdb_cleaner.yaml +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/utilities/docs/pdb_quality_assessment.yaml +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/utilities/docs/pdb_to_fasta.yaml +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/utilities/docs/protein_relaxer.yaml +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/utilities/maxit_convert.py +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/utilities/mol_size_calculator.py +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/utilities/obabel_convert.py +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/utilities/pdb_bfactor_overwrite.py +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/utilities/pdb_cleaner.py +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/utilities/pdb_quality_assessment.py +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/utilities/pdb_to_fasta.py +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/utilities/protein_relaxer.py +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools_cmd.py +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/main.py +0 -0
- {amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/registry.py +0 -0
|
@@ -775,7 +775,7 @@ def download(
|
|
|
775
775
|
response_path.write_text(json.dumps(result, indent=2, default=str))
|
|
776
776
|
|
|
777
777
|
try:
|
|
778
|
-
downloaded = download_results(result, output)
|
|
778
|
+
downloaded, failed = download_results(result, output)
|
|
779
779
|
if downloaded:
|
|
780
780
|
console.print(f"[green]\u2713[/green] Downloaded {len(downloaded)} file(s) to {output}/")
|
|
781
781
|
for path in downloaded:
|
|
@@ -797,8 +797,20 @@ def download(
|
|
|
797
797
|
|
|
798
798
|
tool_metadata = get_tool(job_info.get("tool_name", ""))
|
|
799
799
|
render_tool_output(result, tool_metadata)
|
|
800
|
-
|
|
800
|
+
elif not failed:
|
|
801
801
|
console.print("[dim]No output files to download.[/dim]")
|
|
802
|
+
# Per-file failures are reported separately from total-failure (StorageError).
|
|
803
|
+
# A non-empty `failed` dict means a partial success \u2014 the caller (often an
|
|
804
|
+
# agent) needs to know which files are missing so it can rerun `amina jobs
|
|
805
|
+
# download` to mint fresh signed URLs and retry just the gaps.
|
|
806
|
+
if failed:
|
|
807
|
+
console.print(f"[yellow]Warning:[/yellow] {len(failed)} file(s) failed to download:")
|
|
808
|
+
for file_type, err in failed.items():
|
|
809
|
+
console.print(f" - {file_type}: {err}")
|
|
810
|
+
console.print(
|
|
811
|
+
"[dim]Re-run `amina jobs download` to mint fresh signed URLs and retry the missing files.[/dim]"
|
|
812
|
+
)
|
|
813
|
+
raise typer.Exit(1)
|
|
802
814
|
except StorageError as e:
|
|
803
815
|
# Show signed URLs as fallback
|
|
804
816
|
signed_urls = result.get("signed_urls", {})
|
|
@@ -819,6 +831,40 @@ def download(
|
|
|
819
831
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
820
832
|
|
|
821
833
|
|
|
834
|
+
def _missing_artifacts(response_path: Path, dir_path: Path) -> list[str]:
|
|
835
|
+
"""Return artifact file_types in ``response.json`` whose local file is missing.
|
|
836
|
+
|
|
837
|
+
Used by ``reconcile`` to detect partial downloads: a response.json on disk
|
|
838
|
+
with declared ``output_files`` but no corresponding local files indicates
|
|
839
|
+
the original download partially failed (a stale signed URL, an HTTP 400,
|
|
840
|
+
a network blip). The dir needs a retry with fresh URLs, not a skip.
|
|
841
|
+
|
|
842
|
+
Returns an empty list when:
|
|
843
|
+
- response.json doesn't exist or is malformed (caller already handled)
|
|
844
|
+
- the result declared no ``output_files`` (e.g. data-only tools)
|
|
845
|
+
- every declared file is on disk
|
|
846
|
+
|
|
847
|
+
Returns a list of ``file_type`` keys (e.g. ``["pdb_filepath",
|
|
848
|
+
"csv_filepath"]``) for each missing artifact.
|
|
849
|
+
"""
|
|
850
|
+
try:
|
|
851
|
+
result = json.loads(response_path.read_text())
|
|
852
|
+
except (OSError, json.JSONDecodeError):
|
|
853
|
+
# If we can't parse it, downstream code will re-resolve from scratch.
|
|
854
|
+
return []
|
|
855
|
+
output_files = result.get("output_files") or {}
|
|
856
|
+
if not isinstance(output_files, dict):
|
|
857
|
+
return []
|
|
858
|
+
missing: list[str] = []
|
|
859
|
+
for file_type, remote_path in output_files.items():
|
|
860
|
+
if not remote_path:
|
|
861
|
+
continue
|
|
862
|
+
local = dir_path / Path(remote_path).name
|
|
863
|
+
if not local.exists():
|
|
864
|
+
missing.append(file_type)
|
|
865
|
+
return missing
|
|
866
|
+
|
|
867
|
+
|
|
822
868
|
def _find_submission_files(root: Path, recursive: bool) -> list[tuple[str, Path]]:
|
|
823
869
|
"""Walk ``root`` for ``submission.json`` files and extract job_id from each.
|
|
824
870
|
|
|
@@ -945,11 +991,23 @@ def reconcile(
|
|
|
945
991
|
response_path = dir_path / "response.json"
|
|
946
992
|
entry: dict = {"job_id": job_id, "dir": str(dir_path)}
|
|
947
993
|
|
|
994
|
+
# Idempotency check has two parts: response.json must exist AND every
|
|
995
|
+
# declared artifact must be on local disk. Checking only response.json
|
|
996
|
+
# leaves silent gaps when the original download partially failed
|
|
997
|
+
# (HTTP 400 on a single file, stale URL after >1h, etc.) — reconcile
|
|
998
|
+
# would forever mark such dirs as `already_done` despite missing PDB
|
|
999
|
+
# or TRB outputs.
|
|
948
1000
|
if response_path.exists():
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
1001
|
+
missing = _missing_artifacts(response_path, dir_path)
|
|
1002
|
+
if not missing:
|
|
1003
|
+
summary["already_done"] += 1
|
|
1004
|
+
entry["action"] = "skipped_response_json_exists"
|
|
1005
|
+
per_job.append(entry)
|
|
1006
|
+
continue
|
|
1007
|
+
# Fall through to re-resolve status. _resolve_job_status will
|
|
1008
|
+
# mint fresh signed URLs (any in the on-disk response.json are
|
|
1009
|
+
# likely expired) and we'll retry the download below.
|
|
1010
|
+
entry["missing_artifacts_before_retry"] = missing
|
|
953
1011
|
|
|
954
1012
|
job_info = get_job_info(job_id)
|
|
955
1013
|
if not job_info:
|
|
@@ -958,19 +1016,20 @@ def reconcile(
|
|
|
958
1016
|
per_job.append(entry)
|
|
959
1017
|
continue
|
|
960
1018
|
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
#
|
|
972
|
-
#
|
|
973
|
-
#
|
|
1019
|
+
# Always go to the server for the truth, even if the local cache says
|
|
1020
|
+
# non-terminal. Reason: the bulk /list_jobs refresh above can return
|
|
1021
|
+
# stale data when the worker's completion callback dropped (Supabase
|
|
1022
|
+
# blip, gateway crash mid-handler) — a 300-job campaign reported 197
|
|
1023
|
+
# jobs still_running while all 300 had finished on Modal. The CLI
|
|
1024
|
+
# used to early-return on local non-terminal status, which made this
|
|
1025
|
+
# bug unrecoverable from the client. Now reconcile mirrors what
|
|
1026
|
+
# `amina jobs status` does: ask Modal directly via call_id.
|
|
1027
|
+
#
|
|
1028
|
+
# Cost: O(pending) HTTP calls per cycle. Bounded by jobs the user is
|
|
1029
|
+
# actively waiting on (response.json-present dirs short-circuit
|
|
1030
|
+
# above), and the gateway's heal-on-read upgrade makes the post-merge
|
|
1031
|
+
# local cache mostly fresh anyway, so this fall-through is rarely hit
|
|
1032
|
+
# in practice. It exists as a defence-in-depth for gateway brownouts.
|
|
974
1033
|
try:
|
|
975
1034
|
status_result = _resolve_job_status(job_info)
|
|
976
1035
|
except ToolExecutionError as fetch_err:
|
|
@@ -981,6 +1040,15 @@ def reconcile(
|
|
|
981
1040
|
continue
|
|
982
1041
|
_persist_terminal_status(job_info, status_result)
|
|
983
1042
|
|
|
1043
|
+
if not status_result.get("terminal", False) and status_result.get("status") not in TERMINAL_STATUSES:
|
|
1044
|
+
# Genuinely still running on the server. Count, record the
|
|
1045
|
+
# FRESH status (not the stale-cache one we started with), move on.
|
|
1046
|
+
summary["still_running"] += 1
|
|
1047
|
+
entry["action"] = "still_running"
|
|
1048
|
+
entry["status"] = status_result.get("status", "running")
|
|
1049
|
+
per_job.append(entry)
|
|
1050
|
+
continue
|
|
1051
|
+
|
|
984
1052
|
# Build the response payload. For completed jobs the gateway hands us
|
|
985
1053
|
# a full `result` dict (output_files, signed_urls, data, ...). For
|
|
986
1054
|
# failed/cancelled jobs Modal often returns only an error string with
|
|
@@ -1002,8 +1070,15 @@ def reconcile(
|
|
|
1002
1070
|
entry["action"] = "wrote_response_json"
|
|
1003
1071
|
if download:
|
|
1004
1072
|
try:
|
|
1005
|
-
downloaded = download_results(result_payload, dir_path)
|
|
1073
|
+
downloaded, failed = download_results(result_payload, dir_path)
|
|
1006
1074
|
entry["downloaded_files"] = [p.name for p in downloaded]
|
|
1075
|
+
# Per-file failures are partial successes; count and surface
|
|
1076
|
+
# them so the agent can detect missing artifacts without
|
|
1077
|
+
# post-hoc disk inspection. Idempotency check above will
|
|
1078
|
+
# also catch this on the next pass and retry the gaps.
|
|
1079
|
+
if failed:
|
|
1080
|
+
summary["download_failed"] += 1
|
|
1081
|
+
entry["download_failures"] = failed
|
|
1007
1082
|
except StorageError as dl_err:
|
|
1008
1083
|
summary["download_failed"] += 1
|
|
1009
1084
|
entry["download_error"] = str(dl_err)
|
|
@@ -382,12 +382,15 @@ def run_tool_with_progress(
|
|
|
382
382
|
|
|
383
383
|
progress.update(task, description="Downloading results...")
|
|
384
384
|
|
|
385
|
-
# Download output files
|
|
386
|
-
|
|
385
|
+
# Download output files. download_results returns (downloaded, failed):
|
|
386
|
+
# per-file errors land in `failed` rather than aborting the loop, so a
|
|
387
|
+
# single stale signed URL no longer silently abandons the other files.
|
|
388
|
+
downloaded: list[Path] = []
|
|
389
|
+
failed: dict[str, str] = {}
|
|
387
390
|
try:
|
|
388
|
-
downloaded = download_results(result, output_dir)
|
|
391
|
+
downloaded, failed = download_results(result, output_dir)
|
|
389
392
|
except StorageError as e:
|
|
390
|
-
#
|
|
393
|
+
# Total failure (e.g. no signed URLs and no Supabase credentials).
|
|
391
394
|
signed_urls = result.get("signed_urls", {})
|
|
392
395
|
if signed_urls:
|
|
393
396
|
console.print("\n[yellow]Warning:[/yellow] Could not download files automatically.")
|
|
@@ -398,6 +401,16 @@ def run_tool_with_progress(
|
|
|
398
401
|
else:
|
|
399
402
|
console.print(f"\n[yellow]Warning:[/yellow] Download failed: {e}")
|
|
400
403
|
|
|
404
|
+
# Surface per-file failures so agents don't silently end up with
|
|
405
|
+
# response.json present but artifacts missing.
|
|
406
|
+
if failed:
|
|
407
|
+
console.print(f"\n[yellow]Warning:[/yellow] {len(failed)} file(s) failed to download:")
|
|
408
|
+
for file_type, err in failed.items():
|
|
409
|
+
console.print(f" - {file_type}: {err}")
|
|
410
|
+
console.print(
|
|
411
|
+
"[dim]Re-run `amina jobs download` to mint fresh signed URLs and retry the missing files.[/dim]"
|
|
412
|
+
)
|
|
413
|
+
|
|
401
414
|
# Persist the structured response payload (success or failure) so agents
|
|
402
415
|
# and scripts can read metrics/warnings/cost without re-parsing stdout.
|
|
403
416
|
# Soft-fail on filesystem errors — the job has already run and been
|
|
@@ -136,12 +136,14 @@ def download_results(
|
|
|
136
136
|
result: dict,
|
|
137
137
|
output_dir: Optional[Path] = None,
|
|
138
138
|
bucket: str = DEFAULT_BUCKET,
|
|
139
|
-
) -> list[Path]:
|
|
139
|
+
) -> tuple[list[Path], dict[str, str]]:
|
|
140
140
|
"""
|
|
141
141
|
Download all output files from a tool result.
|
|
142
142
|
|
|
143
143
|
Prefers signed URLs (no credentials needed) over direct Supabase access.
|
|
144
|
-
|
|
144
|
+
Individual file failures are recorded but do not stop the loop — every
|
|
145
|
+
file gets its own attempt. This means a single bad signed URL no longer
|
|
146
|
+
aborts the whole download and silently abandons the rest.
|
|
145
147
|
|
|
146
148
|
Args:
|
|
147
149
|
result: Tool execution result with 'output_files' and optional 'signed_urls'
|
|
@@ -149,23 +151,31 @@ def download_results(
|
|
|
149
151
|
bucket: Supabase storage bucket (only used as fallback)
|
|
150
152
|
|
|
151
153
|
Returns:
|
|
152
|
-
|
|
154
|
+
Tuple ``(downloaded, failed)`` where:
|
|
155
|
+
- ``downloaded`` is the list of locally-written file paths
|
|
156
|
+
- ``failed`` is a dict ``{file_type: error_message}`` for files
|
|
157
|
+
that could not be downloaded. An empty ``failed`` dict means
|
|
158
|
+
the download was complete; callers should treat any non-empty
|
|
159
|
+
``failed`` as a partial success that must be retried.
|
|
153
160
|
|
|
154
161
|
Raises:
|
|
155
|
-
StorageError:
|
|
162
|
+
StorageError: Only when there is no way to attempt any download at
|
|
163
|
+
all (e.g. no signed URLs AND no Supabase credentials). Per-file
|
|
164
|
+
failures are reported in the ``failed`` dict instead.
|
|
156
165
|
"""
|
|
157
166
|
output_files = result.get("output_files", {})
|
|
158
167
|
signed_urls = result.get("signed_urls", {})
|
|
159
168
|
|
|
160
169
|
if not output_files and not signed_urls:
|
|
161
|
-
return []
|
|
170
|
+
return [], {}
|
|
162
171
|
|
|
163
172
|
if output_dir is None:
|
|
164
173
|
output_dir = Path.cwd()
|
|
165
174
|
|
|
166
175
|
output_dir.mkdir(parents=True, exist_ok=True)
|
|
167
176
|
|
|
168
|
-
downloaded = []
|
|
177
|
+
downloaded: list[Path] = []
|
|
178
|
+
failed: dict[str, str] = {}
|
|
169
179
|
|
|
170
180
|
# Prefer signed URLs (no credentials needed)
|
|
171
181
|
if signed_urls:
|
|
@@ -182,10 +192,13 @@ def download_results(
|
|
|
182
192
|
filename = f"{file_type.replace('_filepath', '')}.out"
|
|
183
193
|
|
|
184
194
|
local_path = output_dir / filename
|
|
185
|
-
|
|
186
|
-
|
|
195
|
+
try:
|
|
196
|
+
download_from_signed_url(signed_url, local_path)
|
|
197
|
+
downloaded.append(local_path)
|
|
198
|
+
except StorageError as e:
|
|
199
|
+
failed[file_type] = str(e)
|
|
187
200
|
|
|
188
|
-
return downloaded
|
|
201
|
+
return downloaded, failed
|
|
189
202
|
|
|
190
203
|
# No signed URLs available - direct Supabase access requires credentials
|
|
191
204
|
# Check if we have credentials before attempting
|
|
@@ -203,10 +216,13 @@ def download_results(
|
|
|
203
216
|
|
|
204
217
|
filename = Path(remote_path).name
|
|
205
218
|
local_path = output_dir / filename
|
|
206
|
-
|
|
207
|
-
|
|
219
|
+
try:
|
|
220
|
+
download_file(remote_path, local_path, bucket)
|
|
221
|
+
downloaded.append(local_path)
|
|
222
|
+
except StorageError as e:
|
|
223
|
+
failed[file_type] = str(e)
|
|
208
224
|
|
|
209
|
-
return downloaded
|
|
225
|
+
return downloaded, failed
|
|
210
226
|
|
|
211
227
|
|
|
212
228
|
def upload_file(
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/analysis/docs/hydrophobicity.yaml
RENAMED
|
File without changes
|
{amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/analysis/docs/mmseqs2_cluster.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/analysis/docs/simple_rmsd.yaml
RENAMED
|
File without changes
|
{amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/analysis/docs/surface_charge.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/analysis/mmseqs2_cluster.py
RENAMED
|
File without changes
|
{amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/analysis/residue_accessibility.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/design/docs/protein_mc.yaml
RENAMED
|
File without changes
|
{amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/design/docs/proteinmpnn.yaml
RENAMED
|
File without changes
|
{amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/design/docs/rfdiffusion.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/folding/docs/openfold3.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/interactions/autodock_vina.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/interactions/docs/diffdock.yaml
RENAMED
|
File without changes
|
{amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/interactions/docs/dockq.yaml
RENAMED
|
File without changes
|
{amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/interactions/docs/emngly.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/interactions/docs/isoglyp.yaml
RENAMED
|
File without changes
|
{amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/interactions/docs/lmngly.yaml
RENAMED
|
File without changes
|
{amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/interactions/docs/p2rank.yaml
RENAMED
|
File without changes
|
{amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/interactions/docs/pesto.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/properties/docs/aminosol.yaml
RENAMED
|
File without changes
|
|
File without changes
|
{amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/properties/docs/esm2_embedding.yaml
RENAMED
|
File without changes
|
|
File without changes
|
{amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/properties/esm2_embedding.py
RENAMED
|
File without changes
|
|
File without changes
|
{amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/utilities/activesite_verifier.py
RENAMED
|
File without changes
|
|
File without changes
|
{amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/utilities/distance_calculator.py
RENAMED
|
File without changes
|
|
File without changes
|
{amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/utilities/docs/chain_select.yaml
RENAMED
|
File without changes
|
|
File without changes
|
{amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/utilities/docs/maxit_convert.yaml
RENAMED
|
File without changes
|
|
File without changes
|
{amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/utilities/docs/obabel_convert.yaml
RENAMED
|
File without changes
|
|
File without changes
|
{amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/utilities/docs/pdb_cleaner.yaml
RENAMED
|
File without changes
|
|
File without changes
|
{amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/utilities/docs/pdb_to_fasta.yaml
RENAMED
|
File without changes
|
{amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/utilities/docs/protein_relaxer.yaml
RENAMED
|
File without changes
|
|
File without changes
|
{amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/utilities/mol_size_calculator.py
RENAMED
|
File without changes
|
{amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/utilities/obabel_convert.py
RENAMED
|
File without changes
|
{amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/utilities/pdb_bfactor_overwrite.py
RENAMED
|
File without changes
|
|
File without changes
|
{amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/utilities/pdb_quality_assessment.py
RENAMED
|
File without changes
|
|
File without changes
|
{amina_cli-0.4.1 → amina_cli-0.4.3}/src/amina_cli/commands/tools/utilities/protein_relaxer.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|