From 075490527073576dcb5abe7d16cc866ea728705f Mon Sep 17 00:00:00 2001 From: Cameron Kurotori Date: Tue, 10 Sep 2024 07:55:01 -0700 Subject: [PATCH] add custom url resolver to enable using other endpoints (#1914) * custom url resolver * deprecate get_runtime_api_base_url --- qiskit_ibm_runtime/api/client_parameters.py | 11 +++++--- qiskit_ibm_runtime/qiskit_runtime_service.py | 5 ++++ qiskit_ibm_runtime/utils/__init__.py | 1 + qiskit_ibm_runtime/utils/utils.py | 16 ++++++++++- release-notes/unreleased/1914.deprecation.rst | 1 + release-notes/unreleased/1914.feat.rst | 14 ++++++++++ test/unit/test_client_parameters.py | 27 +++++++++++++++++-- 7 files changed, 69 insertions(+), 6 deletions(-) create mode 100644 release-notes/unreleased/1914.deprecation.rst create mode 100644 release-notes/unreleased/1914.feat.rst diff --git a/qiskit_ibm_runtime/api/client_parameters.py b/qiskit_ibm_runtime/api/client_parameters.py index 375afd3fd..b6d4a94b5 100644 --- a/qiskit_ibm_runtime/api/client_parameters.py +++ b/qiskit_ibm_runtime/api/client_parameters.py @@ -12,10 +12,10 @@ """Represent IBM Quantum account client parameters.""" -from typing import Dict, Optional, Any, Union +from typing import Dict, Optional, Any, Union, Callable from ..proxies import ProxyConfiguration -from ..utils import get_runtime_api_base_url +from ..utils import default_runtime_url_resolver from ..api.auth import QuantumAuth, CloudAuth TEMPLATE_IBM_HUBS = "{prefix}/Network/{hub}/Groups/{group}/Projects/{project}" @@ -34,6 +34,7 @@ def __init__( proxies: Optional[ProxyConfiguration] = None, verify: bool = True, private_endpoint: Optional[bool] = False, + url_resolver: Optional[Callable[[str, str, Optional[bool]], str]] = None, ) -> None: """ClientParameters constructor. @@ -45,6 +46,7 @@ def __init__( proxies: Proxy configuration. verify: If ``False``, ignores SSL certificates errors. private_endpoint: Connect to private API URL. + url_resolver: Function used to resolve the runtime url. """ self.token = token self.instance = instance @@ -53,6 +55,9 @@ def __init__( self.proxies = proxies self.verify = verify self.private_endpoint = private_endpoint + if not url_resolver: + url_resolver = default_runtime_url_resolver + self.url_resolver = url_resolver def get_auth_handler(self) -> Union[CloudAuth, QuantumAuth]: """Returns the respective authentication handler.""" @@ -63,7 +68,7 @@ def get_auth_handler(self) -> Union[CloudAuth, QuantumAuth]: def get_runtime_api_base_url(self) -> str: """Returns the Runtime API base url.""" - return get_runtime_api_base_url(self.url, self.instance, self.private_endpoint) + return self.url_resolver(self.url, self.instance, self.private_endpoint) def connection_parameters(self) -> Dict[str, Any]: """Construct connection related parameters. diff --git a/qiskit_ibm_runtime/qiskit_runtime_service.py b/qiskit_ibm_runtime/qiskit_runtime_service.py index 8907f70c7..ffe37ec16 100644 --- a/qiskit_ibm_runtime/qiskit_runtime_service.py +++ b/qiskit_ibm_runtime/qiskit_runtime_service.py @@ -78,6 +78,7 @@ def __init__( verify: Optional[bool] = None, channel_strategy: Optional[str] = None, private_endpoint: Optional[bool] = None, + url_resolver: Optional[Callable[[str, str, Optional[bool]], str]] = None, ) -> None: """QiskitRuntimeService constructor @@ -117,6 +118,7 @@ def __init__( verify: Whether to verify the server's TLS certificate. channel_strategy: Error mitigation strategy. private_endpoint: Connect to private API URL. + url_resolver: Function used to resolve the runtime url. Returns: An instance of QiskitRuntimeService or QiskitRuntimeLocalService for local channel. @@ -149,11 +151,13 @@ def __init__( proxies=self._account.proxies, verify=self._account.verify, private_endpoint=self._account.private_endpoint, + url_resolver=url_resolver, ) self._channel_strategy = channel_strategy or self._account.channel_strategy self._channel = self._account.channel self._backend_allowed_list: List[str] = [] + self._url_resolver = url_resolver if self._channel == "ibm_cloud": self._api_client = RuntimeClient(self._client_params) @@ -359,6 +363,7 @@ def _initialize_hgps( ), proxies=self._account.proxies, verify=self._account.verify, + url_resolver=self._url_resolver, ) # Build the hgp. diff --git a/qiskit_ibm_runtime/utils/__init__.py b/qiskit_ibm_runtime/utils/__init__.py index 2b2eb4b30..a2971dbef 100644 --- a/qiskit_ibm_runtime/utils/__init__.py +++ b/qiskit_ibm_runtime/utils/__init__.py @@ -22,6 +22,7 @@ to_python_identifier, is_crn, get_runtime_api_base_url, + default_runtime_url_resolver, resolve_crn, are_circuits_dynamic, ) diff --git a/qiskit_ibm_runtime/utils/utils.py b/qiskit_ibm_runtime/utils/utils.py index 4fb688b62..74b17db24 100644 --- a/qiskit_ibm_runtime/utils/utils.py +++ b/qiskit_ibm_runtime/utils/utils.py @@ -31,6 +31,7 @@ from qiskit.circuit import QuantumCircuit, ControlFlowOp from qiskit.transpiler import Target from qiskit.providers.backend import BackendV1, BackendV2 +from .deprecation import deprecate_function def is_simulator(backend: BackendV1 | BackendV2) -> bool: @@ -157,7 +158,20 @@ def is_crn(locator: str) -> bool: return isinstance(locator, str) and locator.startswith("crn:") -def get_runtime_api_base_url(url: str, instance: str, private_endpoint: bool = False) -> str: +@deprecate_function( + "get_runtime_api_base_url()", + "0.30.0", + "Please use default_runtime_url_resolver() instead.", + stacklevel=1, +) +def get_runtime_api_base_url( + url: str, instance: str, private_endpoint: Optional[bool] = False +) -> str: + """Computes the Runtime API base URL based on the provided input parameters.""" + return default_runtime_url_resolver(url, instance, private_endpoint=private_endpoint) + + +def default_runtime_url_resolver(url: str, instance: str, private_endpoint: bool = False) -> str: """Computes the Runtime API base URL based on the provided input parameters. Args: diff --git a/release-notes/unreleased/1914.deprecation.rst b/release-notes/unreleased/1914.deprecation.rst new file mode 100644 index 000000000..fee4d04d8 --- /dev/null +++ b/release-notes/unreleased/1914.deprecation.rst @@ -0,0 +1 @@ +Deprecate util function `get_runtime_api_base_url`: use `default_runtime_url_resolver` instead. \ No newline at end of file diff --git a/release-notes/unreleased/1914.feat.rst b/release-notes/unreleased/1914.feat.rst new file mode 100644 index 000000000..273ff7c11 --- /dev/null +++ b/release-notes/unreleased/1914.feat.rst @@ -0,0 +1,14 @@ +Add `url_resolver` optional input to :class:`.QiskitRuntimeService` +constructor to enable custom generation of the Qiskit Runtime API URL +based on the provided `url`, `instance` and `private_endpoint`. If +not specified, the default resolver will be used. + +.. code:: python + # Define a custom resolver. In this case returns the concatenation of the provided `url` and the `instance` + def custom_url_resolver(url, instance, *args, **kwargs): + return f"{url}/{instance}" + + service = QiskitRuntimeService(channel="ibm_quantum", instance="ibm-q/open/main", url="https://baseurl.org" url_resolver=custom_url_resolver) + # resulting resolved url will be: `https://baseurl.org/ibm-q/open/main` + +Add util function `default_runtime_url_resolver`. \ No newline at end of file diff --git a/test/unit/test_client_parameters.py b/test/unit/test_client_parameters.py index fe5019f9c..98cc036c2 100644 --- a/test/unit/test_client_parameters.py +++ b/test/unit/test_client_parameters.py @@ -57,31 +57,52 @@ def test_get_runtime_api_base_url(self) -> None: "ibm_cloud", "crn:v1:bluemix:public:quantum-computing:us-east:a/...:...::", "https://cloud.ibm.com", + None, "https://us-east.quantum-computing.cloud.ibm.com", ), ( "ibm_cloud", "crn:v1:bluemix:public:quantum-computing:my-region:a/...:...::", "https://cloud.ibm.com", + None, "https://my-region.quantum-computing.cloud.ibm.com", ), ( "ibm_cloud", "crn:v1:bluemix:public:quantum-computing:my-region:a/...:...::", "https://api-ntc-name.experimental-us-someid.us-east.containers.appdomain.cloud", + None, "https://api-ntc-name.experimental-us-someid.us-east.containers.appdomain.cloud", ), ( "ibm_quantum", "h/g/p", "https://auth.quantum-computing.ibm.com/api", + None, "https://auth.quantum-computing.ibm.com/api", ), + ( + "ibm_cloud", + "crn:v1:bluemix:public:quantum-computing:my-region:a/...:...::", + "https://api-ntc-name.experimental-us-someid.us-east.containers.appdomain.cloud", + lambda a, b, c: f"{a}:{b}:{c}", + "https://api-ntc-name.experimental-us-someid.us-east.containers.appdomain.cloud:" + + "crn:v1:bluemix:public:quantum-computing:my-region:a/...:...:::False", + ), + ( + "ibm_quantum", + "h/g/p", + "https://auth.quantum-computing.ibm.com/api", + lambda a, b, c: f"{a}:{b}:{c}", + "https://auth.quantum-computing.ibm.com/api:h/g/p:False", + ), ] for spec in test_specs: - channel, instance, url, expected = spec + channel, instance, url, url_resolver, expected = spec with self.subTest(instance=instance, url=url): - params = self._get_client_params(channel=channel, instance=instance, url=url) + params = self._get_client_params( + channel=channel, instance=instance, url=url, url_resolver=url_resolver + ) self.assertEqual(params.get_runtime_api_base_url(), expected) def test_proxies_param_with_ntlm(self) -> None: @@ -153,6 +174,7 @@ def _get_client_params( instance=None, proxies=None, verify=None, + url_resolver=None, ): """Return a custom ClientParameters.""" if verify is None: @@ -164,4 +186,5 @@ def _get_client_params( instance=instance, proxies=proxies, verify=verify, + url_resolver=url_resolver, )