ionq_core.ionq_client

IonQ-specific client convenience wrapper.

The IonQClient factory function is the recommended way to create an API client. It reads the API key from the environment, configures retries with exponential backoff, sets a descriptive User-Agent header, and wires up both the sync and async httpx transports.

  1# SPDX-FileCopyrightText: 2026 IonQ, Inc.
  2# SPDX-License-Identifier: Apache-2.0
  3
  4"""IonQ-specific client convenience wrapper.
  5
  6The `IonQClient` factory function is the recommended way to create an API client.
  7It reads the API key from the environment, configures retries with exponential
  8backoff, sets a descriptive User-Agent header, and wires up both the sync and
  9async httpx transports.
 10"""
 11
 12__all__ = ["IonQClient", "__version__"]
 13
 14import os
 15import platform
 16import warnings
 17from importlib.metadata import PackageNotFoundError
 18from importlib.metadata import version as _pkg_version
 19
 20import httpx
 21
 22from ._transport import DEFAULT_MAX_RETRIES, RETRYABLE_STATUS_CODES, build_transport
 23from .client import AuthenticatedClient
 24from .extensions import ClientExtension, HookTransport
 25
 26try:
 27    __version__ = _pkg_version("ionq-core")
 28except PackageNotFoundError:
 29    __version__ = "0.0.0"
 30
 31DEFAULT_BASE_URL = "https://api.ionq.co/v0.4"
 32DEFAULT_TIMEOUT = httpx.Timeout(60.0, connect=10.0)
 33_AUTH_PREFIX = "apiKey"
 34_AUTH_HEADER = "Authorization"
 35
 36
 37# Factory named in PascalCase (deliberately, not a class) so call sites read
 38# like construction. Returns the generated `AuthenticatedClient`.
 39def IonQClient(
 40    *,
 41    api_key: str | None = None,
 42    base_url: str = DEFAULT_BASE_URL,
 43    max_retries: int | None = None,
 44    timeout: httpx.Timeout | None = None,
 45    additional_user_agent: str | None = None,
 46    extension: ClientExtension | None = None,
 47    **kwargs,
 48) -> AuthenticatedClient:
 49    """Create an authenticated IonQ API client.
 50
 51    This is the recommended entry point for using the library. It handles
 52    authentication, retry configuration, User-Agent construction, and transport
 53    setup for both sync and async usage.
 54
 55    Args:
 56        api_key: IonQ API key. If not provided, reads the ``IONQ_API_KEY``
 57            environment variable.
 58        base_url: API base URL. Defaults to the IonQ production API.
 59        max_retries: Maximum retry attempts for transient errors (429, 5xx).
 60            Defaults to 2. Set to 0 to disable retries.
 61        timeout: Request timeout as an ``httpx.Timeout`` instance. Defaults to
 62            60 seconds with a 10-second connect timeout.
 63        additional_user_agent: Extra token appended to the User-Agent header,
 64            useful for identifying calling applications.
 65        extension: A `ClientExtension` bundle provided by a downstream SDK.
 66            Allows injecting hooks, custom headers, transport wrappers, and
 67            error mappers.
 68        **kwargs: Passed through to `AuthenticatedClient`.
 69
 70    Returns:
 71        An `AuthenticatedClient` configured with retry transport and
 72        authentication headers, ready for both sync and async API calls.
 73
 74    Raises:
 75        ValueError: If no API key is provided and ``IONQ_API_KEY`` is not set.
 76
 77    Examples:
 78        Basic usage with environment variable:
 79
 80        ```python
 81        from ionq_core import IonQClient
 82        from ionq_core.api.backends import get_backends
 83
 84        client = IonQClient()
 85        backends = get_backends.sync(client=client)
 86        ```
 87
 88        Explicit configuration:
 89
 90        ```python
 91        import httpx
 92        from ionq_core import IonQClient
 93
 94        client = IonQClient(
 95            api_key="your-api-key",
 96            max_retries=5,
 97            timeout=httpx.Timeout(30.0, connect=10.0),
 98        )
 99        ```
100
101        Async usage with context manager:
102
103        ```python
104        async with IonQClient() as client:
105            backends = await get_backends.asyncio(client=client)
106        ```
107    """
108    key = api_key or os.environ.get("IONQ_API_KEY")
109    if not key:
110        raise ValueError("api_key or IONQ_API_KEY environment variable required")
111
112    if not base_url.startswith("https://"):
113        warnings.warn(
114            f"base_url {base_url!r} does not use HTTPS. API keys will be sent in cleartext.",
115            UserWarning,
116            stacklevel=2,
117        )
118    if kwargs.get("verify_ssl") is False:
119        warnings.warn(
120            "verify_ssl=False disables TLS certificate verification. "
121            "Your API key may be intercepted by a network attacker.",
122            UserWarning,
123            stacklevel=2,
124        )
125
126    ext = extension or ClientExtension()
127    ua_parts = [
128        f"ionq-core/{__version__}",
129        f"python/{platform.python_version()}",
130        f"httpx/{httpx.__version__}",
131        f"os/{platform.system().lower()}",
132        *filter(None, (additional_user_agent, ext.user_agent_token)),
133    ]
134    user_agent = " ".join(ua_parts)
135    effective_timeout = timeout or ext.timeout or DEFAULT_TIMEOUT
136    effective_retries = next(v for v in (max_retries, ext.max_retries, DEFAULT_MAX_RETRIES) if v is not None)
137
138    headers = {**ext.default_headers, "User-Agent": user_agent}
139
140    sync_transport = async_transport = build_transport(
141        effective_retries,
142        ext.retryable_status_codes or RETRYABLE_STATUS_CODES,
143    )
144
145    if ext.event_hooks or ext.error_mapper:
146        sync_transport = HookTransport(
147            sync_transport,
148            ext.event_hooks,
149            debug=ext.debug_hooks,
150            error_mapper=ext.error_mapper,
151        )
152    if ext.async_event_hooks or ext.error_mapper:
153        async_transport = HookTransport(
154            async_transport,
155            ext.async_event_hooks,
156            debug=ext.debug_hooks,
157            error_mapper=ext.error_mapper,
158        )
159    if ext.transport_wrapper:
160        sync_transport = ext.transport_wrapper(sync_transport)
161    if ext.async_transport_wrapper:
162        async_transport = ext.async_transport_wrapper(async_transport)
163
164    client = AuthenticatedClient(
165        base_url=base_url,
166        token=key,
167        prefix=_AUTH_PREFIX,
168        auth_header_name=_AUTH_HEADER,
169        timeout=effective_timeout,
170        headers=headers,
171        httpx_args={"transport": sync_transport},
172        **kwargs,
173    )
174    # `set_async_httpx_client` bypasses `AuthenticatedClient`'s lazy auth-header
175    # injection (see generated `client.py::get_async_httpx_client`), so we merge
176    # `Authorization` in manually here. The `_verify_ssl` / `_follow_redirects`
177    # fields are private on the generated `AuthenticatedClient` but are the only
178    # way to mirror the caller's choices onto the async transport; do not add a
179    # public accessor in the hand-written layer — they belong to generated code.
180    client.set_async_httpx_client(
181        httpx.AsyncClient(
182            base_url=base_url,
183            headers={**headers, _AUTH_HEADER: f"{_AUTH_PREFIX} {key}"},
184            timeout=effective_timeout,
185            transport=async_transport,
186            verify=client._verify_ssl,
187            follow_redirects=client._follow_redirects,
188        )
189    )
190    return client
def IonQClient( *, api_key: str | None = None, base_url: str = 'https://api.ionq.co/v0.4', max_retries: int | None = None, timeout: httpx.Timeout | None = None, additional_user_agent: str | None = None, extension: ionq_core.ClientExtension | None = None, **kwargs) -> ionq_core.AuthenticatedClient:
 40def IonQClient(
 41    *,
 42    api_key: str | None = None,
 43    base_url: str = DEFAULT_BASE_URL,
 44    max_retries: int | None = None,
 45    timeout: httpx.Timeout | None = None,
 46    additional_user_agent: str | None = None,
 47    extension: ClientExtension | None = None,
 48    **kwargs,
 49) -> AuthenticatedClient:
 50    """Create an authenticated IonQ API client.
 51
 52    This is the recommended entry point for using the library. It handles
 53    authentication, retry configuration, User-Agent construction, and transport
 54    setup for both sync and async usage.
 55
 56    Args:
 57        api_key: IonQ API key. If not provided, reads the ``IONQ_API_KEY``
 58            environment variable.
 59        base_url: API base URL. Defaults to the IonQ production API.
 60        max_retries: Maximum retry attempts for transient errors (429, 5xx).
 61            Defaults to 2. Set to 0 to disable retries.
 62        timeout: Request timeout as an ``httpx.Timeout`` instance. Defaults to
 63            60 seconds with a 10-second connect timeout.
 64        additional_user_agent: Extra token appended to the User-Agent header,
 65            useful for identifying calling applications.
 66        extension: A `ClientExtension` bundle provided by a downstream SDK.
 67            Allows injecting hooks, custom headers, transport wrappers, and
 68            error mappers.
 69        **kwargs: Passed through to `AuthenticatedClient`.
 70
 71    Returns:
 72        An `AuthenticatedClient` configured with retry transport and
 73        authentication headers, ready for both sync and async API calls.
 74
 75    Raises:
 76        ValueError: If no API key is provided and ``IONQ_API_KEY`` is not set.
 77
 78    Examples:
 79        Basic usage with environment variable:
 80
 81        ```python
 82        from ionq_core import IonQClient
 83        from ionq_core.api.backends import get_backends
 84
 85        client = IonQClient()
 86        backends = get_backends.sync(client=client)
 87        ```
 88
 89        Explicit configuration:
 90
 91        ```python
 92        import httpx
 93        from ionq_core import IonQClient
 94
 95        client = IonQClient(
 96            api_key="your-api-key",
 97            max_retries=5,
 98            timeout=httpx.Timeout(30.0, connect=10.0),
 99        )
100        ```
101
102        Async usage with context manager:
103
104        ```python
105        async with IonQClient() as client:
106            backends = await get_backends.asyncio(client=client)
107        ```
108    """
109    key = api_key or os.environ.get("IONQ_API_KEY")
110    if not key:
111        raise ValueError("api_key or IONQ_API_KEY environment variable required")
112
113    if not base_url.startswith("https://"):
114        warnings.warn(
115            f"base_url {base_url!r} does not use HTTPS. API keys will be sent in cleartext.",
116            UserWarning,
117            stacklevel=2,
118        )
119    if kwargs.get("verify_ssl") is False:
120        warnings.warn(
121            "verify_ssl=False disables TLS certificate verification. "
122            "Your API key may be intercepted by a network attacker.",
123            UserWarning,
124            stacklevel=2,
125        )
126
127    ext = extension or ClientExtension()
128    ua_parts = [
129        f"ionq-core/{__version__}",
130        f"python/{platform.python_version()}",
131        f"httpx/{httpx.__version__}",
132        f"os/{platform.system().lower()}",
133        *filter(None, (additional_user_agent, ext.user_agent_token)),
134    ]
135    user_agent = " ".join(ua_parts)
136    effective_timeout = timeout or ext.timeout or DEFAULT_TIMEOUT
137    effective_retries = next(v for v in (max_retries, ext.max_retries, DEFAULT_MAX_RETRIES) if v is not None)
138
139    headers = {**ext.default_headers, "User-Agent": user_agent}
140
141    sync_transport = async_transport = build_transport(
142        effective_retries,
143        ext.retryable_status_codes or RETRYABLE_STATUS_CODES,
144    )
145
146    if ext.event_hooks or ext.error_mapper:
147        sync_transport = HookTransport(
148            sync_transport,
149            ext.event_hooks,
150            debug=ext.debug_hooks,
151            error_mapper=ext.error_mapper,
152        )
153    if ext.async_event_hooks or ext.error_mapper:
154        async_transport = HookTransport(
155            async_transport,
156            ext.async_event_hooks,
157            debug=ext.debug_hooks,
158            error_mapper=ext.error_mapper,
159        )
160    if ext.transport_wrapper:
161        sync_transport = ext.transport_wrapper(sync_transport)
162    if ext.async_transport_wrapper:
163        async_transport = ext.async_transport_wrapper(async_transport)
164
165    client = AuthenticatedClient(
166        base_url=base_url,
167        token=key,
168        prefix=_AUTH_PREFIX,
169        auth_header_name=_AUTH_HEADER,
170        timeout=effective_timeout,
171        headers=headers,
172        httpx_args={"transport": sync_transport},
173        **kwargs,
174    )
175    # `set_async_httpx_client` bypasses `AuthenticatedClient`'s lazy auth-header
176    # injection (see generated `client.py::get_async_httpx_client`), so we merge
177    # `Authorization` in manually here. The `_verify_ssl` / `_follow_redirects`
178    # fields are private on the generated `AuthenticatedClient` but are the only
179    # way to mirror the caller's choices onto the async transport; do not add a
180    # public accessor in the hand-written layer — they belong to generated code.
181    client.set_async_httpx_client(
182        httpx.AsyncClient(
183            base_url=base_url,
184            headers={**headers, _AUTH_HEADER: f"{_AUTH_PREFIX} {key}"},
185            timeout=effective_timeout,
186            transport=async_transport,
187            verify=client._verify_ssl,
188            follow_redirects=client._follow_redirects,
189        )
190    )
191    return client

