Skip to content

Expand integration tests, fix transport response reading bug#10

Merged
splch merged 18 commits into
mainfrom
feat/integration-tests-and-transport-fix
Apr 22, 2026
Merged

Expand integration tests, fix transport response reading bug#10
splch merged 18 commits into
mainfrom
feat/integration-tests-and-transport-fix

Conversation

@splch

@splch splch commented Apr 20, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Expands integration tests from 4 to 30, covering all API endpoint groups
  • Fixes a bug in _transport.py where _parse_error_body accessed response.json()/response.text without calling response.read() first, causing ResponseNotRead on error status codes

Integration test coverage

Group Tests Endpoints covered
Backends 4 get_backends, get_backend
Characterizations 3 get_characterizations_for_backend, get_characterization
Jobs 14 create_job, get_job, get_jobs, cancel_job, delete_job, get_job_cost, get_job_probabilities, estimate_job_cost, wait_for_job, iter_jobs
Sessions 3 get_sessions, create_session, end_session, get_session_jobs, SessionManager
Whoami 2 get_whoami (sync + detailed)
Usage 1 get_usages
Async 3 asyncio variants of whoami, list jobs, aiter_jobs

Tests that depend on account-specific features (sessions, usage) skip gracefully when the API key lacks the required scope.

Test plan

  • uv run pytest - 234 unit tests pass, 100% coverage
  • Integration: 24 passed, 4 skipped (sessions 404, usage scope), 1 known failure (simulator stuck at submitted)

splch added 18 commits April 20, 2026 16:24
Transport:
- Move response.read()/aread() before _should_retry so exhausted
  retries also have a read body for error parsing
