Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 11 additions & 7 deletions gridappsd-python-lib/gridappsd/difference_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -67,16 +65,22 @@ 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()
39 changes: 24 additions & 15 deletions gridappsd-python-lib/gridappsd/topics.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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):
Expand Down
82 changes: 82 additions & 0 deletions gridappsd-python-lib/tests/test_difference_builder.py
Original file line number Diff line number Diff line change
@@ -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"
}]
63 changes: 63 additions & 0 deletions gridappsd-python-lib/tests/test_topics.py
Original file line number Diff line number Diff line change
@@ -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