Create an authenticated IonQ API client.

This is the recommended entry point for using the library. It handles authentication, retry configuration, User-Agent construction, and transport setup for both sync and async usage.

Arguments:
  • api_key: IonQ API key. If not provided, reads the IONQ_API_KEY environment variable.
  • base_url: API base URL. Defaults to the IonQ production API.
  • max_retries: Maximum retry attempts for transient errors (429, 5xx). Defaults to 2. Set to 0 to disable retries.
  • timeout: Request timeout as an httpx.Timeout instance. Defaults to 60 seconds with a 10-second connect timeout.
  • additional_user_agent: Extra token appended to the User-Agent header, useful for identifying calling applications.
  • extension: A ClientExtension bundle provided by a downstream SDK. Allows injecting hooks, custom headers, transport wrappers, and error mappers.
  • **kwargs: Passed through to AuthenticatedClient.
Returns:

An AuthenticatedClient configured with retry transport and authentication headers, ready for both sync and async API calls.

Raises:
  • ValueError: If no API key is provided and IONQ_API_KEY is not set.
Examples:

Basic usage with environment variable:

from ionq_core import IonQClient
from ionq_core.api.backends import get_backends

client = IonQClient()
backends = get_backends.sync(client=client)

Explicit configuration:

import httpx
from ionq_core import IonQClient

client = IonQClient(
    api_key="your-api-key",
    max_retries=5,
    timeout=httpx.Timeout(30.0, connect=10.0),
)

Async usage with context manager:

async with IonQClient() as client:
    backends = await get_backends.asyncio(client=client)
__version__ = '0.1.1'