ionq_core.polling

Job polling helpers for waiting on quantum job completion.

After submitting a job, use wait_for_job (or async_wait_for_job) to block until it reaches a terminal state (completed, failed, or canceled). Polling starts at _DEFAULT_INTERVAL and grows by _BACKOFF_FACTOR each iteration up to _MAX_INTERVAL; the default total wait is _DEFAULT_TIMEOUT seconds.

Example:
from ionq_core import IonQClient, wait_for_job
from ionq_core.api.default import create_job

client = IonQClient()
job = create_job.sync(client=client, body=payload)
completed = wait_for_job(client, job.id, timeout=300)
print(completed.status)  # "completed"
  1# SPDX-FileCopyrightText: 2026 IonQ, Inc.
  2# SPDX-License-Identifier: Apache-2.0
  3
  4"""Job polling helpers for waiting on quantum job completion.
  5
  6After submitting a job, use `wait_for_job` (or `async_wait_for_job`) to
  7block until it reaches a terminal state (completed, failed, or canceled).
  8Polling starts at `_DEFAULT_INTERVAL` and grows by `_BACKOFF_FACTOR` each
  9iteration up to `_MAX_INTERVAL`; the default total wait is
 10`_DEFAULT_TIMEOUT` seconds.
 11
 12Example:
 13    ```python
 14    from ionq_core import IonQClient, wait_for_job
 15    from ionq_core.api.default import create_job
 16
 17    client = IonQClient()
 18    job = create_job.sync(client=client, body=payload)
 19    completed = wait_for_job(client, job.id, timeout=300)
 20    print(completed.status)  # "completed"
 21    ```
 22"""
 23
 24from __future__ import annotations
 25
 26__all__ = ["JobFailedError", "JobTimeoutError", "async_wait_for_job", "wait_for_job"]
 27
 28import asyncio
 29import logging
 30import time
 31from typing import TYPE_CHECKING
 32
 33from .api.default import get_job
 34from .exceptions import IonQError
 35from .types import Unset
 36
 37if TYPE_CHECKING:
 38    from .client import AuthenticatedClient
 39    from .models.get_job_response import GetJobResponse
 40
 41logger = logging.getLogger("ionq_core")
 42
 43_TERMINAL = frozenset({"completed", "failed", "canceled"})
 44_DEFAULT_INTERVAL = 1.0
 45_DEFAULT_TIMEOUT = 300.0
 46_MAX_INTERVAL = 30.0
 47_BACKOFF_FACTOR = 1.5
 48
 49
 50class JobTimeoutError(IonQError):
 51    """Raised when a job does not reach a terminal state within the timeout.
 52
 53    Attributes:
 54        job_id: The ID of the job that timed out.
 55        timeout: The timeout value in seconds that was exceeded.
 56        last_status: The last observed status before the timeout
 57            (e.g. ``"running"``, ``"submitted"``).
 58    """
 59
 60    def __init__(self, job_id: str, timeout: float, last_status: str) -> None:
 61        self.job_id = job_id
 62        self.timeout = timeout
 63        self.last_status = last_status
 64        super().__init__(f"Job {job_id} did not complete within {timeout}s (last status: {last_status})")
 65
 66
 67class JobFailedError(IonQError):
 68    """Raised when a polled job reaches ``"failed"`` status.
 69
 70    Attributes:
 71        job_id: The ID of the failed job.
 72        failure: The failure detail object from the API response, or ``None``
 73            if no failure details were provided.
 74    """
 75
 76    def __init__(self, job_id: str, failure: object) -> None:
 77        self.job_id = job_id
 78        self.failure = failure
 79        super().__init__(f"Job {job_id} failed: {failure}")
 80
 81
 82def _check_terminal(job: GetJobResponse, raise_on_failure: bool) -> bool:
 83    if job.status not in _TERMINAL:
 84        return False
 85    if raise_on_failure and job.status == "failed":
 86        failure = job.failure if not isinstance(job.failure, Unset) else None
 87        raise JobFailedError(job.id, failure)
 88    return True
 89
 90
 91def wait_for_job(
 92    client: AuthenticatedClient,
 93    job_id: str,
 94    *,
 95    poll_interval: float = _DEFAULT_INTERVAL,
 96    timeout: float = _DEFAULT_TIMEOUT,
 97    raise_on_failure: bool = True,
 98) -> GetJobResponse:
 99    """Poll a job until it reaches a terminal state.
100
101    Terminal states are ``"completed"``, ``"failed"``, and ``"canceled"``.
102    Polling starts at ``poll_interval`` and increases by 1.5x each
103    iteration, capped at 30 seconds.
104
105    Args:
106        client: An authenticated API client.
107        job_id: The UUID of the job to poll.
108        poll_interval: Initial interval between polls in seconds.
109            Defaults to 1.0.
110        timeout: Maximum total wait time in seconds. Defaults to 300
111            (5 minutes).
112        raise_on_failure: If ``True`` (the default), raise `JobFailedError`
113            when the job status is ``"failed"``. If ``False``, return the
114            failed job response instead.
115
116    Returns:
117        The final job response once a terminal state is reached.
118
119    Raises:
120        JobTimeoutError: If the job does not finish within ``timeout``.
121        JobFailedError: If ``raise_on_failure`` is ``True`` and the job fails.
122        IonQError: If the API returns a ``None`` response.
123    """
124    deadline = time.monotonic() + timeout
125    interval = poll_interval
126    while True:
127        job = get_job.sync(uuid=job_id, client=client)
128        if job is None:
129            raise IonQError(f"Failed to fetch job {job_id}")
130        logger.debug("Job %s status: %s", job_id, job.status)
131        if _check_terminal(job, raise_on_failure):
132            return job
133        if time.monotonic() >= deadline:
134            raise JobTimeoutError(job_id, timeout, job.status)
135        time.sleep(max(0, min(interval, deadline - time.monotonic())))
136        interval = min(interval * _BACKOFF_FACTOR, _MAX_INTERVAL)
137
138
139async def async_wait_for_job(
140    client: AuthenticatedClient,
141    job_id: str,
142    *,
143    poll_interval: float = _DEFAULT_INTERVAL,
144    timeout: float = _DEFAULT_TIMEOUT,
145    raise_on_failure: bool = True,
146) -> GetJobResponse:
147    """Async version of `wait_for_job`.
148
149    Args:
150        client: An authenticated API client.
151        job_id: The UUID of the job to poll.
152        poll_interval: Initial interval between polls in seconds.
153            Defaults to 1.0.
154        timeout: Maximum total wait time in seconds. Defaults to 300.
155        raise_on_failure: If ``True``, raise `JobFailedError` on failure.
156
157    Returns:
158        The final job response once a terminal state is reached.
159
160    Raises:
161        JobTimeoutError: If the job does not finish within ``timeout``.
162        JobFailedError: If ``raise_on_failure`` is ``True`` and the job fails.
163        IonQError: If the API returns a ``None`` response.
164    """
165    deadline = time.monotonic() + timeout
166    interval = poll_interval
167    while True:
168        job = await get_job.asyncio(uuid=job_id, client=client)
169        if job is None:
170            raise IonQError(f"Failed to fetch job {job_id}")
171        logger.debug("Job %s status: %s", job_id, job.status)
172        if _check_terminal(job, raise_on_failure):
173            return job
174        if time.monotonic() >= deadline:
175            raise JobTimeoutError(job_id, timeout, job.status)
176        await asyncio.sleep(max(0, min(interval, deadline - time.monotonic())))
177        interval = min(interval * _BACKOFF_FACTOR, _MAX_INTERVAL)
class JobFailedError(ionq_core.exceptions.IonQError):
68class JobFailedError(IonQError):
69    """Raised when a polled job reaches ``"failed"`` status.
70
71    Attributes:
72        job_id: The ID of the failed job.
73        failure: The failure detail object from the API response, or ``None``
74            if no failure details were provided.
75    """
76
77    def __init__(self, job_id: str, failure: object) -> None:
78        self.job_id = job_id
79        self.failure = failure
80        super().__init__(f"Job {job_id} failed: {failure}")

