From 50846ff147696d8fef7b334d5fb8fe72f252b550 Mon Sep 17 00:00:00 2001 From: Craig <3979063+craig8@users.noreply.github.com> Date: Wed, 21 Aug 2024 00:00:25 +0000 Subject: [PATCH 1/3] Modified topics Application and service topics need not have simulation_ids associated with them any longer. --- gridappsd-python-lib/gridappsd/topics.py | 39 ++++++++------ gridappsd-python-lib/tests/test_topics.py | 63 +++++++++++++++++++++++ 2 files changed, 87 insertions(+), 15 deletions(-) create mode 100644 gridappsd-python-lib/tests/test_topics.py diff --git a/gridappsd-python-lib/gridappsd/topics.py b/gridappsd-python-lib/gridappsd/topics.py index aeead75..89a345e 100644 --- a/gridappsd-python-lib/gridappsd/topics.py +++ b/gridappsd-python-lib/gridappsd/topics.py @@ -80,7 +80,7 @@ def platform_log_topic(): return "/topic/{}.{}".format(BASE_TOPIC_PREFIX, "platform.log") -def service_input_topic(service_id, simulation_id): +def service_input_topic(service_id: str, simulation_id: int | str | None = None): """ Utility method for getting the input topic for a specific service. The service id should be the registered service with the platform. One @@ -95,11 +95,14 @@ def service_input_topic(service_id, simulation_id): :return: """ assert service_id, "service_id cannot be empty" - assert simulation_id, "simulation_id cannot be empty" - return "{}.{}.{}.input".format(BASE_SIMULATION_TOPIC, service_id, simulation_id) + if simulation_id: + return f"{BASE_SIMULATION_TOPIC}.{service_id}.{simulation_id}.input" -def service_output_topic(service_id, simulation_id): + return f"{BASE_SIMULATION_TOPIC}.{service_id}.input" + + +def service_output_topic(service_id: str, simulation_id: int | str | None = None): """ Utility method for getting the output topic for a specific service. The service id should be the registered service with the platform. One @@ -114,11 +117,14 @@ def service_output_topic(service_id, simulation_id): :return:str: Topic to subscribe to for service specific output. """ assert service_id, "Service id cannot be empty" - assert simulation_id, "Simulation id cannot be empty" - return "{}.{}.{}.output".format(BASE_SIMULATION_TOPIC, service_id, simulation_id) + if simulation_id: + return f"{BASE_SIMULATION_TOPIC}.{service_id}.{simulation_id}.output" + + return f"{BASE_SIMULATION_TOPIC}.{service_id}.output" -def application_input_topic(application_id, simulation_id): + +def application_input_topic(application_id: str, simulation_id: int | str | None = None): """ Utility method for getting the input topic for a specific application. The application_id should be the registered service with the platform. One @@ -130,11 +136,14 @@ def application_input_topic(application_id, simulation_id): :return:str: Topic to publish to for a specific application. """ assert application_id, "application_id cannot be empty" - assert simulation_id, "simulation_id cannot be empty" - return "{}.{}.{}.input".format(BASE_SIMULATION_TOPIC, application_id, simulation_id) + if simulation_id: + return f"{BASE_SIMULATION_TOPIC}.{application_id}.{simulation_id}.input" + + return f"{BASE_TOPIC}.{application_id}.input" -def application_output_topic(application_id, simulation_id): + +def application_output_topic(application_id: str, simulation_id: int | str | None = None): """ Utility method for getting the output topic for a specific application. The application_id should be the registered service with the platform. One @@ -146,11 +155,11 @@ def application_output_topic(application_id, simulation_id): :return: str: Topic to subscribe to for application specific output. """ assert application_id, "application_id cannot be empty" - #assert simulation_id, "simulation_id cannot be empty" - if simulation_id is None: - return "{}.{}.output".format(BASE_TOPIC, application_id) - else: - return "{}.{}.{}.output".format(BASE_SIMULATION_TOPIC, application_id, simulation_id) + + if simulation_id: + return f"{BASE_SIMULATION_TOPIC}.{application_id}.{simulation_id}.output" + + return f"{BASE_TOPIC}.{application_id}.output" def simulation_output_topic(simulation_id): diff --git a/gridappsd-python-lib/tests/test_topics.py b/gridappsd-python-lib/tests/test_topics.py new file mode 100644 index 0000000..277ddd0 --- /dev/null +++ b/gridappsd-python-lib/tests/test_topics.py @@ -0,0 +1,63 @@ +from gridappsd import topics + + +# Correct topic strings are generated for given service, application, and simulation IDs +def test_correct_topic_strings(): + service_id = "dnp3" + application_id = "app1" + simulation_id = 12345 + + assert topics.service_input_topic( + service_id) == "/topic/goss.gridappsd.simulation.dnp3.input" + assert topics.service_input_topic( + service_id, + simulation_id) == "/topic/goss.gridappsd.simulation.dnp3.12345.input" + assert topics.service_output_topic( + service_id) == "/topic/goss.gridappsd.simulation.dnp3.output" + assert topics.service_output_topic( + service_id, + simulation_id) == "/topic/goss.gridappsd.simulation.dnp3.12345.output" + assert topics.application_input_topic( + application_id) == "/topic/goss.gridappsd.app1.input" + assert topics.application_input_topic( + application_id, + simulation_id) == "/topic/goss.gridappsd.simulation.app1.12345.input" + assert topics.application_output_topic( + application_id) == "/topic/goss.gridappsd.app1.output" + assert topics.application_output_topic( + application_id, + simulation_id) == "/topic/goss.gridappsd.simulation.app1.12345.output" + assert topics.simulation_output_topic( + simulation_id) == "/topic/goss.gridappsd.simulation.output.12345" + assert topics.simulation_input_topic( + simulation_id) == "/topic/goss.gridappsd.simulation.input.12345" + + +def test_handling_none_values(): + service_id = "dnp3" + application_id = "app1" + + assert topics.service_input_topic( + service_id, None) == "/topic/goss.gridappsd.simulation.dnp3.input" + assert topics.service_output_topic( + service_id, None) == "/topic/goss.gridappsd.simulation.dnp3.output" + assert topics.application_input_topic( + application_id, None) == "/topic/goss.gridappsd.app1.input" + assert topics.application_output_topic( + application_id, None) == "/topic/goss.gridappsd.app1.output" + + +def test_service_input_topic_without_simulation_id(): + service_id = "dnp3" + simulation_id = None + expected_topic = "/topic/goss.gridappsd.simulation.dnp3.input" + result = topics.service_input_topic(service_id, simulation_id) + assert result == expected_topic + + +def test_service_input_topic_with_simulation_id(): + service_id = "dnp3" + simulation_id = 1234 + expected_topic = "/topic/goss.gridappsd.simulation.dnp3.1234.input" + result = topics.service_input_topic(service_id, simulation_id) + assert result == expected_topic From b937f74240a5a25d16d3c407d50481b80740dd80 Mon Sep 17 00:00:00 2001 From: Craig <3979063+craig8@users.noreply.github.com> Date: Wed, 21 Aug 2024 19:14:51 +0000 Subject: [PATCH 2/3] Update difference_builder.py --- .../gridappsd/difference_builder.py | 19 +++-- .../tests/test_difference_builder.py | 82 +++++++++++++++++++ 2 files changed, 94 insertions(+), 7 deletions(-) create mode 100644 gridappsd-python-lib/tests/test_difference_builder.py diff --git a/gridappsd-python-lib/gridappsd/difference_builder.py b/gridappsd-python-lib/gridappsd/difference_builder.py index a578a8f..8c83381 100644 --- a/gridappsd-python-lib/gridappsd/difference_builder.py +++ b/gridappsd-python-lib/gridappsd/difference_builder.py @@ -42,19 +42,17 @@ from uuid import uuid4 import time -import pytz - class DifferenceBuilder(object): """ Automates the building of forward and reverse cim differences """ - def __init__(self, simulation_id): + def __init__(self, simulation_id: str | int | None = None): self._simulation_id = simulation_id - self._forward = [] - self._reverse = [] + self._forward: list[dict] = [] + self._reverse: list[dict] = [] def add_difference(self, object_id, attribute, forward_value, reverse_value): """ Add forward and reverse unit for an object attribute. @@ -67,16 +65,23 @@ def add_difference(self, object_id, attribute, forward_value, reverse_value): self._reverse.append(reverse) def clear(self): + """ Clear the forward and reverse differences """ self._forward = [] self._reverse = [] def get_message(self, epoch=None): + """ Get the message to send to the GOSS message bus + + :param epoch: The epoch time to use for the message timestamp. If None, the current time (GMT) is used. + """ if epoch is None: epoch = calendar.timegm(time.gmtime()) msg = dict(command="update", - input=dict(simulation_id=self._simulation_id, - message=dict(timestamp=epoch, + input=dict(message=dict(timestamp=epoch, difference_mrid=str(uuid4()), reverse_differences=self._reverse, forward_differences=self._forward))) + if self._simulation_id is not None: + msg['input']['simulation_id'] = self._simulation_id return msg.copy() + diff --git a/gridappsd-python-lib/tests/test_difference_builder.py b/gridappsd-python-lib/tests/test_difference_builder.py new file mode 100644 index 0000000..a2d2d8e --- /dev/null +++ b/gridappsd-python-lib/tests/test_difference_builder.py @@ -0,0 +1,82 @@ +def test_initialization_with_and_without_simulation_id(): + from gridappsd.difference_builder import DifferenceBuilder + + # Test with simulation_id + builder_with_id = DifferenceBuilder(simulation_id=12345) + assert builder_with_id._simulation_id == 12345 + assert builder_with_id._forward == [] + assert builder_with_id._reverse == [] + + # Test without simulation_id + builder_without_id = DifferenceBuilder() + assert builder_without_id._simulation_id is None + assert builder_without_id._forward == [] + assert builder_without_id._reverse == [] + +def test_initialization_with_none_simulation_id(): + from gridappsd.difference_builder import DifferenceBuilder + + # Test with None as simulation_id + builder_with_none = DifferenceBuilder(simulation_id=None) + assert builder_with_none._simulation_id is None + assert builder_with_none._forward == [] + assert builder_with_none._reverse == [] + + +def test_returns_message_with_current_gmt_time_when_epoch_is_none(): + import calendar + import time + from uuid import UUID + from gridappsd.difference_builder import DifferenceBuilder + + builder = DifferenceBuilder(simulation_id=None) + result = builder.get_message(epoch=None) + + current_epoch = calendar.timegm(time.gmtime()) + assert abs(result['input']['message']['timestamp'] - current_epoch) < 2 # Allowing a small time difference + assert isinstance(UUID(result['input']['message']['difference_mrid']), UUID) + +def test_handles_empty_forward_differences_list(): + from gridappsd.difference_builder import DifferenceBuilder + + builder = DifferenceBuilder(simulation_id=None) + builder._forward = [] + result = builder.get_message(epoch=1234567890) + + assert result['input']['message']['forward_differences'] == [] + + +def test_adds_forward_and_reverse_differences(): + from gridappsd.difference_builder import DifferenceBuilder + + db = DifferenceBuilder() + db.add_difference("obj1", "attr1", "forward_val", "reverse_val") + + assert db._forward == [{ + "object": "obj1", + "attribute": "attr1", + "value": "forward_val" + }] + assert db._reverse == [{ + "object": "obj1", + "attribute": "attr1", + "value": "reverse_val" + }] + + +def test_handles_empty_strings(): + from gridappsd.difference_builder import DifferenceBuilder + + db = DifferenceBuilder() + db.add_difference("", "", "forward_val", "reverse_val") + + assert db._forward == [{ + "object": "", + "attribute": "", + "value": "forward_val" + }] + assert db._reverse == [{ + "object": "", + "attribute": "", + "value": "reverse_val" + }] From 9acc8a2aa40acc0ce10f7339fcbc3031e0580f97 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 21 Aug 2024 19:16:50 +0000 Subject: [PATCH 3/3] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- gridappsd-python-lib/gridappsd/difference_builder.py | 1 - 1 file changed, 1 deletion(-) diff --git a/gridappsd-python-lib/gridappsd/difference_builder.py b/gridappsd-python-lib/gridappsd/difference_builder.py index 8c83381..d7f585e 100644 --- a/gridappsd-python-lib/gridappsd/difference_builder.py +++ b/gridappsd-python-lib/gridappsd/difference_builder.py @@ -84,4 +84,3 @@ def get_message(self, epoch=None): if self._simulation_id is not None: msg['input']['simulation_id'] = self._simulation_id return msg.copy() -