Skip to content

Chore/papi 4760 stop using download url - #507

Merged
nahua-aignx merged 1 commit into
mainfrom
chore/PAPI-4760-stop-using-download-url
Mar 27, 2026
Merged

Chore/papi 4760 stop using download url#507
nahua-aignx merged 1 commit into
mainfrom
chore/PAPI-4760-stop-using-download-url

Conversation

@nahua-aignx

Copy link
Copy Markdown
Contributor

Gist of the PR

  • We will remove download_url from the artifact schema
  • Dima already added this new endpoint GET runs/{run-id}/artifacts/{artifact-id}/file which can be used to download the file instead of using the Signed URL directly
  • Python SDK (and launchpad?) must construct this URL based on the artifact-id which is still return from the artifact schema to download files

CopilotAI review requested due to automatic review settings March 24, 2026 12:02

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the SDK’s artifact downloading flow to stop relying on download_url from the artifact schema and instead resolve a presigned URL via the new GET /v1/runs/{run_id}/artifacts/{artifact_id}/file endpoint.

Changes:

  • Added Run.get_artifact_download_url() and updated run artifact downloads to resolve URLs via the new endpoint.
  • Updated application download logic to accept an optional (run_id, artifact_id) -> url resolver callback.
  • Updated the Launchpad run describe page to resolve URLs on-demand for preview/download actions, and added/updated unit tests.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 7 comments.

Show a summary per file
FileDescription
src/aignostics/platform/resources/runs.pyAdds URL resolution helper and switches artifact downloads to use artifact IDs + file endpoint.
src/aignostics/application/_download.pyAdds optional URL resolver callback and threads resolved URL into download_item_artifact.
src/aignostics/application/_service.pyWires a resolver callback into the application run download loop.
src/aignostics/application/_gui/_page_application_run_describe.pyUpdates GUI preview/download buttons to resolve presigned URLs via the run endpoint.
tests/aignostics/platform/resources/runs_test.pyAdds tests for URL resolution + updated artifact download behavior.
tests/aignostics/application/download_test.pyAdds tests for resolver callback behavior and explicit URL handling in downloads.

Comment threadsrc/aignostics/platform/resources/runs.py
Comment threadsrc/aignostics/application/_download.py Outdated
Comment threadtests/aignostics/platform/resources/runs_test.py Outdated
Comment threadtests/aignostics/platform/resources/runs_test.py Outdated
Comment threadtests/aignostics/platform/resources/runs_test.py
Comment threadtests/aignostics/platform/resources/runs_test.py Outdated
Comment threadsrc/aignostics/platform/resources/runs.py
CopilotAI review requested due to automatic review settings March 24, 2026 12:28
@nahua-aignx
nahua-aignxforce-pushed the chore/PAPI-4760-stop-using-download-url branch from 34a2d49 to 97eb372CompareMarch 24, 2026 12:28

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

@codecov

codecovBot commented Mar 24, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 76.97842% with 32 lines in your changes missing coverage. Please review.
✅ All tests successful. No failed tests found.

Files with missing linesPatch %Lines
...application/_gui/_page_application_run_describe.py45.45%23 Missing and 1 partial ⚠️
src/aignostics/platform/resources/runs.py89.47%5 Missing and 3 partials ⚠️
Files with missing linesCoverage Δ
src/aignostics/application/_download.py97.40% <100.00%> (+52.79%)⬆️
src/aignostics/application/_service.py61.32% <100.00%> (+6.94%)⬆️
src/aignostics/platform/__init__.py100.00% <100.00%> (ø)
src/aignostics/platform/resources/runs.py78.32% <89.47%> (+13.10%)⬆️
...application/_gui/_page_application_run_describe.py57.69% <45.45%> (-1.50%)⬇️

... and 6 files with indirect coverage changes