Raised when a polled job reaches "failed" status.

Attributes:
  • job_id: The ID of the failed job.
  • failure: The failure detail object from the API response, or None if no failure details were provided.
JobFailedError(job_id: str, failure: object)
77    def __init__(self, job_id: str, failure: object) -> None:
78        self.job_id = job_id
79        self.failure = failure
80        super().__init__(f"Job {job_id} failed: {failure}")
job_id
failure
class JobTimeoutError(ionq_core.exceptions.IonQError):
51class JobTimeoutError(IonQError):
52    """Raised when a job does not reach a terminal state within the timeout.
53
54    Attributes:
55        job_id: The ID of the job that timed out.
56        timeout: The timeout value in seconds that was exceeded.
57        last_status: The last observed status before the timeout
58            (e.g. ``"running"``, ``"submitted"``).
59    """
60
61    def __init__(self, job_id: str, timeout: float, last_status: str) -> None:
62        self.job_id = job_id
63        self.timeout = timeout
64        self.last_status = last_status
65        super().__init__(f"Job {job_id} did not complete within {timeout}s (last status: {last_status})")

Raised when a job does not reach a terminal state within the timeout.

Attributes:
  • job_id: The ID of the job that timed out.
  • timeout: The timeout value in seconds that was exceeded.
  • last_status: The last observed status before the timeout (e.g. "running", "submitted").
