From 181cd630f1f7ceb57089fe3ccd889712080c3bff Mon Sep 17 00:00:00 2001 From: clydeu Date: Mon, 14 Apr 2025 14:48:16 +1000 Subject: [PATCH 1/6] [DEV-2834] Call EAS APIs for hosting capacity calibration Signed-off-by: clydeu --- src/zepben/eas/client/eas_client.py | 92 +++++++++++++++++++++++++ test/test_eas_client.py | 102 ++++++++++++++++++++++++++++ 2 files changed, 194 insertions(+) diff --git a/src/zepben/eas/client/eas_client.py b/src/zepben/eas/client/eas_client.py index fc3a024..04fdcc9 100644 --- a/src/zepben/eas/client/eas_client.py +++ b/src/zepben/eas/client/eas_client.py @@ -528,3 +528,95 @@ async def async_upload_study(self, study: Study): else: response = await response.text() return response + + def run_hosting_capacity_calibration(self, name: str): + """ + Send request to run hosting capacity calibration + :param name: A string representation of the calibration name + :return: The HTTP response received from the Evolve App Server after attempting to upload the study + """ + return get_event_loop().run_until_complete(self.async_run_hosting_capacity_calibration(name)) + + async def async_run_hosting_capacity_calibration(self, name: str): + """ + Send asynchronous request to run hosting capacity calibration + :param name: A string representation of the calibration name + :return: The HTTP response received from the Evolve App Server after attempting to upload the study + """ + with warnings.catch_warnings(): + if not self._verify_certificate: + warnings.filterwarnings("ignore", category=InsecureRequestWarning) + json = { + "query": """ + mutation runCalibration(name: String!) { + runCalibration(name: $name) + } + """, + "variables": { + "name": name + } + } + if self._verify_certificate: + sslcontext = ssl.create_default_context(cafile=self._ca_filename) + + async with self.session.post( + construct_url(protocol=self._protocol, host=self._host, port=self._port, path="/api/graphql"), + headers=self._get_request_headers(), + json=json, + ssl=sslcontext if self._verify_certificate else False + ) as response: + if response.ok: + response = await response.json() + else: + response = await response.text() + return response + + def get_hosting_capacity_calibration_run(self, id: str): + """ + Send request to run hosting capacity calibration + :param id: A string representation of the calibration run + :return: The HTTP response received from the Evolve App Server after attempting to upload the study + """ + return get_event_loop().run_until_complete(self.async_get_hosting_capacity_calibration_run(id)) + + async def async_get_hosting_capacity_calibration_run(self, id: str): + """ + Send asynchronous request to run hosting capacity calibration + :param name: A string representation of the calibration run + :return: The HTTP response received from the Evolve App Server after attempting to upload the study + """ + with warnings.catch_warnings(): + if not self._verify_certificate: + warnings.filterwarnings("ignore", category=InsecureRequestWarning) + json = { + "query": """ + query getCalibrationRun(id: String!) { + getCalibrationRun(id: $id) { + id + name + workflowId + runId + startAt + completedAt + status + } + } + """, + "variables": { + "id": id + } + } + if self._verify_certificate: + sslcontext = ssl.create_default_context(cafile=self._ca_filename) + + async with self.session.post( + construct_url(protocol=self._protocol, host=self._host, port=self._port, path="/api/graphql"), + headers=self._get_request_headers(), + json=json, + ssl=sslcontext if self._verify_certificate else False + ) as response: + if response.ok: + response = await response.json() + else: + response = await response.text() + return response diff --git a/test/test_eas_client.py b/test/test_eas_client.py index a2da5e7..77b3a97 100644 --- a/test/test_eas_client.py +++ b/test/test_eas_client.py @@ -3,6 +3,7 @@ # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at https://mozilla.org/MPL/2.0/. +import json import random import ssl import string @@ -12,6 +13,7 @@ import pytest import trustme from pytest_httpserver import HTTPServer +from werkzeug import Response from zepben.auth import ZepbenTokenFetcher from zepben.eas import EasClient, Study @@ -492,3 +494,103 @@ def test_raises_error_if_access_token_and_client_secret_configured(httpserver: H ) assert "Incompatible arguments passed to connect to secured Evolve App Server. You cannot provide multiple types of authentication. When using an access_token, do not provide client_id, client_secret, username, password, or token_fetcher." in str( error_message_for_username.value) + +def hosting_capacity_run_calibration_request_handler(request): + actual_body = json.loads(request.data.decode()) + query = " ".join(actual_body['query'].split()) + + assert query == "mutation runCalibration(name: String!) { runCalibration(name: $name) }" + assert actual_body['variables'] == {"name": "TEST CALIBRATION"} + + return Response(json.dumps({"result": "success"}), status=200, content_type="application/json") + +def test_run_hosting_capacity_calibration_no_verify_success(httpserver: HTTPServer): + eas_client = EasClient( + LOCALHOST, + httpserver.port, + verify_certificate=False + ) + + httpserver.expect_oneshot_request("/api/graphql").respond_with_handler(hosting_capacity_run_calibration_request_handler) + res = eas_client.run_hosting_capacity_calibration("TEST CALIBRATION") + httpserver.check_assertions() + assert res == {"result": "success"} + + +def test_run_hosting_capacity_calibration_invalid_certificate_failure(ca: trustme.CA, httpserver: HTTPServer): + with trustme.Blob(b"invalid ca").tempfile() as ca_filename: + eas_client = EasClient( + LOCALHOST, + httpserver.port, + verify_certificate=True, + ca_filename=ca_filename + ) + + httpserver.expect_oneshot_request("/api/graphql").respond_with_json({"result": "success"}) + with pytest.raises(ssl.SSLError): + eas_client.run_hosting_capacity_calibration("TEST CALIBRATION") + + +def test_run_hosting_capacity_calibration_valid_certificate_success(ca: trustme.CA, httpserver: HTTPServer): + with ca.cert_pem.tempfile() as ca_filename: + eas_client = EasClient( + LOCALHOST, + httpserver.port, + verify_certificate=True, + ca_filename=ca_filename + ) + + httpserver.expect_oneshot_request("/api/graphql").respond_with_handler(hosting_capacity_run_calibration_request_handler) + res = eas_client.run_hosting_capacity_calibration("TEST CALIBRATION") + httpserver.check_assertions() + assert res == {"result": "success"} + +def get_hosting_capacity_run_calibration_request_handler(request): + actual_body = json.loads(request.data.decode()) + query = " ".join(actual_body['query'].split()) + + assert query == "query getCalibrationRun(id: String!) { getCalibrationRun(id: $id) { id name workflowId runId startAt completedAt status } }" + assert actual_body['variables'] == {"id": "calibration-id"} + + return Response(json.dumps({"result": "success"}), status=200, content_type="application/json") + +def test_get_hosting_capacity_calibration_run_no_verify_success(httpserver: HTTPServer): + eas_client = EasClient( + LOCALHOST, + httpserver.port, + verify_certificate=False + ) + + httpserver.expect_oneshot_request("/api/graphql").respond_with_handler(get_hosting_capacity_run_calibration_request_handler) + res = eas_client.get_hosting_capacity_calibration_run("calibration-id") + httpserver.check_assertions() + assert res == {"result": "success"} + + +def test_get_hosting_capacity_calibration_run_invalid_certificate_failure(ca: trustme.CA, httpserver: HTTPServer): + with trustme.Blob(b"invalid ca").tempfile() as ca_filename: + eas_client = EasClient( + LOCALHOST, + httpserver.port, + verify_certificate=True, + ca_filename=ca_filename + ) + + httpserver.expect_oneshot_request("/api/graphql").respond_with_json({"result": "success"}) + with pytest.raises(ssl.SSLError): + eas_client.get_hosting_capacity_calibration_run("calibration-id") + + +def test_get_hosting_capacity_calibration_run_valid_certificate_success(ca: trustme.CA, httpserver: HTTPServer): + with ca.cert_pem.tempfile() as ca_filename: + eas_client = EasClient( + LOCALHOST, + httpserver.port, + verify_certificate=True, + ca_filename=ca_filename + ) + + httpserver.expect_oneshot_request("/api/graphql").respond_with_handler(get_hosting_capacity_run_calibration_request_handler) + res = eas_client.get_hosting_capacity_calibration_run("calibration-id") + httpserver.check_assertions() + assert res == {"result": "success"} \ No newline at end of file From 026eef4680057afa44fd61082c247385d398d11f Mon Sep 17 00:00:00 2001 From: clydeu Date: Mon, 14 Apr 2025 14:52:59 +1000 Subject: [PATCH 2/6] changelog. Signed-off-by: clydeu --- changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/changelog.md b/changelog.md index 00e4258..161c0ba 100644 --- a/changelog.md +++ b/changelog.md @@ -5,6 +5,7 @@ ### New Features * Update `ModelConfig` to contain an optional `transformer_tap_settings` field to specify a set of distribution transformer tap settings to be applied by the model-processor. +* Added basic client method to run a hosting capacity calibration and method to query its status. ### Enhancements * Added work package config documentation. From de4f13a0216551b8cac052471589e332feae609d Mon Sep 17 00:00:00 2001 From: clydeu Date: Mon, 14 Apr 2025 17:36:58 +1000 Subject: [PATCH 3/6] fixed graphql query syntax. Signed-off-by: clydeu --- src/zepben/eas/client/eas_client.py | 8 ++++---- test/test_eas_client.py | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/zepben/eas/client/eas_client.py b/src/zepben/eas/client/eas_client.py index 04fdcc9..f962480 100644 --- a/src/zepben/eas/client/eas_client.py +++ b/src/zepben/eas/client/eas_client.py @@ -548,7 +548,7 @@ async def async_run_hosting_capacity_calibration(self, name: str): warnings.filterwarnings("ignore", category=InsecureRequestWarning) json = { "query": """ - mutation runCalibration(name: String!) { + mutation runCalibration($name: String!) { runCalibration(name: $name) } """, @@ -574,7 +574,7 @@ async def async_run_hosting_capacity_calibration(self, name: str): def get_hosting_capacity_calibration_run(self, id: str): """ Send request to run hosting capacity calibration - :param id: A string representation of the calibration run + :param id: The calibration run ID :return: The HTTP response received from the Evolve App Server after attempting to upload the study """ return get_event_loop().run_until_complete(self.async_get_hosting_capacity_calibration_run(id)) @@ -582,7 +582,7 @@ def get_hosting_capacity_calibration_run(self, id: str): async def async_get_hosting_capacity_calibration_run(self, id: str): """ Send asynchronous request to run hosting capacity calibration - :param name: A string representation of the calibration run + :param id: The calibration run ID :return: The HTTP response received from the Evolve App Server after attempting to upload the study """ with warnings.catch_warnings(): @@ -590,7 +590,7 @@ async def async_get_hosting_capacity_calibration_run(self, id: str): warnings.filterwarnings("ignore", category=InsecureRequestWarning) json = { "query": """ - query getCalibrationRun(id: String!) { + query getCalibrationRun($id: ID!) { getCalibrationRun(id: $id) { id name diff --git a/test/test_eas_client.py b/test/test_eas_client.py index 77b3a97..bb6c23b 100644 --- a/test/test_eas_client.py +++ b/test/test_eas_client.py @@ -499,7 +499,7 @@ def hosting_capacity_run_calibration_request_handler(request): actual_body = json.loads(request.data.decode()) query = " ".join(actual_body['query'].split()) - assert query == "mutation runCalibration(name: String!) { runCalibration(name: $name) }" + assert query == "mutation runCalibration($name: String!) { runCalibration(name: $name) }" assert actual_body['variables'] == {"name": "TEST CALIBRATION"} return Response(json.dumps({"result": "success"}), status=200, content_type="application/json") @@ -549,7 +549,7 @@ def get_hosting_capacity_run_calibration_request_handler(request): actual_body = json.loads(request.data.decode()) query = " ".join(actual_body['query'].split()) - assert query == "query getCalibrationRun(id: String!) { getCalibrationRun(id: $id) { id name workflowId runId startAt completedAt status } }" + assert query == "query getCalibrationRun($id: ID!) { getCalibrationRun(id: $id) { id name workflowId runId startAt completedAt status } }" assert actual_body['variables'] == {"id": "calibration-id"} return Response(json.dumps({"result": "success"}), status=200, content_type="application/json") From 8fd895cad7edbef29defa0a380b7d98554ff0c67 Mon Sep 17 00:00:00 2001 From: clydeu Date: Tue, 15 Apr 2025 13:54:55 +1000 Subject: [PATCH 4/6] tweak graphql. Signed-off-by: clydeu --- src/zepben/eas/client/eas_client.py | 12 ++++++------ test/test_eas_client.py | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/zepben/eas/client/eas_client.py b/src/zepben/eas/client/eas_client.py index f962480..bb9e80a 100644 --- a/src/zepben/eas/client/eas_client.py +++ b/src/zepben/eas/client/eas_client.py @@ -537,10 +537,10 @@ def run_hosting_capacity_calibration(self, name: str): """ return get_event_loop().run_until_complete(self.async_run_hosting_capacity_calibration(name)) - async def async_run_hosting_capacity_calibration(self, name: str): + async def async_run_hosting_capacity_calibration(self, calibrationSet: str): """ Send asynchronous request to run hosting capacity calibration - :param name: A string representation of the calibration name + :param calibrationSet: A string representation of the calibration name :return: The HTTP response received from the Evolve App Server after attempting to upload the study """ with warnings.catch_warnings(): @@ -548,12 +548,12 @@ async def async_run_hosting_capacity_calibration(self, name: str): warnings.filterwarnings("ignore", category=InsecureRequestWarning) json = { "query": """ - mutation runCalibration($name: String!) { - runCalibration(name: $name) + mutation runCalibration($calibrationSet: String!) { + runCalibration(calibrationSet: $calibrationSet) } """, "variables": { - "name": name + "calibrationSet": calibrationSet } } if self._verify_certificate: @@ -591,7 +591,7 @@ async def async_get_hosting_capacity_calibration_run(self, id: str): json = { "query": """ query getCalibrationRun($id: ID!) { - getCalibrationRun(id: $id) { + getCalibrationRun(calibrationRunId: $id) { id name workflowId diff --git a/test/test_eas_client.py b/test/test_eas_client.py index bb6c23b..2e9b858 100644 --- a/test/test_eas_client.py +++ b/test/test_eas_client.py @@ -499,8 +499,8 @@ def hosting_capacity_run_calibration_request_handler(request): actual_body = json.loads(request.data.decode()) query = " ".join(actual_body['query'].split()) - assert query == "mutation runCalibration($name: String!) { runCalibration(name: $name) }" - assert actual_body['variables'] == {"name": "TEST CALIBRATION"} + assert query == "mutation runCalibration($calibrationSet: String!) { runCalibration(calibrationSet: $calibrationSet) }" + assert actual_body['variables'] == {"calibrationSet": "TEST CALIBRATION"} return Response(json.dumps({"result": "success"}), status=200, content_type="application/json") @@ -549,7 +549,7 @@ def get_hosting_capacity_run_calibration_request_handler(request): actual_body = json.loads(request.data.decode()) query = " ".join(actual_body['query'].split()) - assert query == "query getCalibrationRun($id: ID!) { getCalibrationRun(id: $id) { id name workflowId runId startAt completedAt status } }" + assert query == "query getCalibrationRun($id: ID!) { getCalibrationRun(calibrationRunId: $id) { id name workflowId runId startAt completedAt status } }" assert actual_body['variables'] == {"id": "calibration-id"} return Response(json.dumps({"result": "success"}), status=200, content_type="application/json") From d07d00af464e2109b06c5ae5763243cbb1a736ae Mon Sep 17 00:00:00 2001 From: clydeu Date: Wed, 16 Apr 2025 10:40:30 +1000 Subject: [PATCH 5/6] fix wording on descriptions. Signed-off-by: clydeu --- src/zepben/eas/client/eas_client.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/zepben/eas/client/eas_client.py b/src/zepben/eas/client/eas_client.py index bb9e80a..0cf103d 100644 --- a/src/zepben/eas/client/eas_client.py +++ b/src/zepben/eas/client/eas_client.py @@ -529,19 +529,19 @@ async def async_upload_study(self, study: Study): response = await response.text() return response - def run_hosting_capacity_calibration(self, name: str): + def run_hosting_capacity_calibration(self, calibration_set: str): """ Send request to run hosting capacity calibration - :param name: A string representation of the calibration name - :return: The HTTP response received from the Evolve App Server after attempting to upload the study + :param calibration_set: A string representation of the calibration name + :return: The HTTP response received from the Evolve App Server after attempting to run the calibration """ - return get_event_loop().run_until_complete(self.async_run_hosting_capacity_calibration(name)) + return get_event_loop().run_until_complete(self.async_run_hosting_capacity_calibration(calibration_set)) async def async_run_hosting_capacity_calibration(self, calibrationSet: str): """ Send asynchronous request to run hosting capacity calibration :param calibrationSet: A string representation of the calibration name - :return: The HTTP response received from the Evolve App Server after attempting to upload the study + :return: The HTTP response received from the Evolve App Server after attempting to run the calibration """ with warnings.catch_warnings(): if not self._verify_certificate: @@ -573,17 +573,17 @@ async def async_run_hosting_capacity_calibration(self, calibrationSet: str): def get_hosting_capacity_calibration_run(self, id: str): """ - Send request to run hosting capacity calibration + Retrieve information of a hosting capacity calibration run :param id: The calibration run ID - :return: The HTTP response received from the Evolve App Server after attempting to upload the study + :return: The HTTP response received from the Evolve App Server after requesting calibration run info """ return get_event_loop().run_until_complete(self.async_get_hosting_capacity_calibration_run(id)) async def async_get_hosting_capacity_calibration_run(self, id: str): """ - Send asynchronous request to run hosting capacity calibration + Retrieve information of a hosting capacity calibration run :param id: The calibration run ID - :return: The HTTP response received from the Evolve App Server after attempting to upload the study + :return: The HTTP response received from the Evolve App Server after requesting calibration run info """ with warnings.catch_warnings(): if not self._verify_certificate: From 760dddc65588d76b782ea38f1eb7e29572ee5679 Mon Sep 17 00:00:00 2001 From: vince Date: Thu, 1 May 2025 13:12:13 +1000 Subject: [PATCH 6/6] rename Signed-off-by: vince --- src/zepben/eas/client/eas_client.py | 16 ++++++++-------- test/test_eas_client.py | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/zepben/eas/client/eas_client.py b/src/zepben/eas/client/eas_client.py index 0cf103d..5f8227b 100644 --- a/src/zepben/eas/client/eas_client.py +++ b/src/zepben/eas/client/eas_client.py @@ -529,18 +529,18 @@ async def async_upload_study(self, study: Study): response = await response.text() return response - def run_hosting_capacity_calibration(self, calibration_set: str): + def run_hosting_capacity_calibration(self, calibration_name: str): """ Send request to run hosting capacity calibration - :param calibration_set: A string representation of the calibration name + :param calibration_name: A string representation of the calibration name :return: The HTTP response received from the Evolve App Server after attempting to run the calibration """ - return get_event_loop().run_until_complete(self.async_run_hosting_capacity_calibration(calibration_set)) + return get_event_loop().run_until_complete(self.async_run_hosting_capacity_calibration(calibration_name)) - async def async_run_hosting_capacity_calibration(self, calibrationSet: str): + async def async_run_hosting_capacity_calibration(self, calibration_name: str): """ Send asynchronous request to run hosting capacity calibration - :param calibrationSet: A string representation of the calibration name + :param calibration_name: A string representation of the calibration name :return: The HTTP response received from the Evolve App Server after attempting to run the calibration """ with warnings.catch_warnings(): @@ -548,12 +548,12 @@ async def async_run_hosting_capacity_calibration(self, calibrationSet: str): warnings.filterwarnings("ignore", category=InsecureRequestWarning) json = { "query": """ - mutation runCalibration($calibrationSet: String!) { - runCalibration(calibrationSet: $calibrationSet) + mutation runCalibration($calibrationName: String!) { + runCalibration(calibrationName: $calibrationName) } """, "variables": { - "calibrationSet": calibrationSet + "calibrationName": calibration_name } } if self._verify_certificate: diff --git a/test/test_eas_client.py b/test/test_eas_client.py index 2e9b858..48785bc 100644 --- a/test/test_eas_client.py +++ b/test/test_eas_client.py @@ -499,8 +499,8 @@ def hosting_capacity_run_calibration_request_handler(request): actual_body = json.loads(request.data.decode()) query = " ".join(actual_body['query'].split()) - assert query == "mutation runCalibration($calibrationSet: String!) { runCalibration(calibrationSet: $calibrationSet) }" - assert actual_body['variables'] == {"calibrationSet": "TEST CALIBRATION"} + assert query == "mutation runCalibration($calibrationName: String!) { runCalibration(calibrationName: $calibrationName) }" + assert actual_body['variables'] == {"calibrationName": "TEST CALIBRATION"} return Response(json.dumps({"result": "success"}), status=200, content_type="application/json")