- Add await response.aread() in async transport (was sync-only)
- Remove response.read() from _parse_error_body (caller's job)

Tests:
- Consolidate redundant API calls via shared fixtures
- Fix session tests: status() returns a string, not Session object
- Actually test context manager protocol with `with` statement
- Use itertools.islice instead of zip(range())
- Remove redundant warnings.catch_warnings blocks (conftest handles it)
- Extract _submit() helper for repeated job creation
- Add docstring explaining why async_client fixture exists separately
- Flatten TestListBackends into plain functions with a shared fixture
- Flatten single-test classes (cancel, delete, estimate, poll)
- Add assert to _submit helper for clearer failures
- Use context manager in test_session_lifecycle instead of manual cleanup
- scope="module" on backends fixture (1 API call instead of 3)
- scope="class" on completed_job_id fixture (1 call instead of 3)
- _submit accepts **overrides, simplifying dry_run test
- Remove test_session_context_manager (covered by test_session_lifecycle)
- Move SSL warning filters from module-level to session-scoped
  autouse fixture so they don't leak into unit test collection
- Add track_job to test_delete_job for cleanup if delete fails
Add missing blank line between fixture and module-level variable.
…ompat

pip install -e '.[dev]' only works with [project.optional-dependencies],
not [dependency-groups] (PEP 735). Add the dev extra so both pip and uv
workflows work. This matches the pattern used by pypa/build.
Extract _paginate/_apaginate helpers to eliminate duplicated cursor-
following logic across 4 pagination functions. Extract _ext() helper
in IonQClient to consolidate the repeated extension-or-default
precedence pattern and group sync/async transport chain construction.

Net reduction: ~25 lines with improved readability.
Replace the _ext() closure in IonQClient with direct attribute access.
The closure's docstring claimed extension-or-default precedence, but its
implementation couldn't actually resolve extension attributes when a
default was provided.

Change _build_settings() to return UNSET instead of None, matching the
auto-generated model's field type and eliminating conditional dict-splat
at both sync and async call sites.
Replace the custom RetryTransport/AsyncRetryTransport (192 lines) with
httpx-retries, a lightweight library (8.9kB) that provides the same
retry logic out of the box: exponential backoff, jitter, Retry-After
header support, status code filtering, and idempotent method awareness.

What changed:
- _transport.py: Remove 7 helper functions and 2 transport classes,
  replace with build_retry/build_sync_transport/build_async_transport
  that configure httpx-retries, plus thin ErrorRaisingTransport that
  converts error responses to structured IonQ exceptions.
- _extensions.py: Switch ClientExtension from stdlib dataclasses to
  attrs for consistency with the rest of the codebase.
- ionq_client.py: Use new transport builder functions.

Hand-written code reduced from 1,130 to 1,058 lines (-72).
All 221 tests pass with 100% branch coverage.
Merge ErrorRaisingTransport/AsyncErrorRaisingTransport into a single
dual-inheritance class (same pattern httpx-retries uses). Do the same
for HookTransport, _ErrorMapperTransport, and their async counterparts.

Also inline _parse_error_body and _parse_retry_after into
_raise_for_response, remove unused future annotations imports, unify
build_sync_transport/build_async_transport into build_transport, and
trim docstrings to essential content.

Hand-written code: 1058 -> 966 lines (-92, -8.7%).
All 220 tests pass with 100% branch coverage.
RetryTransport(retry=retry) with no explicit inner transport auto-
creates both sync and async transports internally (per httpx-retries
docs). Remove the async_ parameter and build a single transport that
serves both the sync Client and AsyncClient.

Hand-written code: 968 -> 965 lines.
…tations

Replace typing.Any on the transport parameter with a proper Protocol
that defines both sync and async transport interfaces. Move
ErrorRaisingTransport above build_transport to eliminate the forward
reference, removing the need for from __future__ import annotations.

The remaining files (_session.py, _pagination.py, _polling.py) keep
from __future__ import annotations because they use TYPE_CHECKING
guards to avoid circular imports - the correct pattern for Python
3.12-3.13 per PEP 563/749.
- Remove redundant Retry() params (respect_retry_after_header, allowed_methods)
  that matched library defaults
- Use @attrs.frozen shorthand instead of @attrs.define(frozen=True)
- Inline _build_user_agent and use ClientExtension() default to simplify
  IonQClient transport chain setup
- Merge sync/async test transport classes using dual-inheritance pattern
- Extract shared make_job_json() helper to conftest, replacing duplicated
  20+ line job JSON builders in test_polling and test_pagination
Bump dev dependency floors to match actual major versions in use:
- pytest >=8 -> >=9 (required by pytest-httpx >=0.36)
- pytest-httpx >=0.35 -> >=0.36
- pytest-asyncio >=0.25 -> >=1
- pytest-cov >=6 -> >=7
- ruff >=0.9 -> >=0.15

Lock file upgraded:
- certifi 2026.2.25 -> 2026.4.22
- idna 3.11 -> 3.13
- packaging 26.0 -> 26.1
- pytest-httpx 0.36.0 -> 0.36.2
- ruff 0.15.9 -> 0.15.11
- ty 0.0.29 -> 0.0.32
- Fix bug where ClientExtension(max_retries=0) was silently ignored
  because Python's `or` operator treats 0 as falsy, falling through
  to DEFAULT_MAX_RETRIES. Use explicit `is not None` checks instead.

- Remove duplicate [project.optional-dependencies] dev section that
  mirrored [dependency-groups] dev; CI uses `uv sync` which reads
  dependency-groups.

- Add asyncio_default_fixture_loop_scope to silence pytest-asyncio
  deprecation warning about unset default scope.

- Replace 50-iteration response registration loops with pytest-httpx
  is_reusable=True in polling timeout tests.

- Remove 11 unnecessary assert_all_responses_were_requested=False
  markers from tests where the single registered response is always
  consumed.

- Fix pre-existing ruff format violation in build_transport().
@splch splch merged commit 144a0c0 into main Apr 22, 2026
7 checks passed
@splch splch deleted the feat/integration-tests-and-transport-fix branch April 22, 2026 23:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant