gridappsd-python 2025.3.2a13__py3-none-any.whl → 2025.3.2a17__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- gridappsd/__init__.py +8 -7
- gridappsd/__main__.py +18 -18
- gridappsd/app_registration.py +34 -41
- gridappsd/difference_builder.py +18 -15
- gridappsd/field_interface/__init__.py +39 -19
- gridappsd/goss.py +50 -64
- gridappsd/gridappsd.py +27 -28
- gridappsd/houses.py +34 -16
- gridappsd/json_extension.py +82 -81
- gridappsd/loghandler.py +21 -20
- gridappsd/register_app.py +11 -10
- gridappsd/simulation.py +73 -61
- gridappsd/timeseries.py +10 -12
- gridappsd/topics.py +28 -33
- gridappsd/utils.py +6 -4
- {gridappsd_python-2025.3.2a13.dist-info → gridappsd_python-2025.3.2a17.dist-info}/METADATA +14 -22
- gridappsd_python-2025.3.2a17.dist-info/RECORD +20 -0
- {gridappsd_python-2025.3.2a13.dist-info → gridappsd_python-2025.3.2a17.dist-info}/WHEEL +1 -1
- gridappsd_python-2025.3.2a17.dist-info/entry_points.txt +3 -0
- gridappsd_python-2025.3.2a13.dist-info/RECORD +0 -20
- gridappsd_python-2025.3.2a13.dist-info/entry_points.txt +0 -4
gridappsd/__init__.py
CHANGED
|
@@ -37,20 +37,21 @@
|
|
|
37
37
|
# PACIFIC NORTHWEST NATIONAL LABORATORY operated by BATTELLE for the
|
|
38
38
|
# UNITED STATES DEPARTMENT OF ENERGY under Contract DE-AC05-76RL01830
|
|
39
39
|
# -------------------------------------------------------------------------------
|
|
40
|
-
"""
|
|
40
|
+
"""GridAPPSD Python Connection Library
|
|
41
41
|
|
|
42
42
|
The :mod:`gridappsd` contains a connection class :class:`gridappsd.GridAPPSD` for connecting with the
|
|
43
43
|
main GridAPPSD executable.
|
|
44
44
|
|
|
45
45
|
"""
|
|
46
|
+
|
|
46
47
|
import pathlib
|
|
47
48
|
import typing
|
|
48
49
|
|
|
49
50
|
StrPath = typing.Union[str, pathlib.Path]
|
|
50
51
|
|
|
51
|
-
from gridappsd.goss import GOSS
|
|
52
|
-
from gridappsd.utils import ProcessStatusEnum
|
|
53
|
-
from gridappsd.gridappsd import GridAPPSD
|
|
54
|
-
from gridappsd.difference_builder import DifferenceBuilder
|
|
55
|
-
from gridappsd.app_registration import ApplicationController
|
|
56
|
-
import
|
|
52
|
+
from gridappsd.goss import GOSS as GOSS
|
|
53
|
+
from gridappsd.utils import ProcessStatusEnum as ProcessStatusEnum
|
|
54
|
+
from gridappsd.gridappsd import GridAPPSD as GridAPPSD
|
|
55
|
+
from gridappsd.difference_builder import DifferenceBuilder as DifferenceBuilder
|
|
56
|
+
from gridappsd.app_registration import ApplicationController as ApplicationController
|
|
57
|
+
from gridappsd import json_extension as json
|
gridappsd/__main__.py
CHANGED
|
@@ -48,28 +48,30 @@ from dotenv import load_dotenv
|
|
|
48
48
|
from pathlib import Path
|
|
49
49
|
|
|
50
50
|
from gridappsd import GridAPPSD
|
|
51
|
+
from gridappsd.simulation import Simulation
|
|
51
52
|
|
|
52
53
|
assert sys.version_info >= (3, 10), "Minimum version is python 3.10"
|
|
53
54
|
|
|
54
|
-
logging.basicConfig(
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
logging.basicConfig(
|
|
56
|
+
stream=sys.stdout, level=logging.INFO, format="'%(asctime)s: %(name)-20s - %(levelname)-6s - %(message)s"
|
|
57
|
+
)
|
|
57
58
|
|
|
58
|
-
logging.getLogger(
|
|
59
|
+
logging.getLogger("stomp.py").setLevel(logging.WARNING)
|
|
59
60
|
_log = logging.getLogger("gridappsd.__main__")
|
|
60
61
|
|
|
61
|
-
if __name__ ==
|
|
62
|
-
|
|
62
|
+
if __name__ == "__main__":
|
|
63
63
|
parser = ArgumentParser()
|
|
64
64
|
|
|
65
65
|
group = parser.add_mutually_exclusive_group(required=True)
|
|
66
|
-
group.add_argument(
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
66
|
+
group.add_argument(
|
|
67
|
+
"-s",
|
|
68
|
+
"--run-simulation",
|
|
69
|
+
type=argparse.FileType("r"),
|
|
70
|
+
help="Start running a simulation from a passed simulation file.",
|
|
71
|
+
)
|
|
72
|
+
group.add_argument(
|
|
73
|
+
"--env", required=False, type=str, default=".env", help="Load environment variables from a .env file."
|
|
74
|
+
)
|
|
73
75
|
opts = parser.parse_args()
|
|
74
76
|
|
|
75
77
|
if opts.run_simulation:
|
|
@@ -91,8 +93,6 @@ if __name__ == '__main__':
|
|
|
91
93
|
gappsd = GridAPPSD()
|
|
92
94
|
run_args = yaml.safe_load(opts.run_simulation)
|
|
93
95
|
|
|
94
|
-
#
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
simulation = gappsd.run_simulation(run_args)
|
|
98
|
-
simulation.simulation_main_loop()
|
|
96
|
+
# Create and start the simulation
|
|
97
|
+
simulation = Simulation(gappsd, run_args)
|
|
98
|
+
simulation.start_simulation()
|
gridappsd/app_registration.py
CHANGED
|
@@ -1,17 +1,12 @@
|
|
|
1
|
-
#import json
|
|
1
|
+
# import json
|
|
2
2
|
import logging
|
|
3
3
|
import os
|
|
4
|
-
|
|
5
|
-
from queue import Queue
|
|
6
|
-
except ImportError:
|
|
7
|
-
from Queue import Queue
|
|
4
|
+
from queue import Queue
|
|
8
5
|
import time
|
|
9
|
-
import select
|
|
10
6
|
import subprocess
|
|
11
7
|
import threading
|
|
12
8
|
import shlex
|
|
13
9
|
import sys
|
|
14
|
-
import os
|
|
15
10
|
|
|
16
11
|
from .gridappsd import GridAPPSD
|
|
17
12
|
from .topics import REQUEST_REGISTER_APP
|
|
@@ -21,12 +16,11 @@ _log = logging.getLogger(__name__)
|
|
|
21
16
|
|
|
22
17
|
# determine OS type
|
|
23
18
|
posix = False
|
|
24
|
-
if os.name ==
|
|
19
|
+
if os.name == "posix":
|
|
25
20
|
posix = True
|
|
26
21
|
|
|
27
22
|
|
|
28
23
|
class Job(threading.Thread):
|
|
29
|
-
|
|
30
24
|
def __init__(self, args, out=sys.stdout, err=sys.stderr):
|
|
31
25
|
threading.Thread.__init__(self)
|
|
32
26
|
_log.debug("Creating job")
|
|
@@ -41,31 +35,30 @@ class Job(threading.Thread):
|
|
|
41
35
|
def run(self):
|
|
42
36
|
try:
|
|
43
37
|
self.running = True
|
|
44
|
-
os.environ[
|
|
38
|
+
os.environ["GRIDAPPSD_APPLICATION_STATUS"] = "RUNNING"
|
|
45
39
|
|
|
46
40
|
p = subprocess.Popen(args=self._args, shell=False, stdout=self._out, stderr=self._err)
|
|
47
41
|
|
|
48
42
|
# Loop while process is executing
|
|
49
43
|
while p.poll() is None and self.running:
|
|
50
|
-
os.environ[
|
|
44
|
+
os.environ["GRIDAPPSD_APPLICATION_STATUS"] = "RUNNING"
|
|
51
45
|
time.sleep(1)
|
|
52
46
|
|
|
53
47
|
except Exception as e:
|
|
54
|
-
os.environ[
|
|
48
|
+
os.environ["GRIDAPPSD_APPLICATION_STATUS"] = "ERROR"
|
|
55
49
|
_log.error(repr(e))
|
|
56
50
|
else:
|
|
57
|
-
os.environ[
|
|
51
|
+
os.environ["GRIDAPPSD_APPLICATION_STATUS"] = "STOPPED"
|
|
58
52
|
|
|
59
53
|
|
|
60
54
|
class ApplicationController(object):
|
|
61
|
-
|
|
62
55
|
def __init__(self, config, gridappsd=None, heatbeat_period=10):
|
|
63
56
|
if not isinstance(config, dict):
|
|
64
57
|
raise ValueError("Config should be dictionary")
|
|
65
58
|
if not isinstance(gridappsd, GridAPPSD):
|
|
66
59
|
raise ValueError("Invalid gridappsd instance passed.")
|
|
67
60
|
|
|
68
|
-
os.environ[
|
|
61
|
+
os.environ["GRIDAPPSD_APPLICATION_STATUS"] = "STOPPED"
|
|
69
62
|
self._configDict = config.copy()
|
|
70
63
|
self._validate_config()
|
|
71
64
|
self._gapd = gridappsd
|
|
@@ -87,15 +80,16 @@ class ApplicationController(object):
|
|
|
87
80
|
self._end_callback = None
|
|
88
81
|
self._print_queue = Queue()
|
|
89
82
|
self._heartbeat_thread = None
|
|
90
|
-
os.environ[
|
|
83
|
+
os.environ["GRIDAPPSD_APPLICATION_STATUS"] = "STOPPED"
|
|
91
84
|
|
|
92
|
-
if "type" not in self._configDict or self._configDict[
|
|
93
|
-
_log.warning(
|
|
94
|
-
|
|
95
|
-
|
|
85
|
+
if "type" not in self._configDict or self._configDict["type"] != "REMOTE":
|
|
86
|
+
_log.warning(
|
|
87
|
+
'Setting type to REMOTE you can remove this error by putting "type": "REMOTE" in the app config file.'
|
|
88
|
+
)
|
|
89
|
+
self._configDict["type"] = "REMOTE"
|
|
96
90
|
|
|
97
91
|
def _validate_config(self):
|
|
98
|
-
required = [
|
|
92
|
+
required = ["id", "execution_path"]
|
|
99
93
|
missing = [x for x in required if x not in self._configDict]
|
|
100
94
|
|
|
101
95
|
if missing:
|
|
@@ -114,30 +108,30 @@ class ApplicationController(object):
|
|
|
114
108
|
self._gapd.get_logger().debug("Started App Registration")
|
|
115
109
|
|
|
116
110
|
response = self._gapd.get_response(REQUEST_REGISTER_APP, self._configDict, 60)
|
|
117
|
-
if
|
|
111
|
+
if "message" in response:
|
|
118
112
|
_log.error("An error regisering the application occured")
|
|
119
|
-
_log.error(response.get(
|
|
120
|
-
raise ValueError(response.get(
|
|
121
|
-
self._application_id = response.get(
|
|
122
|
-
self._heartbeat_topic = response.get(
|
|
123
|
-
self._heartbeat_period = response.get(
|
|
124
|
-
self._start_control_topic = response.get(
|
|
125
|
-
self._stop_control_topic = response.get(
|
|
113
|
+
_log.error(response.get("message"))
|
|
114
|
+
raise ValueError(response.get("message"))
|
|
115
|
+
self._application_id = response.get("applicationId")
|
|
116
|
+
self._heartbeat_topic = response.get("heartbeatTopic")
|
|
117
|
+
self._heartbeat_period = response.get("heartbeatPeriod", 10)
|
|
118
|
+
self._start_control_topic = response.get("startControlTopic")
|
|
119
|
+
self._stop_control_topic = response.get("stopControlTopic")
|
|
126
120
|
|
|
127
121
|
os.environ["GRIDAPPSD_APPLICATION_ID"] = self._application_id
|
|
128
|
-
os.environ[
|
|
122
|
+
os.environ["GRIDAPPSD_APPLICATION_STATUS"] = "STOPPED"
|
|
129
123
|
|
|
130
124
|
self._gapd.subscribe(self._stop_control_topic, self.__handle_stop)
|
|
131
125
|
self._gapd.subscribe(self._start_control_topic, self.__handle_start)
|
|
132
126
|
self._end_callback = end_callback
|
|
133
127
|
|
|
134
128
|
# TODO assuming good response start the heartbeat
|
|
135
|
-
self._heartbeat_thread = threading.Thread(target=self.__start_heartbeat,
|
|
136
|
-
args=[self.__heartbeat_error])
|
|
129
|
+
self._heartbeat_thread = threading.Thread(target=self.__start_heartbeat, args=[self.__heartbeat_error])
|
|
137
130
|
self._heartbeat_thread.daemon = True
|
|
138
131
|
self._heartbeat_thread.start()
|
|
139
|
-
self._gapd.get_logger().debug(
|
|
140
|
-
utils.get_gridappsd_application_id())
|
|
132
|
+
self._gapd.get_logger().debug(
|
|
133
|
+
"Heartbeat registereed for application {}".format(utils.get_gridappsd_application_id())
|
|
134
|
+
)
|
|
141
135
|
|
|
142
136
|
def __heartbeat_error(self):
|
|
143
137
|
self._heartbeat_thread = None
|
|
@@ -151,8 +145,7 @@ class ApplicationController(object):
|
|
|
151
145
|
# print("Seanding heartbeat {} {}".format(self._heartbeat_topic, self._application_id))
|
|
152
146
|
# print("Heartbeat period: {}".format(self._heartbeat_period))
|
|
153
147
|
self._gapd.send(self._heartbeat_topic, self._application_id)
|
|
154
|
-
time.sleep(self._heartbeat_period -
|
|
155
|
-
((time.time() - starttime) % self._heartbeat_period))
|
|
148
|
+
time.sleep(self._heartbeat_period - ((time.time() - starttime) % self._heartbeat_period))
|
|
156
149
|
except:
|
|
157
150
|
error_callback()
|
|
158
151
|
|
|
@@ -167,15 +160,15 @@ class ApplicationController(object):
|
|
|
167
160
|
obj = json.loads(message)
|
|
168
161
|
else:
|
|
169
162
|
obj = message
|
|
170
|
-
os.environ[
|
|
163
|
+
os.environ["GRIDAPPSD_APPLICATION_STATUS"] = "STARTING"
|
|
171
164
|
self._gapd.get_logger().debug("Handling Start: {}\ndict:\n{}".format(headers, obj))
|
|
172
165
|
|
|
173
|
-
if
|
|
166
|
+
if "command" not in obj:
|
|
174
167
|
# Send log to gridappsd
|
|
175
168
|
_log.error("Invalid message sent on start app.")
|
|
176
169
|
else:
|
|
177
170
|
_log.debug("CWD IS: {}".format(os.getcwd()))
|
|
178
|
-
args = shlex.split(obj[
|
|
171
|
+
args = shlex.split(obj["command"])
|
|
179
172
|
job = Job(args)
|
|
180
173
|
job.daemon = True
|
|
181
174
|
job.start()
|
|
@@ -183,12 +176,12 @@ class ApplicationController(object):
|
|
|
183
176
|
|
|
184
177
|
def __handle_stop(self, headers, message):
|
|
185
178
|
print("Handling Stop: {} {}".format(headers, message))
|
|
186
|
-
os.environ[
|
|
179
|
+
os.environ["GRIDAPPSD_APPLICATION_STATUS"] = "STOPPING"
|
|
187
180
|
if self._thread:
|
|
188
181
|
self._thread.join()
|
|
189
182
|
if self._end_callback is not None:
|
|
190
183
|
self._end_callback()
|
|
191
|
-
os.environ[
|
|
184
|
+
os.environ["GRIDAPPSD_APPLICATION_STATUS"] = "STOPPED"
|
|
192
185
|
|
|
193
186
|
def shutdown(self):
|
|
194
187
|
self._shutting_down = True
|
gridappsd/difference_builder.py
CHANGED
|
@@ -44,43 +44,46 @@ import time
|
|
|
44
44
|
|
|
45
45
|
|
|
46
46
|
class DifferenceBuilder(object):
|
|
47
|
-
"""
|
|
48
|
-
|
|
49
|
-
"""
|
|
47
|
+
"""Automates the building of forward and reverse cim differences"""
|
|
50
48
|
|
|
51
49
|
def __init__(self, simulation_id: str | int | None = None):
|
|
52
|
-
|
|
53
50
|
self._simulation_id = simulation_id
|
|
54
51
|
self._forward: list[dict] = []
|
|
55
52
|
self._reverse: list[dict] = []
|
|
56
53
|
|
|
57
54
|
def add_difference(self, object_id, attribute, forward_value, reverse_value):
|
|
58
|
-
"""
|
|
55
|
+
"""Add forward and reverse unit for an object attribute.
|
|
59
56
|
|
|
60
|
-
|
|
61
|
-
|
|
57
|
+
All of the parameters must be serializable for sending the GOSS message bus.
|
|
58
|
+
"""
|
|
62
59
|
forward = dict(object=object_id, attribute=attribute, value=forward_value)
|
|
63
60
|
reverse = dict(object=object_id, attribute=attribute, value=reverse_value)
|
|
64
61
|
self._forward.append(forward)
|
|
65
62
|
self._reverse.append(reverse)
|
|
66
63
|
|
|
67
64
|
def clear(self):
|
|
68
|
-
"""
|
|
65
|
+
"""Clear the forward and reverse differences"""
|
|
69
66
|
self._forward = []
|
|
70
67
|
self._reverse = []
|
|
71
68
|
|
|
72
69
|
def get_message(self, epoch=None):
|
|
73
|
-
"""
|
|
70
|
+
"""Get the message to send to the GOSS message bus
|
|
74
71
|
|
|
75
72
|
:param epoch: The epoch time to use for the message timestamp. If None, the current time (GMT) is used.
|
|
76
73
|
"""
|
|
77
74
|
if epoch is None:
|
|
78
75
|
epoch = calendar.timegm(time.gmtime())
|
|
79
|
-
msg = dict(
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
76
|
+
msg = dict(
|
|
77
|
+
command="update",
|
|
78
|
+
input=dict(
|
|
79
|
+
message=dict(
|
|
80
|
+
timestamp=epoch,
|
|
81
|
+
difference_mrid=str(uuid4()),
|
|
82
|
+
reverse_differences=self._reverse,
|
|
83
|
+
forward_differences=self._forward,
|
|
84
|
+
)
|
|
85
|
+
),
|
|
86
|
+
)
|
|
84
87
|
if self._simulation_id is not None:
|
|
85
|
-
msg[
|
|
88
|
+
msg["input"]["simulation_id"] = self._simulation_id
|
|
86
89
|
return msg.copy()
|
|
@@ -1,32 +1,52 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import sys
|
|
3
|
+
import warnings
|
|
3
4
|
|
|
4
5
|
_log = logging.getLogger(__name__)
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
# These will be populated if gridappsd-field-bus is installed
|
|
8
|
+
context = None
|
|
9
|
+
context_managers = None
|
|
10
|
+
agents = None
|
|
11
|
+
field_proxy_forwarder = None
|
|
12
|
+
gridappsd_field_bus = None
|
|
13
|
+
interfaces = None
|
|
8
14
|
|
|
15
|
+
try:
|
|
9
16
|
import gridappsd_field_bus.field_interface.context as _context
|
|
10
17
|
import gridappsd_field_bus.field_interface.context_managers as _context_managers
|
|
11
18
|
import gridappsd_field_bus.field_interface as _field_interface
|
|
12
19
|
import gridappsd_field_bus.field_interface.agents as _agents
|
|
13
|
-
import gridappsd_field_bus.field_interface.field_proxy_forwarder as _field_proxy_forwarder
|
|
14
20
|
import gridappsd_field_bus.field_interface.gridappsd_field_bus as _gridappsd_field_bus
|
|
15
21
|
import gridappsd_field_bus.field_interface.interfaces as _interfaces
|
|
16
22
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
23
|
+
# Expose as module attributes for `from gridappsd.field_interface import X` syntax
|
|
24
|
+
context = _context
|
|
25
|
+
context_managers = _context_managers
|
|
26
|
+
agents = _agents
|
|
27
|
+
gridappsd_field_bus = _gridappsd_field_bus
|
|
28
|
+
interfaces = _interfaces
|
|
29
|
+
|
|
30
|
+
# Also register in sys.modules for backwards compatibility
|
|
31
|
+
sys.modules["gridappsd.field_interface.interfaces"] = _interfaces
|
|
32
|
+
sys.modules["gridappsd.field_interface.context"] = _context
|
|
33
|
+
sys.modules["gridappsd.field_interface.context_managers"] = _context_managers
|
|
34
|
+
sys.modules["gridappsd.field_interface.agents"] = _agents
|
|
35
|
+
sys.modules["gridappsd.field_interface.gridappsd_field_bus"] = _gridappsd_field_bus
|
|
36
|
+
|
|
37
|
+
# field_proxy_forwarder has optional dependencies that may not be available
|
|
38
|
+
try:
|
|
39
|
+
import gridappsd_field_bus.field_interface.field_proxy_forwarder as _field_proxy_forwarder
|
|
40
|
+
|
|
41
|
+
field_proxy_forwarder = _field_proxy_forwarder
|
|
42
|
+
sys.modules["gridappsd.field_interface.field_proxy_forwarder"] = _field_proxy_forwarder
|
|
43
|
+
except ImportError:
|
|
44
|
+
_log.debug("field_proxy_forwarder not available (missing optional dependencies)")
|
|
45
|
+
|
|
46
|
+
warnings.warn(
|
|
47
|
+
message="gridappsd.field_interface is deprecated and will be removed in a future release. "
|
|
48
|
+
"Use gridappsd_field_bus.field_interface instead.",
|
|
49
|
+
category=DeprecationWarning,
|
|
50
|
+
)
|
|
51
|
+
except ImportError as e:
|
|
52
|
+
_log.error(f"Could not import field_interface: {e}. Install gridappsd-field-bus to get those functions.")
|
gridappsd/goss.py
CHANGED
|
@@ -42,9 +42,11 @@ Created on March 1, 2018
|
|
|
42
42
|
|
|
43
43
|
@author: Craig Allwardt
|
|
44
44
|
"""
|
|
45
|
+
|
|
45
46
|
import base64
|
|
46
47
|
import inspect
|
|
47
|
-
|
|
48
|
+
|
|
49
|
+
# import json
|
|
48
50
|
import logging
|
|
49
51
|
import os
|
|
50
52
|
import random
|
|
@@ -78,22 +80,22 @@ class TimeoutError(Exception):
|
|
|
78
80
|
|
|
79
81
|
|
|
80
82
|
class GOSS(object):
|
|
81
|
-
"""
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
logging.getLogger(
|
|
96
|
-
logging.getLogger(
|
|
83
|
+
"""Base class providing connections to a GOSS instance via stomp protocol"""
|
|
84
|
+
|
|
85
|
+
def __init__(
|
|
86
|
+
self,
|
|
87
|
+
username=None,
|
|
88
|
+
password=None,
|
|
89
|
+
stomp_address="localhost",
|
|
90
|
+
stomp_port="61613",
|
|
91
|
+
attempt_connection=True,
|
|
92
|
+
override_threading=None,
|
|
93
|
+
stomp_log_level=logging.WARNING,
|
|
94
|
+
goss_log_level=logging.INFO,
|
|
95
|
+
use_auth_token=True,
|
|
96
|
+
):
|
|
97
|
+
logging.getLogger("stomp.py").setLevel(stomp_log_level)
|
|
98
|
+
logging.getLogger("goss").setLevel(goss_log_level)
|
|
97
99
|
|
|
98
100
|
self.__user__ = username
|
|
99
101
|
self.__pass__ = password
|
|
@@ -154,12 +156,9 @@ class GOSS(object):
|
|
|
154
156
|
if isinstance(message, list) or isinstance(message, dict):
|
|
155
157
|
message = json.dumps(message)
|
|
156
158
|
_log.debug("Sending topic: {} body: {}".format(topic, message))
|
|
157
|
-
self._conn.send(
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
'GOSS_HAS_SUBJECT': True,
|
|
161
|
-
'GOSS_SUBJECT': self.__token
|
|
162
|
-
})
|
|
159
|
+
self._conn.send(
|
|
160
|
+
body=message, destination=topic, headers={"GOSS_HAS_SUBJECT": True, "GOSS_SUBJECT": self.__token}
|
|
161
|
+
)
|
|
163
162
|
|
|
164
163
|
def get_response(self, topic, message, timeout=5):
|
|
165
164
|
id = datetime.now().strftime("%Y%m%d%h%M%S%f")[:-3]
|
|
@@ -168,8 +167,8 @@ class GOSS(object):
|
|
|
168
167
|
if isinstance(message, str):
|
|
169
168
|
message = json.loads(message)
|
|
170
169
|
|
|
171
|
-
if
|
|
172
|
-
self.result_format = message[
|
|
170
|
+
if "resultFormat" in message:
|
|
171
|
+
self.result_format = message["resultFormat"]
|
|
173
172
|
|
|
174
173
|
# Change message to string if we have a dictionary.
|
|
175
174
|
if isinstance(message, dict):
|
|
@@ -178,17 +177,15 @@ class GOSS(object):
|
|
|
178
177
|
message = json.dumps(message)
|
|
179
178
|
|
|
180
179
|
class ResponseListener(object):
|
|
181
|
-
|
|
182
180
|
def __init__(self, topic, result_format):
|
|
183
181
|
self.response = None
|
|
184
182
|
self._topic = topic
|
|
185
183
|
self.result_format = result_format
|
|
186
184
|
|
|
187
185
|
def on_message(self, header, message):
|
|
188
|
-
|
|
189
186
|
_log.debug("Internal on message is: {} {}".format(header, message))
|
|
190
187
|
try:
|
|
191
|
-
if self.result_format ==
|
|
188
|
+
if self.result_format == "JSON":
|
|
192
189
|
if isinstance(message, dict):
|
|
193
190
|
self.response = message
|
|
194
191
|
else:
|
|
@@ -196,9 +193,7 @@ class GOSS(object):
|
|
|
196
193
|
else:
|
|
197
194
|
self.response = message
|
|
198
195
|
except ValueError:
|
|
199
|
-
self.response = dict(error="Invalid json returned",
|
|
200
|
-
header=header,
|
|
201
|
-
message=message)
|
|
196
|
+
self.response = dict(error="Invalid json returned", header=header, message=message)
|
|
202
197
|
|
|
203
198
|
def on_error(self, headers, message):
|
|
204
199
|
_log.error("ERR: {}".format(headers))
|
|
@@ -210,13 +205,11 @@ class GOSS(object):
|
|
|
210
205
|
listener = ResponseListener(reply_to, self.result_format)
|
|
211
206
|
self.subscribe(reply_to, listener)
|
|
212
207
|
|
|
213
|
-
self._conn.send(
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
'GOSS_SUBJECT': self.__token
|
|
219
|
-
})
|
|
208
|
+
self._conn.send(
|
|
209
|
+
body=message,
|
|
210
|
+
destination=topic,
|
|
211
|
+
headers={"reply-to": reply_to, "GOSS_HAS_SUBJECT": True, "GOSS_SUBJECT": self.__token},
|
|
212
|
+
)
|
|
220
213
|
count = 0
|
|
221
214
|
|
|
222
215
|
while count < timeout:
|
|
@@ -255,8 +248,8 @@ class GOSS(object):
|
|
|
255
248
|
|
|
256
249
|
self._make_connection()
|
|
257
250
|
|
|
258
|
-
if self._conn.get_listener(
|
|
259
|
-
self._conn.set_listener(
|
|
251
|
+
if self._conn.get_listener("gridappsd") is None:
|
|
252
|
+
self._conn.set_listener("gridappsd", self._router_callback)
|
|
260
253
|
|
|
261
254
|
if callable(callback):
|
|
262
255
|
self._router_callback.add_callback(topic, callback)
|
|
@@ -265,13 +258,12 @@ class GOSS(object):
|
|
|
265
258
|
# CallbackWrapperListener(callback, conn_id))
|
|
266
259
|
else:
|
|
267
260
|
# Case where the callback is (supposedly) a class.
|
|
268
|
-
if not hasattr(callback,
|
|
261
|
+
if not hasattr(callback, "on_message"):
|
|
269
262
|
m = "The given callback must have an 'on_message' method!"
|
|
270
263
|
raise AttributeError(m)
|
|
271
264
|
|
|
272
265
|
if not callable(callback.on_message):
|
|
273
|
-
m = "The given callback's 'on_message' attribute must be "
|
|
274
|
-
"callable!"
|
|
266
|
+
m = "The given callback's 'on_message' attribute must be callable!"
|
|
275
267
|
raise TypeError(m)
|
|
276
268
|
|
|
277
269
|
# Fix for https://github.com/GRIDAPPSD/GOSS-GridAPPS-D/issues/1072
|
|
@@ -284,7 +276,7 @@ class GOSS(object):
|
|
|
284
276
|
# CallbackWrapperListener(callback.on_message, conn_id))
|
|
285
277
|
|
|
286
278
|
_log.debug("Subscribing to {topic}".format(topic=topic))
|
|
287
|
-
self._conn.subscribe(destination=topic, ack=
|
|
279
|
+
self._conn.subscribe(destination=topic, ack="auto", id=conn_id)
|
|
288
280
|
|
|
289
281
|
return conn_id
|
|
290
282
|
|
|
@@ -295,7 +287,6 @@ class GOSS(object):
|
|
|
295
287
|
if self._conn is None or not self._conn.is_connected():
|
|
296
288
|
_log.debug("Creating connection")
|
|
297
289
|
if self.use_auth_token is True and not self.__token:
|
|
298
|
-
|
|
299
290
|
# get token
|
|
300
291
|
# get initial connection
|
|
301
292
|
dt = datetime.now()
|
|
@@ -309,13 +300,14 @@ class GOSS(object):
|
|
|
309
300
|
# send request to token topic
|
|
310
301
|
tokenTopic = "/topic/pnnl.goss.token.topic"
|
|
311
302
|
|
|
312
|
-
tmpConn = Connection(
|
|
303
|
+
tmpConn = Connection(
|
|
304
|
+
[(self.stomp_address, self.stomp_port)], heartbeats=(self._heartbeat, self._heartbeat)
|
|
305
|
+
)
|
|
313
306
|
if self._override_thread_fc is not None:
|
|
314
307
|
tmpConn.transport.override_threading(self._override_thread_fc)
|
|
315
308
|
tmpConn.connect(self.__user__, self.__pass__, wait=True)
|
|
316
309
|
|
|
317
|
-
class TokenResponseListener
|
|
318
|
-
|
|
310
|
+
class TokenResponseListener:
|
|
319
311
|
def __init__(self):
|
|
320
312
|
self.__token = None
|
|
321
313
|
|
|
@@ -339,11 +331,9 @@ class GOSS(object):
|
|
|
339
331
|
listener = TokenResponseListener()
|
|
340
332
|
|
|
341
333
|
# self.subscribe(replyDest, listener)
|
|
342
|
-
tmpConn.subscribe(
|
|
343
|
-
tmpConn.set_listener(
|
|
344
|
-
tmpConn.send(body=base64Str,
|
|
345
|
-
destination=tokenTopic,
|
|
346
|
-
headers={'reply-to': replyDest})
|
|
334
|
+
tmpConn.subscribe("/queue/" + replyDest, 123)
|
|
335
|
+
tmpConn.set_listener("token_resp", listener)
|
|
336
|
+
tmpConn.send(body=base64Str, destination=tokenTopic, headers={"reply-to": replyDest})
|
|
347
337
|
# while token is null or for x iterations
|
|
348
338
|
iter = 0
|
|
349
339
|
while not self.__token and iter < 10:
|
|
@@ -352,14 +342,16 @@ class GOSS(object):
|
|
|
352
342
|
sleep(1)
|
|
353
343
|
iter += 1
|
|
354
344
|
|
|
355
|
-
self._conn = Connection(
|
|
345
|
+
self._conn = Connection(
|
|
346
|
+
[(self.stomp_address, self.stomp_port)], heartbeats=(self._heartbeat, self._heartbeat)
|
|
347
|
+
)
|
|
356
348
|
if self._override_thread_fc is not None:
|
|
357
349
|
self._conn.transport.override_threading(self._override_thread_fc)
|
|
358
350
|
try:
|
|
359
351
|
if self.use_auth_token and self.__token is not None:
|
|
360
352
|
self._conn.connect(self.__token, "", wait=True)
|
|
361
353
|
else:
|
|
362
|
-
self._conn.connect(self.__user__,self.__pass__, wait=True)
|
|
354
|
+
self._conn.connect(self.__user__, self.__pass__, wait=True)
|
|
363
355
|
except TypeError as e:
|
|
364
356
|
_log.error("TypeError: {e}".format(e=e))
|
|
365
357
|
except NotConnectedException as e:
|
|
@@ -369,7 +361,6 @@ class GOSS(object):
|
|
|
369
361
|
|
|
370
362
|
|
|
371
363
|
class CallbackRouter(object):
|
|
372
|
-
|
|
373
364
|
def __init__(self):
|
|
374
365
|
self.callbacks = {}
|
|
375
366
|
self._topics_callback_map = defaultdict(list)
|
|
@@ -393,7 +384,7 @@ class CallbackRouter(object):
|
|
|
393
384
|
sleep(0.01)
|
|
394
385
|
|
|
395
386
|
def add_callback(self, topic, callback):
|
|
396
|
-
if not topic.startswith(
|
|
387
|
+
if not topic.startswith("/topic/") and not topic.startswith("/temp-queue/"):
|
|
397
388
|
topic = "/queue/{topic}".format(topic=topic)
|
|
398
389
|
if callback in self._topics_callback_map[topic]:
|
|
399
390
|
raise ValueError("Callbacks can only be used one time per topic")
|
|
@@ -409,7 +400,7 @@ class CallbackRouter(object):
|
|
|
409
400
|
pass
|
|
410
401
|
|
|
411
402
|
def on_message(self, headers, message):
|
|
412
|
-
destination = headers[
|
|
403
|
+
destination = headers["destination"]
|
|
413
404
|
# _log.debug("Topic map keys are: {keys}".format(keys=self._topics_callback_map.keys()))
|
|
414
405
|
if destination in self._topics_callback_map:
|
|
415
406
|
self._queue_callerback.put((self._topics_callback_map[destination], headers, message))
|
|
@@ -421,13 +412,8 @@ class CallbackRouter(object):
|
|
|
421
412
|
_log.error(header)
|
|
422
413
|
_log.error(message)
|
|
423
414
|
|
|
424
|
-
def on_error(self, header, message):
|
|
425
|
-
_log.error("Error in callback router")
|
|
426
|
-
_log.error(header)
|
|
427
|
-
_log.error(message)
|
|
428
|
-
|
|
429
415
|
def on_heartbeat_timeout(self):
|
|
430
416
|
_log.error("Heartbeat timeout")
|
|
431
|
-
|
|
417
|
+
|
|
432
418
|
def on_disconnected(self):
|
|
433
419
|
_log.info("Disconnected")
|