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)
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
Noneif no failure details were provided.
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").
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, raiseJobFailedErroron 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_failureisTrueand the job fails. - IonQError: If the API returns a
Noneresponse.
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), raiseJobFailedErrorwhen the job status is"failed". IfFalse, 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_failureisTrueand the job fails. - IonQError: If the API returns a
Noneresponse.