diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c88033e..2bf4971 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -31,11 +31,13 @@ Most of the code in `ionq_core/` is **auto-generated** from the IonQ OpenAPI spe **Hand-written (edit freely):** - `ionq_core/__init__.py` -- public API exports - `ionq_core/ionq_client.py` -- IonQClient convenience wrapper -- `ionq_core/_exceptions.py` -- exception hierarchy -- `ionq_core/_extensions.py` -- extension API for downstream SDKs -- `ionq_core/_transport.py` -- retry transport -- `ionq_core/_pagination.py` -- pagination helpers -- `ionq_core/_polling.py` -- job polling helpers +- `ionq_core/exceptions.py` -- exception hierarchy +- `ionq_core/extensions.py` -- extension API for downstream SDKs +- `ionq_core/_transport.py` -- retry transport (internal) +- `ionq_core/pagination.py` -- pagination helpers +- `ionq_core/polling.py` -- job polling helpers +- `ionq_core/gates.py` -- native gate matrices +- `ionq_core/session.py` -- session lifecycle manager - `tests/` -- all tests ## Regenerating the client @@ -65,6 +67,10 @@ uvx openapi-python-client==0.28.3 generate \ - CI must pass before merging (lint, tests, type check, generated code staleness check). - The generated code staleness check on PRs verifies that `ionq_core/` matches what the generator produces. If it fails, regenerate and commit the result. +## Contributor License Agreement + +To receive IonQ's CLA, please contact @mjk or email [opensource@ionq.com](mailto:opensource@ionq.com). + ## License By submitting a pull request, you represent that you have the right to license your diff --git a/custom-templates/package_init.py.jinja b/custom-templates/package_init.py.jinja index c602cf0..e532b23 100644 --- a/custom-templates/package_init.py.jinja +++ b/custom-templates/package_init.py.jinja @@ -1,6 +1,6 @@ {% from "helpers.jinja" import safe_docstring %} {{ safe_docstring(package_description) }} -from ._exceptions import ( +from .exceptions import ( APIConnectionError, APIError, APITimeoutError, @@ -12,16 +12,16 @@ from ._exceptions import ( RateLimitError, ServerError, ) -from ._extensions import AsyncEventHook, ClientExtension, EventHook -from ._gates import gpi2_matrix, gpi_matrix, ms_matrix, zz_matrix -from ._pagination import aiter_jobs, aiter_session_jobs, iter_jobs, iter_session_jobs -from ._polling import ( +from .extensions import AsyncEventHook, ClientExtension, EventHook +from .gates import gpi2_matrix, gpi_matrix, ms_matrix, zz_matrix +from .pagination import aiter_jobs, aiter_session_jobs, iter_jobs, iter_session_jobs +from .polling import ( JobFailedError, JobTimeoutError, async_wait_for_job, wait_for_job, ) -from ._session import SessionManager +from .session import SessionManager from .client import AuthenticatedClient, Client from .ionq_client import IonQClient, __version__ from .types import UNSET, Unset diff --git a/ionq_core/__init__.py b/ionq_core/__init__.py index fbee532..b05def5 100644 --- a/ionq_core/__init__.py +++ b/ionq_core/__init__.py @@ -3,7 +3,8 @@ """A client library for accessing IonQ Cloud Platform API""" -from ._exceptions import ( +from .client import AuthenticatedClient, Client +from .exceptions import ( APIConnectionError, APIError, APITimeoutError, @@ -15,18 +16,17 @@ RateLimitError, ServerError, ) -from ._extensions import AsyncEventHook, ClientExtension, EventHook -from ._gates import gpi2_matrix, gpi_matrix, ms_matrix, zz_matrix -from ._pagination import aiter_jobs, aiter_session_jobs, iter_jobs, iter_session_jobs -from ._polling import ( +from .extensions import AsyncEventHook, ClientExtension, EventHook +from .gates import gpi2_matrix, gpi_matrix, ms_matrix, zz_matrix +from .ionq_client import IonQClient, __version__ +from .pagination import aiter_jobs, aiter_session_jobs, iter_jobs, iter_session_jobs +from .polling import ( JobFailedError, JobTimeoutError, async_wait_for_job, wait_for_job, ) -from ._session import SessionManager -from .client import AuthenticatedClient, Client -from .ionq_client import IonQClient, __version__ +from .session import SessionManager from .types import UNSET, Unset __all__ = ( diff --git a/ionq_core/_transport.py b/ionq_core/_transport.py index e342cfc..5720d75 100644 --- a/ionq_core/_transport.py +++ b/ionq_core/_transport.py @@ -16,7 +16,7 @@ import httpx from httpx_retries import Retry, RetryTransport -from ._exceptions import APIConnectionError, APITimeoutError, raise_for_status +from .exceptions import APIConnectionError, APITimeoutError, raise_for_status RETRYABLE_STATUS_CODES: frozenset[int] = frozenset({429, 500, 502, 503, *range(520, 530)}) """HTTP status codes that trigger an automatic retry.""" diff --git a/ionq_core/_exceptions.py b/ionq_core/exceptions.py similarity index 100% rename from ionq_core/_exceptions.py rename to ionq_core/exceptions.py diff --git a/ionq_core/_extensions.py b/ionq_core/extensions.py similarity index 100% rename from ionq_core/_extensions.py rename to ionq_core/extensions.py diff --git a/ionq_core/_gates.py b/ionq_core/gates.py similarity index 100% rename from ionq_core/_gates.py rename to ionq_core/gates.py diff --git a/ionq_core/ionq_client.py b/ionq_core/ionq_client.py index ab371dc..c80036c 100644 --- a/ionq_core/ionq_client.py +++ b/ionq_core/ionq_client.py @@ -17,9 +17,9 @@ import httpx -from ._extensions import ClientExtension, HookTransport from ._transport import DEFAULT_MAX_RETRIES, RETRYABLE_STATUS_CODES, build_transport from .client import AuthenticatedClient +from .extensions import ClientExtension, HookTransport try: __version__ = _pkg_version("ionq-core") diff --git a/ionq_core/_pagination.py b/ionq_core/pagination.py similarity index 99% rename from ionq_core/_pagination.py rename to ionq_core/pagination.py index 8669d20..1c9f5fa 100644 --- a/ionq_core/_pagination.py +++ b/ionq_core/pagination.py @@ -23,8 +23,8 @@ from collections.abc import AsyncIterator, Callable, Iterator from typing import TYPE_CHECKING, Any -from ._exceptions import IonQError from .api.default import get_jobs, get_session_jobs +from .exceptions import IonQError from .types import UNSET, Unset if TYPE_CHECKING: diff --git a/ionq_core/_polling.py b/ionq_core/polling.py similarity index 99% rename from ionq_core/_polling.py rename to ionq_core/polling.py index c93aa1e..bdf8959 100644 --- a/ionq_core/_polling.py +++ b/ionq_core/polling.py @@ -27,8 +27,8 @@ import time from typing import TYPE_CHECKING -from ._exceptions import IonQError from .api.default import get_job +from .exceptions import IonQError from .types import Unset if TYPE_CHECKING: diff --git a/ionq_core/_session.py b/ionq_core/session.py similarity index 99% rename from ionq_core/_session.py rename to ionq_core/session.py index fd13f64..d2df148 100644 --- a/ionq_core/_session.py +++ b/ionq_core/session.py @@ -30,8 +30,8 @@ import logging from typing import TYPE_CHECKING -from ._exceptions import IonQError from .api.default import create_session, end_session, get_session +from .exceptions import IonQError from .models.create_session_request import CreateSessionRequest from .models.session_cost_limit import SessionCostLimit from .models.session_settings_request import SessionSettingsRequest diff --git a/tests/integration/test_sessions.py b/tests/integration/test_sessions.py index 22c293a..15a074c 100644 --- a/tests/integration/test_sessions.py +++ b/tests/integration/test_sessions.py @@ -3,8 +3,8 @@ import pytest from ionq_core import SessionManager -from ionq_core._exceptions import NotFoundError from ionq_core.api.default import get_session_jobs, get_sessions +from ionq_core.exceptions import NotFoundError pytestmark = pytest.mark.integration diff --git a/tests/integration/test_usage.py b/tests/integration/test_usage.py index 545cc6e..85ffaa4 100644 --- a/tests/integration/test_usage.py +++ b/tests/integration/test_usage.py @@ -4,8 +4,8 @@ import pytest -from ionq_core._exceptions import PermissionDeniedError from ionq_core.api.usage import get_usages +from ionq_core.exceptions import PermissionDeniedError pytestmark = pytest.mark.integration diff --git a/tests/test_exceptions.py b/tests/test_exceptions.py index bfa0726..f8ece26 100644 --- a/tests/test_exceptions.py +++ b/tests/test_exceptions.py @@ -1,6 +1,6 @@ import pytest -from ionq_core._exceptions import ( +from ionq_core.exceptions import ( APIError, AuthenticationError, BadRequestError, diff --git a/tests/test_extensions.py b/tests/test_extensions.py index 63e182e..e7d3075 100644 --- a/tests/test_extensions.py +++ b/tests/test_extensions.py @@ -4,12 +4,12 @@ import pytest from ionq_core import ClientExtension, EventHook, IonQClient -from ionq_core._exceptions import APIError, NotFoundError -from ionq_core._extensions import ( +from ionq_core._transport import ErrorRaisingTransport +from ionq_core.exceptions import APIError, NotFoundError +from ionq_core.extensions import ( AsyncEventHook, HookTransport, ) -from ionq_core._transport import ErrorRaisingTransport _BACKENDS_URL = "https://api.ionq.co/v0.4/backends" diff --git a/tests/test_gates.py b/tests/test_gates.py index 6f12623..c74eadc 100644 --- a/tests/test_gates.py +++ b/tests/test_gates.py @@ -2,7 +2,7 @@ import pytest -from ionq_core._gates import gpi2_matrix, gpi_matrix, ms_matrix, zz_matrix +from ionq_core.gates import gpi2_matrix, gpi_matrix, ms_matrix, zz_matrix _PHIS = [0, 0.1, 0.25, 0.5, 0.73] diff --git a/tests/test_polling.py b/tests/test_polling.py index 420c3e6..6ca6bc2 100644 --- a/tests/test_polling.py +++ b/tests/test_polling.py @@ -2,8 +2,8 @@ import pytest -from ionq_core._exceptions import IonQError -from ionq_core._polling import JobFailedError, JobTimeoutError, async_wait_for_job, wait_for_job +from ionq_core.exceptions import IonQError +from ionq_core.polling import JobFailedError, JobTimeoutError, async_wait_for_job, wait_for_job from tests.conftest import make_job_json _real_sleep = asyncio.sleep @@ -17,7 +17,7 @@ def test_already_completed(self, httpx_mock, auth_client): assert wait_for_job(auth_client, "j1", timeout=5).status == "completed" def test_polls_until_completed(self, httpx_mock, auth_client, monkeypatch): - monkeypatch.setattr("ionq_core._polling.time.sleep", lambda _: None) + monkeypatch.setattr("ionq_core.polling.time.sleep", lambda _: None) for s in ("submitted", "ready", "completed"): httpx_mock.add_response(json=make_job_json("j1", s)) assert wait_for_job(auth_client, "j1", poll_interval=0.01, timeout=10).status == "completed" @@ -42,7 +42,7 @@ def test_none_response_raises(self, httpx_mock, auth_client): wait_for_job(auth_client, "j1", timeout=5) def test_timeout_raises(self, httpx_mock, auth_client, monkeypatch): - monkeypatch.setattr("ionq_core._polling.time.sleep", lambda _: None) + monkeypatch.setattr("ionq_core.polling.time.sleep", lambda _: None) httpx_mock.add_response(json=make_job_json("j1", "submitted"), is_reusable=True) with pytest.raises(JobTimeoutError, match="j1"): wait_for_job(auth_client, "j1", poll_interval=0.01, timeout=0.0) @@ -69,13 +69,13 @@ async def test_none_response_raises(self, httpx_mock, auth_client): await async_wait_for_job(auth_client, "j1", timeout=5) async def test_timeout_raises(self, httpx_mock, auth_client, monkeypatch): - monkeypatch.setattr("ionq_core._polling.asyncio.sleep", lambda _: _real_sleep(0)) + monkeypatch.setattr("ionq_core.polling.asyncio.sleep", lambda _: _real_sleep(0)) httpx_mock.add_response(json=make_job_json("j1", "submitted"), is_reusable=True) with pytest.raises(JobTimeoutError, match="j1"): await async_wait_for_job(auth_client, "j1", poll_interval=0.01, timeout=0.0) async def test_polls_until_completed(self, httpx_mock, auth_client, monkeypatch): - monkeypatch.setattr("ionq_core._polling.asyncio.sleep", lambda _: _real_sleep(0)) + monkeypatch.setattr("ionq_core.polling.asyncio.sleep", lambda _: _real_sleep(0)) for s in ("submitted", "ready", "completed"): httpx_mock.add_response(json=make_job_json("j1", s)) result = await async_wait_for_job(auth_client, "j1", poll_interval=0.01, timeout=10) diff --git a/tests/test_session.py b/tests/test_session.py index 38ad7a3..8dd710d 100644 --- a/tests/test_session.py +++ b/tests/test_session.py @@ -3,8 +3,8 @@ import httpx import pytest -from ionq_core._exceptions import IonQError -from ionq_core._session import SessionManager +from ionq_core.exceptions import IonQError +from ionq_core.session import SessionManager _BASE = "https://test.invalid/v0.4" diff --git a/tests/test_transport.py b/tests/test_transport.py index 1355d58..8e38971 100644 --- a/tests/test_transport.py +++ b/tests/test_transport.py @@ -1,7 +1,8 @@ import httpx import pytest -from ionq_core._exceptions import ( +from ionq_core._transport import ErrorRaisingTransport, build_transport +from ionq_core.exceptions import ( APIConnectionError, APITimeoutError, AuthenticationError, @@ -10,7 +11,6 @@ RateLimitError, ServerError, ) -from ionq_core._transport import ErrorRaisingTransport, build_transport _URL = "https://api.ionq.co/v0.4/backends"