Skip to content

Revert "Chore/papi 4760 stop using download url"#514

Merged
nahua-aignx merged 1 commit intomainfrom
revert-507-chore/PAPI-4760-stop-using-download-url
Mar 27, 2026
Merged

Revert "Chore/papi 4760 stop using download url"#514
nahua-aignx merged 1 commit intomainfrom
revert-507-chore/PAPI-4760-stop-using-download-url

Conversation

@nahua-aignx
Copy link
Copy Markdown
Contributor

@nahua-aignx nahua-aignx commented Mar 27, 2026

Reverts #507

We can re-revert this later after https://aignx.atlassian.net/browse/PAPI-4868 is merged and released to production.

A SAMIA API bug would prevent the new download feature in python-sdk from working. So the decision is to revert the feature addition, fix SAMIA, release SAMIA to production, then re-introduce the feature again.

Copilot AI review requested due to automatic review settings March 27, 2026 09:25
@nahua-aignx nahua-aignx marked this pull request as ready for review March 27, 2026 09:25
@claude
Copy link
Copy Markdown

claude bot commented Mar 27, 2026

Claude finished @nahua-aignx's task in 5m 6s —— View job


PR Review Complete ✅

Reviewing revert PR #514: Revert "Chore/papi 4760 stop using download url"


Summary

This PR cleanly reverts PR #507 which attempted to migrate from using the deprecated download_url field to the new SAMIA run artifact file endpoint (/api/v1/runs/{run_id}/artifacts/{artifact_id}/file). The revert is necessary to unblock work until PAPI-4868 is resolved.

Verification Results

✅ All Critical Checks Passed

  1. Test Markers

    • All 779 tests have proper category markers (unit, integration, or e2e)
    • No unmarked tests found (verified with pytest -m "not unit and not integration and not e2e" --collect-only)
  2. Linting

    • ruff check: All checks passed
    • ruff format: 165 files already formatted
    • pyright: 0 errors, 0 warnings
    • mypy: Success - no issues found in 81 source files
  3. Commit Message

    • Follows conventional commits: Revert "chore: Use SAMIA run artifact file endpoint to download artifacts (#507)"
  4. Test Coverage

    • No coverage concerns (revert removes both code and tests together)

What's Being Reverted

Removed Code (Going Back To)

platform/resources/runs.py (-177 lines):

  • Artifact class - provided wrapper for artifact operations
  • Run.artifact(artifact_id) - factory method for Artifact instances
  • Run.get_artifact_download_url(artifact_id) - presigned URL resolution via file endpoint
  • Artifact._fetch_redirect_url() - manual redirect handling using requests library
  • Restored: Direct usage of artifact.download_url field (deprecated but functional)

application/_download.py (-53 lines):

  • _resolve_artifact_url() - fallback logic (new endpoint → deprecated field)
  • get_artifact_download_url parameter from download functions
  • Restored: Direct artifact.download_url usage

application/_service.py (-14 lines):

  • ❌ Passing get_artifact_download_url callback to download functions

platform/init.py (-1 export):

  • Artifact class removed from public API exports

Removed Tests (-910 lines)

tests/platform/resources/runs_test.py (-487 lines):

  • Tests for Artifact class
  • Tests for get_artifact_download_url() method
  • Tests for redirect URL resolution with retries

tests/application/download_test.py (-368 lines):

  • Tests for _resolve_artifact_url() fallback logic
  • Tests for new endpoint with fallback behavior

tests/application/service_test.py (-55 lines):

  • Integration tests using artifact file endpoint

Review Findings

✅ No Issues Found

This is a clean, correct revert with no issues identified:

  1. Complete Removal: All code related to the artifact file endpoint has been cleanly removed
  2. No Orphaned References: Verified no remaining calls to removed methods (grep search found none)
  3. Export Consistency: Artifact properly removed from platform/__init__.py exports
  4. Test Cleanup: All tests for removed functionality also removed (proper hygiene)
  5. Fallback Restored: Code now uses the deprecated artifact.download_url field again
  6. No Breaking Changes: For end users, artifact downloads continue working (via old mechanism)

Architecture Compliance ✅

  • Modulith Pattern: Preserved (no architectural changes)
  • Type Safety: All type hints remain valid
  • Error Handling: Restored to previous error handling pattern
  • Logging: Logging statements appropriately reverted

Why This Revert Is Safe

  1. Temporary: Explicitly stated this will be re-reverted after PAPI-4868 is resolved
  2. Functional: Old download_url mechanism still works (though deprecated)
  3. No Data Loss: No user-facing functionality is lost, just implementation detail
  4. Clean State: Returns codebase to known-good state pre-PR Chore/papi 4760 stop using download url #507

CI/CD Impact

Expected CI Behavior

  • ✅ All test suites will pass (unit, integration, e2e)
  • ✅ Linting already verified to pass
  • ✅ No long-running tests affected
  • ✅ Coverage maintained (code and tests removed together)

Recommendation

Already applied: PR has label skip:test_long_running ✅ (good practice per CLAUDE.md)


Medical Device Software Compliance

