fprime-gds 3.4.3__py3-none-any.whl → 3.4.4a2__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.
- fprime_gds/common/communication/adapters/base.py +30 -58
- fprime_gds/common/communication/adapters/ip.py +23 -5
- fprime_gds/common/communication/adapters/uart.py +20 -7
- fprime_gds/common/communication/checksum.py +1 -3
- fprime_gds/common/communication/framing.py +53 -4
- fprime_gds/common/data_types/event_data.py +6 -1
- fprime_gds/common/data_types/exceptions.py +16 -11
- fprime_gds/common/loaders/ch_json_loader.py +107 -0
- fprime_gds/common/loaders/ch_xml_loader.py +5 -5
- fprime_gds/common/loaders/cmd_json_loader.py +85 -0
- fprime_gds/common/loaders/dict_loader.py +1 -1
- fprime_gds/common/loaders/event_json_loader.py +108 -0
- fprime_gds/common/loaders/event_xml_loader.py +10 -6
- fprime_gds/common/loaders/json_loader.py +222 -0
- fprime_gds/common/loaders/xml_loader.py +31 -9
- fprime_gds/common/pipeline/dictionaries.py +38 -3
- fprime_gds/common/tools/seqgen.py +4 -4
- fprime_gds/common/utils/string_util.py +57 -65
- fprime_gds/common/zmq_transport.py +37 -20
- fprime_gds/executables/apps.py +150 -0
- fprime_gds/executables/cli.py +239 -103
- fprime_gds/executables/comm.py +17 -27
- fprime_gds/executables/data_product_writer.py +935 -0
- fprime_gds/executables/run_deployment.py +55 -14
- fprime_gds/executables/utils.py +24 -12
- fprime_gds/flask/sequence.py +1 -1
- fprime_gds/flask/static/addons/commanding/command-input.js +3 -2
- fprime_gds/plugin/__init__.py +0 -0
- fprime_gds/plugin/definitions.py +71 -0
- fprime_gds/plugin/system.py +225 -0
- {fprime_gds-3.4.3.dist-info → fprime_gds-3.4.4a2.dist-info}/METADATA +3 -2
- {fprime_gds-3.4.3.dist-info → fprime_gds-3.4.4a2.dist-info}/RECORD +37 -28
- {fprime_gds-3.4.3.dist-info → fprime_gds-3.4.4a2.dist-info}/WHEEL +1 -1
- {fprime_gds-3.4.3.dist-info → fprime_gds-3.4.4a2.dist-info}/entry_points.txt +2 -3
- {fprime_gds-3.4.3.dist-info → fprime_gds-3.4.4a2.dist-info}/LICENSE.txt +0 -0
- {fprime_gds-3.4.3.dist-info → fprime_gds-3.4.4a2.dist-info}/NOTICE.txt +0 -0
- {fprime_gds-3.4.3.dist-info → fprime_gds-3.4.4a2.dist-info}/top_level.txt +0 -0
@@ -9,12 +9,51 @@ Note: This function has an identical copy in fprime-gds
|
|
9
9
|
|
10
10
|
import logging
|
11
11
|
import re
|
12
|
+
from typing import Any, Union
|
12
13
|
|
13
14
|
LOGGER = logging.getLogger("string_util_logger")
|
14
15
|
|
15
16
|
|
16
|
-
def format_string_template(
|
17
|
-
|
17
|
+
def format_string_template(template: str, value: Union[tuple, list, Any]) -> str:
|
18
|
+
"""
|
19
|
+
Function to format a string template with values. This function is a simple wrapper around the
|
20
|
+
format function. It accepts a tuple, list, or single value and passes it to the format function
|
21
|
+
|
22
|
+
Args:
|
23
|
+
template (str): String template to be formatted
|
24
|
+
value (Union[tuple, list, Any]): Value(s) to be inserted into the template
|
25
|
+
|
26
|
+
Returns:
|
27
|
+
str: Formatted string
|
28
|
+
"""
|
29
|
+
if not isinstance(value, (tuple, list)):
|
30
|
+
value = (value,)
|
31
|
+
try:
|
32
|
+
return template.format(*value)
|
33
|
+
except (IndexError, ValueError) as e:
|
34
|
+
LOGGER.error(
|
35
|
+
f"Error formatting string template: {template} with value: {str(value)}"
|
36
|
+
)
|
37
|
+
raise e
|
38
|
+
|
39
|
+
|
40
|
+
def preprocess_fpp_format_str(format_str: str) -> str:
|
41
|
+
"""Preprocess a FPP-style format string and convert it to Python format string
|
42
|
+
FPP format strings are documented https://nasa.github.io/fpp/fpp-spec.html#Format-Strings
|
43
|
+
For example "{x}" -> "{:x}" or "{.2f}" -> "{:.2f}"
|
44
|
+
|
45
|
+
Args:
|
46
|
+
format_str (str): FPP-style format string
|
47
|
+
|
48
|
+
Returns:
|
49
|
+
str: Python-style format string
|
50
|
+
"""
|
51
|
+
pattern = r"{(\d*\.?\d*[cdxoefgCDXOEFG])}"
|
52
|
+
return re.sub(pattern, r"{:\1}", format_str)
|
53
|
+
|
54
|
+
|
55
|
+
def preprocess_c_style_format_str(format_str: str) -> str:
|
56
|
+
"""
|
18
57
|
Function to convert C-string style to python format
|
19
58
|
without using python interpolation
|
20
59
|
Considered the following format for C-string:
|
@@ -31,20 +70,26 @@ def format_string_template(format_str, given_values):
|
|
31
70
|
This function will keep the flags, width, and .precision of C-string
|
32
71
|
template.
|
33
72
|
|
34
|
-
It will keep f,
|
35
|
-
Other types will be duck-typed by python
|
36
|
-
interpreter.
|
73
|
+
It will keep f, x, o, and e flags and remove all other types.
|
74
|
+
Other types will be duck-typed by python interpreter.
|
37
75
|
|
38
76
|
lengths will also be removed since they are not meaningful to Python interpreter.
|
39
77
|
`See: https://docs.python.org/3/library/stdtypes.html#printf-style-string-formatting`
|
40
|
-
|
41
78
|
`Regex Source: https://www.regexlib.com/REDetails.aspx?regexp_id=3363`
|
79
|
+
|
80
|
+
For example "%x" -> "{:x}" or "%.2f" -> "{:.2f}"
|
81
|
+
|
82
|
+
Args:
|
83
|
+
format_str (str): C-style format string
|
84
|
+
|
85
|
+
Returns:
|
86
|
+
str: Python-style format string
|
42
87
|
"""
|
43
88
|
|
44
|
-
def convert(match_obj
|
89
|
+
def convert(match_obj: re.Match):
|
45
90
|
if match_obj.group() is None:
|
46
91
|
return match_obj
|
47
|
-
flags, width, precision,
|
92
|
+
flags, width, precision, _, conversion_type = match_obj.groups()
|
48
93
|
format_template = ""
|
49
94
|
if flags:
|
50
95
|
format_template += f"{flags}"
|
@@ -53,66 +98,13 @@ def format_string_template(format_str, given_values):
|
|
53
98
|
if precision:
|
54
99
|
format_template += f"{precision}"
|
55
100
|
|
56
|
-
if conversion_type:
|
57
|
-
|
58
|
-
[
|
59
|
-
str(conversion_type).lower() == "f",
|
60
|
-
str(conversion_type).lower() == "x",
|
61
|
-
str(conversion_type).lower() == "o",
|
62
|
-
str(conversion_type).lower() == "e",
|
63
|
-
]
|
64
|
-
):
|
65
|
-
format_template += f"{conversion_type}"
|
66
|
-
elif all([not ignore_int, str(conversion_type).lower() == "d"]):
|
67
|
-
format_template += f"{conversion_type}"
|
101
|
+
if conversion_type and str(conversion_type).lower() in {"f", "x", "o", "e"}:
|
102
|
+
format_template += f"{conversion_type}"
|
68
103
|
|
69
104
|
return "{}" if format_template == "" else "{:" + format_template + "}"
|
70
105
|
|
71
|
-
|
72
|
-
return convert(match_obj, ignore_int=False)
|
73
|
-
|
74
|
-
def convert_ignore_int(match_obj):
|
75
|
-
return convert(match_obj, ignore_int=True)
|
76
|
-
|
77
|
-
# Allowing single, list and tuple inputs
|
78
|
-
if not isinstance(given_values, (list, tuple)):
|
79
|
-
values = (given_values,)
|
80
|
-
elif isinstance(given_values, list):
|
81
|
-
values = tuple(given_values)
|
82
|
-
else:
|
83
|
-
values = given_values
|
84
|
-
|
85
|
-
pattern = r"(?<!%)(?:%%)*%([\-\+0\ \#])?(\d+|\*)?(\.\*|\.\d+)?([hLIw]|l{1,2}|I32|I64)?([cCdiouxXeEfgGaAnpsSZ])"
|
106
|
+
pattern = r"(?<!%)(?:%%)*%([\-\+0\ \#])?(\d+|\*)?(\.\*|\.\d+)?([hLIw]|l{1,2}|I32|I64)?([cCdiouxXeEfgGaAnpsSZ])" # NOSONAR
|
86
107
|
|
87
108
|
match = re.compile(pattern)
|
88
109
|
|
89
|
-
|
90
|
-
try:
|
91
|
-
formatted_str = re.sub(match, convert_include_all, format_str)
|
92
|
-
result = formatted_str.format(*values)
|
93
|
-
result = result.replace("%%", "%")
|
94
|
-
return result
|
95
|
-
except Exception:
|
96
|
-
msg = "Value and format string do not match. "
|
97
|
-
msg += " Will ignore integer flags `d` in string template. "
|
98
|
-
msg += f"values: {values}. "
|
99
|
-
msg += f"format_str: {format_str}. "
|
100
|
-
msg += f"given_values: {given_values}"
|
101
|
-
LOGGER.warning(msg)
|
102
|
-
|
103
|
-
# Second try by not including %d.
|
104
|
-
# This will resolve failing ENUMs with %d
|
105
|
-
# but will fail on other types.
|
106
|
-
try:
|
107
|
-
formatted_str = re.sub(match, convert_ignore_int, format_str)
|
108
|
-
result = formatted_str.format(*values)
|
109
|
-
result = result.replace("%%", "%")
|
110
|
-
return result
|
111
|
-
except ValueError as e:
|
112
|
-
msg = "Value and format string do not match. "
|
113
|
-
msg += f"values: {values}. "
|
114
|
-
msg += f"format_str: {format_str}. "
|
115
|
-
msg += f"given_values: {given_values}"
|
116
|
-
msg += f"Err Msg: {str(e)}\n"
|
117
|
-
LOGGER.error(msg)
|
118
|
-
raise ValueError
|
110
|
+
return re.sub(match, convert, format_str).replace("%%", "%")
|
@@ -9,6 +9,7 @@ replace the ThreadedTcpServer for several reasons as described below.
|
|
9
9
|
|
10
10
|
@author lestarch
|
11
11
|
"""
|
12
|
+
|
12
13
|
import logging
|
13
14
|
import struct
|
14
15
|
from typing import Tuple
|
@@ -24,6 +25,7 @@ from fprime_gds.common.transport import (
|
|
24
25
|
|
25
26
|
LOGGER = logging.getLogger("transport")
|
26
27
|
|
28
|
+
|
27
29
|
class ZmqWrapper(object):
|
28
30
|
"""Handler for ZMQ functions for use in other objects"""
|
29
31
|
|
@@ -50,7 +52,9 @@ class ZmqWrapper(object):
|
|
50
52
|
sub_topic: subscription topic used to filter incoming messages
|
51
53
|
pub_topic: publication topic supplied for remote subscription filters
|
52
54
|
"""
|
53
|
-
assert
|
55
|
+
assert (
|
56
|
+
len(transport_url) == 2
|
57
|
+
), f"Must supply a pair of URLs for ZeroMQ not '{transport_url}'"
|
54
58
|
self.pub_topic = pub_topic
|
55
59
|
self.sub_topic = sub_topic
|
56
60
|
self.transport_url = transport_url
|
@@ -78,8 +82,12 @@ class ZmqWrapper(object):
|
|
78
82
|
The connection is made using self.transport_url, and as such, this must be configured before running. This is
|
79
83
|
intended to be called on the sending thread.
|
80
84
|
"""
|
81
|
-
assert
|
82
|
-
|
85
|
+
assert (
|
86
|
+
self.transport_url is not None and len(self.transport_url) == 2
|
87
|
+
), "Must configure before connecting"
|
88
|
+
assert (
|
89
|
+
self.zmq_socket_outgoing is None
|
90
|
+
), "Cannot connect outgoing multiple times"
|
83
91
|
assert self.pub_topic is not None, "Must configure sockets before connecting"
|
84
92
|
self.zmq_socket_outgoing = self.context.socket(zmq.PUB)
|
85
93
|
self.zmq_socket_outgoing.setsockopt(zmq.SNDHWM, 0)
|
@@ -93,7 +101,7 @@ class ZmqWrapper(object):
|
|
93
101
|
self.zmq_socket_outgoing.connect(self.transport_url[0])
|
94
102
|
|
95
103
|
def connect_incoming(self):
|
96
|
-
"""
|
104
|
+
"""Sets up a ZeroMQ connection for incoming data
|
97
105
|
|
98
106
|
ZeroMQ allows multiple connections to a single endpoint. This only affects incoming connections as sockets must
|
99
107
|
be created on their owning threads. This will connect the ZeroMQ topology and if self.server is set, will bind
|
@@ -102,8 +110,12 @@ class ZmqWrapper(object):
|
|
102
110
|
The connection is made using self.transport_url, and as such, this must be configured before running. This is
|
103
111
|
intended to be called on the receiving thread.
|
104
112
|
"""
|
105
|
-
assert
|
106
|
-
|
113
|
+
assert (
|
114
|
+
self.transport_url is not None and len(self.transport_url) == 2
|
115
|
+
), "Must configure before connecting"
|
116
|
+
assert (
|
117
|
+
self.zmq_socket_incoming is None
|
118
|
+
), "Cannot connect incoming multiple times"
|
107
119
|
assert self.sub_topic is not None, "Must configure sockets before connecting"
|
108
120
|
self.zmq_socket_incoming = self.context.socket(zmq.SUB)
|
109
121
|
self.zmq_socket_incoming.setsockopt(zmq.RCVHWM, 0)
|
@@ -118,14 +130,16 @@ class ZmqWrapper(object):
|
|
118
130
|
|
119
131
|
def disconnect_outgoing(self):
|
120
132
|
"""Disconnect the ZeroMQ sockets"""
|
121
|
-
self.zmq_socket_outgoing
|
133
|
+
if self.zmq_socket_outgoing is not None:
|
134
|
+
self.zmq_socket_outgoing.close()
|
122
135
|
|
123
136
|
def disconnect_incoming(self):
|
124
137
|
"""Disconnect the ZeroMQ sockets"""
|
125
|
-
self.zmq_socket_incoming
|
138
|
+
if self.zmq_socket_incoming is not None:
|
139
|
+
self.zmq_socket_incoming.close()
|
126
140
|
|
127
141
|
def terminate(self):
|
128
|
-
"""
|
142
|
+
"""Terminate the ZeroMQ context"""
|
129
143
|
self.context.term()
|
130
144
|
|
131
145
|
def recv(self, timeout=None):
|
@@ -162,11 +176,14 @@ class ZmqClient(ThreadedTransportClient):
|
|
162
176
|
self.zmq = ZmqWrapper()
|
163
177
|
|
164
178
|
def connect(
|
165
|
-
self,
|
179
|
+
self,
|
180
|
+
transport_url: Tuple[str],
|
181
|
+
sub_routing: RoutingTag,
|
182
|
+
pub_routing: RoutingTag,
|
166
183
|
):
|
167
184
|
"""Connects to the ZeroMQ network"""
|
168
185
|
self.zmq.configure(transport_url, sub_routing.value, pub_routing.value)
|
169
|
-
self.zmq.connect_outgoing()
|
186
|
+
self.zmq.connect_outgoing() # Outgoing socket, for clients, exists on the current thread
|
170
187
|
super().connect(transport_url, sub_routing, pub_routing)
|
171
188
|
|
172
189
|
def disconnect(self):
|
@@ -176,16 +193,18 @@ class ZmqClient(ThreadedTransportClient):
|
|
176
193
|
|
177
194
|
def send(self, data):
|
178
195
|
"""Send data via ZeroMQ"""
|
179
|
-
if data[:4] == b
|
196
|
+
if data[:4] == b"ZZZZ":
|
180
197
|
data = data[4:]
|
181
|
-
self.zmq.send(
|
198
|
+
self.zmq.send(
|
199
|
+
data
|
200
|
+
) # Must strip out ZZZZ as that is a ThreadedTcpServer only property
|
182
201
|
|
183
202
|
def recv(self, timeout=None):
|
184
203
|
"""Receives data from ZeroMQ"""
|
185
204
|
return self.zmq.recv(timeout)
|
186
205
|
|
187
206
|
def recv_thread(self):
|
188
|
-
"""
|
207
|
+
"""Overrides the recv_thread method
|
189
208
|
|
190
209
|
Overrides the recv_thread method of the superclass such that the ZeroMQ socket may be created/destroyed
|
191
210
|
before/after the main recv loop.
|
@@ -203,11 +222,11 @@ class ZmqGround(GroundHandler):
|
|
203
222
|
to the display and processing layer(s). This effectively acts as the "FSW" side of that interface as it
|
204
223
|
frames/deframes packets heading to that layer.
|
205
224
|
|
206
|
-
Since there is likely only one communications client to the FSW users should
|
225
|
+
Since there is likely only one communications client to the FSW users should instantiate with server=True
|
207
226
|
to ensure that it binds to resources for the network. This is not forced in case of multiple FSW connections.
|
208
227
|
"""
|
209
228
|
|
210
|
-
def __init__(self, transport_url):
|
229
|
+
def __init__(self, transport_url, server=True):
|
211
230
|
"""Initialize this interface with the transport_url needed to connect
|
212
231
|
|
213
232
|
Args:
|
@@ -217,6 +236,8 @@ class ZmqGround(GroundHandler):
|
|
217
236
|
self.zmq = ZmqWrapper()
|
218
237
|
self.transport_url = transport_url
|
219
238
|
self.timeout = 10
|
239
|
+
if server:
|
240
|
+
self.zmq.make_server()
|
220
241
|
|
221
242
|
def open(self):
|
222
243
|
"""Open this ground interface. Delegates to the connect method
|
@@ -242,10 +263,6 @@ class ZmqGround(GroundHandler):
|
|
242
263
|
self.zmq.disconnect_outgoing()
|
243
264
|
self.zmq.terminate()
|
244
265
|
|
245
|
-
def make_server(self):
|
246
|
-
"""Makes it into a server"""
|
247
|
-
self.zmq.make_server()
|
248
|
-
|
249
266
|
def receive_all(self):
|
250
267
|
"""Receive all available packets
|
251
268
|
|
@@ -0,0 +1,150 @@
|
|
1
|
+
""" fprime_gds.executables.apps: an implementation of start-up apps in fprime
|
2
|
+
|
3
|
+
There are twp ways to approach start=up applications in fprime. First, is to implement a run method via a subclass of
|
4
|
+
`GdsFunction`. This gives the implementor the ability to run anything within the run function that python offers,
|
5
|
+
however; this comes with complexity of setting up a new thread/process/isolation to ensure that the plugin does not
|
6
|
+
threaten the fprime-gds core functionality and processes.
|
7
|
+
|
8
|
+
The second method is to inherit from `GdsApp` implementing the `get_process_invocation` function to return the necessary
|
9
|
+
command line that will be spun into its own process.
|
10
|
+
|
11
|
+
@author lestarch
|
12
|
+
"""
|
13
|
+
import subprocess
|
14
|
+
from abc import ABC, abstractmethod
|
15
|
+
from typing import List, Type
|
16
|
+
|
17
|
+
from fprime_gds.plugin.definitions import gds_plugin_specification
|
18
|
+
|
19
|
+
|
20
|
+
class GdsBaseFunction(ABC):
|
21
|
+
""" Base functionality for pluggable GDS start-up functions
|
22
|
+
|
23
|
+
GDS start-up functionality is pluggable. This class acts as a base for pluggable functionality supplies helpers to
|
24
|
+
the various start-up plugins.
|
25
|
+
|
26
|
+
Developers who intend to run in an isolated subprocess are strongly encouraged to use `GdsApp` (see below).
|
27
|
+
Developers who need flexibility may use GdsFunction.
|
28
|
+
"""
|
29
|
+
|
30
|
+
@abstractmethod
|
31
|
+
def run(self):
|
32
|
+
""" Run the start-up function
|
33
|
+
|
34
|
+
Run the start-up function unconstrained by the limitations of running in a dedicated subprocess.
|
35
|
+
|
36
|
+
"""
|
37
|
+
raise NotImplementedError()
|
38
|
+
|
39
|
+
|
40
|
+
class GdsFunction(GdsBaseFunction, ABC):
|
41
|
+
""" Functionality for pluggable GDS start-up functions
|
42
|
+
|
43
|
+
GDS start-up functionality is pluggable. This class acts as a wide-open implementation of functionality via a single
|
44
|
+
`run` callback. Developers have complete control of the start-up functionality. However, this comes at the cost of
|
45
|
+
instability in that case of poorly designed functions.
|
46
|
+
|
47
|
+
Developers who intend to run in an isolated subprocess are strongly encouraged to use `GdsApp` (see below).
|
48
|
+
|
49
|
+
Plugin developers are required to implement a single function `run`, which must take care of setting up and running
|
50
|
+
the start-up function. Developers **must** handle the isolation of this functionality including spinning off a new
|
51
|
+
thread, subprocess, etc. Additionally, the developer must define the `register_gds_function_plugin` class method
|
52
|
+
annotated with the @gds_plugin_implementation annotation.
|
53
|
+
|
54
|
+
Standard plug-in functions (get_name, get_arguments) are available should the implementer desire these features.
|
55
|
+
Arguments will be supplied to the class's `__init__` function.
|
56
|
+
"""
|
57
|
+
|
58
|
+
@classmethod
|
59
|
+
@gds_plugin_specification
|
60
|
+
def register_gds_function_plugin(cls) -> Type["GdsFunction"]:
|
61
|
+
"""Register gds start-up functionality
|
62
|
+
|
63
|
+
Plugin hook for registering a plugin that supplies start-up functionality. This functionality will run on start-up
|
64
|
+
of the GDS network.
|
65
|
+
|
66
|
+
Note: users should return the class, not an instance of the class. Needed arguments for instantiation are
|
67
|
+
determined from class methods, solicited via the command line, and provided at construction time to the chosen
|
68
|
+
instantiation.
|
69
|
+
|
70
|
+
Returns:
|
71
|
+
GDSFunction subclass
|
72
|
+
"""
|
73
|
+
raise NotImplementedError()
|
74
|
+
|
75
|
+
|
76
|
+
class GdsApp(GdsBaseFunction):
|
77
|
+
""" GDS start-up process functionality
|
78
|
+
|
79
|
+
A pluggable base class used to start a new process as part of the GDS command line invocation. This allows
|
80
|
+
developers to add process-isolated functionality to the GDS network.
|
81
|
+
|
82
|
+
Plugin developers are required to implement the `get_process_invocation` function that returns a list of arguments
|
83
|
+
needed to invoke the process via python's `subprocess`. Additionally, the developer must define the
|
84
|
+
`register_gds_function_plugin` class method annotated with the @gds_plugin_implementation annotation.
|
85
|
+
|
86
|
+
Standard plug-in functions (get_name, get_arguments) are available should the implementer desire these features.
|
87
|
+
Arguments will be supplied to the class's `__init__` function.
|
88
|
+
"""
|
89
|
+
def __init__(self, **arguments):
|
90
|
+
""" Construct the communication applications around the arguments
|
91
|
+
|
92
|
+
Command line arguments are passed in to match those returned from the `get_arguments` functions.
|
93
|
+
|
94
|
+
Args:
|
95
|
+
arguments: arguments from the command line
|
96
|
+
"""
|
97
|
+
self.process = None
|
98
|
+
self.arguments = arguments
|
99
|
+
|
100
|
+
def run(self):
|
101
|
+
""" Run the application as an isolated process
|
102
|
+
|
103
|
+
GdsFunction objects require an implementation of the `run` command. This implementation will take the arguments
|
104
|
+
provided from `get_process_invocation` function and supplies them as an invocation of the isolated subprocess.
|
105
|
+
"""
|
106
|
+
invocation_arguments = self.get_process_invocation()
|
107
|
+
self.process = subprocess.Popen(invocation_arguments)
|
108
|
+
|
109
|
+
def wait(self, timeout=None):
|
110
|
+
""" Wait for the app to complete then return the return code
|
111
|
+
|
112
|
+
Waits (blocking) for the process to complete. Then returns the return code of the underlying process. If timeout
|
113
|
+
is non-None then the process will be killed after waiting for the timeout and another wait of timeout will be
|
114
|
+
allowed for the killed process to exit.
|
115
|
+
|
116
|
+
Return:
|
117
|
+
return code of the underlying process
|
118
|
+
"""
|
119
|
+
try:
|
120
|
+
_, _ = self.process.wait(timeout=timeout)
|
121
|
+
except subprocess.TimeoutExpired:
|
122
|
+
self.process.kill()
|
123
|
+
_, _ = self.process.wait(timeout=timeout)
|
124
|
+
return self.process.returncode
|
125
|
+
|
126
|
+
@abstractmethod
|
127
|
+
def get_process_invocation(self) -> List[str]:
|
128
|
+
""" Run the start-up function
|
129
|
+
|
130
|
+
Run the start-up function unconstrained by the limitations of running in a dedicated subprocess.
|
131
|
+
|
132
|
+
"""
|
133
|
+
raise NotImplementedError()
|
134
|
+
|
135
|
+
@classmethod
|
136
|
+
@gds_plugin_specification
|
137
|
+
def register_gds_app_plugin(cls) -> Type["GdsApp"]:
|
138
|
+
"""Register a gds start-up application
|
139
|
+
|
140
|
+
Plugin hook for registering a plugin that supplies start-up functionality. This functionality will run on start-up
|
141
|
+
of the GDS network isolated into a dedicated process.
|
142
|
+
|
143
|
+
Note: users should return the class, not an instance of the class. Needed arguments for instantiation are
|
144
|
+
determined from class methods, solicited via the command line, and provided at construction time to the chosen
|
145
|
+
instantiation.
|
146
|
+
|
147
|
+
Returns:
|
148
|
+
GdsApp subclass
|
149
|
+
"""
|
150
|
+
raise NotImplementedError()
|