From cc36cb0f9094318a3bfb05a4bd2bcdb470a70a72 Mon Sep 17 00:00:00 2001 From: Spencer Churchill <25377399+splch@users.noreply.github.com> Date: Thu, 30 Apr 2026 11:46:49 -0700 Subject: [PATCH 1/3] Fix integration Bell circuit fixture so simulator preflight accepts it The QIS circuit input was missing `qubits`, which preflight requires to construct the cypress circuit; without it the parser hits an unguarded IndexError that surfaces to the customer as a generic UnexpectedCompilationError. Switching the cnot to plural targets/controls matches the documented form in the rest of the fixture. Verified by submitting Bell circuits with this exact payload to the simulator and observing successful completion. --- tests/integration/test_simulator_job.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/integration/test_simulator_job.py b/tests/integration/test_simulator_job.py index fb748ca..1873d49 100644 --- a/tests/integration/test_simulator_job.py +++ b/tests/integration/test_simulator_job.py @@ -25,9 +25,10 @@ "shots": 100, "input": { "gateset": "qis", + "qubits": 2, "circuit": [ {"gate": "h", "targets": [0]}, - {"gate": "cnot", "control": 0, "target": 1}, + {"gate": "cnot", "controls": [0], "targets": [1]}, ], }, } From b63a6aa976f9a7095b6b2ef13ac5d56809220b81 Mon Sep 17 00:00:00 2001 From: Spencer Churchill <25377399+splch@users.noreply.github.com> Date: Thu, 30 Apr 2026 12:17:58 -0700 Subject: [PATCH 2/3] Codify the qubits requirement in an OpenAPI overlay and regenerate The upstream spec marks QisCircuitInput.qubits as optional and floating-point, but the simulator preflight rejects payloads without it and qubit counts are non-negative integers. Add an openapi-overlay.yaml that the existing codegen workflow already knows how to apply, making qubits required and integer-typed at the SDK level so this class of mistake fails at construction rather than after a 120s wait. Regenerate ionq_core/models/qis_circuit_input.py and update the README example and unit-test stub to match the tightened contract. --- README.md | 3 ++- ionq_core/models/qis_circuit_input.py | 18 ++++++++---------- openapi-overlay.yaml | 23 +++++++++++++++++++++++ tests/test_api.py | 2 +- 4 files changed, 34 insertions(+), 12 deletions(-) create mode 100644 openapi-overlay.yaml diff --git a/README.md b/README.md index a13559a..93978ae 100644 --- a/README.md +++ b/README.md @@ -47,9 +47,10 @@ body = CircuitJobCreationPayload.from_dict({ "shots": 100, "input": { "gateset": "qis", + "qubits": 2, "circuit": [ {"gate": "h", "targets": [0]}, - {"gate": "cnot", "control": 0, "target": 1}, + {"gate": "cnot", "controls": [0], "targets": [1]}, ], }, }) diff --git a/ionq_core/models/qis_circuit_input.py b/ionq_core/models/qis_circuit_input.py index d8c1ac2..358b3c5 100644 --- a/ionq_core/models/qis_circuit_input.py +++ b/ionq_core/models/qis_circuit_input.py @@ -14,7 +14,6 @@ from ..models.qis_circuit_input_gateset import check_qis_circuit_input_gateset from ..models.qis_circuit_input_gateset import QisCircuitInputGateset -from ..types import UNSET, Unset from typing import cast if TYPE_CHECKING: @@ -32,14 +31,14 @@ class QisCircuitInput: """ Attributes: + qubits (int): circuit (list[GateQisGate]): gateset (QisCircuitInputGateset): - qubits (float | Unset): """ + qubits: int circuit: list[GateQisGate] gateset: QisCircuitInputGateset - qubits: float | Unset = UNSET @@ -47,6 +46,8 @@ class QisCircuitInput: def to_dict(self) -> dict[str, Any]: from ..models.gate_qis_gate import GateQisGate + qubits = self.qubits + circuit = [] for circuit_item_data in self.circuit: circuit_item = circuit_item_data.to_dict() @@ -56,17 +57,14 @@ def to_dict(self) -> dict[str, Any]: gateset: str = self.gateset - qubits = self.qubits - field_dict: dict[str, Any] = {} field_dict.update({ + "qubits": qubits, "circuit": circuit, "gateset": gateset, }) - if qubits is not UNSET: - field_dict["qubits"] = qubits return field_dict @@ -76,6 +74,8 @@ def to_dict(self) -> dict[str, Any]: def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: from ..models.gate_qis_gate import GateQisGate d = dict(src_dict) + qubits = d.pop("qubits") + circuit = [] _circuit = d.pop("circuit") for circuit_item_data in (_circuit): @@ -91,12 +91,10 @@ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: - qubits = d.pop("qubits", UNSET) - qis_circuit_input = cls( + qubits=qubits, circuit=circuit, gateset=gateset, - qubits=qubits, ) return qis_circuit_input diff --git a/openapi-overlay.yaml b/openapi-overlay.yaml new file mode 100644 index 0000000..00c0976 --- /dev/null +++ b/openapi-overlay.yaml @@ -0,0 +1,23 @@ +overlay: 1.0.0 +info: + title: ionq-core-python local OpenAPI fixes + version: 0.1.0 + description: | + Patches applied to openapi.json before client generation. The upstream + spec marks QisCircuitInput.qubits as optional and floating-point, but + the simulator preflight rejects payloads without it (surfacing as + UnexpectedCompilationError) and qubit counts are non-negative integers. + +actions: + - target: $.components.schemas.QisCircuitInput.required + remove: true + - target: $.components.schemas.QisCircuitInput + update: + required: + - circuit + - gateset + - qubits + - target: $.components.schemas.QisCircuitInput.properties.qubits + update: + type: integer + format: int32 diff --git a/tests/test_api.py b/tests/test_api.py index 1a665e9..b3c0955 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -112,7 +112,7 @@ def test_sync(self, httpx_mock, auth_client): "type": "ionq.circuit.v1", "backend": "simulator", "shots": 100, - "input": {"gateset": "qis", "circuit": [{"gate": "h", "targets": [0]}]}, + "input": {"gateset": "qis", "qubits": 1, "circuit": [{"gate": "h", "targets": [0]}]}, } ) result = create_job.sync(client=auth_client, body=body) From 86f4d605bd4454a97aea78952dde26ca9d5cd833 Mon Sep 17 00:00:00 2001 From: Spencer Churchill <25377399+splch@users.noreply.github.com> Date: Thu, 30 Apr 2026 16:47:35 -0700 Subject: [PATCH 3/3] Use singular target/control form in examples Verified end-to-end against the simulator that singular target/control round-trips identically to the plural form once `qubits` is set, so prefer the more concise singular form in the docs and tests. --- README.md | 4 ++-- tests/integration/test_simulator_job.py | 4 ++-- tests/test_api.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 93978ae..992bbde 100644 --- a/README.md +++ b/README.md @@ -49,8 +49,8 @@ body = CircuitJobCreationPayload.from_dict({ "gateset": "qis", "qubits": 2, "circuit": [ - {"gate": "h", "targets": [0]}, - {"gate": "cnot", "controls": [0], "targets": [1]}, + {"gate": "h", "target": 0}, + {"gate": "cnot", "control": 0, "target": 1}, ], }, }) diff --git a/tests/integration/test_simulator_job.py b/tests/integration/test_simulator_job.py index 1873d49..602535d 100644 --- a/tests/integration/test_simulator_job.py +++ b/tests/integration/test_simulator_job.py @@ -27,8 +27,8 @@ "gateset": "qis", "qubits": 2, "circuit": [ - {"gate": "h", "targets": [0]}, - {"gate": "cnot", "controls": [0], "targets": [1]}, + {"gate": "h", "target": 0}, + {"gate": "cnot", "control": 0, "target": 1}, ], }, } diff --git a/tests/test_api.py b/tests/test_api.py index b3c0955..176829a 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -112,7 +112,7 @@ def test_sync(self, httpx_mock, auth_client): "type": "ionq.circuit.v1", "backend": "simulator", "shots": 100, - "input": {"gateset": "qis", "qubits": 1, "circuit": [{"gate": "h", "targets": [0]}]}, + "input": {"gateset": "qis", "qubits": 1, "circuit": [{"gate": "h", "target": 0}]}, } ) result = create_job.sync(client=auth_client, body=body)