Skip to content

Commit 9a8310e

Browse files
authored
Merge pull request #21 from ionq/improve-pdoc-docstrings
Improve docstrings across all hand-written modules for pdoc
2 parents c98809a + 5e73173 commit 9a8310e

10 files changed

Lines changed: 787 additions & 59 deletions

File tree

custom-templates/package_init.py.jinja

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,75 @@
22
# Copyright 2026 IonQ, Inc.
33
# SPDX-License-Identifier: Apache-2.0
44

5-
{{ safe_docstring(package_description) }}
5+
"""A Python client library for the [IonQ Cloud Platform API](https://docs.ionq.com/).
6+
7+
Provides full access to IonQ's quantum computing services with typed models for all
8+
request and response objects. Supports both synchronous and asynchronous usage.
9+
10+
## Quick start
11+
12+
```python
13+
from ionq_core import IonQClient
14+
from ionq_core.api.backends import get_backends
15+
16+
# Authenticate with the IONQ_API_KEY environment variable
17+
client = IonQClient()
18+
19+
# List available quantum backends
20+
for backend in get_backends.sync(client=client):
21+
print(f"{backend.backend}: {backend.status}")
22+
```
23+
24+
## Authentication
25+
26+
Get an API key from the [IonQ Cloud Console](https://cloud.ionq.com), then
27+
either set the ``IONQ_API_KEY`` environment variable or pass it directly:
28+
29+
```python
30+
client = IonQClient() # reads IONQ_API_KEY
31+
client = IonQClient(api_key="your-key") # explicit key
32+
```
33+
34+
## Submitting a job
35+
36+
```python
37+
from ionq_core.api.default import create_job
38+
from ionq_core.models.circuit_job_creation_payload import CircuitJobCreationPayload
39+
40+
job = create_job.sync(
41+
client=client,
42+
body=CircuitJobCreationPayload.from_dict({
43+
"type": "ionq.circuit.v1",
44+
"backend": "simulator",
45+
"shots": 1000,
46+
"input": {
47+
"gateset": "qis",
48+
"circuit": [
49+
{"gate": "h", "targets": [0]},
50+
{"gate": "cnot", "targets": [0], "controls": [1]},
51+
],
52+
},
53+
}),
54+
)
55+
```
56+
57+
## Key features
58+
59+
- **Sync and async** - every endpoint has ``.sync()`` and ``.asyncio()`` variants.
60+
- **Automatic retries** - transient errors (429, 5xx) are retried with exponential
61+
backoff. See `IonQClient` for configuration.
62+
- **Typed exceptions** - HTTP errors are raised as `AuthenticationError`,
63+
`RateLimitError`, `ServerError`, etc. See `_exceptions` for the full hierarchy.
64+
- **Pagination helpers** - `iter_jobs` and `aiter_jobs` follow cursors automatically.
65+
- **Job polling** - `wait_for_job` and `async_wait_for_job` poll until completion.
66+
- **Session management** - `SessionManager` wraps the session lifecycle as a
67+
context manager.
68+
- **Native gate matrices** - `gpi_matrix`, `gpi2_matrix`, `ms_matrix`, and
69+
`zz_matrix` return pure-Python unitary matrices for simulation and verification.
70+
- **Extensibility** - `ClientExtension` lets downstream SDKs inject hooks, headers,
71+
custom transports, and error mappers without modifying this library.
72+
"""
73+
674
from ._exceptions import (
775
APIConnectionError,
876
APIError,

ionq_core/__init__.py

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,76 @@
11
# Copyright 2026 IonQ, Inc.
22
# SPDX-License-Identifier: Apache-2.0
33

4-
"""A client library for accessing IonQ Cloud Platform API"""
4+
"""A Python client library for the [IonQ Cloud Platform API](https://docs.ionq.com/).
5+
6+
Provides full access to IonQ's quantum computing services with typed models for all
7+
request and response objects. Supports both synchronous and asynchronous usage.
8+
9+
## Quick start
10+
11+
```python
12+
from ionq_core import IonQClient
13+
from ionq_core.api.backends import get_backends
14+
15+
# Authenticate with the IONQ_API_KEY environment variable
16+
client = IonQClient()
17+
18+
# List available quantum backends
19+
for backend in get_backends.sync(client=client):
20+
print(f"{backend.backend}: {backend.status}")
21+
```
22+
23+
## Authentication
24+
25+
Get an API key from the [IonQ Cloud Console](https://cloud.ionq.com), then
26+
either set the ``IONQ_API_KEY`` environment variable or pass it directly:
27+
28+
```python
29+
client = IonQClient() # reads IONQ_API_KEY
30+
client = IonQClient(api_key="your-key") # explicit key
31+
```
32+
33+
## Submitting a job
34+
35+
```python
36+
from ionq_core.api.default import create_job
37+
from ionq_core.models.circuit_job_creation_payload import CircuitJobCreationPayload
38+
39+
job = create_job.sync(
40+
client=client,
41+
body=CircuitJobCreationPayload.from_dict(
42+
{
43+
"type": "ionq.circuit.v1",
44+
"backend": "simulator",
45+
"shots": 1000,
46+
"input": {
47+
"gateset": "qis",
48+
"circuit": [
49+
{"gate": "h", "targets": [0]},
50+
{"gate": "cnot", "targets": [0], "controls": [1]},
51+
],
52+
},
53+
}
54+
),
55+
)
56+
```
57+
58+
## Key features
59+
60+
- **Sync and async** - every endpoint has ``.sync()`` and ``.asyncio()`` variants.
61+
- **Automatic retries** - transient errors (429, 5xx) are retried with exponential
62+
backoff. See `IonQClient` for configuration.
63+
- **Typed exceptions** - HTTP errors are raised as `AuthenticationError`,
64+
`RateLimitError`, `ServerError`, etc. See `_exceptions` for the full hierarchy.
65+
- **Pagination helpers** - `iter_jobs` and `aiter_jobs` follow cursors automatically.
66+
- **Job polling** - `wait_for_job` and `async_wait_for_job` poll until completion.
67+
- **Session management** - `SessionManager` wraps the session lifecycle as a
68+
context manager.
69+
- **Native gate matrices** - `gpi_matrix`, `gpi2_matrix`, `ms_matrix`, and
70+
`zz_matrix` return pure-Python unitary matrices for simulation and verification.
71+
- **Extensibility** - `ClientExtension` lets downstream SDKs inject hooks, headers,
72+
custom transports, and error mappers without modifying this library.
73+
"""
574

675
from ._exceptions import (
776
APIConnectionError,

ionq_core/_exceptions.py

Lines changed: 111 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,75 @@
11
# Copyright 2026 IonQ, Inc.
22
# SPDX-License-Identifier: Apache-2.0
33

4-
"""Structured exceptions for the IonQ API client."""
4+
"""Structured exceptions for the IonQ API client.
5+
6+
All exceptions inherit from `IonQError`. The hierarchy is:
7+
8+
```
9+
IonQError
10+
+-- APIConnectionError # network / DNS failures
11+
| +-- APITimeoutError # request timed out
12+
+-- APIError # HTTP 4xx / 5xx responses
13+
| +-- BadRequestError # 400
14+
| +-- AuthenticationError # 401
15+
| +-- PermissionDeniedError # 403
16+
| +-- NotFoundError # 404
17+
| +-- RateLimitError # 429 (includes retry_after)
18+
| +-- ServerError # 5xx
19+
```
20+
21+
Example:
22+
```python
23+
from ionq_core import IonQClient, RateLimitError, AuthenticationError
24+
25+
client = IonQClient()
26+
try:
27+
job = create_job.sync(client=client, body=payload)
28+
except AuthenticationError:
29+
print("Invalid API key")
30+
except RateLimitError as e:
31+
print(f"Rate limited, retry after {e.retry_after}s")
32+
```
33+
"""
534

635

736
class IonQError(Exception):
8-
"""Base exception for all IonQ API errors."""
37+
"""Base exception for all IonQ errors.
38+
39+
Catch this to handle any error raised by the library, including connection
40+
failures, API errors, polling timeouts, and job failures.
41+
"""
942

1043

1144
class APIConnectionError(IonQError):
12-
"""Raised when a connection to the IonQ API cannot be established."""
45+
"""Raised when a connection to the IonQ API cannot be established.
46+
47+
This covers DNS resolution failures, refused connections, and other
48+
network-level errors. The original ``httpx`` exception is chained
49+
via ``__cause__``.
50+
"""
1351

1452

1553
class APITimeoutError(APIConnectionError):
16-
"""Raised when a request to the IonQ API times out."""
54+
"""Raised when a request to the IonQ API times out.
55+
56+
Inherits from `APIConnectionError` so that catching connection errors
57+
also catches timeouts.
58+
"""
1759

1860

1961
class APIError(IonQError):
20-
"""Raised when the IonQ API returns an error response."""
62+
"""Raised when the IonQ API returns an HTTP error response (4xx or 5xx).
63+
64+
Attributes:
65+
status_code: The HTTP status code.
66+
body: The parsed response body (``dict`` if JSON, ``str`` otherwise,
67+
or ``None`` if the body could not be read).
68+
message: A human-readable error message extracted from the response,
69+
or a default ``"HTTP <status>"`` string.
70+
request_id: The ``x-request-id`` header from the response, useful for
71+
contacting IonQ support about a specific request.
72+
"""
2173

2274
def __init__(
2375
self,
@@ -35,23 +87,45 @@ def __init__(
3587

3688

3789
class AuthenticationError(APIError):
38-
"""Raised on 401 Unauthorized."""
90+
"""Raised on ``401 Unauthorized``.
91+
92+
Typically means the API key is missing, invalid, or revoked.
93+
"""
3994

4095

4196
class PermissionDeniedError(APIError):
42-
"""Raised on 403 Forbidden."""
97+
"""Raised on ``403 Forbidden``.
98+
99+
The API key is valid but lacks permission for the requested operation.
100+
"""
43101

44102

45103
class NotFoundError(APIError):
46-
"""Raised on 404 Not Found."""
104+
"""Raised on ``404 Not Found``.
105+
106+
The requested resource (job, session, backend, etc.) does not exist.
107+
"""
47108

48109

49110
class BadRequestError(APIError):
50-
"""Raised on 400 Bad Request."""
111+
"""Raised on ``400 Bad Request``.
112+
113+
The request body or query parameters failed server-side validation.
114+
Inspect ``body`` for details.
115+
"""
51116

52117

53118
class RateLimitError(APIError):
54-
"""Raised on 429 Too Many Requests."""
119+
"""Raised on ``429 Too Many Requests``.
120+
121+
The client has exceeded the API rate limit. The ``retry_after`` attribute
122+
indicates how many seconds to wait before retrying, if the server provided
123+
a ``Retry-After`` header.
124+
125+
Attributes:
126+
retry_after: Seconds to wait before retrying, or ``None`` if the
127+
server did not include a ``Retry-After`` header.
128+
"""
55129

56130
def __init__(
57131
self,
@@ -67,7 +141,11 @@ def __init__(
67141

68142

69143
class ServerError(APIError):
70-
"""Raised on 5xx server errors."""
144+
"""Raised on ``5xx`` server errors.
145+
146+
These are typically transient and are automatically retried by the default
147+
transport (see `IonQClient`).
148+
"""
71149

72150

73151
_STATUS_TO_EXCEPTION: dict[int, type[APIError]] = {
@@ -87,6 +165,28 @@ def raise_for_status(
87165
*,
88166
request_id: str | None = None,
89167
) -> None:
168+
"""Raise an appropriate `APIError` subclass for an HTTP error status.
169+
170+
Does nothing for status codes below 400. For 4xx codes, raises the
171+
specific subclass (e.g. `AuthenticationError` for 401). For 5xx codes
172+
or unrecognized 4xx codes, raises `ServerError` or `APIError` respectively.
173+
174+
Args:
175+
status_code: The HTTP status code.
176+
body: The parsed response body.
177+
retry_after: Value from the ``Retry-After`` header, if present.
178+
message: A human-readable error message.
179+
request_id: The ``x-request-id`` response header.
180+
181+
Raises:
182+
BadRequestError: On 400.
183+
AuthenticationError: On 401.
184+
PermissionDeniedError: On 403.
185+
NotFoundError: On 404.
186+
RateLimitError: On 429.
187+
ServerError: On 5xx.
188+
APIError: On other 4xx codes.
189+
"""
90190
if status_code < 400:
91191
return
92192
exc_cls = _STATUS_TO_EXCEPTION.get(status_code, ServerError if status_code >= 500 else APIError)

0 commit comments

Comments
 (0)