diff --git a/examples/run_successful_job.py b/examples/run_successful_job.py index 50ed402..5e40f2f 100644 --- a/examples/run_successful_job.py +++ b/examples/run_successful_job.py @@ -1,3 +1,5 @@ +import time + import qibo from qibo_client import TII @@ -14,6 +16,7 @@ # run the circuit print(f"{'*'*20}\nPost first circuit") +start = time.time() result = client.run_circuit(circuit, nshots=100, device="sim") - print(result) +print(f"Program done in {time.time() - start:.4f}s") diff --git a/poetry.lock b/poetry.lock index 9319273..eab1bcb 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. [[package]] name = "alabaster" @@ -1863,4 +1863,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = ">=3.9,<3.12" -content-hash = "f3e5d67729a393b3a4ea5721feb199f5914b306fd7adb5572bb8f005d6abb5bf" +content-hash = "9942bcafe1dd9b96cc8ed67b0a749304e5969bcfa712d61f1ca90039b46c034f" diff --git a/pyproject.toml b/pyproject.toml index c66f93a..0d492b1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,7 @@ packages = [{ include = "qibo_client", from = "src" }] [tool.poetry.dependencies] python = ">=3.9,<3.12" -qibo = ">=0.2.2" +qibo = ">=0.2.4" requests = "^2.31.0" [tool.poetry.group.dev.dependencies] diff --git a/src/qibo_client/config_logging.py b/src/qibo_client/config_logging.py new file mode 100644 index 0000000..58d3a68 --- /dev/null +++ b/src/qibo_client/config_logging.py @@ -0,0 +1,8 @@ +import logging +import os + +# configure logger +logging.basicConfig(format="[%(asctime)s] %(levelname)s: %(message)s") +logger = logging.getLogger(__name__) +logging_level = os.environ.get("QIBO_CLIENT_LOGGER_LEVEL", logging.INFO) +logger.setLevel(logging_level) diff --git a/src/qibo_client/constants.py b/src/qibo_client/constants.py new file mode 100644 index 0000000..7624132 --- /dev/null +++ b/src/qibo_client/constants.py @@ -0,0 +1,9 @@ +import os +from pathlib import Path + +MINIMUM_QIBO_VERSION_ALLOWED = "0.2.4" + +RESULTS_BASE_FOLDER = Path(os.environ.get("RESULTS_BASE_FOLDER", "/tmp/qibo_client")) +SECONDS_BETWEEN_CHECKS = os.environ.get("SECONDS_BETWEEN_CHECKS", 2) + +TIMEOUT = 10 diff --git a/src/qibo_client/qibo_client.py b/src/qibo_client/qibo_client.py index fa4ba64..2f6bb50 100644 --- a/src/qibo_client/qibo_client.py +++ b/src/qibo_client/qibo_client.py @@ -1,7 +1,5 @@ """The module implementing the TIIProvider class.""" -import logging -import os import tarfile import tempfile import time @@ -12,22 +10,9 @@ import qibo import requests +from . import constants from .config import JobPostServerError, MalformedResponseError - -RESULTS_BASE_FOLDER = os.environ.get("RESULTS_BASE_FOLDER", "/tmp/qibo_client") -SECONDS_BETWEEN_CHECKS = os.environ.get("SECONDS_BETWEEN_CHECKS", 2) - -RESULTS_BASE_FOLDER = Path(RESULTS_BASE_FOLDER) -RESULTS_BASE_FOLDER.mkdir(exist_ok=True) - -TIMEOUT = 10 - - -# configure logger -logging.basicConfig(format="[%(asctime)s] %(levelname)s: %(message)s") -logger = logging.getLogger(__name__) -LOGGING_LEVEL = logging.INFO -logger.setLevel(LOGGING_LEVEL) +from .config_logging import logger def wait_for_response_to_get_request(url: str) -> requests.models.Response: @@ -40,9 +25,10 @@ def wait_for_response_to_get_request(url: str) -> requests.models.Response: :rtype: requests.models.Response """ while True: - response = requests.get(url, timeout=TIMEOUT) + response = requests.get(url, timeout=constants.TIMEOUT) + # @TODO: change this ! if response.content == b"Job still in progress": - time.sleep(SECONDS_BETWEEN_CHECKS) + time.sleep(constants.SECONDS_BETWEEN_CHECKS) continue return response @@ -135,18 +121,27 @@ def check_client_server_qibo_versions(self): Raise assertion error if the two versions are not the same. """ + qibo_local_version = qibo.__version__ + msg = ( + "The qibo-client package requires an installed qibo package version" + f">={constants.MINIMUM_QIBO_VERSION_ALLOWED}, the local qibo " + f"version is {qibo_local_version}" + ) + assert qibo_local_version >= constants.MINIMUM_QIBO_VERSION_ALLOWED, msg + url = self.url + "qibo_version/" - response = requests.get(url, timeout=TIMEOUT) + response = requests.get(url, timeout=constants.TIMEOUT) response.raise_for_status() check_response_has_keys(response, ["qibo_version"]) qibo_server_version = response.json()["qibo_version"] - qibo_local_version = qibo.__version__ - msg = ( - "Local Qibo package version does not match the server one, please " - f"upgrade: {qibo_local_version} -> {qibo_server_version}" - ) - assert qibo_local_version == qibo_server_version, msg + if qibo_local_version != qibo_server_version: + logger.warning( + "Local Qibo package version does not match the server one, please " + "upgrade: %s -> %s", + qibo_local_version, + qibo_server_version, + ) def run_circuit( self, circuit: qibo.Circuit, nshots: int = 1000, device: str = "sim" @@ -176,7 +171,9 @@ def run_circuit( # retrieve results logger.info("Job posted on server with pid %s", self.pid) - logger.info("Check results every %d seconds ...", SECONDS_BETWEEN_CHECKS) + logger.info( + "Check results every %d seconds ...", constants.SECONDS_BETWEEN_CHECKS + ) result = self._get_result() return result @@ -192,7 +189,7 @@ def _post_circuit( "nshots": nshots, "device": device, } - response = requests.post(url, json=payload, timeout=TIMEOUT) + response = requests.post(url, json=payload, timeout=constants.TIMEOUT) # checks response.raise_for_status() @@ -219,8 +216,8 @@ def _get_result(self) -> Optional[np.ndarray]: response = wait_for_response_to_get_request(url) # create the job results folder - self.results_folder = RESULTS_BASE_FOLDER / self.pid - self.results_folder.mkdir(exist_ok=True) + self.results_folder = constants.RESULTS_BASE_FOLDER / self.pid + self.results_folder.mkdir(parents=True, exist_ok=True) # Save the stream to disk try: diff --git a/tests/test_config_logging.py b/tests/test_config_logging.py new file mode 100644 index 0000000..ec25662 --- /dev/null +++ b/tests/test_config_logging.py @@ -0,0 +1,30 @@ +from unittest import TestCase +from unittest.mock import patch + +MOD = "qibo_client.config_logging" + + +def logging_wrap_function(logger_object): + logger_object.info("A debug log") + logger_object.error("An error log") + + +class TestLogger(TestCase): + @patch(f"{MOD}.os.environ", {"QIBO_CLIENT_LOGGER_LEVEL": "info"}) + def test_logging_with_info_level(self, mock_os): + from qibo_client.config_logging import logger + + with self.assertLogs() as captured: + logging_wrap_function(logger) + self.assertEqual(len(captured.records), 1) + self.assertEqual(captured.records[0].getMessage(), "An error log") + + @patch(f"{MOD}.os.environ", {"QIBO_CLIENT_LOGGER_LEVEL": "notset"}) + def test_logging_with_info_level(self): + from qibo_client.config_logging import logger + + with self.assertLogs() as captured: + logging_wrap_function(logger) + self.assertEqual(len(captured.records), 2) + self.assertEqual(captured.records[0].getMessage(), "A debug log") + self.assertEqual(captured.records[1].getMessage(), "An error log") diff --git a/tests/test_qibo_client.py b/tests/test_qibo_client.py index e540cb7..9c63915 100644 --- a/tests/test_qibo_client.py +++ b/tests/test_qibo_client.py @@ -12,7 +12,7 @@ PKG = "qibo_client.qibo_client" LOCAL_URL = "http://localhost:8000/" -FAKE_QIBO_VERSION = "0.0.1" +FAKE_QIBO_VERSION = "0.2.4" FAKE_PID = "123" ARCHIVE_NAME = "file.tar.gz" TIMEOUT = 1 @@ -30,7 +30,7 @@ def mock_qibo(): @pytest.fixture(scope="module", autouse=True) def mock_timeout(): """Ensure that all the requests are made on localhost""" - with patch(f"{PKG}.TIMEOUT", TIMEOUT) as _fixture: + with patch(f"{PKG}.constants.TIMEOUT", TIMEOUT) as _fixture: yield _fixture @@ -38,7 +38,7 @@ def mock_timeout(): def results_base_folder(tmp_path: Path): results_base_folder = tmp_path / "results" results_base_folder.mkdir() - with patch(f"{PKG}.RESULTS_BASE_FOLDER", results_base_folder): + with patch(f"{PKG}.constants.RESULTS_BASE_FOLDER", results_base_folder): yield results_base_folder @@ -129,23 +129,23 @@ def test_check_client_server_qibo_versions_with_version_match(mock_request: Mock ) -def test_check_client_server_qibo_versions_with_version_mismatch(mock_request: Mock): - remote_qibo_version = "0.2.2" - - def _new_side_effect(url, timeout): - return utils.MockedResponse( - status_code=200, json_data={"qibo_version": remote_qibo_version} - ) +def test_check_client_server_qibo_versions_with_version_mismatch( + mock_qibo: Mock, mock_request: Mock +): + mock_qibo.__version__ = "0.2.1" + with ( + patch(f"{PKG}.constants.MINIMUM_QIBO_VERSION_ALLOWED", "0.1.9"), + patch(f"{PKG}.logger") as mock_logger, + ): + _get_tii_client() + mock_logger.warning.assert_called_once() - mock_request.get.side_effect = _new_side_effect +def test_check_client_server_qibo_versions_with_low_local_version(mock_qibo: Mock): + mock_qibo.__version__ = "0.0.1" with pytest.raises(AssertionError): _get_tii_client() - mock_request.get.assert_called_once_with( - LOCAL_URL + "qibo_version/", timeout=TIMEOUT - ) - def test__post_circuit_with_invalid_token(mock_request: Mock): def _new_side_effect(url, json, timeout): @@ -194,7 +194,7 @@ def test_wait_for_response_to_get_request(mock_request: Mock): mock_request.get.side_effect = [keep_waiting] * failed_attempts + [job_done] - with patch(f"{PKG}.SECONDS_BETWEEN_CHECKS", 1e-4): + with patch(f"{PKG}.constants.SECONDS_BETWEEN_CHECKS", 1e-4): qibo_client.wait_for_response_to_get_request(url) assert mock_request.get.call_count == failed_attempts + 1