CopilotAI review requested due to automatic review settings March 24, 2026 14:24

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Comment threadsrc/aignostics/platform/resources/runs.py Outdated
Comment threadsrc/aignostics/platform/resources/runs.py Outdated
@nahua-aignx
nahua-aignxforce-pushed the chore/PAPI-4760-stop-using-download-url branch from cd90866 to f621afeCompareMarch 24, 2026 14:40
CopilotAI review requested due to automatic review settings March 24, 2026 14:51
@nahua-aignx
nahua-aignxforce-pushed the chore/PAPI-4760-stop-using-download-url branch from f621afe to 7f71a87CompareMarch 24, 2026 14:51

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Comment threadsrc/aignostics/platform/resources/runs.py Outdated
Comment threadsrc/aignostics/platform/resources/runs.py
@nahua-aignx
nahua-aignxforce-pushed the chore/PAPI-4760-stop-using-download-url branch from 7f71a87 to ae504bcCompareMarch 25, 2026 06:58
@nahua-aignx
nahua-aignx marked this pull request as ready for review March 25, 2026 07:32
CopilotAI review requested due to automatic review settings March 25, 2026 07:32
@claude

claudeBot commented Mar 25, 2026

Copy link
Copy Markdown

Claude finished @nahua-aignx's task in 7m 45s —— View job


PR Review Complete

Reviewing PR #507: Stop using download_url from artifact schema


Summary

This PR migrates artifact downloads from using the deprecated download_url field to resolving presigned URLs via the new GET /v1/runs/{run_id}/artifacts/{artifact_id}/file endpoint. The implementation is well-structured with comprehensive tests, but there are critical exception handling issues causing the E2E test failures.


🔴 BLOCKING ISSUES (Must Fix)

1. Incomplete Exception Handling in get_artifact_download_url() - ROOT CAUSE OF TEST FAILURES

Location:src/aignostics/platform/resources/runs.py:376-406

Problem: The method catches some requests exceptions but not all, causing unhandled exceptions to escape:

def_resolve() ->str:
token=token_provider()
try:
response=requests.get(...) # Line 379exceptrequests.Timeoutase:
raiseServiceException(...) frome# ✅ Handledexceptrequests.ConnectionErrorase:
raiseServiceException(...) frome# ✅ Handled# ❌ Missing: requests.HTTPError, requests.RequestException# ... status code checks ...response.raise_for_status() # Line 404 - Raises requests.HTTPError!# ❌ This HTTPError is NOT caught!

Impact:

  • Breaks E2E tests: Both failing tests timeout waiting for download completion because exceptions are escaping
  • No retry logic: Uncaught requests exceptions are NOT in RETRYABLE_EXCEPTIONS, so Tenacity won't retry
  • Inconsistent error handling: Users get raw requests exceptions instead of SDK exceptions

Fix Required:

def_resolve() ->str:
token=token_provider()
try:
response=requests.get(
endpoint_url,
headers={"Authorization": f"Bearer {token}", "User-Agent": user_agent()},
allow_redirects=False,
timeout=settings().run_timeout,
)
exceptrequests.Timeoutase:
raiseServiceException(status=HTTPStatus.SERVICE_UNAVAILABLE, reason="Request timed out") fromeexceptrequests.ConnectionErrorase:
raiseServiceException(status=HTTPStatus.SERVICE_UNAVAILABLE, reason="Connection failed") fromeexceptrequests.RequestExceptionase: # ✅ ADD THIS - Catch all other requests exceptionsraiseServiceException(
status=HTTPStatus.SERVICE_UNAVAILABLE, reason=f"Request failed: {e}"
) frome# Status code handlingifresponse.status_codein {
HTTPStatus.MOVED_PERMANENTLY,
HTTPStatus.FOUND,
HTTPStatus.TEMPORARY_REDIRECT,
HTTPStatus.PERMANENT_REDIRECT,
}:
location=response.headers.get("Location")
ifnotlocation:
msg=f"Redirect response {response.status_code} missing Location header for artifact {artifact_id}"raiseRuntimeError(msg)
returnlocationifresponse.status_code==HTTPStatus.NOT_FOUND:
raiseNotFoundException(status=HTTPStatus.NOT_FOUND, reason="Artifact not found")
ifresponse.status_code>=HTTPStatus.INTERNAL_SERVER_ERROR:
raiseServiceException(status=response.status_code, reason=response.reason)
# Handle 4xx errors (other than 404)ifresponse.status_code>=400: # ✅ ADD THIS - Explicit 4xx handlingraiseServiceException(status=response.status_code, reason=response.reason)
# Remove raise_for_status() since we handle all error cases abovemsg=f"Unexpected status {response.status_code} from artifact file endpoint for artifact {artifact_id}"raiseRuntimeError(msg)

Why This Fixes the E2E Tests:

  1. All requests exceptions are now caught and converted to ServiceException
  2. ServiceException is in RETRYABLE_EXCEPTIONS, so transient failures will be retried
  3. No more unhandled exceptions escaping to break the download flow