This revert maintains compliance requirements:

  • No Security Impact: Uses same authentication/authorization as before
  • No Data Privacy Impact: Same data handling (artifact downloads)
  • Audit Trail: Git history preserves full change history
  • Rollback Safety: Clean revert enables easy rollback if needed

Recommendations

✅ Approve This PR

This is a well-executed revert:

  • Clean code removal
  • Complete test cleanup
  • No breaking changes
  • Clear rationale (PAPI-4868 blocker)
  • Temporary measure with re-revert planned

Future Work (After PAPI-4868)

When re-reverting later:

  1. Verify PAPI-4868 is deployed to production
  2. Re-apply PR Chore/papi 4760 stop using download url #507 changes
  3. Test artifact file endpoint thoroughly in staging
  4. Monitor for redirect handling issues

Approval

Status: ✅ APPROVED (already approved by @olivermeyer)

My Review: ✅ APPROVED

  • All automated checks pass
  • Clean revert with no issues
  • Proper justification provided
  • Safe to merge

@sonarqubecloud
Copy link
Copy Markdown

@nahua-aignx nahua-aignx enabled auto-merge (squash) March 27, 2026 09:29
Copy link
Copy Markdown

Copilot AI left a comment

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 reverts prior work (#507) that stopped using download_url for output artifacts, restoring the SDK’s previous behavior of consuming artifact.download_url directly for previews and downloads until the new backend endpoint (PAPI-4868) is production-ready.

Changes:

  • Removes the Artifact helper and the “resolve presigned URL via redirect” logic from the platform Run resource.
  • Reverts application download flow to rely on artifact.download_url (removing the callback-based resolver path).
  • Updates GUI artifact preview/download actions to use artifact.download_url directly.

Reviewed changes

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

Show a summary per file
File Description
src/aignostics/platform/resources/runs.py Removes Artifact and switches item artifact downloads back to artifact.download_url.
src/aignostics/platform/__init__.py Stops exporting Artifact from the platform module.
src/aignostics/application/_download.py Removes URL resolver/callback support and downloads artifacts via artifact.download_url.
src/aignostics/application/_service.py Removes passing the artifact URL resolver callback into download_available_items.
src/aignostics/application/_gui/_page_application_run_describe.py Uses artifact.download_url for preview/download; adjusts download button handling.
tests/aignostics/platform/resources/runs_test.py Drops tests covering Artifact and presigned URL resolution behavior.
tests/aignostics/application/service_test.py Removes test that validated the callback closure delegation.
tests/aignostics/application/download_test.py Removes most tests for result-artifact downloading utilities, leaving only URL-to-file tests.
Comments suppressed due to low confidence (2)

src/aignostics/application/_download.py:268

  • download_item_artifact passes artifact.download_url directly into download_file_with_progress without validating it's set. If an output artifact is present but has download_url=None (or missing), this will fail later inside requests.get with a confusing error. Add an explicit check (and either skip with a warning or raise a clear ValueError) before attempting the download.
    metadata = artifact.metadata or {}
    metadata_checksum = metadata.get("checksum_base64_crc32c", "") or metadata.get("checksum_crc32c", "")
    if not metadata_checksum:
        message = f"No checksum metadata found for artifact {artifact.name}"
        logger.error(message)
        raise ValueError(message)

    artifact_path = (
        destination_directory
        / f"{prefix}{sanitize_path_component(artifact.name)}{get_file_extension_for_artifact(artifact)}"
    )

    if artifact_path.exists():
        checksum = crc32c.CRC32CHash()
        with open(artifact_path, "rb") as f:
            while chunk := f.read(APPLICATION_RUN_FILE_READ_CHUNK_SIZE):
                checksum.update(chunk)
        existing_checksum = base64.b64encode(checksum.digest()).decode("ascii")
        if existing_checksum == metadata_checksum:
            logger.trace("File {} already exists with correct checksum", artifact_path)
            return

    download_file_with_progress(
        progress,
        artifact.download_url,
        artifact_path,
        metadata_checksum,
        download_progress_queue,
        download_progress_callable,
    )

src/aignostics/platform/resources/runs.py:407

  • downloaded_at_least_one_artifact is only set to True in the "file does not exist" branch. If an existing file has a checksum mismatch (the "Resume download" path), the code still downloads but will print/log "already present" at the end, which is misleading. Set the flag whenever a download is actually performed (both fresh and resumed).
                if file_path.exists():
                    file_checksum = calculate_file_crc32c(file_path)
                    if file_checksum != checksum:
                        logger.trace("Resume download for {} to {}", artifact.name, file_path)
                        print(f"> Resume download for {artifact.name} to {file_path}") if print_status else None
                    else:
                        continue
                else:
                    downloaded_at_least_one_artifact = True
                    logger.trace("Download for {} to {}", artifact.name, file_path)
                    print(f"> Download for {artifact.name} to {file_path}") if print_status else None

                # if file is not there at all or only partially downloaded yet
                download_file(artifact.download_url, str(file_path), checksum)

        if downloaded_at_least_one_artifact:
            logger.trace("Downloaded results for item: {} to {}", item.external_id, item_dir)
            print(f"Downloaded results for item: {item.external_id} to {item_dir}") if print_status else None
        else:
            logger.trace("Results for item: {} already present in {}", item.external_id, item_dir)
            print(f"Results for item: {item.external_id} already present in {item_dir}") if print_status else None

Comment on lines 341 to +379
@@ -365,24 +364,19 @@ async def start_download() -> None:
else:
ui.notify("Download completed.", type="positive")
show_in_file_manager(str(results_folder))
except Exception as e:
logger.exception(
"Download failed for run {} (qupath_project={}, marimo={}, folder={})",
run.run_id,
current_qupath_project,
current_marimo,
current_folder,
)
except ValueError as e:
ui.notify(f"Download failed: {e}", type="negative", multi_line=True)
finally:
progress_timer.deactivate()
progress_state["queue"] = None
download_button.props(remove="loading")
download_button.enable()
download_item_status.set_visibility(False)
download_item_progress.set_visibility(False)
download_artifact_status.set_visibility(False)
download_artifact_progress.set_visibility(False)
return
progress_timer.deactivate()
progress_state["queue"] = None
download_button.props(remove="loading")
download_button.enable()
download_item_status.set_visibility(False)
download_item_progress.set_visibility(False)
download_artifact_status.set_visibility(False)
download_artifact_progress.set_visibility(False)
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

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

In start_download, the cleanup that re-enables the download button / removes the loading state is no longer in a finally. If Service.application_run_download_static raises (including the handled ValueError path), the function returns early without resetting the UI, leaving the button stuck disabled/loading and progress widgets potentially visible. Restore a try/finally (or ensure cleanup runs on all exit paths) and consider catching broader exceptions for user-facing notification while still logging details.

Copilot uses AI. Check for mistakes.
Comment on lines +9 to 10
from aignostics.application._download import download_url_to_file_with_progress, extract_filename_from_url
from aignostics.application._models import DownloadProgress, DownloadProgressState
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

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

This change drops most unit coverage for result artifact downloading (e.g., download_available_items, download_item_artifact, checksum handling, and skip/resume behavior). These paths are still core to ApplicationService.application_run_download, so removing the tests risks regressions and may impact the project's coverage gate. Consider restoring the relevant tests (updated to match the reverted API) rather than removing them wholesale.

Copilot generated this review using guidance from repository custom instructions.
Comment on lines 369 to +379
progress_timer.deactivate()
progress_state["queue"] = None
download_button.props(remove="loading")
download_button.enable()
download_item_status.set_visibility(False)
download_item_progress.set_visibility(False)
download_artifact_status.set_visibility(False)
download_artifact_progress.set_visibility(False)
return
progress_timer.deactivate()
progress_state["queue"] = None
download_button.props(remove="loading")
download_button.enable()
download_item_status.set_visibility(False)
download_item_progress.set_visibility(False)
download_artifact_status.set_visibility(False)
download_artifact_progress.set_visibility(False)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Bug: The start_download function only handles ValueError, leaving the UI in a permanently disabled state if other common exceptions (e.g., network errors) occur during download.
Severity: HIGH

Suggested Fix

Restore the finally block to ensure UI cleanup code (disabling the timer, clearing the queue, and resetting the button state) runs regardless of whether the download succeeds or fails. Broaden the except block to catch other potential exceptions like RuntimeError and requests.HTTPError, not just ValueError.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.

Location: src/aignostics/application/_gui/_page_application_run_describe.py#L367-L379

Potential issue: In the `start_download` function, the exception handling was changed to
only catch `ValueError`. Other common exceptions that can occur during the download
process, such as `RuntimeError` or `requests.HTTPError` from network failures, are not
caught. When one of these unhandled exceptions occurs, the cleanup logic is skipped.
This leaves the UI in a permanently stuck state: the download button remains disabled
and in a loading state, and the progress timer continues to run indefinitely. The user
cannot retry the download and must reload the page to restore functionality.

Did we get this right? 👍 / 👎 to inform future reviews.

@nahua-aignx nahua-aignx merged commit 3b736f8 into main Mar 27, 2026
31 of 32 checks passed
@codecov
Copy link
Copy Markdown

codecov bot commented Mar 27, 2026

Codecov Report

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

Files with missing lines Patch % Lines
src/aignostics/platform/resources/runs.py 10.00% 18 Missing ⚠️
...application/_gui/_page_application_run_describe.py 78.94% 2 Missing and 2 partials ⚠️
Files with missing lines Coverage Δ
src/aignostics/application/_download.py 46.76% <100.00%> (-50.00%) ⬇️
src/aignostics/application/_service.py 61.03% <ø> (+3.43%) ⬆️
src/aignostics/platform/__init__.py 100.00% <100.00%> (ø)
...application/_gui/_page_application_run_describe.py 59.19% <78.94%> (+1.49%) ⬆️
src/aignostics/platform/resources/runs.py 65.21% <10.00%> (-13.11%) ⬇️

... and 3 files with indirect coverage changes

Sign up for free to 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