Repository: rigetti/qcs-sdk-rust (language: python)

Issue:
feat(python): export QcsClient URL values via getters
As part of https://github.com/rigetti/pyquil/issues/1494 the pure-python `QCSClientConfiguration` needs to be replaced with the pyo3-python `QcsClient` from this repo.

Parity support requires making the `quilc_url` and `qvm_url` available to pyquil, which still interact directly with these services, even though issues like https://github.com/rigetti/qcs-sdk-rust/issues/207 plan to change that in the future.


Discussion / hints:

Fix patch:
diff --git a/crates/lib/src/qpu/client.rs b/crates/lib/src/qpu/client.rs
index 0b00f93bc..a881253dd 100644
--- a/crates/lib/src/qpu/client.rs
+++ b/crates/lib/src/qpu/client.rs
@@ -57,7 +57,9 @@ impl Qcs {
         self
     }
 
-    pub(crate) fn get_config(&self) -> ClientConfiguration {
+    /// Return a copy of all settings parsed and resolved from configuration sources.
+    #[must_use]
+    pub fn get_config(&self) -> ClientConfiguration {
         self.config.clone()
     }
 
diff --git a/crates/python/qcs_sdk/qpu/client.pyi b/crates/python/qcs_sdk/qpu/client.pyi
index c59311ed5..a2966cded 100644
--- a/crates/python/qcs_sdk/qpu/client.pyi
+++ b/crates/python/qcs_sdk/qpu/client.pyi
@@ -23,7 +23,7 @@ class QcsClient:
         ...
     
     @staticmethod
-    def load(
+    async def load(
         profile_name: Optional[str] = None,
         use_gateway: Optional[bool] = None,
     ) -> "QcsClient":
@@ -34,6 +34,26 @@ class QcsClient:
         """
         ...
 
+    @property
+    def api_url(self) -> str:
+        """URL to access the QCS API."""
+        ...
+
+    @property
+    def grpc_api_url(self) -> str:
+        """URL to access the gRPC API."""
+        ...
+
+    @property
+    def quilc_url(self) -> str:
+        """URL to access the `quilc` compiler."""
+        ...
+
+    @property
+    def qvm_url(self) -> str:
+        """URL to access the QVM."""
+        ...
+
 
 class QcsClientAuthServer:
     """Authentication server configuration for the QCS API."""
diff --git a/crates/python/src/qpu/client.rs b/crates/python/src/qpu/client.rs
index 87b3a2f42..fa35acd9f 100644
--- a/crates/python/src/qpu/client.rs
+++ b/crates/python/src/qpu/client.rs
@@ -210,6 +210,26 @@ impl PyQcsClient {
         })
     }
 
+    #[getter]
+    pub fn api_url(&self) -> String {
+        self.as_ref().get_config().api_url().to_string()
+    }
+
+    #[getter]
+    pub fn grpc_api_url(&self) -> String {
+        self.as_ref().get_config().grpc_api_url().to_string()
+    }
+
+    #[getter]
+    pub fn quilc_url(&self) -> String {
+        self.as_ref().get_config().quilc_url().to_string()
+    }
+
+    #[getter]
+    pub fn qvm_url(&self) -> String {
+        self.as_ref().get_config().qvm_url().to_string()
+    }
+
     fn __richcmp__(&self, other: &Self, op: CompareOp, py: Python<'_>) -> PyObject {
         match op {
             CompareOp::Eq => (self == other).into_py(py),


Test patch:
diff --git a/crates/python/tests/test_client.py b/crates/python/tests/test_client.py
index 29552b395..b9ac4fea2 100644
--- a/crates/python/tests/test_client.py
+++ b/crates/python/tests/test_client.py
@@ -1,4 +1,5 @@
 import pytest
+from urllib.parse import urlparse
 
 from qcs_sdk import QcsClient
 from qcs_sdk.qpu.client import QcsLoadError, QcsClientAuthServer, QcsClientTokens
@@ -7,6 +8,14 @@
 def default_client():
     return QcsClient()
 
+@pytest.mark.asyncio
+async def test_client_has_url_from_env(default_client: QcsClient):
+    """The default client is configured with valid urls."""
+    assert urlparse(default_client.api_url).geturl() != ""
+    assert urlparse(default_client.grpc_api_url).geturl() != ""
+    assert urlparse(default_client.quilc_url).geturl() != ""
+    assert urlparse(default_client.qvm_url).geturl() != ""
+
 
 @pytest.mark.asyncio
 async def test_client_empty_profile_is_default(default_client: QcsClient):