Verify Fix:

uv run pytest tests/aignostics/application/gui_test.py::test_gui_run_download -v
uv run pytest tests/aignostics/qupath/gui_test.py::test_gui_run_qupath_install_to_inspect -v

2. Missing Logging in URL Resolution

Location:src/aignostics/platform/resources/runs.py:351-414

Problem: No logging when URL resolution succeeds or fails, making debugging difficult.

Fix:

defget_artifact_download_url(self, artifact_id: str) ->str:
"""Get a presigned download URL for an artifact via the file endpoint."""endpoint_url= (
f"{self._api.api_client.configuration.host.rstrip('/')}/v1/runs/{self.run_id}/artifacts/{artifact_id}/file"
)
logger.trace("Resolving download URL for artifact {} via {}", artifact_id, endpoint_url) # ✅ ADD# ... _resolve() implementation ...result=Retrying(...)(resolve)
logger.trace("Resolved download URL for artifact {}: {}", artifact_id, result[:100] +"...") # ✅ ADDreturnresult

⚠️SUGGESTIONS (Non-Blocking)

3. Enhance Artifact Skipping Feedback

Location:src/aignostics/platform/resources/runs.py:441-449

Copilot's suggestion is good - improve user feedback when artifacts are skipped:

ifnotartifact.output_artifact_id:
logger.error( # Changed from warning"Skipping artifact %s for item %s, missing output_artifact_id",
artifact.name,
item.external_id,
)
ifprint_status:
print(
f"> Skipping artifact {artifact.name} for item {item.external_id}, missing output_artifact_id"
)
continue

Rationale: Missing output_artifact_id is an error condition, not just a warning.


4. Clarify Deprecated Fallback Path

Location:src/aignostics/application/_download.py:208-215

The fallback logic is correct, but could be clearer:

# Resolve artifact download URL via the new file endpointifget_artifact_download_urlandartifact.output_artifact_id:
artifact_url=get_artifact_download_url(application_run.run_id, artifact.output_artifact_id)
elifdownload_url:=getattr(artifact, "download_url", None):
# Deprecated fallback: Once download_url is removed from schema, this will be Noneartifact_url=download_urllogger.warning(
"Using deprecated download_url fallback for artifact {} - this will stop working when PAPI-4760 is complete",
artifact.output_artifact_idorartifact.name
)
else:
logger.warning("No download URL available for artifact {}", artifact.output_artifact_id)
continue

POSITIVE FINDINGS

Test Coverage

  • Excellent: All new tests have proper @pytest.mark.unit markers ✅
  • Comprehensive: Tests cover URL resolution, fallback, retries, edge cases ✅
  • Well-structured: Clear test names and assertions ✅

Test Files:

  • tests/aignostics/application/download_test.py: 17 new unit tests
  • tests/aignostics/platform/resources/runs_test.py: 6 new unit tests

Code Quality

  • Good separation: URL resolution in runs.py, download logic in _download.py
  • Proper retry logic: Uses Tenacity with exponential backoff
  • Fallback path: Maintains backward compatibility during migration
  • Type hints: Proper typing throughout

Architecture

  • Clean callback pattern: get_artifact_download_url parameter allows flexible URL resolution
  • Proper error propagation: Most exceptions are wrapped in SDK types (except the bug above)

📋 REVIEW CHECKLIST

CheckStatusNotes
Test MarkersAll new tests have @pytest.mark.unit
Test CoverageComprehensive unit test coverage
Linting🔄Running...
Breaking Changes⚠️Gradual migration - download_url fallback preserves compatibility
SecurityProper token handling, no secrets exposed
DocumentationClear docstrings and inline comments
Exception Handling🔴Critical issue - see #1 above
E2E Test Failures🔴Caused by exception handling bug - see #1

