Skip to content
This repository was archived by the owner on Feb 4, 2025. It is now read-only.
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
8 changes: 4 additions & 4 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ qi = "quantuminspire.cli.command_list:app"
python = "^3.9"
typer = {extras = ["all"], version = "^0.15.1"}
pydantic = "^2.10.6"
qi-compute-api-client = "^0.44.0"
qi-compute-api-client = "^0.45.0"
qxelarator = {version = "^0.7.2", optional = true}
pydantic-settings = "^2.7.1"
qiskit = "1.0.2"
Expand Down
18 changes: 16 additions & 2 deletions quantuminspire/cli/command_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
from typing import Optional

import typer
from compute_api_client import JobStatus
from rich import print
from typer import Typer

from quantuminspire.sdk.models.cqasm_algorithm import CqasmAlgorithm
Expand Down Expand Up @@ -298,21 +300,32 @@ def run_file(
typer.echo(f"{results}")


def _has_job_failed(backend: RemoteBackend, job_id: int) -> None:
"""Check if a job has failed and exit with error_code 1 if it has."""
job = backend.get_job(job_id)
if job.status == JobStatus.FAILED:
job.message = job.message or "Job failed."
typer.echo(job.message, err=True)
typer.echo(f"Trace id: {job.trace_id}", err=True)
raise typer.Exit(1)


@results_app.command("get")
def get_results(job_id: int = typer.Argument(..., help="The id of the run")) -> None:
"""Retrieve the results for a run.

Takes the id as returned by upload_files and retrieves the results for that run, if it's finished.
"""
backend = RemoteBackend()
_has_job_failed(backend, job_id)
results = backend.get_results(job_id)

if results is None:
typer.echo("No results.")
raise typer.Exit(1)

typer.echo("Raw results:")
typer.echo(results)
print(results.model_dump())


@final_results_app.command("get")
Expand All @@ -322,14 +335,15 @@ def get_final_results(job_id: int = typer.Argument(..., help="The id of the run"
Takes the id as returned by upload_files and retrieves the final results for that job, if it's finished.
"""
backend = RemoteBackend()
_has_job_failed(backend, job_id)
results = backend.get_final_results(job_id)

if results is None:
typer.echo("No final results.")
raise typer.Exit(1)

typer.echo("Raw final results:")
typer.echo(results)
print(results.model_dump())


@app.command("login")
Expand Down
8 changes: 8 additions & 0 deletions quantuminspire/util/api/remote_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ def run(
"""Execute provided algorithm/circuit."""
return asyncio.run(self._create_flow(program, backend_type_id, job_options=options))

async def _get_job(self, job_id: int) -> Any:
async with ApiClient(self._configuration) as api_client:
return await self._read_job(api_client, job_id)

async def _get_results(self, job_id: int) -> Any:
async with ApiClient(self._configuration) as api_client:
job = await self._read_job(api_client, job_id)
Expand All @@ -95,6 +99,10 @@ async def _get_final_results(self, job_id: int) -> Any:

return await self._read_final_results_for_job(api_client, job)

def get_job(self, job_id: int) -> Any:
"""Get job for algorithm/circuit."""
return asyncio.run(self._get_job(job_id))

def get_results(self, job_id: int) -> Any:
"""Get results for algorithm/circuit."""
return asyncio.run(self._get_results(job_id))
Expand Down
35 changes: 35 additions & 0 deletions tests/cli/test_command_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from unittest.mock import MagicMock

import pytest
from compute_api_client import JobStatus
from pytest_mock import MockerFixture
from typer.testing import CliRunner

Expand Down Expand Up @@ -109,6 +110,24 @@ def test_results_get(mocker: MockerFixture) -> None:
mock_remote_backend_inst.get_results.assert_called_once()


def test_results_get_failed_job(mocker: MockerFixture) -> None:
mock_remote_backend_inst = MagicMock()
job = MagicMock()
job.id = 1
job.status = JobStatus.FAILED
job.message = "Job failed."
job.trace_id = "trace_id"
mock_remote_backend_inst.get_job.return_value = job
mocker.patch("quantuminspire.cli.command_list.RemoteBackend", return_value=mock_remote_backend_inst)

result = runner.invoke(app, ["results", "get", "1"])

print(result.stdout)

assert result.stdout == "Job failed.\nTrace id: trace_id\n"
assert result.exit_code == 1


def test_results_get_no_results(mocker: MockerFixture) -> None:
mock_remote_backend_inst = MagicMock()
mock_remote_backend_inst.get_results.return_value = None
Expand All @@ -130,6 +149,22 @@ def test_final_results_get(mocker: MockerFixture) -> None:
mock_remote_backend_inst.get_final_results.assert_called_once()


def test_final_results_get_failed_job(mocker: MockerFixture) -> None:
mock_remote_backend_inst = MagicMock()
job = MagicMock()
job.id = 1
job.status = JobStatus.FAILED
job.message = "Job failed."
job.trace_id = "trace_id"
mock_remote_backend_inst.get_job.return_value = job
mocker.patch("quantuminspire.cli.command_list.RemoteBackend", return_value=mock_remote_backend_inst)

result = runner.invoke(app, ["final_results", "get", "1"])

assert result.stdout == "Job failed.\nTrace id: trace_id\n"
assert result.exit_code == 1


def test_final_results_get_no_results(mocker: MockerFixture) -> None:
mock_remote_backend_inst = MagicMock()
mock_remote_backend_inst.get_final_results.return_value = None
Expand Down
11 changes: 11 additions & 0 deletions tests/util/api/test_remote_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,17 @@ def test_run(
api_client.assert_has_calls([call().__aenter__(), call().__aexit__(None, None, None)])


def test_get_job(
mocker: MockerFixture, api_client: MagicMock, mocked_settings: MagicMock, mocked_authentication: MagicMock
) -> None:
backend = RemoteBackend()
jobs_api_instance = AsyncMock()
job_id = 1
mocker.patch("quantuminspire.util.api.remote_backend.JobsApi", return_value=jobs_api_instance)
backend.get_job(job_id)
jobs_api_instance.read_job_jobs_id_get.assert_called_with(job_id)


def test_get_results(
mocker: MockerFixture, api_client: MagicMock, mocked_settings: MagicMock, mocked_authentication: MagicMock
) -> None:
Expand Down
Loading