Skip to content

feat(runtime): add QUDORA provider - #1292

Merged
ryanhill1 merged 2 commits into
mainfrom
feature/qudora-integration
Aug 18, 2026
Merged

feat(runtime): add QUDORA provider#1292
ryanhill1 merged 2 commits into
mainfrom
feature/qudora-integration

Conversation

@TheGupta2012

Copy link
Copy Markdown
Member

Summary of changes

Adds a direct-REST QUDORA runtime provider to the qBraid SDK. It talks to https://api.qudora.com over HTTPS with Bearer auth (no vendor SDK) and reuses qBraid's existing qasm2/qasm3 program specs, so there is no new dependency and no converter.

  • New qbraid/runtime/qudora/ package: QudoraSession (qbraid_core Session subclass with bearer auth and retries), QudoraProvider (get_devices/get_device, _build_profile; device_id is the backend username), QudoraDevice (status, submit, available_settings, OpenQASM language auto-detection with a pyqasm validate hook), and QudoraJob (status mapping, result parsing).
  • Entry-points and registration: qudora qbraid.providers/qbraid.jobs entry-points and mypy exclude in pyproject.toml, plus lazy-load registration in qbraid/runtime/__init__.py.
  • Tests: fully-mocked tests/runtime/qudora/ suite and a tests/runtime/test_loader.py update.

Related

  • qBraid/qbraid-account#579
  • qBraid/qbraid-infrastructure#619
  • qBraid/docs#285
  • Companion runtime-api change: qBraid/qbraid-runtime-api branch feature/qudora-integration.

@TheGupta2012
TheGupta2012 requested a review from ryanhill1 as a code owner July 24, 2026 13:11
@coderabbitai

coderabbitai Bot commented Jul 24, 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: 57a1b055-649f-4b6c-8e04-676a87bbbca2

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
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/qudora-integration

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.

@TheGupta2012
TheGupta2012 force-pushed the feature/qudora-integration branch from 0092483 to abdffee Compare July 24, 2026 13:12
@codecov

codecov Bot commented Jul 24, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 99.40120% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
qbraid/runtime/qudora/device.py 97.91% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@argus-eye

argus-eye Bot commented Aug 7, 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 (±): 943
  • Historical avg: ~228.7k tokens · ~$1.53 · across last 2 review(s)

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

@TheGupta2012

TheGupta2012 commented Aug 7, 2026

Copy link
Copy Markdown
Member Author

@ryanhill1 I have added some changes to #1306 and tested against staging. Can you please approve the current PR so that we can merge the stack ?

Add a direct-REST QUDORA runtime integration under qbraid/runtime/qudora
(provider, device, job, and session classes), register lazy-load and
packaging entry-points, and add fully-mocked tests. Reuses the existing
qasm2/qasm3 program specs, so no vendor dependency or converter is needed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@TheGupta2012
TheGupta2012 force-pushed the feature/qudora-integration branch from a13fcd5 to 0dd4f9e Compare August 11, 2026 06:02
ryanhill1
ryanhill1 previously approved these changes Aug 17, 2026
* fix(qudora): correct backend field mapping against the live API

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.

* fix(qudora): detect QASM version via shared typing, not a header scan

`_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.

* fix(qudora): raise typed errors and resolve device_id without a device

Unmapped vendor status values raised a bare KeyError out of QudoraDevice.status()
and QudoraJob._map_status(). Raising is right -- defaulting a device to ONLINE or a
job to UNKNOWN would report a state QUDORA never sent, and UNKNOWN is not terminal,
so result() would poll a finished job until wait_for_final_state timed out. Only the
type was wrong: raise QudoraDeviceError / QudoraJobError, matching the AQT provider,
and name the known values in the message.

Result.device_id fell back to the job record's `target` when the job carried no
device, and `target` is the backend's display name ("QVLS-Q1 Emulator") rather than
the username jobs are submitted against. Resolve it through the backend listing so a
job loaded without a device still yields an id get_device() accepts, falling back to
the display name only for a backend that is no longer published.

available_settings() indexed `user_settings_schema` strictly but then tolerated a
None value. Every backend publishes the schema (asserted live), so drop the `or {}`
and cover the one legitimate empty case: a schema declaring no properties.

Type-check clean now that runtime/qudora/ is no longer in the mypy exclude list:
TargetProfile allows extra keys but the __init__ mypy synthesizes only names the
declared fields, so the vendor-specific ones are passed as a mapping, which keeps
the declared arguments checked. The two deliberate signature narrowings are ignored
explicitly rather than widened.

Also assert the job-record field contract on a cancelled job: result() reads
`user_error` by strict index only on the non-completed path, which the existing
completed-job contract test cannot reach.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: TheGupta2012 <harshit.11235@gmail.com>
@ryanhill1
ryanhill1 merged commit b0b12c8 into main Aug 18, 2026
13 checks passed
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.

2 participants