🔧 REQUIRED ACTIONS

  1. FIX BLOCKING ISSUE chore(deps): Bump actions/upload-artifact from 4.6.1 to 4.6.2 #1: Add comprehensive exception handling in get_artifact_download_url()
  2. VERIFY: Run E2E tests after fix:
    uv run pytest tests/aignostics/application/gui_test.py::test_gui_run_download -v
    uv run pytest tests/aignostics/qupath/gui_test.py::test_gui_run_qupath_install_to_inspect -v
  3. IMPLEMENT: Add logging (Suggestion chore(deps): update actions/upload-artifact action to v4.6.2 #2)
  4. CONSIDER: Suggestions chore(deps): update python docker tag to v3.13 - autoclosed #3 and Dependency Dashboard #4

📚 REFERENCES

  • CLAUDE.md: Architecture compliance ✅
  • Testing Requirements: 85% coverage maintained, proper markers ✅
  • Copilot Review: Identified same exception handling issue
  • SonarQube: Quality gate passed (with 6 new issues - review separately)

Recommendation:REQUEST CHANGES - Critical exception handling bug must be fixed before merge. The E2E test failures are not flaky - they're caused by unhandled requests exceptions escaping from get_artifact_download_url().

Once the blocking issue is fixed, this will be a solid migration that properly decouples the SDK from the deprecated download_url field.


CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Comment threadsrc/aignostics/platform/resources/runs.py Outdated
Comment threadtests/aignostics/platform/resources/runs_test.py
@nahua-aignx
nahua-aignxforce-pushed the chore/PAPI-4760-stop-using-download-url branch from ae504bc to 710ad74CompareMarch 25, 2026 07:53
CopilotAI review requested due to automatic review settings March 25, 2026 08:24
@nahua-aignx
nahua-aignxforce-pushed the chore/PAPI-4760-stop-using-download-url branch from 710ad74 to 8b7b9edCompareMarch 25, 2026 08:24
Comment threadsrc/aignostics/platform/resources/runs.py Outdated
@nahua-aignx
nahua-aignxforce-pushed the chore/PAPI-4760-stop-using-download-url branch from 8b7b9ed to f5382bdCompareMarch 25, 2026 08:31

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.

Comment threadsrc/aignostics/platform/resources/runs.py Outdated
@nahua-aignx

Copy link
Copy Markdown
ContributorAuthor

Response to codecov/patch issue: The 23 uncovered lines in the GUI file are the bodies of _preview_tiff, _preview_csv, and _download_artifact — the async inner functions on the per-artifact buttons. Testing them properly requires NiceGUI's async test harness, mocking nicegui_run.io_bound, and simulating button clicks in the artifact expansion panels. That's significant test scaffolding for three functions that all follow identical structure (set loading → resolve URL → open → clear loading → notify on error). The e2e tests already exercise the critical start_download() path.

Comment threadsrc/aignostics/platform/resources/runs.py Outdated
@nahua-aignx
nahua-aignxforce-pushed the chore/PAPI-4760-stop-using-download-url branch from 318bff8 to b3d3e55CompareMarch 25, 2026 13:58
CopilotAI review requested due to automatic review settings March 25, 2026 13:58

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Comment threadtests/aignostics/platform/resources/runs_test.py
Comment threadsrc/aignostics/platform/resources/runs.py Outdated
Comment threadsrc/aignostics/platform/resources/runs.py Outdated
CopilotAI review requested due to automatic review settings March 26, 2026 09:06

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Comment threadsrc/aignostics/platform/resources/runs.py
Comment threadsrc/aignostics/platform/resources/runs.py
CopilotAI review requested due to automatic review settings March 26, 2026 11:09

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.

Comment threadtests/aignostics/platform/resources/runs_test.py
Comment threadsrc/aignostics/application/_download.py Outdated
@nahua-aignx
nahua-aignxforce-pushed the chore/PAPI-4760-stop-using-download-url branch from a0ffee1 to d2320f2CompareMarch 26, 2026 12:55
CopilotAI review requested due to automatic review settings March 26, 2026 12:55

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.

Comment threadtests/aignostics/platform/resources/runs_test.py
Comment threadtests/aignostics/platform/resources/runs_test.py
Comment threadtests/aignostics/platform/resources/runs_test.py
@nahua-aignx
nahua-aignxforce-pushed the chore/PAPI-4760-stop-using-download-url branch from d2320f2 to 7b34d58CompareMarch 27, 2026 06:52
@sonarqubecloud

Copy link
Copy Markdown

@nahua-aignx
nahua-aignx merged commit d552c89 into mainMar 27, 2026
27 of 28 checks passed
nahua-aignx added a commit that referenced this pull request Mar 27, 2026
nahua-aignx added a commit that referenced this pull request Mar 27, 2026
helmut-hoffer-von-ankershoffen added a commit that referenced this pull request Apr 26, 2026
…e PYSDK-109
Codecov flagged 17 uncovered lines on the GUI page handler — all in the
async URL-resolve closure I added in this PR. PR #507 hit the same gap and
deferred the coverage; the principled fix is to extract the helper from the
closure into a module-level function so it can be unit-tested without the
NiceGUI page harness.
- src/aignostics/application/_gui/_page_application_run_describe.py:
- extract _resolve_artifact_url_or_notify to module level, taking `run`
as a parameter (instead of closing over the page-handler variable).
- inner open_tiff_preview / open_csv_preview / open_artifact_download
now delegate to the module-level helper.
- tests/aignostics/application/gui_test.py: 4 new unit tests covering:
1. Happy path: io_bound returns a URL → helper returns it, no notify.
2. Failure path: io_bound raises → helper notifies user with a warning,
returns None (so the calling closure short-circuits).
3. Button loading state toggled on success (add then remove).
4. Button loading state toggled on exception (regression guard against
a stuck spinner if the resolve fails).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
helmut-hoffer-von-ankershoffen added a commit that referenced this pull request Apr 26, 2026
…PYSDK-109] (#598)
* feat(platform): replace deprecated download_url with /file endpoint PYSDK-109
SAMIA deprecated OutputArtifactResultReadResponse.download_url in favour of
on-demand presigned URLs from GET /api/v1/runs/{run_id}/artifacts/{artifact_id}/file
(307 Location). Migrate the SDK before SAMIA stops emitting the deprecated field.
Re-introduces the change reverted in #514 (forced by upstream PAPI-4868, now fixed).
Replaces stale community PR #478. See ticket for the full diff vs PR #478.
- platform/resources/runs.py: new Artifact resource with retry, typed exceptions,
no private codegen access; Run.artifact() / get_artifact_download_url() delegates;
ensure_artifacts_downloaded gated on AVAILABLE; latent if-ItemOutput.FULL bug fixed
- application/_download.py: pass Run, fetch URL fresh per artifact, skip non-AVAILABLE
- application/_utils.py: drop Download URL print line (presigned URL leak vector)
- _page_application_run_describe.py: async click handlers via nicegui_run.io_bound
- 17 new unit tests + e2e assertion now exercises the new endpoint
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(platform): honor token_provider, fix sonar hotspots, expand tests PYSDK-109
Address review feedback on PR #598:
- Copilot review: Artifact._fetch_redirect_url now uses
configuration.token_provider when set (set up by Client.get_api_client with
use_cache=cache_token), falling back to get_token() only when configuration
was built outside Client. Honors the user's cache_token=False choice.
- SonarQube python:S5332: replace http://proxy.local in proxy test with
https://corp-proxy.local; the test verifies value propagation, the scheme
doesn't matter for that.
- Coverage: add tests for Run.ensure_artifacts_downloaded covering AVAILABLE
gating, non-AVAILABLE skipping, resume-on-checksum-mismatch, no-metadata
skip, and the staticmethod->instance method conversion. Add a test for the
post-termination loop in download_to_folder pinning the latent
if-ItemOutput.FULL enum-truthiness fix. Add a test asserting
configuration.token_provider takes precedence over get_token() fallback.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* test(platform): fix sonar S1192 dup literal and S7632 noqa syntax PYSDK-109
Address remaining SonarCloud code-smell issues on PR #598:
- python:S1192 line 1151: extract repeated
"aignostics.platform.resources.runs.mime_type_to_file_ending" patch target
into _PATCH_MIME_TYPE_TO_FILE_ENDING constant (was duplicated 3 times in
the new ensure_artifacts_downloaded test fixtures).
- python:S7632 line 30: drop the parenthetical explanation from
'# noqa: S105 (mock target string, not a credential)' which the parser
rejected; ruff accepts the bare '# noqa: S105' which is what we now use.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(platform): support 303 See Other; reorder metadata vs mime check PYSDK-109
Address two new Copilot review comments on PR #598:
- _REDIRECT_STATUSES was missing HTTPStatus.SEE_OTHER (303). The /file
endpoint contractually returns 307, but accepting all well-known redirect
statuses (301/302/303/307/308) makes the SDK robust against backend
changes. Test parametrization extended to include 303.
- ensure_artifacts_downloaded computed the MIME-derived file_ending before
the empty-metadata guard, so artifacts with empty metadata would raise
ValueError("Unknown mime type: application/octet-stream") in
mime_type_to_file_ending instead of being skipped cleanly. The metadata
check now runs first and returns early; MIME lookup only runs for
artifacts that have metadata. Test simplified — no longer needs MIME mocks.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* test(e2e): add PR-time canary for SAMIA /file endpoint reachability PYSDK-109
Existing e2e coverage of the new /file endpoint sits behind the
@scheduled_only marker (test_platform_*_app_find_and_validate via
_validate_output) or behind @long_running (test_gui_run_download), neither
of which runs on a PR with skip:test:long_running. That left a window where a
SAMIA-side regression on /api/v1/runs/{run_id}/artifacts/{artifact_id}/file
(PAPI-4868 was exactly this kind of bug) would ship green through PR CI and
be caught only by the next scheduled run, up to 6h later.
Add test_platform_artifact_file_endpoint_resolves_to_working_url:
- @pytest.mark.e2e + 60s timeout, NOT long_running, NOT scheduled_only.
- Picks any recent successful HETA run tagged 'scheduled', resolves a
presigned URL through Run.get_artifact_download_url against real SAMIA,
HEAD-checks the URL to confirm storage backend serves it.
- Skips gracefully when no scheduled run is yet available.
Covers end-to-end at PR time:
- /file 4xx/5xx -> typed exception
- /file 3xx with empty Location -> RuntimeError
- /file Location pointing at broken / wrong-scope URL -> HEAD fails
Helper _find_available_artifact_in_recent_heta_run extracted to keep the
test body simple and under ruff's C901 complexity threshold.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* test(gui): cover _resolve_artifact_url_or_notify, raise patch coverage PYSDK-109
Codecov flagged 17 uncovered lines on the GUI page handler — all in the
async URL-resolve closure I added in this PR. PR #507 hit the same gap and
deferred the coverage; the principled fix is to extract the helper from the
closure into a module-level function so it can be unit-tested without the
NiceGUI page harness.
- src/aignostics/application/_gui/_page_application_run_describe.py:
- extract _resolve_artifact_url_or_notify to module level, taking `run`
as a parameter (instead of closing over the page-handler variable).
- inner open_tiff_preview / open_csv_preview / open_artifact_download
now delegate to the module-level helper.
- tests/aignostics/application/gui_test.py: 4 new unit tests covering:
1. Happy path: io_bound returns a URL → helper returns it, no notify.
2. Failure path: io_bound raises → helper notifies user with a warning,
returns None (so the calling closure short-circuits).
3. Button loading state toggled on success (add then remove).
4. Button loading state toggled on exception (regression guard against
a stuck spinner if the resolve fails).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* test(gui): collapse 3 inner closures into _resolve_artifact_url_and_invoke PYSDK-109
Codecov was still failing the project gate (70% target, hard gate per
codecov.yml) on the GUI page handler — even after extracting the
URL-resolve helper to module level. The 9 remaining missing patch lines
were the bodies of the three inner async closures (open_tiff_preview,
open_csv_preview, open_artifact_download): each a 2-line wrapper with the
same shape (await resolve → if URL non-None, do X with it).
Replace all three with a single module-level helper
_resolve_artifact_url_and_invoke(run, artifact_id, button, on_success):
the three button on_click lambdas now call this directly, passing the
appropriate on_success closure (tiff_dialog_open, csv_dialog_open,
webbrowser.open). The closures inside the page handler shrink to a single
inline lambda each.
Add 2 unit tests:
- on_success is invoked exactly once with the resolved URL on success
- on_success is NOT invoked when resolution fails (the user has already
been notified via ui.notify; calling on_success(None) would either
crash or open empty dialogs)
This + the prior 4 helper tests give us full unit coverage of every
non-trivial line introduced by the GUI changes in this PR. The closures
that remain inside _page_application_run_describe (open_qupath, dialog
helpers) are unchanged from main and not in this PR's patch.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* docs(platform): mark download_url deprecated; show /file endpoint in examples PYSDK-109
Update the Module CLAUDE.md to teach the new pattern:
- ArtifactOutput pseudo-model now annotates `download_url` as DEPRECATED and
documents output_artifact_id + Run.get_artifact_download_url(artifact_id)
as the supported way to obtain a presigned URL.
- "Checking Item Status" example replaced `print(artifact.download_url)`
with `run.get_artifact_download_url(artifact.output_artifact_id)` so
copy-pasted user code is on the supported path from day one.
The Sphinx lib_reference.rst is `.. automodule::` so the new public API
(Artifact resource, Run.artifact(), Run.get_artifact_download_url) gets
documented from docstrings — no manual rst edit needed.
Out of scope for this PR: Ketryx ADR-7 ("Result Download Infrastructure")
should add a "URL Resolver (Run.get_artifact_download_url)" step between
Run Status Validator and File Downloader. Lives in Ketryx, will track as
follow-up on PYSDK-109.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* docs(spec): update SPEC_PLATFORM_SERVICE.md for Artifact resource + new Run methods PYSDK-109
Earlier spec audit missed the in-repo specifications/ directory. The
SPEC_PLATFORM_SERVICE.md spec on main lists ApplicationRun's public API
explicitly; this PR adds three new public methods (artifact,
get_artifact_download_url, ensure_artifacts_downloaded) and one new public
class (Artifact), so the spec was stale.
Updated:
- Section 2.2 "Key Components" table: ApplicationRun row now lists the
three new methods; new Artifact row added with its single public method
get_download_url and a one-liner on its purpose.
- Section 4.1 pseudo-Python class definitions: ApplicationRun gains
artifact / get_artifact_download_url / ensure_artifacts_downloaded with
docstrings explaining the /file endpoint resolution and the typed
exception mapping; new Artifact class block added with constructor +
get_download_url docstring.
- download_to_folder docstring annotated to note that it now resolves a
fresh presigned URL per artifact internally (user-facing behaviour
unchanged).
Other in-repo specs (SPEC-APPLICATION-SERVICE.md, SPEC-BUCKET-SERVICE.md,
etc.) were re-audited and don't reference deprecated download_url field
or the new endpoint at a level of detail that would make them stale.
Note on ADR-7: an ADR-7-RESULT-DOWNLOAD-INFRASTRUCTURE.md file existed on
an old (unmerged) branch (commit 2e83f87). It is NOT on main, so cannot
be updated as part of this PR. The Ketryx counterpart is the authoritative
copy and needs the same URLResolver-step update post-merge.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* ci: retrigger CI after removing skip:test:long_running label PYSDK-109
This PR touches core SDK download flow (platform.Run, GUI page handler,
application._download); per Helmut's call we want long_running tests in CI
to validate the GUI download e2e path on this branch, not just the
short-running matrix.
Empty commit so the workflow re-evaluates labels at trigger time.
* fix: address 4 new Copilot review comments PYSDK-109
* runs.py download_to_folder: propagate `print_status` into the two
ensure_artifacts_downloaded call sites so a top-level
download_to_folder(print_status=False) actually suppresses ALL console
output, not just the loop's own messages. Was a real bug — the inner
call kept printing because ensure_artifacts_downloaded defaults to True.
* e2e_test.py canary: client.runs.list yields Run handles directly, so
Run.for_run_id(run_summary.run_id) is redundant and adds an extra API
client construction. Use the iterator's handle directly.
* e2e_test.py canary: don't materialize ALL scheduled HETA runs with
list(client.runs.list(...)) — staging accumulates runs and this risks
the canary's 60s timeout. Iterate the generator lazily via
itertools.islice(...) capped at _CANARY_MAX_RUNS_TO_INSPECT (20). The
list is already newest-first so capping is biased toward recent data.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix: propagate print_status to second ensure_artifacts_downloaded call site PYSDK-109
Copilot review on e3beb28 noticed my prior fix only updated the
polling-loop call site (24-space indent) and missed the post-termination
call site (20-space indent) — replace_all matched on indentation, not
on the call signature. Both call sites now forward print_status, so
download_to_folder(print_status=False) consistently suppresses ALL
console output.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(e2e): split canary helper to drop SonarQube cognitive complexity below 15 PYSDK-109
SonarQube python:S3776 flagged _find_available_artifact_in_recent_heta_run
at cognitive complexity 18 (>15). The triple-nested loop with compound
state/output filter conditions tripped it.
Extract the per-run artifact scan into _find_available_output_artifact_id.
The outer function now does only the run-level filtering and delegates the
item/artifact walk; complexity drops below 15. No behavioural change.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* chore: fix end-of-file in scheduled_tasks.lock
* style: ruff reformat 3 files after ruff version bump in lock-file maintenance
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@nahua-aignx@olivermeyer