JobTimeoutError(job_id: str, timeout: float, last_status: str)
61    def __init__(self, job_id: str, timeout: float, last_status: str) -> None:
62        self.job_id = job_id
63        self.timeout = timeout
64        self.last_status = last_status
65        super().__init__(f"Job {job_id} did not complete within {timeout}s (last status: {last_status})")
job_id
timeout
last_status
async def async_wait_for_job( client: ionq_core.AuthenticatedClient, job_id: str, *, poll_interval: float = 1.0, timeout: float = 300.0, raise_on_failure: bool = True) -> ionq_core.models.get_job_response.GetJobResponse:
140async def async_wait_for_job(
141    client: AuthenticatedClient,
142    job_id: str,
143    *,
144    poll_interval: float = _DEFAULT_INTERVAL,
145    timeout: float = _DEFAULT_TIMEOUT,
146    raise_on_failure: bool = True,
147) -> GetJobResponse:
148    """Async version of `wait_for_job`.
149
150    Args:
151        client: An authenticated API client.
152        job_id: The UUID of the job to poll.
153        poll_interval: Initial interval between polls in seconds.
154            Defaults to 1.0.
155        timeout: Maximum total wait time in seconds. Defaults to 300.
156        raise_on_failure: If ``True``, raise `JobFailedError` on failure.
157
158    Returns:
159        The final job response once a terminal state is reached.
160
161    Raises:
162        JobTimeoutError: If the job does not finish within ``timeout``.
163        JobFailedError: If ``raise_on_failure`` is ``True`` and the job fails.
164        IonQError: If the API returns a ``None`` response.
165    """
166    deadline = time.monotonic() + timeout
167    interval = poll_interval
168    while True:
169        job = await get_job.asyncio(uuid=job_id, client=client)
170        if job is None:
171            raise IonQError(f"Failed to fetch job {job_id}")
172        logger.debug("Job %s status: %s", job_id, job.status)
173        if _check_terminal(job, raise_on_failure):
174            return job
175        if time.monotonic() >= deadline:
176            raise JobTimeoutError(job_id, timeout, job.status)
177        await asyncio.sleep(max(0, min(interval, deadline - time.monotonic())))
178        interval = min(interval * _BACKOFF_FACTOR, _MAX_INTERVAL)

Async version of wait_for_job.

Arguments:
  • client: An authenticated API client.
  • job_id: The UUID of the job to poll.
  • poll_interval: Initial interval between polls in seconds. Defaults to 1.0.
  • timeout: Maximum total wait time in seconds. Defaults to 300.
  • raise_on_failure: If True, raise JobFailedError on failure.
Returns:

The final job response once a terminal state is reached.

Raises:
  • JobTimeoutError: If the job does not finish within timeout.
  • JobFailedError: If raise_on_failure is True and the job fails.
  • IonQError: If the API returns a None response.
def wait_for_job( client: ionq_core.AuthenticatedClient, job_id: str, *, poll_interval: float = 1.0, timeout: float = 300.0, raise_on_failure: bool = True) -> ionq_core.models.get_job_response.GetJobResponse:
 92def wait_for_job(
 93    client: AuthenticatedClient,
 94    job_id: str,
 95    *,
 96    poll_interval: float = _DEFAULT_INTERVAL,
 97    timeout: float = _DEFAULT_TIMEOUT,
 98    raise_on_failure: bool = True,
 99) -> GetJobResponse:
100    """Poll a job until it reaches a terminal state.
101
102    Terminal states are ``"completed"``, ``"failed"``, and ``"canceled"``.
103    Polling starts at ``poll_interval`` and increases by 1.5x each
104    iteration, capped at 30 seconds.
105
106    Args:
107        client: An authenticated API client.
108        job_id: The UUID of the job to poll.
109        poll_interval: Initial interval between polls in seconds.
110            Defaults to 1.0.
111        timeout: Maximum total wait time in seconds. Defaults to 300
112            (5 minutes).
113        raise_on_failure: If ``True`` (the default), raise `JobFailedError`
114            when the job status is ``"failed"``. If ``False``, return the
115            failed job response instead.
116
117    Returns:
118        The final job response once a terminal state is reached.
119
120    Raises:
121        JobTimeoutError: If the job does not finish within ``timeout``.
122        JobFailedError: If ``raise_on_failure`` is ``True`` and the job fails.
123        IonQError: If the API returns a ``None`` response.
124    """
125    deadline = time.monotonic() + timeout
126    interval = poll_interval
127    while True:
128        job = get_job.sync(uuid=job_id, client=client)
129        if job is None:
130            raise IonQError(f"Failed to fetch job {job_id}")
131        logger.debug("Job %s status: %s", job_id, job.status)
132        if _check_terminal(job, raise_on_failure):
133            return job
134        if time.monotonic() >= deadline:
135            raise JobTimeoutError(job_id, timeout, job.status)
136        time.sleep(max(0, min(interval, deadline - time.monotonic())))
137        interval = min(interval * _BACKOFF_FACTOR, _MAX_INTERVAL)

Poll a job until it reaches a terminal state.

Terminal states are "completed", "failed", and "canceled". Polling starts at poll_interval and increases by 1.5x each iteration, capped at 30 seconds.

Arguments:
  • client: An authenticated API client.
  • job_id: The UUID of the job to poll.
  • poll_interval: Initial interval between polls in seconds. Defaults to 1.0.
  • timeout: Maximum total wait time in seconds. Defaults to 300 (5 minutes).
  • raise_on_failure: If True (the default), raise JobFailedError when the job status is "failed". If False, return the failed job response instead.
Returns:

The final job response once a terminal state is reached.

Raises:
  • JobTimeoutError: If the job does not finish within timeout.
  • JobFailedError: If raise_on_failure is True and the job fails.
  • IonQError: If the API returns a None response.