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.multi_circuit_job import MultiCircuitJob 40 from .models.qaoa_job import QaoaJob 41 from .models.quantum_function_job import QuantumFunctionJob 42 from .models.single_circuit_job import SingleCircuitJob 43 44 # The spec's GetJobResponse component is an anyOf union, which the 45 # generator inlines instead of emitting as a model class. 46 GetJobResponse = SingleCircuitJob | MultiCircuitJob | QaoaJob | QuantumFunctionJob 47 48logger = logging.getLogger("ionq_core") 49 50_TERMINAL = frozenset({"completed", "failed", "canceled"}) 51_DEFAULT_INTERVAL = 1.0 52_DEFAULT_TIMEOUT = 300.0 53_MAX_INTERVAL = 30.0 54_BACKOFF_FACTOR = 1.5 55 56 57class JobTimeoutError(IonQError): 58 """Raised when a job does not reach a terminal state within the timeout. 59 60 Attributes: 61 job_id: The ID of the job that timed out. 62 timeout: The timeout value in seconds that was exceeded. 63 last_status: The last observed status before the timeout 64 (e.g. ``"running"``, ``"submitted"``). 65 """ 66 67 def __init__(self, job_id: str, timeout: float, last_status: str) -> None: 68 self.job_id = job_id 69 self.timeout = timeout 70 self.last_status = last_status 71 super().__init__(f"Job {job_id} did not complete within {timeout}s (last status: {last_status})") 72 73 74class JobFailedError(IonQError): 75 """Raised when a polled job reaches ``"failed"`` status. 76 77 Attributes: 78 job_id: The ID of the failed job. 79 failure: The failure detail object from the API response, or ``None`` 80 if no failure details were provided. 81 """ 82 83 def __init__(self, job_id: str, failure: object) -> None: 84 self.job_id = job_id 85 self.failure = failure 86 super().__init__(f"Job {job_id} failed: {failure}") 87 88 89def _check_terminal(job: GetJobResponse, raise_on_failure: bool) -> bool: 90 if job.status not in _TERMINAL: 91 return False 92 if raise_on_failure and job.status == "failed": 93 failure = job.failure if not isinstance(job.failure, Unset) else None 94 raise JobFailedError(job.id, failure) 95 return True 96 97 98def wait_for_job( 99 client: AuthenticatedClient, 100 job_id: str, 101 *, 102 poll_interval: float = _DEFAULT_INTERVAL, 103 timeout: float = _DEFAULT_TIMEOUT, 104 raise_on_failure: bool = True, 105) -> GetJobResponse: 106 """Poll a job until it reaches a terminal state. 107 108 Terminal states are ``"completed"``, ``"failed"``, and ``"canceled"``. 109 Polling starts at ``poll_interval`` and increases by 1.5x each 110 iteration, capped at 30 seconds. 111 112 Args: 113 client: An authenticated API client. 114 job_id: The UUID of the job to poll. 115 poll_interval: Initial interval between polls in seconds. 116 Defaults to 1.0. 117 timeout: Maximum total wait time in seconds. Defaults to 300 118 (5 minutes). 119 raise_on_failure: If ``True`` (the default), raise `JobFailedError` 120 when the job status is ``"failed"``. If ``False``, return the 121 failed job response instead. 122 123 Returns: 124 The final job response once a terminal state is reached. 125 126 Raises: 127 JobTimeoutError: If the job does not finish within ``timeout``. 128 JobFailedError: If ``raise_on_failure`` is ``True`` and the job fails. 129 IonQError: If the API returns a ``None`` response. 130 """ 131 deadline = time.monotonic() + timeout 132 interval = poll_interval 133 while True: 134 job = get_job.sync(uuid=job_id, client=client) 135 if job is None: 136 raise IonQError(f"Failed to fetch job {job_id}") 137 logger.debug("Job %s status: %s", job_id, job.status) 138 if _check_terminal(job, raise_on_failure): 139 return job 140 if time.monotonic() >= deadline: 141 raise JobTimeoutError(job_id, timeout, job.status) 142 time.sleep(max(0, min(interval, deadline - time.monotonic()))) 143 interval = min(interval * _BACKOFF_FACTOR, _MAX_INTERVAL) 144 145 146async def async_wait_for_job( 147 client: AuthenticatedClient, 148 job_id: str, 149 *, 150 poll_interval: float = _DEFAULT_INTERVAL, 151 timeout: float = _DEFAULT_TIMEOUT, 152 raise_on_failure: bool = True, 153) -> GetJobResponse: 154 """Async version of `wait_for_job`. 155 156 Args: 157 client: An authenticated API client. 158 job_id: The UUID of the job to poll. 159 poll_interval: Initial interval between polls in seconds. 160 Defaults to 1.0. 161 timeout: Maximum total wait time in seconds. Defaults to 300. 162 raise_on_failure: If ``True``, raise `JobFailedError` on failure. 163 164 Returns: 165 The final job response once a terminal state is reached. 166 167 Raises: 168 JobTimeoutError: If the job does not finish within ``timeout``. 169 JobFailedError: If ``raise_on_failure`` is ``True`` and the job fails. 170 IonQError: If the API returns a ``None`` response. 171 """ 172 deadline = time.monotonic() + timeout 173 interval = poll_interval 174 while True: 175 job = await get_job.asyncio(uuid=job_id, client=client) 176 if job is None: 177 raise IonQError(f"Failed to fetch job {job_id}") 178 logger.debug("Job %s status: %s", job_id, job.status) 179 if _check_terminal(job, raise_on_failure): 180 return job 181 if time.monotonic() >= deadline: 182 raise JobTimeoutError(job_id, timeout, job.status) 183 await asyncio.sleep(max(0, min(interval, deadline - time.monotonic()))) 184 interval = min(interval * _BACKOFF_FACTOR, _MAX_INTERVAL)
75class JobFailedError(IonQError): 76 """Raised when a polled job reaches ``"failed"`` status. 77 78 Attributes: 79 job_id: The ID of the failed job. 80 failure: The failure detail object from the API response, or ``None`` 81 if no failure details were provided. 82 """ 83 84 def __init__(self, job_id: str, failure: object) -> None: 85 self.job_id = job_id 86 self.failure = failure 87 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.
58class JobTimeoutError(IonQError): 59 """Raised when a job does not reach a terminal state within the timeout. 60 61 Attributes: 62 job_id: The ID of the job that timed out. 63 timeout: The timeout value in seconds that was exceeded. 64 last_status: The last observed status before the timeout 65 (e.g. ``"running"``, ``"submitted"``). 66 """ 67 68 def __init__(self, job_id: str, timeout: float, last_status: str) -> None: 69 self.job_id = job_id 70 self.timeout = timeout 71 self.last_status = last_status 72 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").
147async def async_wait_for_job( 148 client: AuthenticatedClient, 149 job_id: str, 150 *, 151 poll_interval: float = _DEFAULT_INTERVAL, 152 timeout: float = _DEFAULT_TIMEOUT, 153 raise_on_failure: bool = True, 154) -> GetJobResponse: 155 """Async version of `wait_for_job`. 156 157 Args: 158 client: An authenticated API client. 159 job_id: The UUID of the job to poll. 160 poll_interval: Initial interval between polls in seconds. 161 Defaults to 1.0. 162 timeout: Maximum total wait time in seconds. Defaults to 300. 163 raise_on_failure: If ``True``, raise `JobFailedError` on failure. 164 165 Returns: 166 The final job response once a terminal state is reached. 167 168 Raises: 169 JobTimeoutError: If the job does not finish within ``timeout``. 170 JobFailedError: If ``raise_on_failure`` is ``True`` and the job fails. 171 IonQError: If the API returns a ``None`` response. 172 """ 173 deadline = time.monotonic() + timeout 174 interval = poll_interval 175 while True: 176 job = await get_job.asyncio(uuid=job_id, client=client) 177 if job is None: 178 raise IonQError(f"Failed to fetch job {job_id}") 179 logger.debug("Job %s status: %s", job_id, job.status) 180 if _check_terminal(job, raise_on_failure): 181 return job 182 if time.monotonic() >= deadline: 183 raise JobTimeoutError(job_id, timeout, job.status) 184 await asyncio.sleep(max(0, min(interval, deadline - time.monotonic()))) 185 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.
99def wait_for_job( 100 client: AuthenticatedClient, 101 job_id: str, 102 *, 103 poll_interval: float = _DEFAULT_INTERVAL, 104 timeout: float = _DEFAULT_TIMEOUT, 105 raise_on_failure: bool = True, 106) -> GetJobResponse: 107 """Poll a job until it reaches a terminal state. 108 109 Terminal states are ``"completed"``, ``"failed"``, and ``"canceled"``. 110 Polling starts at ``poll_interval`` and increases by 1.5x each 111 iteration, capped at 30 seconds. 112 113 Args: 114 client: An authenticated API client. 115 job_id: The UUID of the job to poll. 116 poll_interval: Initial interval between polls in seconds. 117 Defaults to 1.0. 118 timeout: Maximum total wait time in seconds. Defaults to 300 119 (5 minutes). 120 raise_on_failure: If ``True`` (the default), raise `JobFailedError` 121 when the job status is ``"failed"``. If ``False``, return the 122 failed job response instead. 123 124 Returns: 125 The final job response once a terminal state is reached. 126 127 Raises: 128 JobTimeoutError: If the job does not finish within ``timeout``. 129 JobFailedError: If ``raise_on_failure`` is ``True`` and the job fails. 130 IonQError: If the API returns a ``None`` response. 131 """ 132 deadline = time.monotonic() + timeout 133 interval = poll_interval 134 while True: 135 job = get_job.sync(uuid=job_id, client=client) 136 if job is None: 137 raise IonQError(f"Failed to fetch job {job_id}") 138 logger.debug("Job %s status: %s", job_id, job.status) 139 if _check_terminal(job, raise_on_failure): 140 return job 141 if time.monotonic() >= deadline: 142 raise JobTimeoutError(job_id, timeout, job.status) 143 time.sleep(max(0, min(interval, deadline - time.monotonic()))) 144 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.