Skip to content

fix(qudora): correct backend field mapping against the live API - #1306

Open
ryanhill1 wants to merge 2 commits into
feature/qudora-integrationfrom
fix/qudora-live-api-corrections
Open

fix(qudora): correct backend field mapping against the live API#1306
ryanhill1 wants to merge 2 commits into
feature/qudora-integrationfrom
fix/qudora-live-api-corrections

Conversation

@ryanhill1

Copy link
Copy Markdown
Member

Targets feature/qudora-integration (#1292), not main.

I ran the provider against api.qudora.com with a real token while reviewing #1292. The mocked fixtures were modeled on assumption rather than a captured response, and diverge from what the API actually returns — which hid two bugs.

What the live API returns

Field Fixture assumed Live API
username "qamelion" "xg1-emulator-gwdg@qudora.com" (an email)
numeric id "id": 1 no id key — it is user_id
simulator absent, hardcoded True published as a real field
status_name / is_online absent both present in the listing
max_programs_per_job 10 / 5 500
basis_gates absent ["rxx", "r"]

Bugs this surfaced

Every device reported ONLINE unconditionally. _build_profile read backend.get("id"), a key no record has, so qudora_backend_id was always None and status() took its "id unavailable ⇒ assume online" branch every time. The status endpoint was unreachable in practice. GET /backends/status/108 returns "Idle"; passing the username gives a 422.

Result.device_id was the display name. QUDORA echoes the backend's full_name back in the job record's target, so a real job produced device_id="QVLS-Q1 Emulator" — matching no device the provider can return. result() now prefers the device's own id.

Changes

  • provider.pysimulator and basis_gates from the real fields, qudora_backend_id from user_id, required fields indexed directly. Drops the two comments asserting fields don't exist, including the plans/qudora/qudora-sdk.md reference (not in the repo).
  • device.py — removes the ONLINE fallback; _DEVICE_STATUS_MAP[status_name] indexed directly.
  • job.py_JOB_STATUS_MAP hoisted to module level (matching _QUANTINUUM_STATUS_MAP) and indexed directly. An unmapped JobStatusName now raises rather than degrading to JobStatus.UNKNOWN, which is not a terminal state and would have made result() poll a finished job until wait_for_final_state timed out.
  • Annotations converted to PEP 604 (str | None), per the CONTRIBUTING section merged in docs: tighten Unreleased changelog entries and add length guidance #1288.
  • pyproject.toml — drops the mypy exclusion. Both qudora-specific errors are fixed; the remaining ones are the same structural errors rigetti, quantinuum and pasqal already produce unexcluded.
  • docs/api/qbraid.runtime.rst — registers qudora, the only provider missing from the submodule list.

Tests

Fixtures rebuilt from captured payloads (BACKENDS verbatim from the live listing, _job_record modeled on real job 85974). New cases cover the user_id-vs-id trap, unmapped statuses raising, missing-field failures, and the device_id divergence.

test_qudora_remote.py adds 7 tests behind the remote marker, asserting the response shape the unit fixtures hard-code so upstream drift fails there instead of silently.

Adding tests/runtime/qudora/__init__.py matters more than it looks: without a package marker, pylint qbraid bin tests skipped the entire test file in CI. Adding it surfaced 5 line-length violations, now fixed.

Verified end-to-end on simulator-prod@qudora.com (jobs 85974–85977): single circuit, batch, and cancel all complete. Bit ordering confirmed — x q[0] gives {"01": 199, "00": 1} and a batch of x q[0] / x q[1] gives [{"01": 100}, {"10": 99, "11": 1}], so qubit 0 is rightmost. The existing orientation test's assumption was correct; it just had no ground truth behind it.

  • tests/runtime/qudora: 64 passed (including 7 live-API tests)
  • tests/runtime: 916 passed, 74 skipped
  • tox -e format-check equivalent: pylint 10.00/10, isort/black/ruff/headers clean

🤖 Generated with Claude Code

Verified the provider against api.qudora.com with a real token. The mocked
fixtures were modeled on assumption rather than a captured response, and
diverged from what the API returns in ways that hid two bugs:

- `_build_profile` read `backend["id"]`, a key no backend record has (the
  numeric identifier is `user_id`). `qudora_backend_id` was therefore `None`
  for every device, so `QudoraDevice.status()` always took its "id
  unavailable" branch and reported ONLINE without calling the status
  endpoint, which was unreachable in practice. `/backends/status/108`
  returns "Idle"; the `username` is rejected with a 422.
- `QudoraJob.result()` read `device_id` from the job record's `target`, but
  QUDORA echoes the backend's display name there ("QVLS-Q1 Emulator"), not
  the id jobs are submitted against. Results came back with a `device_id`
  matching no device the provider can return; it now prefers the device's id.

The listing also publishes `simulator`, `basis_gates`, `status_name` and
`is_online`, so `simulator=True` is no longer hardcoded and `basis_gates`
is populated. Device ids are backend `username`s, which are email addresses.

Per CONTRIBUTING, required fields are now indexed directly rather than
fetched with a default: an unmapped `JobStatusName` raises instead of
degrading to `JobStatus.UNKNOWN`, which is not terminal and would have made
`result()` poll a finished job until `wait_for_final_state` timed out.
Annotations use PEP 604 syntax, and the mypy exclusion is dropped — the
remaining errors are the same structural ones rigetti, quantinuum and pasqal
already produce unexcluded.

Fixtures are rebuilt from captured payloads, and `test_qudora_remote.py`
adds credentialed tests behind the `remote` marker that assert the response
shape the unit tests hard-code, so upstream drift fails loudly. Adding
`tests/runtime/qudora/__init__.py` also puts the suite back under pylint,
which skipped the directory entirely for lack of a package marker.

Registers `qudora` in docs/api/qbraid.runtime.rst, the only provider missing.
@argus-eye

argus-eye Bot commented Jul 31, 2026

Copy link
Copy Markdown

Argus review

Auto-review is off for this repo. Tick the box below to run a review on this PR.

  • Trigger Argus review

Estimated cost

  • Files changed: 9
  • Diff lines (±): 495

Tip: you can also comment @argus-eye review at any time.

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: d98969d0-66de-4c65-8f4c-63b662d0f559

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

`_detect_language` scanned line-by-line for a prefix of "OPENQASM", which
reads a version named inside a block comment as the program's own:

    /*
    OPENQASM 2.0;
    */
    OPENQASM 3.0;
    ...

returned OpenQASM2, sending the wrong `language` to QUDORA. It also accepted
`OPENQASM 3` with no semicolon as a valid declaration.

Delegates to `qbraid.programs.typer.get_qasm_type_alias`, which routes through
pyqasm's `extract_qasm_version` (already a dependency here via `_validate_qasm`):
comments are stripped before the header is located, and the version must match
`OPENQASM <major>[.<minor>];`. The alias is mapped to the QUDORA language, so a
dialect QUDORA does not accept — `qasm2_kirin` — is now named in the error
rather than silently matching the "OPENQASM 2" prefix.

Also documents why `submit()` defaults `name` to "qbraid" rather than omitting
it. QUDORA's schema requires a string: both a null value and a missing key are
rejected with 422 `{"type": "string_type", "loc": ["body", "name"]}`, verified
against the live API and pinned by a remote test.
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