Repository: rigetti/pyquil (python)

Issue:
Get version info requests to quilc should go through the QCS SDK
Currently, the qcs-sdk handles all external requests to `quilc` _except_ for getting version info. We need add a method for getting that data to QCS SDK Rust (see [this issue](https://github.com/rigetti/qcs-sdk-rust/issues/205)), then follow-up and use it here. 

This supports #1485


Discussion:
Good catch!

Patch:
diff --git a/pyquil/api/_abstract_compiler.py b/pyquil/api/_abstract_compiler.py
--- a/pyquil/api/_abstract_compiler.py
+++ b/pyquil/api/_abstract_compiler.py
@@ -102,12 +102,15 @@ def __init__(
         self._timeout = timeout
 
         self._client_configuration = client_configuration or QCSClientConfiguration.load()
-        self._compiler_client = CompilerClient(client_configuration=self._client_configuration, request_timeout=timeout)
 
         if event_loop is None:
             event_loop = asyncio.get_event_loop()
         self._event_loop = event_loop
 
+        self._compiler_client = CompilerClient(
+            client_configuration=self._client_configuration, request_timeout=timeout, event_loop=self._event_loop
+        )
+
         self._connect()
 
     def get_version_info(self) -> Dict[str, Any]:
diff --git a/pyquil/api/_compiler_client.py b/pyquil/api/_compiler_client.py
--- a/pyquil/api/_compiler_client.py
+++ b/pyquil/api/_compiler_client.py
@@ -13,10 +13,12 @@
 #    See the License for the specific language governing permissions and
 #    limitations under the License.
 ##############################################################################
+import asyncio
 from contextlib import contextmanager
 from dataclasses import dataclass
-from typing import Iterator, Optional, List
+from typing import Iterator, List, Optional
 
+import qcs_sdk
 import rpcq
 from qcs_api_client.client import QCSClientConfiguration
 from rpcq.messages import TargetDevice as TargetQuantumProcessor
@@ -151,7 +153,13 @@ class CompilerClient:
     Client for making requests to a Quil compiler.
     """
 
-    def __init__(self, *, client_configuration: QCSClientConfiguration, request_timeout: float = 10.0) -> None:
+    def __init__(
+        self,
+        *,
+        client_configuration: QCSClientConfiguration,
+        request_timeout: float = 10.0,
+        event_loop: Optional[asyncio.AbstractEventLoop] = None,
+    ) -> None:
         """
         Instantiate a new compiler client.
 
@@ -164,17 +172,19 @@ def __init__(self, *, client_configuration: QCSClientConfiguration, request_time
 
         self.base_url = base_url
         self.timeout = request_timeout
+        if event_loop is None:
+            event_loop = asyncio.get_event_loop()
+        self._event_loop = event_loop
 
     def get_version(self) -> str:
         """
         Get version info for compiler server.
         """
-        with self._rpcq_client() as rpcq_client:  # type: rpcq.Client
-            version: Optional[str] = rpcq_client.call("get_version_info").get("quilc")
-            if version is None:
-                raise ValueError("Expected compiler version info to contain a 'quilc' field.")
 
-            return version
+        async def _get_quilc_version() -> str:
+            return await qcs_sdk.get_quilc_version()
+
+        return self._event_loop.run_until_complete(_get_quilc_version())
 
     def compile_to_native_quil(self, request: CompileToNativeQuilRequest) -> CompileToNativeQuilResponse:
         """


Test patch:
diff --git a/test/unit/conftest.py b/test/unit/conftest.py
--- a/test/unit/conftest.py
+++ b/test/unit/conftest.py
@@ -1,6 +1,5 @@
 import json
 import os
-from pathlib import Path
 from typing import Dict, Any
 
 import numpy as np
diff --git a/test/unit/test_compiler_client.py b/test/unit/test_compiler_client.py
--- a/test/unit/test_compiler_client.py
+++ b/test/unit/test_compiler_client.py
@@ -14,6 +14,14 @@
 #    limitations under the License.
 ##############################################################################
 
+from test.unit.utils import patch_rpcq_client
+
+try:
+    from unittest.mock import AsyncMock
+except ImportError:  # 3.7 requires this backport of AsyncMock
+    from asyncmock import AsyncMock
+
+import qcs_sdk
 import rpcq
 from _pytest.monkeypatch import MonkeyPatch
 from pytest import raises
@@ -22,16 +30,15 @@
 
 from pyquil.api._compiler_client import (
     CompilerClient,
-    GenerateRandomizedBenchmarkingSequenceResponse,
-    GenerateRandomizedBenchmarkingSequenceRequest,
-    ConjugatePauliByCliffordResponse,
+    CompileToNativeQuilRequest,
+    CompileToNativeQuilResponse,
     ConjugatePauliByCliffordRequest,
+    ConjugatePauliByCliffordResponse,
+    GenerateRandomizedBenchmarkingSequenceRequest,
+    GenerateRandomizedBenchmarkingSequenceResponse,
     NativeQuilMetadataResponse,
-    CompileToNativeQuilResponse,
-    CompileToNativeQuilRequest,
 )
 from pyquil.external.rpcq import CompilerISA, compiler_isa_to_target_quantum_processor
-from test.unit.utils import patch_rpcq_client
 
 
 def test_init__sets_base_url_and_timeout(monkeypatch: MonkeyPatch):
@@ -70,12 +77,11 @@ def test_get_version__returns_version(mocker: MockerFixture):
     client_configuration = QCSClientConfiguration.load()
     compiler_client = CompilerClient(client_configuration=client_configuration)
 
-    rpcq_client = patch_rpcq_client(mocker=mocker, return_value={"quilc": "1.2.3"})
+    version_mock = AsyncMock(return_value="1.2.3")
+    get_quilc_version_mock = mocker.patch("qcs_sdk.get_quilc_version", version_mock)
 
     assert compiler_client.get_version() == "1.2.3"
-    rpcq_client.call.assert_called_once_with(
-        "get_version_info"
-    )
+    assert get_quilc_version_mock.call_count == 1
 
 
 def test_compile_to_native_quil__returns_native_quil(
@@ -99,7 +105,7 @@ def test_compile_to_native_quil__returns_native_quil(
                 topological_swaps=3,
                 qpu_runtime_estimation=0.1618,
             ),
-        )
+        ),
     )
     request = CompileToNativeQuilRequest(
         program="some-program",
@@ -130,12 +136,12 @@ def test_compile_to_native_quil__returns_native_quil(
     )
 
 
-def test_conjugate_pauli_by_clifford__returns_conjugation_result(
-    mocker: MockerFixture
-):
+def test_conjugate_pauli_by_clifford__returns_conjugation_result(mocker: MockerFixture):
     client_configuration = QCSClientConfiguration.load()
     compiler_client = CompilerClient(client_configuration=client_configuration)
-    rpcq_client = patch_rpcq_client(mocker=mocker, return_value=rpcq.messages.ConjugateByCliffordResponse(phase=42, pauli="pauli"))
+    rpcq_client = patch_rpcq_client(
+        mocker=mocker, return_value=rpcq.messages.ConjugateByCliffordResponse(phase=42, pauli="pauli")
+    )
 
     request = ConjugatePauliByCliffordRequest(
         pauli_indices=[0, 1, 2],
@@ -151,7 +157,7 @@ def test_conjugate_pauli_by_clifford__returns_conjugation_result(
         rpcq.messages.ConjugateByCliffordRequest(
             pauli=rpcq.messages.PauliTerm(indices=[0, 1, 2], symbols=["x", "y", "z"]),
             clifford="cliff",
-        )
+        ),
     )
 
 
@@ -161,7 +167,9 @@ def test_generate_randomized_benchmarking_sequence__returns_benchmarking_sequenc
     client_configuration = QCSClientConfiguration.load()
     compiler_client = CompilerClient(client_configuration=client_configuration)
 
-    rpcq_client = patch_rpcq_client(mocker=mocker, return_value=rpcq.messages.RandomizedBenchmarkingResponse(sequence=[[3, 1, 4], [1, 6, 1]]))
+    rpcq_client = patch_rpcq_client(
+        mocker=mocker, return_value=rpcq.messages.RandomizedBenchmarkingResponse(sequence=[[3, 1, 4], [1, 6, 1]])
+    )
 
     request = GenerateRandomizedBenchmarkingSequenceRequest(
         depth=42,
@@ -181,5 +189,5 @@ def test_generate_randomized_benchmarking_sequence__returns_benchmarking_sequenc
             gateset=["some", "gate", "set"],
             seed=314,
             interleaver="some-interleaver",
-        )
+        ),
     )