From cc15db44767efd06f6405d5d2409a136352a418c Mon Sep 17 00:00:00 2001 From: Fran Cabrera Date: Wed, 18 Dec 2024 14:43:04 +0000 Subject: [PATCH 1/3] Improved documentation for the private option (#2086) * Improved documentation for the private option * Updating docstring * updating incorrect wording --- qiskit_ibm_runtime/runtime_options.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/qiskit_ibm_runtime/runtime_options.py b/qiskit_ibm_runtime/runtime_options.py index 19c56013a..822fc0aa0 100644 --- a/qiskit_ibm_runtime/runtime_options.py +++ b/qiskit_ibm_runtime/runtime_options.py @@ -70,7 +70,12 @@ def __init__( this time limit, it is forcibly cancelled. Simulator jobs continue to use wall clock time. session_time: Length of session in seconds. - private: Boolean of whether or not the job is marked as private. + private: Boolean that indicates whether the job is marked as private. This is only + supported for ``ibm_quantum`` channel. When set to true, input parameters are not + returned, and the results can only be read once. After the results are read or after + a specified time after the job is completed, the results are deleted from the service. + When set to false, the input parameters and results follow the standard retention + behavior. """ self.backend = backend self.image = image From 3dd6fd0d7097d75b40c7284aca4284d0166a6bdf Mon Sep 17 00:00:00 2001 From: Fran Cabrera Date: Wed, 18 Dec 2024 15:31:04 +0000 Subject: [PATCH 2/3] Updating URL subdomain (#2081) Co-authored-by: Kevin Tian --- CONTRIBUTING.md | 2 +- README.md | 8 ++++---- qiskit_ibm_runtime/accounts/account.py | 2 +- qiskit_ibm_runtime/constants.py | 2 +- qiskit_ibm_runtime/qiskit_runtime_service.py | 4 ++-- test/decorators.py | 2 +- test/unit/test_account.py | 4 ++-- test/unit/test_client_parameters.py | 8 ++++---- 8 files changed, 16 insertions(+), 16 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 042fd1fee..108fd4270 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -229,7 +229,7 @@ Integration and E2E tests require an environment configuration and can be run ag Sample configuration for IBM Quantum ```bash QISKIT_IBM_TOKEN=... # IBM Quantum API token -QISKIT_IBM_URL=https://auth.quantum-computing.ibm.com/api # IBM Quantum API URL +QISKIT_IBM_URL=https://auth.quantum.ibm.com/api # IBM Quantum API URL QISKIT_IBM_INSTANCE=ibm-q/open/main # IBM Quantum provider to use (hub/group/project) QISKIT_IBM_QPU=... # IBM Quantum Processing Unit to use ``` diff --git a/README.md b/README.md index 523cd2575..a0d81b7b4 100644 --- a/README.md +++ b/README.md @@ -280,7 +280,7 @@ Access to IBM Quantum Platform channel is controlled by the instances (previousl > **_NOTE:_** IBM Cloud instances are different from IBM Quantum Platform instances. IBM Cloud does not use the hub/group/project structure for user management. To view and create IBM Cloud instances, visit the [IBM Cloud Quantum Instances page](https://cloud.ibm.com/quantum/instances). -To view a list of your instances, visit your [account settings page](https://www.quantum-computing.ibm.com/account) or use the `instances()` method. +To view a list of your instances, visit your [account settings page](https://www.quantum.ibm.com/account) or use the `instances()` method. You can specify an instance when initializing the service or provider, or when picking a backend: @@ -352,9 +352,9 @@ If you use Qiskit, please cite as per the included [BibTeX file](https://github. [Apache License 2.0]. -[IBM Quantum]: https://www.ibm.com/quantum-computing/ -[IBM Quantum login page]: https://quantum-computing.ibm.com/login -[IBM Quantum account page]: https://quantum-computing.ibm.com/account +[IBM Quantum]: https://www.ibm.com/quantum/ +[IBM Quantum login page]: https://quantum.ibm.com/login +[IBM Quantum account page]: https://quantum.ibm.com/account [contribution guidelines]: https://github.com/Qiskit/qiskit-ibm-runtime/blob/main/CONTRIBUTING.md [code of conduct]: https://github.com/Qiskit/qiskit-ibm-runtime/blob/main/CODE_OF_CONDUCT.md [GitHub issues]: https://github.com/Qiskit/qiskit-ibm-runtime/issues diff --git a/qiskit_ibm_runtime/accounts/account.py b/qiskit_ibm_runtime/accounts/account.py index f369f02ac..1a53155b5 100644 --- a/qiskit_ibm_runtime/accounts/account.py +++ b/qiskit_ibm_runtime/accounts/account.py @@ -28,7 +28,7 @@ AccountType = Optional[Literal["cloud", "legacy"]] ChannelType = Optional[Literal["ibm_cloud", "ibm_quantum", "local"]] -IBM_QUANTUM_API_URL = "https://auth.quantum-computing.ibm.com/api" +IBM_QUANTUM_API_URL = "https://auth.quantum.ibm.com/api" IBM_CLOUD_API_URL = "https://cloud.ibm.com" logger = logging.getLogger(__name__) diff --git a/qiskit_ibm_runtime/constants.py b/qiskit_ibm_runtime/constants.py index ca7f93e1d..2a23bbabe 100644 --- a/qiskit_ibm_runtime/constants.py +++ b/qiskit_ibm_runtime/constants.py @@ -21,7 +21,7 @@ from .utils.runner_result import RunnerResult -QISKIT_IBM_RUNTIME_API_URL = "https://auth.quantum-computing.ibm.com/api" +QISKIT_IBM_RUNTIME_API_URL = "https://auth.quantum.ibm.com/api" API_TO_JOB_STATUS = { "QUEUED": JobStatus.QUEUED, diff --git a/qiskit_ibm_runtime/qiskit_runtime_service.py b/qiskit_ibm_runtime/qiskit_runtime_service.py index 5b4a29e81..b56b622c8 100644 --- a/qiskit_ibm_runtime/qiskit_runtime_service.py +++ b/qiskit_ibm_runtime/qiskit_runtime_service.py @@ -99,7 +99,7 @@ def __init__( token: IBM Cloud API key or IBM Quantum API token. url: The API URL. Defaults to https://cloud.ibm.com (ibm_cloud) or - https://auth.quantum-computing.ibm.com/api (ibm_quantum). + https://auth.quantum.ibm.com/api (ibm_quantum). filename: Full path of the file where the account is created. Default: _DEFAULT_ACCOUNT_CONFIG_JSON_FILE name: Name of the account to load. @@ -664,7 +664,7 @@ def save_account( token: IBM Cloud API key or IBM Quantum API token. url: The API URL. Defaults to https://cloud.ibm.com (ibm_cloud) or - https://auth.quantum-computing.ibm.com/api (ibm_quantum). + https://auth.quantum.ibm.com/api (ibm_quantum). instance: The CRN (ibm_cloud) or hub/group/project (ibm_quantum). channel: Channel type. `ibm_cloud` or `ibm_quantum`. filename: Full path of the file where the account is saved. diff --git a/test/decorators.py b/test/decorators.py index 0ed3d3184..5a4f22f9e 100644 --- a/test/decorators.py +++ b/test/decorators.py @@ -75,7 +75,7 @@ def _get_integration_test_config(): os.getenv("QISKIT_IBM_INSTANCE"), os.getenv("QISKIT_IBM_QPU"), ) - channel: Any = "ibm_quantum" if url.find("quantum-computing.ibm.com") >= 0 else "ibm_cloud" + channel: Any = "ibm_quantum" if url.find("quantum.ibm.com") >= 0 else "ibm_cloud" return channel, token, url, instance, qpu diff --git a/test/unit/test_account.py b/test/unit/test_account.py index 02ff03b4d..6e6bf54d0 100644 --- a/test/unit/test_account.py +++ b/test/unit/test_account.py @@ -44,7 +44,7 @@ _TEST_IBM_QUANTUM_ACCOUNT = Account.create_account( channel="ibm_quantum", token="token-x", - url="https://auth.quantum-computing.ibm.com/api", + url="https://auth.quantum.ibm.com/api", instance="ibm-q/open/main", ) @@ -66,7 +66,7 @@ class TestAccount(IBMTestCase): dummy_token = "123" dummy_ibm_cloud_url = "https://us-east.quantum-computing.cloud.ibm.com" - dummy_ibm_quantum_url = "https://auth.quantum-computing.ibm.com/api" + dummy_ibm_quantum_url = "https://auth.quantum.ibm.com/api" def test_skip_crn_resolution_for_crn(self): """Test that CRN resolution is skipped if the instance value is already a CRN.""" diff --git a/test/unit/test_client_parameters.py b/test/unit/test_client_parameters.py index 98cc036c2..927d84b9b 100644 --- a/test/unit/test_client_parameters.py +++ b/test/unit/test_client_parameters.py @@ -77,9 +77,9 @@ def test_get_runtime_api_base_url(self) -> None: ( "ibm_quantum", "h/g/p", - "https://auth.quantum-computing.ibm.com/api", + "https://auth.quantum.ibm.com/api", None, - "https://auth.quantum-computing.ibm.com/api", + "https://auth.quantum.ibm.com/api", ), ( "ibm_cloud", @@ -92,9 +92,9 @@ def test_get_runtime_api_base_url(self) -> None: ( "ibm_quantum", "h/g/p", - "https://auth.quantum-computing.ibm.com/api", + "https://auth.quantum.ibm.com/api", lambda a, b, c: f"{a}:{b}:{c}", - "https://auth.quantum-computing.ibm.com/api:h/g/p:False", + "https://auth.quantum.ibm.com/api:h/g/p:False", ), ] for spec in test_specs: From 0f345d847c5db22d7e3ffc1836e25a1bdc098f27 Mon Sep 17 00:00:00 2001 From: Kevin Tian Date: Thu, 2 Jan 2025 17:00:39 -0500 Subject: [PATCH 3/3] Add content/accept headers to appropriate http requests (#2088) * Add content-header to http requests * Add accept header * Refactor and add api version * Add simple test --- qiskit_ibm_runtime/api/rest/base.py | 5 +++++ qiskit_ibm_runtime/api/rest/cloud_backend.py | 8 ++++---- qiskit_ibm_runtime/api/rest/program_job.py | 11 ++++++++--- qiskit_ibm_runtime/api/rest/root.py | 12 ++++++++---- qiskit_ibm_runtime/api/rest/runtime.py | 12 ++++++++---- qiskit_ibm_runtime/api/rest/runtime_session.py | 7 ++++--- test/unit/test_runtime_client.py | 5 +++++ 7 files changed, 42 insertions(+), 18 deletions(-) diff --git a/qiskit_ibm_runtime/api/rest/base.py b/qiskit_ibm_runtime/api/rest/base.py index 809dc606f..2e8ea5e16 100644 --- a/qiskit_ibm_runtime/api/rest/base.py +++ b/qiskit_ibm_runtime/api/rest/base.py @@ -23,6 +23,10 @@ class RestAdapterBase: _HEADER_JSON_CONTENT = {"Content-Type": "application/json"} + _HEADER_JSON_ACCEPT = {"Accept": "application/json"} + + _HEADER_API_VERSION = {"IBM-API-Version": "2024-01-01"} + def __init__(self, session: RetrySession, prefix_url: str = "") -> None: """RestAdapterBase constructor. @@ -31,6 +35,7 @@ def __init__(self, session: RetrySession, prefix_url: str = "") -> None: prefix_url: String to be prepend to all URLs. """ self.session = session + self.session.headers = self._HEADER_API_VERSION self.prefix_url = prefix_url def get_url(self, identifier: str) -> str: diff --git a/qiskit_ibm_runtime/api/rest/cloud_backend.py b/qiskit_ibm_runtime/api/rest/cloud_backend.py index 7964e5d58..b9d38d950 100644 --- a/qiskit_ibm_runtime/api/rest/cloud_backend.py +++ b/qiskit_ibm_runtime/api/rest/cloud_backend.py @@ -47,7 +47,7 @@ def configuration(self) -> Dict[str, Any]: JSON response of backend configuration. """ url = self.get_url("configuration") - return self.session.get(url).json() + return self.session.get(url, headers=self._HEADER_JSON_ACCEPT).json() def properties(self, datetime: Optional[python_datetime] = None) -> Dict[str, Any]: """Return backend properties. @@ -61,7 +61,7 @@ def properties(self, datetime: Optional[python_datetime] = None) -> Dict[str, An if datetime: params["updated_before"] = datetime.isoformat() - response = self.session.get(url, params=params).json() + response = self.session.get(url, params=params, headers=self._HEADER_JSON_ACCEPT).json() # Adjust name of the backend. if response: response["backend_name"] = self.backend_name @@ -74,7 +74,7 @@ def pulse_defaults(self) -> Dict[str, Any]: JSON response of pulse defaults. """ url = self.get_url("pulse_defaults") - return self.session.get(url).json() + return self.session.get(url, headers=self._HEADER_JSON_ACCEPT).json() def status(self) -> Dict[str, Any]: """Return backend status. @@ -83,7 +83,7 @@ def status(self) -> Dict[str, Any]: JSON response of backend status. """ url = self.get_url("status") - response = self.session.get(url).json() + response = self.session.get(url, headers=self._HEADER_JSON_ACCEPT).json() # Adjust fields according to the specs (BackendStatus). ret = { "backend_name": self.backend_name, diff --git a/qiskit_ibm_runtime/api/rest/program_job.py b/qiskit_ibm_runtime/api/rest/program_job.py index e50d10af7..18bb64d58 100644 --- a/qiskit_ibm_runtime/api/rest/program_job.py +++ b/qiskit_ibm_runtime/api/rest/program_job.py @@ -56,7 +56,10 @@ def get(self, exclude_params: bool = None) -> Dict: payload = {} if exclude_params: payload["exclude_params"] = "true" - return self.session.get(self.get_url("self"), params=payload).json(cls=RuntimeDecoder) + + return self.session.get( + self.get_url("self"), params=payload, headers=self._HEADER_JSON_ACCEPT + ).json(cls=RuntimeDecoder) def delete(self) -> None: """Delete program job.""" @@ -98,7 +101,7 @@ def metadata(self) -> Dict: Returns: Job Metadata. """ - return self.session.get(self.get_url("metrics")).json() + return self.session.get(self.get_url("metrics"), headers=self._HEADER_JSON_ACCEPT).json() def update_tags(self, tags: list) -> Response: """Update job tags. @@ -106,4 +109,6 @@ def update_tags(self, tags: list) -> Response: Returns: API Response. """ - return self.session.put(self.get_url("tags"), data=json.dumps({"tags": tags})) + return self.session.put( + self.get_url("tags"), data=json.dumps({"tags": tags}), headers=self._HEADER_JSON_CONTENT + ) diff --git a/qiskit_ibm_runtime/api/rest/root.py b/qiskit_ibm_runtime/api/rest/root.py index f5f47af70..7cd868248 100644 --- a/qiskit_ibm_runtime/api/rest/root.py +++ b/qiskit_ibm_runtime/api/rest/root.py @@ -53,7 +53,8 @@ def hubs(self) -> List[Dict[str, Any]]: JSON response. """ url = self.get_url("hubs") - return self.session.get(url).json() + + return self.session.get(url, headers=self._HEADER_JSON_ACCEPT).json() def version(self) -> Dict[str, Union[str, bool]]: """Return the version information. @@ -69,7 +70,7 @@ def version(self) -> Dict[str, Union[str, bool]]: * ``api-*`` (str): The versions of each individual API component """ url = self.get_url("version") - response = self.session.get(url) + response = self.session.get(url, headers=self._HEADER_JSON_ACCEPT) try: version_info = response.json() @@ -89,7 +90,9 @@ def login(self, api_token: str) -> Dict[str, Any]: JSON response. """ url = self.get_url("login") - return self.session.post(url, json={"apiToken": api_token}).json() + return self.session.post( + url, json={"apiToken": api_token}, headers=self._HEADER_JSON_CONTENT + ).json() def user_info(self) -> Dict[str, Any]: """Return user information. @@ -98,6 +101,7 @@ def user_info(self) -> Dict[str, Any]: JSON response of user information. """ url = self.get_url("user_info") - response = self.session.get(url).json() + + response = self.session.get(url, headers=self._HEADER_JSON_ACCEPT).json() return response diff --git a/qiskit_ibm_runtime/api/rest/runtime.py b/qiskit_ibm_runtime/api/rest/runtime.py index 45d39e77b..829f73fcb 100644 --- a/qiskit_ibm_runtime/api/rest/runtime.py +++ b/qiskit_ibm_runtime/api/rest/runtime.py @@ -126,7 +126,9 @@ def program_run( if private: payload["private"] = True data = json.dumps(payload, cls=RuntimeEncoder) - return self.session.post(url, data=data, timeout=900).json() + return self.session.post( + url, data=data, timeout=900, headers=self._HEADER_JSON_CONTENT + ).json() def jobs_get( self, @@ -195,7 +197,7 @@ def jobs_get( payload["sort"] = "ASC" if all([hub, group, project]): payload["provider"] = f"{hub}/{group}/{project}" - return self.session.get(url, params=payload).json() + return self.session.get(url, params=payload, headers=self._HEADER_JSON_ACCEPT).json() def backend(self, backend_name: str) -> CloudBackend: """Return an adapter for the IBM backend. @@ -226,7 +228,9 @@ def backends( params = {} if hgp: params["provider"] = hgp - return self.session.get(url, params=params, timeout=timeout).json() + return self.session.get( + url, params=params, timeout=timeout, headers=self._HEADER_JSON_ACCEPT + ).json() def usage(self) -> Dict[str, Any]: """Return monthly open plan usage information. @@ -235,4 +239,4 @@ def usage(self) -> Dict[str, Any]: JSON response. """ url = self.get_url("usage") - return self.session.get(url).json() + return self.session.get(url, headers=self._HEADER_JSON_ACCEPT).json() diff --git a/qiskit_ibm_runtime/api/rest/runtime_session.py b/qiskit_ibm_runtime/api/rest/runtime_session.py index 7a585b54e..74fca7ad8 100644 --- a/qiskit_ibm_runtime/api/rest/runtime_session.py +++ b/qiskit_ibm_runtime/api/rest/runtime_session.py @@ -62,7 +62,7 @@ def create( payload["max_session_ttl"] = max_time # type: ignore[assignment] else: payload["max_ttl"] = max_time # type: ignore[assignment] - return self.session.post(url, json=payload).json() + return self.session.post(url, json=payload, headers=self._HEADER_JSON_CONTENT).json() def cancel(self) -> None: """Cancel all jobs in the session.""" @@ -74,7 +74,7 @@ def close(self) -> None: payload = {"accepting_jobs": False} url = self.get_url("self") try: - self.session.patch(url, json=payload) + self.session.patch(url, json=payload, headers=self._HEADER_JSON_CONTENT) except RequestsApiError as ex: if ex.status_code == 404: pass @@ -83,4 +83,5 @@ def close(self) -> None: def details(self) -> Dict[str, Any]: """Return the details of this session.""" - return self.session.get(self.get_url("self")).json() + + return self.session.get(self.get_url("self"), headers=self._HEADER_JSON_ACCEPT).json() diff --git a/test/unit/test_runtime_client.py b/test/unit/test_runtime_client.py index 241684de0..c8328ce86 100644 --- a/test/unit/test_runtime_client.py +++ b/test/unit/test_runtime_client.py @@ -85,3 +85,8 @@ def test_custom_client_app_header(self): client._session.custom_header = None client._session._set_custom_header() self.assertNotIn(custom_header, client._session.headers["X-Qx-Client-Application"]) + + def test_header_api_version(self): + """Test IBM-API-Version is in header.""" + client = self._get_client() + self.assertIn("IBM-API-Version", client._session.headers)