Skip to content

Commit 062b5df

Browse files
committed
fixup! fixup! fixup! fixup! chore: Use SAMIA run artifact file endpoint to download artifacts
1 parent 06c8cb0 commit 062b5df

4 files changed

Lines changed: 131 additions & 140 deletions

File tree

src/aignostics/application/_gui/_page_application_run_describe.py

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -339,9 +339,9 @@ async def start_download() -> None:
339339

340340
# Activate the timer now that download is starting
341341
progress_timer.activate()
342+
download_button.disable()
343+
download_button.props(add="loading")
342344
try:
343-
download_button.disable()
344-
download_button.props(add="loading")
345345
results_folder = await nicegui_run.cpu_bound(
346346
Service.application_run_download_static,
347347
run_id=run.run_id,
@@ -365,19 +365,24 @@ async def start_download() -> None:
365365
else:
366366
ui.notify("Download completed.", type="positive")
367367
show_in_file_manager(str(results_folder))
368-
except ValueError as e:
368+
except Exception as e:
369+
logger.exception(
370+
"Download failed for run {} (qupath_project={}, marimo={}, folder={})",
371+
run.run_id,
372+
current_qupath_project,
373+
current_marimo,
374+
current_folder,
375+
)
369376
ui.notify(f"Download failed: {e}", type="negative", multi_line=True)
377+
finally:
370378
progress_timer.deactivate()
371379
progress_state["queue"] = None
372-
return
373-
progress_timer.deactivate()
374-
progress_state["queue"] = None
375-
download_button.props(remove="loading")
376-
download_button.enable()
377-
download_item_status.set_visibility(False)
378-
download_item_progress.set_visibility(False)
379-
download_artifact_status.set_visibility(False)
380-
download_artifact_progress.set_visibility(False)
380+
download_button.props(remove="loading")
381+
download_button.enable()
382+
download_item_status.set_visibility(False)
383+
download_item_progress.set_visibility(False)
384+
download_artifact_status.set_visibility(False)
385+
download_artifact_progress.set_visibility(False)
381386

382387
ui.separator()
383388
with ui.row(align_items="end").classes("w-full justify-end"):

src/aignostics/platform/resources/runs.py

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
import requests
1717
from aignx.codegen.api.public_api import PublicApi
18-
from aignx.codegen.exceptions import NotFoundException, ServiceException
18+
from aignx.codegen.exceptions import ApiException, NotFoundException, ServiceException
1919
from aignx.codegen.models import (
2020
CustomMetadataUpdateRequest,
2121
ItemCreationRequest,
@@ -351,7 +351,7 @@ def download_to_folder( # noqa: C901
351351
def get_artifact_download_url(self, artifact_id: str) -> str:
352352
"""Get a presigned download URL for an artifact via the file endpoint.
353353
354-
Calls GET /v1/runs/{run_id}/artifacts/{artifact_id}/file with
354+
Calls GET /api/v1/runs/{run_id}/artifacts/{artifact_id}/file with
355355
allow_redirects=False and extracts the presigned URL from the Location
356356
header of the 307 redirect response. Using the codegen method is not
357357
viable here because urllib3 follows the redirect automatically, fetching
@@ -365,19 +365,36 @@ def get_artifact_download_url(self, artifact_id: str) -> str:
365365
366366
Raises:
367367
NotFoundException: If the artifact is not found (404).
368-
ServiceException: On 5xx server errors (retried automatically).
368+
ServiceException: On 5xx or other unexpected HTTP errors (retried automatically where transient).
369369
RuntimeError: If the redirect Location header is missing.
370370
"""
371-
endpoint_url = f"{self._api.api_client.configuration.host}/v1/runs/{self.run_id}/artifacts/{artifact_id}/file"
371+
configuration = self._api.api_client.configuration
372+
host = configuration.host.rstrip("/")
373+
endpoint_url = f"{host}/api/v1/runs/{self.run_id}/artifacts/{artifact_id}/file"
374+
token_provider = getattr(configuration, "token_provider", None) or get_token
375+
proxy = getattr(configuration, "proxy", None)
376+
ssl_ca_cert = getattr(configuration, "ssl_ca_cert", None)
377+
verify_ssl = getattr(configuration, "verify_ssl", True)
378+
ssl_verify: bool | str = ssl_ca_cert or verify_ssl
379+
logger.trace("Resolving download URL for artifact {} via {}", artifact_id, endpoint_url)
372380

373381
def _resolve() -> str:
374-
token = get_token()
375-
response = requests.get(
376-
endpoint_url,
377-
headers={"Authorization": f"Bearer {token}", "User-Agent": user_agent()},
378-
allow_redirects=False,
379-
timeout=settings().run_timeout,
380-
)
382+
token = token_provider()
383+
try:
384+
response = requests.get(
385+
endpoint_url,
386+
headers={"Authorization": f"Bearer {token}", "User-Agent": user_agent()},
387+
allow_redirects=False,
388+
timeout=settings().run_timeout,
389+
proxies={"http": proxy, "https": proxy} if proxy else None,
390+
verify=ssl_verify,
391+
)
392+
except requests.Timeout as e:
393+
raise ServiceException(status=HTTPStatus.SERVICE_UNAVAILABLE, reason="Request timed out") from e
394+
except requests.ConnectionError as e:
395+
raise ServiceException(status=HTTPStatus.SERVICE_UNAVAILABLE, reason="Connection failed") from e
396+
except requests.RequestException as e:
397+
raise ServiceException(status=HTTPStatus.SERVICE_UNAVAILABLE, reason=f"Request failed: {e}") from e
381398
if response.status_code in {
382399
HTTPStatus.MOVED_PERMANENTLY,
383400
HTTPStatus.FOUND,
@@ -393,17 +410,20 @@ def _resolve() -> str:
393410
raise NotFoundException(status=HTTPStatus.NOT_FOUND, reason="Artifact not found")
394411
if response.status_code >= HTTPStatus.INTERNAL_SERVER_ERROR:
395412
raise ServiceException(status=response.status_code, reason=response.reason)
396-
response.raise_for_status()
413+
if response.status_code >= HTTPStatus.BAD_REQUEST:
414+
raise ApiException(status=response.status_code, reason=response.reason)
397415
msg = f"Unexpected status {response.status_code} from artifact file endpoint for artifact {artifact_id}"
398416
raise RuntimeError(msg)
399417

400-
return Retrying(
418+
url = Retrying(
401419
retry=retry_if_exception_type(exception_types=RETRYABLE_EXCEPTIONS),
402420
stop=stop_after_attempt(settings().run_retry_attempts),
403421
wait=wait_exponential_jitter(initial=settings().run_retry_wait_min, max=settings().run_retry_wait_max),
404422
before_sleep=_log_retry_attempt,
405423
reraise=True,
406424
)(_resolve)
425+
logger.trace("Resolved download URL for artifact {}", artifact_id)
426+
return url
407427

408428
def ensure_artifacts_downloaded(
409429
self,
@@ -431,6 +451,13 @@ def ensure_artifacts_downloaded(
431451
downloaded_at_least_one_artifact = False
432452
for artifact in item.output_artifacts:
433453
if not artifact.output_artifact_id:
454+
logger.warning(
455+
"Skipping artifact {} for item {}: missing output_artifact_id", artifact.name, item.external_id
456+
)
457+
if print_status:
458+
print(
459+
f"> Skipping artifact {artifact.name} for item {item.external_id}: missing output_artifact_id"
460+
)
434461
continue
435462
item_dir.mkdir(exist_ok=True, parents=True)
436463
file_ending = mime_type_to_file_ending(get_mime_type_for_artifact(artifact))

0 commit comments

Comments
 (0)