nost-tools 2.1.1__py3-none-any.whl → 2.3.0__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.
Potentially problematic release.
This version of nost-tools might be problematic. Click here for more details.
- nost_tools/__init__.py +1 -1
- nost_tools/application.py +120 -50
- nost_tools/application_utils.py +6 -7
- nost_tools/configuration.py +41 -7
- nost_tools/managed_application.py +50 -41
- nost_tools/manager.py +115 -33
- nost_tools/schemas.py +82 -8
- nost_tools/simulator.py +1 -3
- {nost_tools-2.1.1.dist-info → nost_tools-2.3.0.dist-info}/METADATA +1 -1
- nost_tools-2.3.0.dist-info/RECORD +18 -0
- {nost_tools-2.1.1.dist-info → nost_tools-2.3.0.dist-info}/WHEEL +1 -1
- nost_tools-2.1.1.dist-info/RECORD +0 -18
- {nost_tools-2.1.1.dist-info → nost_tools-2.3.0.dist-info}/licenses/LICENSE +0 -0
- {nost_tools-2.1.1.dist-info → nost_tools-2.3.0.dist-info}/top_level.txt +0 -0
nost_tools/__init__.py
CHANGED
nost_tools/application.py
CHANGED
|
@@ -48,13 +48,19 @@ class Application:
|
|
|
48
48
|
time_status_init (:obj:`datetime`): Scenario time of first time status message
|
|
49
49
|
"""
|
|
50
50
|
|
|
51
|
-
def __init__(
|
|
51
|
+
def __init__(
|
|
52
|
+
self,
|
|
53
|
+
app_name: str,
|
|
54
|
+
app_description: str = None,
|
|
55
|
+
setup_signal_handlers: bool = True,
|
|
56
|
+
):
|
|
52
57
|
"""
|
|
53
58
|
Initializes a new application.
|
|
54
59
|
|
|
55
60
|
Args:
|
|
56
61
|
app_name (str): application name
|
|
57
62
|
app_description (str): application description (optional)
|
|
63
|
+
setup_signal_handlers (bool): whether to set up signal handlers (default: True)
|
|
58
64
|
"""
|
|
59
65
|
self.simulator = Simulator()
|
|
60
66
|
self.connection = None
|
|
@@ -85,8 +91,12 @@ class Application:
|
|
|
85
91
|
self._token_refresh_thread = None
|
|
86
92
|
self.token_refresh_interval = None
|
|
87
93
|
self._reconnect_delay = None
|
|
94
|
+
# Offset
|
|
95
|
+
self._wallclock_refresh_thread = None
|
|
96
|
+
self.wallclock_offset_refresh_interval = None
|
|
88
97
|
# Set up signal handlers for graceful shutdown
|
|
89
|
-
|
|
98
|
+
if setup_signal_handlers:
|
|
99
|
+
self._setup_signal_handlers()
|
|
90
100
|
|
|
91
101
|
def _setup_signal_handlers(self):
|
|
92
102
|
"""
|
|
@@ -194,6 +204,42 @@ class Application:
|
|
|
194
204
|
self._token_refresh_thread.start()
|
|
195
205
|
logger.debug("Starting refresh token thread successfully completed.")
|
|
196
206
|
|
|
207
|
+
def start_wallclock_refresh_thread(self): # , interval=30, host="pool.ntp.org"):
|
|
208
|
+
"""
|
|
209
|
+
Starts a background thread to refresh the wallclock offset periodically.
|
|
210
|
+
|
|
211
|
+
Args:
|
|
212
|
+
interval (int): Seconds between wallclock offset refreshes (default: 3600 seconds/1 hour)
|
|
213
|
+
host (str): NTP host to query (default: 'pool.ntp.org')
|
|
214
|
+
"""
|
|
215
|
+
logger.debug("Starting wallclock offset refresh thread.")
|
|
216
|
+
|
|
217
|
+
def refresh_wallclock_periodically():
|
|
218
|
+
while not self._should_stop.wait(
|
|
219
|
+
timeout=self.config.rc.wallclock_offset_properties.wallclock_offset_refresh_interval
|
|
220
|
+
):
|
|
221
|
+
logger.debug("Wallclock refresh thread is running.")
|
|
222
|
+
try:
|
|
223
|
+
logger.info(
|
|
224
|
+
f"Contacting {self.config.rc.wallclock_offset_properties.ntp_host} to retrieve wallclock offset."
|
|
225
|
+
)
|
|
226
|
+
response = ntplib.NTPClient().request(
|
|
227
|
+
self.config.rc.wallclock_offset_properties.ntp_host,
|
|
228
|
+
version=3,
|
|
229
|
+
timeout=2,
|
|
230
|
+
)
|
|
231
|
+
offset = timedelta(seconds=response.offset)
|
|
232
|
+
self.simulator.set_wallclock_offset(offset)
|
|
233
|
+
logger.info(f"Wallclock offset updated to {offset}.")
|
|
234
|
+
except Exception as e:
|
|
235
|
+
logger.debug(f"Failed to refresh wallclock offset: {e}")
|
|
236
|
+
|
|
237
|
+
self._wallclock_refresh_thread = threading.Thread(
|
|
238
|
+
target=refresh_wallclock_periodically
|
|
239
|
+
)
|
|
240
|
+
self._wallclock_refresh_thread.start()
|
|
241
|
+
logger.debug("Starting wallclock offset refresh thread successfully completed.")
|
|
242
|
+
|
|
197
243
|
def update_connection_credentials(self, access_token):
|
|
198
244
|
"""
|
|
199
245
|
Updates the connection credentials with the new access token.
|
|
@@ -203,14 +249,33 @@ class Application:
|
|
|
203
249
|
"""
|
|
204
250
|
self.connection.update_secret(access_token, "secret")
|
|
205
251
|
|
|
252
|
+
def _get_parameters_from_config(self):
|
|
253
|
+
"""
|
|
254
|
+
Gets application parameters from configuration or returns None if not available.
|
|
255
|
+
This method can be overridden by subclasses to customize parameter retrieval.
|
|
256
|
+
|
|
257
|
+
Returns:
|
|
258
|
+
object: Configuration parameters object or None
|
|
259
|
+
"""
|
|
260
|
+
if self.config and self.config.rc.yaml_file:
|
|
261
|
+
try:
|
|
262
|
+
return getattr(
|
|
263
|
+
self.config.rc.simulation_configuration.execution_parameters,
|
|
264
|
+
"application",
|
|
265
|
+
None,
|
|
266
|
+
)
|
|
267
|
+
except (AttributeError, KeyError):
|
|
268
|
+
return None
|
|
269
|
+
return None
|
|
270
|
+
|
|
206
271
|
def start_up(
|
|
207
272
|
self,
|
|
208
273
|
prefix: str,
|
|
209
274
|
config: ConnectionConfig,
|
|
210
|
-
set_offset: bool =
|
|
275
|
+
set_offset: bool = True,
|
|
211
276
|
time_status_step: timedelta = None,
|
|
212
277
|
time_status_init: datetime = None,
|
|
213
|
-
shut_down_when_terminated: bool =
|
|
278
|
+
shut_down_when_terminated: bool = False,
|
|
214
279
|
) -> None:
|
|
215
280
|
"""
|
|
216
281
|
Starts up the application to prepare for scenario execution.
|
|
@@ -225,31 +290,45 @@ class Application:
|
|
|
225
290
|
time_status_init (:obj:`datetime`): scenario time for first time status message
|
|
226
291
|
shut_down_when_terminated (bool): True, if the application should shut down when the simulation is terminated
|
|
227
292
|
"""
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
293
|
+
self.config = config
|
|
294
|
+
|
|
295
|
+
if self.config.rc.yaml_file:
|
|
296
|
+
logger.info(
|
|
297
|
+
f"Collecting start up parameters from YAML configuration file: {self.config.rc.yaml_file}"
|
|
298
|
+
)
|
|
299
|
+
parameters = self._get_parameters_from_config()
|
|
300
|
+
if parameters:
|
|
301
|
+
self.set_offset = getattr(parameters, "set_offset", set_offset)
|
|
302
|
+
self.time_status_step = getattr(
|
|
303
|
+
parameters, "time_status_step", time_status_step
|
|
304
|
+
)
|
|
305
|
+
self.time_status_init = getattr(
|
|
306
|
+
parameters, "time_status_init", time_status_init
|
|
307
|
+
)
|
|
308
|
+
self.shut_down_when_terminated = getattr(
|
|
309
|
+
parameters, "shut_down_when_terminated", shut_down_when_terminated
|
|
310
|
+
)
|
|
311
|
+
else:
|
|
312
|
+
logger.warning("No parameters found in configuration, using defaults")
|
|
313
|
+
self.set_offset = set_offset
|
|
314
|
+
self.time_status_step = time_status_step
|
|
315
|
+
self.time_status_init = time_status_init
|
|
316
|
+
self.shut_down_when_terminated = shut_down_when_terminated
|
|
317
|
+
else:
|
|
318
|
+
logger.info(
|
|
319
|
+
f"Collecting start up parameters from user input or default values."
|
|
320
|
+
)
|
|
234
321
|
self.set_offset = set_offset
|
|
235
322
|
self.time_status_step = time_status_step
|
|
236
323
|
self.time_status_init = time_status_init
|
|
237
324
|
self.shut_down_when_terminated = shut_down_when_terminated
|
|
238
|
-
else:
|
|
239
|
-
self.config = config
|
|
240
|
-
parameters = getattr(
|
|
241
|
-
self.config.rc.simulation_configuration.execution_parameters,
|
|
242
|
-
self.app_name,
|
|
243
|
-
None,
|
|
244
|
-
)
|
|
245
|
-
self.set_offset = parameters.set_offset
|
|
246
|
-
self.time_status_step = parameters.time_status_step
|
|
247
|
-
self.time_status_init = parameters.time_status_init
|
|
248
|
-
self.shut_down_when_terminated = parameters.shut_down_when_terminated
|
|
249
325
|
|
|
250
326
|
if self.set_offset:
|
|
251
|
-
#
|
|
252
|
-
|
|
327
|
+
# Start periodic wallclock offset updates instead of one-time call
|
|
328
|
+
logger.info(
|
|
329
|
+
f"Wallclock offset will be set every {self.config.rc.wallclock_offset_properties.wallclock_offset_refresh_interval} seconds using {self.config.rc.wallclock_offset_properties.ntp_host}."
|
|
330
|
+
)
|
|
331
|
+
self.start_wallclock_refresh_thread()
|
|
253
332
|
|
|
254
333
|
# Set the prefix and configuration parameters
|
|
255
334
|
self.prefix = prefix
|
|
@@ -290,7 +369,6 @@ class Application:
|
|
|
290
369
|
locale=config.rc.server_configuration.servers.rabbitmq.locale,
|
|
291
370
|
blocked_connection_timeout=config.rc.server_configuration.servers.rabbitmq.blocked_connection_timeout,
|
|
292
371
|
)
|
|
293
|
-
logger.info(parameters)
|
|
294
372
|
|
|
295
373
|
# Configure transport layer security (TLS) if needed
|
|
296
374
|
if self.config.rc.server_configuration.servers.rabbitmq.tls:
|
|
@@ -1348,34 +1426,26 @@ class Application:
|
|
|
1348
1426
|
)
|
|
1349
1427
|
else:
|
|
1350
1428
|
logger.info("Closing token refresh thread completed successfully")
|
|
1351
|
-
|
|
1429
|
+
# Also stop wallclock refresh thread if it exists
|
|
1430
|
+
if (
|
|
1431
|
+
hasattr(self, "_wallclock_refresh_thread")
|
|
1432
|
+
and self._wallclock_refresh_thread
|
|
1433
|
+
and self._wallclock_refresh_thread.is_alive()
|
|
1434
|
+
):
|
|
1435
|
+
logger.info("Closing wallclock refresh thread.")
|
|
1436
|
+
# Set a timeout to avoid hanging indefinitely
|
|
1437
|
+
self._wallclock_refresh_thread.join(timeout=60.0)
|
|
1438
|
+
# Check if it's still alive after timeout
|
|
1439
|
+
if self._wallclock_refresh_thread.is_alive():
|
|
1440
|
+
logger.warning(
|
|
1441
|
+
"Closing wallclock refresh thread timed out after 60 seconds. "
|
|
1442
|
+
)
|
|
1443
|
+
else:
|
|
1444
|
+
logger.info(
|
|
1445
|
+
"Closing wallclock refresh thread completed successfully"
|
|
1446
|
+
)
|
|
1352
1447
|
logger.debug("Stop_application completed successfully.")
|
|
1353
1448
|
|
|
1354
|
-
def set_wallclock_offset(
|
|
1355
|
-
self, host="pool.ntp.org", retry_delay_s: int = 5, max_retry: int = 5
|
|
1356
|
-
) -> None:
|
|
1357
|
-
"""
|
|
1358
|
-
Issues a Network Time Protocol (NTP) request to determine the system clock offset.
|
|
1359
|
-
|
|
1360
|
-
Args:
|
|
1361
|
-
host (str): NTP host (default: 'pool.ntp.org')
|
|
1362
|
-
retry_delay_s (int): number of seconds to wait before retrying
|
|
1363
|
-
max_retry (int): maximum number of retries allowed
|
|
1364
|
-
"""
|
|
1365
|
-
for i in range(max_retry):
|
|
1366
|
-
try:
|
|
1367
|
-
logger.info(f"Contacting {host} to retrieve wallclock offset.")
|
|
1368
|
-
response = ntplib.NTPClient().request(host, version=3, timeout=2)
|
|
1369
|
-
offset = timedelta(seconds=response.offset)
|
|
1370
|
-
self.simulator.set_wallclock_offset(offset)
|
|
1371
|
-
logger.info(f"Wallclock offset updated to {offset}.")
|
|
1372
|
-
return
|
|
1373
|
-
except ntplib.NTPException:
|
|
1374
|
-
logger.warning(
|
|
1375
|
-
f"Could not connect to {host}, attempt #{i+1}/{max_retry} in {retry_delay_s} s."
|
|
1376
|
-
)
|
|
1377
|
-
time.sleep(retry_delay_s)
|
|
1378
|
-
|
|
1379
1449
|
def _create_time_status_publisher(
|
|
1380
1450
|
self, time_status_step: timedelta, time_status_init: datetime
|
|
1381
1451
|
) -> None:
|
nost_tools/application_utils.py
CHANGED
|
@@ -253,10 +253,9 @@ class ModeStatusObserver(Observer):
|
|
|
253
253
|
if not isinstance(self.app.prefix, str):
|
|
254
254
|
raise ValueError(f"Exchange ({self.app.prefix}) must be a string")
|
|
255
255
|
|
|
256
|
-
#
|
|
257
|
-
|
|
258
|
-
self.app.
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
)
|
|
256
|
+
# if self.app.channel.is_open and self.app.connection.is_open:
|
|
257
|
+
self.app.send_message(
|
|
258
|
+
app_name=self.app.app_name,
|
|
259
|
+
app_topics="status.mode",
|
|
260
|
+
payload=status.model_dump_json(by_alias=True, exclude_none=True),
|
|
261
|
+
)
|
nost_tools/configuration.py
CHANGED
|
@@ -21,10 +21,12 @@ from .schemas import (
|
|
|
21
21
|
RuntimeConfig,
|
|
22
22
|
ServersConfig,
|
|
23
23
|
SimulationConfig,
|
|
24
|
+
WallclockOffsetProperties,
|
|
24
25
|
)
|
|
25
26
|
|
|
26
27
|
logger = logging.getLogger(__name__)
|
|
27
28
|
|
|
29
|
+
|
|
28
30
|
class ConnectionConfig:
|
|
29
31
|
"""Connection configuration.
|
|
30
32
|
|
|
@@ -51,6 +53,7 @@ class ConnectionConfig:
|
|
|
51
53
|
password: str = None,
|
|
52
54
|
rabbitmq_host: str = None,
|
|
53
55
|
rabbitmq_port: int = None,
|
|
56
|
+
keycloak_authentication: bool = False,
|
|
54
57
|
keycloak_host: str = None,
|
|
55
58
|
keycloak_port: int = None,
|
|
56
59
|
keycloak_realm: str = None,
|
|
@@ -59,6 +62,7 @@ class ConnectionConfig:
|
|
|
59
62
|
virtual_host: str = None,
|
|
60
63
|
is_tls: bool = True,
|
|
61
64
|
yaml_file: str = None,
|
|
65
|
+
app_name: str = None,
|
|
62
66
|
):
|
|
63
67
|
"""
|
|
64
68
|
Initializes a new connection configuration.
|
|
@@ -68,6 +72,7 @@ class ConnectionConfig:
|
|
|
68
72
|
password (str): client password, provided by NOS-T operator
|
|
69
73
|
host (str): broker hostname
|
|
70
74
|
rabbitmq_port (int): RabbitMQ broker port number
|
|
75
|
+
keycloak_authentication (bool): True, if Keycloak IAM authentication is used
|
|
71
76
|
keycloak_port (int): Keycloak IAM port number
|
|
72
77
|
keycloak_realm (str): Keycloak realm name
|
|
73
78
|
client_id (str): Keycloak client ID
|
|
@@ -75,12 +80,14 @@ class ConnectionConfig:
|
|
|
75
80
|
virtual_host (str): RabbitMQ virtual host
|
|
76
81
|
is_tls (bool): True, if the connection uses TLS
|
|
77
82
|
yaml_file (str): Path to the YAML configuration file
|
|
83
|
+
app_name (str): Name of the application to get specific configuration for
|
|
78
84
|
"""
|
|
79
85
|
self.username = username
|
|
80
86
|
self.password = password
|
|
81
87
|
self.rabbitmq_host = rabbitmq_host
|
|
82
88
|
self.keycloak_host = keycloak_host
|
|
83
89
|
self.rabbitmq_port = rabbitmq_port
|
|
90
|
+
self.keycloak_authentication = keycloak_authentication
|
|
84
91
|
self.keycloak_port = keycloak_port
|
|
85
92
|
self.keycloak_realm = keycloak_realm
|
|
86
93
|
self.client_id = client_id
|
|
@@ -93,6 +100,8 @@ class ConnectionConfig:
|
|
|
93
100
|
self.yaml_file = yaml_file
|
|
94
101
|
self.unique_exchanges = {}
|
|
95
102
|
self.channel_configs = []
|
|
103
|
+
self.app_name = app_name
|
|
104
|
+
self.app_specific = None
|
|
96
105
|
|
|
97
106
|
self.create_connection_config()
|
|
98
107
|
|
|
@@ -208,6 +217,29 @@ class ConnectionConfig:
|
|
|
208
217
|
except ValidationError as err:
|
|
209
218
|
raise EnvironmentVariableError(f"Invalid environment variables: {err}")
|
|
210
219
|
|
|
220
|
+
def get_app_specific_config(self, app_name):
|
|
221
|
+
"""
|
|
222
|
+
Get application-specific configuration from execution.managed_applications if available.
|
|
223
|
+
|
|
224
|
+
Args:
|
|
225
|
+
app_name (str): Name of the application
|
|
226
|
+
|
|
227
|
+
Returns:
|
|
228
|
+
dict: Application-specific configuration parameters if available, otherwise None.
|
|
229
|
+
"""
|
|
230
|
+
if not os.path.exists(self.yaml_file):
|
|
231
|
+
raise ConfigurationError("Couldn't load config file (not found)")
|
|
232
|
+
|
|
233
|
+
with open(self.yaml_file, "r", encoding="utf-8") as f:
|
|
234
|
+
yaml_data = yaml.safe_load(f)
|
|
235
|
+
|
|
236
|
+
try:
|
|
237
|
+
return yaml_data["execution"]["managed_applications"][app_name][
|
|
238
|
+
"configuration_parameters"
|
|
239
|
+
]
|
|
240
|
+
except:
|
|
241
|
+
return None
|
|
242
|
+
|
|
211
243
|
def load_yaml_config_file(self):
|
|
212
244
|
"""
|
|
213
245
|
Loads a YAML configuration file and returns the parsed data.
|
|
@@ -243,6 +275,9 @@ class ConnectionConfig:
|
|
|
243
275
|
), "Application names do not match the channels defined in the configuration file."
|
|
244
276
|
except ConfigAssertionError as e:
|
|
245
277
|
raise ValueError(f"Assertion error: {e}")
|
|
278
|
+
# Load app-specific configuration if app_name is provided
|
|
279
|
+
if self.app_name:
|
|
280
|
+
self.app_specific = self.get_app_specific_config(self.app_name)
|
|
246
281
|
else:
|
|
247
282
|
try:
|
|
248
283
|
self.yaml_config = Config(
|
|
@@ -252,6 +287,7 @@ class ConnectionConfig:
|
|
|
252
287
|
port=self.rabbitmq_port,
|
|
253
288
|
virtual_host=self.virtual_host,
|
|
254
289
|
tls=self.is_tls,
|
|
290
|
+
keycloak_authentication=self.keycloak_authentication,
|
|
255
291
|
),
|
|
256
292
|
keycloak=KeycloakConfig(
|
|
257
293
|
host=self.keycloak_host,
|
|
@@ -281,13 +317,8 @@ class ConnectionConfig:
|
|
|
281
317
|
del server_config.execution
|
|
282
318
|
self.server_config = server_config
|
|
283
319
|
|
|
284
|
-
if
|
|
285
|
-
|
|
286
|
-
and self.password is not None
|
|
287
|
-
and self.client_id is not None
|
|
288
|
-
and self.client_secret_key is not None
|
|
289
|
-
):
|
|
290
|
-
logger.info("Using provided credentials.")
|
|
320
|
+
if self.username is not None and self.password is not None:
|
|
321
|
+
logger.info("Using user-provided credentials.")
|
|
291
322
|
self.credentials_config = Credentials(
|
|
292
323
|
username=self.username,
|
|
293
324
|
password=self.password,
|
|
@@ -298,7 +329,10 @@ class ConnectionConfig:
|
|
|
298
329
|
self.load_environment_variables()
|
|
299
330
|
|
|
300
331
|
self.rc = RuntimeConfig(
|
|
332
|
+
wallclock_offset_properties=WallclockOffsetProperties(),
|
|
301
333
|
credentials=self.credentials_config,
|
|
302
334
|
server_configuration=server_config,
|
|
303
335
|
simulation_configuration=self.simulation_config,
|
|
336
|
+
application_configuration=self.app_specific,
|
|
337
|
+
yaml_file=self.yaml_file,
|
|
304
338
|
)
|
|
@@ -32,27 +32,57 @@ class ManagedApplication(Application):
|
|
|
32
32
|
time_step (:obj:`timedelta`): scenario time step used in execution
|
|
33
33
|
"""
|
|
34
34
|
|
|
35
|
-
def __init__(
|
|
35
|
+
def __init__(
|
|
36
|
+
self,
|
|
37
|
+
app_name: str,
|
|
38
|
+
app_description: str = None,
|
|
39
|
+
setup_signal_handlers: bool = True,
|
|
40
|
+
):
|
|
36
41
|
"""
|
|
37
42
|
Initializes a new managed application.
|
|
38
43
|
|
|
39
44
|
Args:
|
|
40
45
|
app_name (str): application name
|
|
41
46
|
app_description (str): application description
|
|
47
|
+
setup_signal_handlers (bool): whether to set up signal handlers (default: True)
|
|
42
48
|
"""
|
|
43
|
-
super().__init__(
|
|
49
|
+
super().__init__(
|
|
50
|
+
app_name, app_description, setup_signal_handlers=setup_signal_handlers
|
|
51
|
+
)
|
|
44
52
|
self.time_step = None
|
|
45
53
|
self._sim_start_time = None
|
|
46
54
|
self._sim_stop_time = None
|
|
47
55
|
|
|
56
|
+
def _get_parameters_from_config(self):
|
|
57
|
+
"""
|
|
58
|
+
Override to get parameters specific to managed applications
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
object: Configuration parameters for this managed application
|
|
62
|
+
"""
|
|
63
|
+
if self.config and self.config.rc.yaml_file:
|
|
64
|
+
try:
|
|
65
|
+
parameters = (
|
|
66
|
+
self.config.rc.simulation_configuration.execution_parameters.managed_applications
|
|
67
|
+
)
|
|
68
|
+
try:
|
|
69
|
+
# Try to get app-specific parameters
|
|
70
|
+
return parameters[self.app_name]
|
|
71
|
+
except KeyError:
|
|
72
|
+
# Fall back to default parameters
|
|
73
|
+
return parameters.get("default")
|
|
74
|
+
except (AttributeError, KeyError):
|
|
75
|
+
return None
|
|
76
|
+
return None
|
|
77
|
+
|
|
48
78
|
def start_up(
|
|
49
79
|
self,
|
|
50
80
|
prefix: str,
|
|
51
81
|
config: ConnectionConfig,
|
|
52
|
-
set_offset: bool =
|
|
82
|
+
set_offset: bool = True,
|
|
53
83
|
time_status_step: timedelta = None,
|
|
54
84
|
time_status_init: datetime = None,
|
|
55
|
-
shut_down_when_terminated: bool =
|
|
85
|
+
shut_down_when_terminated: bool = False,
|
|
56
86
|
time_step: timedelta = None,
|
|
57
87
|
manager_app_name: str = None,
|
|
58
88
|
) -> None:
|
|
@@ -70,48 +100,27 @@ class ManagedApplication(Application):
|
|
|
70
100
|
time_step (:obj:`timedelta`): scenario time step used in execution (Default: 1 second)
|
|
71
101
|
manager_app_name (str): manager application name (Default: manager)
|
|
72
102
|
"""
|
|
73
|
-
|
|
74
|
-
set_offset is not None
|
|
75
|
-
and time_status_step is not None
|
|
76
|
-
and time_status_init is not None
|
|
77
|
-
and shut_down_when_terminated is not None
|
|
78
|
-
and time_step is not None
|
|
79
|
-
and manager_app_name is not None
|
|
80
|
-
):
|
|
81
|
-
self.set_offset = set_offset
|
|
82
|
-
self.time_status_step = time_status_step
|
|
83
|
-
self.time_status_init = time_status_init
|
|
84
|
-
self.shut_down_when_terminated = shut_down_when_terminated
|
|
85
|
-
self.time_step = time_step
|
|
86
|
-
self.manager_app_name = manager_app_name
|
|
87
|
-
else:
|
|
88
|
-
self.config = config
|
|
89
|
-
parameters = (
|
|
90
|
-
self.config.rc.simulation_configuration.execution_parameters.managed_applications
|
|
91
|
-
)
|
|
92
|
-
|
|
93
|
-
try:
|
|
94
|
-
parameters = parameters[self.app_name]
|
|
95
|
-
except KeyError:
|
|
96
|
-
parameters = parameters["default"]
|
|
97
|
-
self.set_offset = parameters.set_offset
|
|
98
|
-
self.time_status_step = parameters.time_status_step
|
|
99
|
-
self.time_status_init = parameters.time_status_init
|
|
100
|
-
self.shut_down_when_terminated = parameters.shut_down_when_terminated
|
|
101
|
-
self.time_step = parameters.time_step
|
|
102
|
-
self.manager_app_name = parameters.manager_app_name
|
|
103
|
+
self.config = config
|
|
103
104
|
|
|
104
|
-
#
|
|
105
|
+
# Call base start_up to handle common parameters
|
|
105
106
|
super().start_up(
|
|
106
107
|
prefix,
|
|
107
108
|
config,
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
109
|
+
set_offset,
|
|
110
|
+
time_status_step,
|
|
111
|
+
time_status_init,
|
|
112
|
+
shut_down_when_terminated,
|
|
112
113
|
)
|
|
113
|
-
|
|
114
|
-
|
|
114
|
+
|
|
115
|
+
# Get additional parameters specific to managed applications
|
|
116
|
+
if self.config.rc.yaml_file:
|
|
117
|
+
parameters = self._get_parameters_from_config()
|
|
118
|
+
if parameters:
|
|
119
|
+
self.time_step = parameters.time_step
|
|
120
|
+
self.manager_app_name = parameters.manager_app_name
|
|
121
|
+
else:
|
|
122
|
+
self.time_step = time_step
|
|
123
|
+
self.manager_app_name = manager_app_name
|
|
115
124
|
|
|
116
125
|
# Register callback functions
|
|
117
126
|
self.add_message_callback(
|
nost_tools/manager.py
CHANGED
|
@@ -13,6 +13,7 @@ from typing import List
|
|
|
13
13
|
from pydantic import ValidationError
|
|
14
14
|
|
|
15
15
|
from .application import Application
|
|
16
|
+
from .application_utils import ConnectionConfig
|
|
16
17
|
from .schemas import (
|
|
17
18
|
InitCommand,
|
|
18
19
|
ReadyStatus,
|
|
@@ -66,12 +67,22 @@ class Manager(Application):
|
|
|
66
67
|
required_apps_status (dict): Ready status for all required applications
|
|
67
68
|
"""
|
|
68
69
|
|
|
69
|
-
def __init__(
|
|
70
|
+
def __init__(
|
|
71
|
+
self,
|
|
72
|
+
app_name: str = "manager",
|
|
73
|
+
app_description: str = None,
|
|
74
|
+
setup_signal_handlers: bool = True,
|
|
75
|
+
):
|
|
70
76
|
"""
|
|
71
77
|
Initializes a new manager.
|
|
78
|
+
|
|
79
|
+
Attributes:
|
|
80
|
+
setup_signal_handlers (bool): whether to set up signal handlers (default: True)
|
|
72
81
|
"""
|
|
73
82
|
# call super class constructor
|
|
74
|
-
super().__init__(
|
|
83
|
+
super().__init__(
|
|
84
|
+
app_name, app_description, setup_signal_handlers=setup_signal_handlers
|
|
85
|
+
)
|
|
75
86
|
self.required_apps_status = {}
|
|
76
87
|
|
|
77
88
|
self.sim_start_time = None
|
|
@@ -128,6 +139,60 @@ class Manager(Application):
|
|
|
128
139
|
f"Heartbeat check: {remaining:.2f} seconds remaining in sleep"
|
|
129
140
|
)
|
|
130
141
|
|
|
142
|
+
def _get_parameters_from_config(self):
|
|
143
|
+
"""
|
|
144
|
+
Override to get parameters specific to manager application
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
object: Configuration parameters for the manager application
|
|
148
|
+
"""
|
|
149
|
+
if self.config and self.config.rc.yaml_file:
|
|
150
|
+
try:
|
|
151
|
+
return getattr(
|
|
152
|
+
self.config.rc.simulation_configuration.execution_parameters,
|
|
153
|
+
"manager",
|
|
154
|
+
None,
|
|
155
|
+
)
|
|
156
|
+
except (AttributeError, KeyError):
|
|
157
|
+
return None
|
|
158
|
+
return None
|
|
159
|
+
|
|
160
|
+
def start_up(
|
|
161
|
+
self,
|
|
162
|
+
prefix: str,
|
|
163
|
+
config: ConnectionConfig,
|
|
164
|
+
set_offset: bool = True,
|
|
165
|
+
time_status_step: timedelta = None,
|
|
166
|
+
time_status_init: datetime = None,
|
|
167
|
+
shut_down_when_terminated: bool = False,
|
|
168
|
+
) -> None:
|
|
169
|
+
"""
|
|
170
|
+
Starts up the application by connecting to message broker, starting a background event loop,
|
|
171
|
+
subscribing to manager events, and registering callback functions.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
prefix (str): execution namespace (prefix)
|
|
175
|
+
config (:obj:`ConnectionConfig`): connection configuration
|
|
176
|
+
set_offset (bool): True, if the system clock offset shall be set using a NTP request prior to execution
|
|
177
|
+
time_status_step (:obj:`timedelta`): scenario duration between time status messages
|
|
178
|
+
time_status_init (:obj:`datetime`): scenario time for first time status message
|
|
179
|
+
shut_down_when_terminated (bool): True, if the application should shut down when the simulation is terminated
|
|
180
|
+
"""
|
|
181
|
+
self.config = config
|
|
182
|
+
|
|
183
|
+
# Call base start_up to handle common parameters
|
|
184
|
+
super().start_up(
|
|
185
|
+
prefix,
|
|
186
|
+
config,
|
|
187
|
+
set_offset,
|
|
188
|
+
time_status_step,
|
|
189
|
+
time_status_init,
|
|
190
|
+
shut_down_when_terminated,
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
# Additional manager-specific setup: establish the exchange
|
|
194
|
+
self.establish_exchange()
|
|
195
|
+
|
|
131
196
|
def execute_test_plan(self, *args, **kwargs) -> None:
|
|
132
197
|
"""
|
|
133
198
|
Starts the test plan execution in a background thread.
|
|
@@ -177,8 +242,33 @@ class Manager(Application):
|
|
|
177
242
|
init_retry_delay_s (float): number of seconds to wait between initialization commands while waiting for required applications
|
|
178
243
|
init_max_retry (int): number of initialization commands while waiting for required applications before continuing to execution
|
|
179
244
|
"""
|
|
180
|
-
|
|
181
|
-
|
|
245
|
+
if self.config.rc.yaml_file:
|
|
246
|
+
logger.info(
|
|
247
|
+
f"Collecting execution parameters from YAML configuration file: {self.config.rc.yaml_file}"
|
|
248
|
+
)
|
|
249
|
+
parameters = getattr(
|
|
250
|
+
self.config.rc.simulation_configuration.execution_parameters,
|
|
251
|
+
self.app_name,
|
|
252
|
+
None,
|
|
253
|
+
)
|
|
254
|
+
self.sim_start_time = parameters.sim_start_time
|
|
255
|
+
self.sim_stop_time = parameters.sim_stop_time
|
|
256
|
+
self.start_time = parameters.start_time
|
|
257
|
+
self.time_step = parameters.time_step
|
|
258
|
+
self.time_scale_factor = parameters.time_scale_factor
|
|
259
|
+
self.time_scale_updates = parameters.time_scale_updates
|
|
260
|
+
self.time_status_step = parameters.time_status_step
|
|
261
|
+
self.time_status_init = parameters.time_status_init
|
|
262
|
+
self.command_lead = parameters.command_lead
|
|
263
|
+
self.required_apps = [
|
|
264
|
+
app for app in parameters.required_apps if app != self.app_name
|
|
265
|
+
]
|
|
266
|
+
self.init_retry_delay_s = parameters.init_retry_delay_s
|
|
267
|
+
self.init_max_retry = parameters.init_max_retry
|
|
268
|
+
else:
|
|
269
|
+
logger.info(
|
|
270
|
+
f"Collecting execution parameters from user input or default values."
|
|
271
|
+
)
|
|
182
272
|
self.sim_start_time = sim_start_time
|
|
183
273
|
self.sim_stop_time = sim_stop_time
|
|
184
274
|
self.start_time = start_time
|
|
@@ -191,34 +281,17 @@ class Manager(Application):
|
|
|
191
281
|
self.required_apps = required_apps
|
|
192
282
|
self.init_retry_delay_s = init_retry_delay_s
|
|
193
283
|
self.init_max_retry = init_max_retry
|
|
194
|
-
else:
|
|
195
|
-
if self.config.rc:
|
|
196
|
-
logger.info("Retrieving execution parameters from YAML file.")
|
|
197
|
-
parameters = getattr(
|
|
198
|
-
self.config.rc.simulation_configuration.execution_parameters,
|
|
199
|
-
self.app_name,
|
|
200
|
-
None,
|
|
201
|
-
)
|
|
202
|
-
self.sim_start_time = parameters.sim_start_time
|
|
203
|
-
self.sim_stop_time = parameters.sim_stop_time
|
|
204
|
-
self.start_time = parameters.start_time
|
|
205
|
-
self.time_step = parameters.time_step
|
|
206
|
-
self.time_scale_factor = parameters.time_scale_factor
|
|
207
|
-
self.time_scale_updates = parameters.time_scale_updates
|
|
208
|
-
self.time_status_step = parameters.time_status_step
|
|
209
|
-
self.time_status_init = parameters.time_status_init
|
|
210
|
-
self.command_lead = parameters.command_lead
|
|
211
|
-
self.required_apps = [
|
|
212
|
-
app for app in parameters.required_apps if app != self.app_name
|
|
213
|
-
]
|
|
214
|
-
self.init_retry_delay_s = parameters.init_retry_delay_s
|
|
215
|
-
self.init_max_retry = parameters.init_max_retry
|
|
216
|
-
else:
|
|
217
|
-
raise ValueError(
|
|
218
|
-
"No configuration runtime. Please provide simulation start and stop times."
|
|
219
|
-
)
|
|
220
284
|
|
|
221
|
-
|
|
285
|
+
# Convert TimeScaleUpdateSchema objects to TimeScaleUpdate objects
|
|
286
|
+
converted_updates = []
|
|
287
|
+
for update_schema in self.time_scale_updates:
|
|
288
|
+
converted_updates.append(
|
|
289
|
+
TimeScaleUpdate(
|
|
290
|
+
time_scale_factor=update_schema.time_scale_factor,
|
|
291
|
+
sim_update_time=update_schema.sim_update_time,
|
|
292
|
+
)
|
|
293
|
+
)
|
|
294
|
+
self.time_scale_updates = converted_updates
|
|
222
295
|
|
|
223
296
|
# Set up tracking of required applications
|
|
224
297
|
self.required_apps_status = dict(
|
|
@@ -490,8 +563,17 @@ class Manager(Application):
|
|
|
490
563
|
app_topics="stop",
|
|
491
564
|
payload=command.model_dump_json(by_alias=True),
|
|
492
565
|
)
|
|
493
|
-
|
|
494
|
-
|
|
566
|
+
|
|
567
|
+
# Update the execution end time if simulator is in EXECUTING mode
|
|
568
|
+
if self.simulator.get_mode() == Mode.EXECUTING:
|
|
569
|
+
try:
|
|
570
|
+
self.simulator.set_end_time(sim_stop_time)
|
|
571
|
+
except RuntimeError as e:
|
|
572
|
+
logger.warning(f"Could not set simulator end time: {e}")
|
|
573
|
+
else:
|
|
574
|
+
logger.debug(
|
|
575
|
+
"Skipping setting simulator end time as simulator is not in EXECUTING mode"
|
|
576
|
+
)
|
|
495
577
|
|
|
496
578
|
def update(self, time_scale_factor: float, sim_update_time: datetime) -> None:
|
|
497
579
|
"""
|
nost_tools/schemas.py
CHANGED
|
@@ -307,8 +307,34 @@ class ServersConfig(BaseModel):
|
|
|
307
307
|
return values
|
|
308
308
|
|
|
309
309
|
|
|
310
|
+
class WallclockOffsetProperties(BaseModel):
|
|
311
|
+
"""
|
|
312
|
+
Properties to report wallclock offset.
|
|
313
|
+
"""
|
|
314
|
+
|
|
315
|
+
wallclock_offset_refresh_interval: Optional[int] = Field(
|
|
316
|
+
10800, description="Wallclock offset refresh interval, in seconds."
|
|
317
|
+
)
|
|
318
|
+
ntp_host: Optional[str] = Field(
|
|
319
|
+
"pool.ntp.org", description="NTP host for wallclock offset synchronization."
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
|
|
310
323
|
class GeneralConfig(BaseModel):
|
|
311
|
-
prefix: str = Field("nost", description="Execution prefix.")
|
|
324
|
+
prefix: Optional[str] = Field("nost", description="Execution prefix.")
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
class TimeScaleUpdateSchema(BaseModel):
|
|
328
|
+
"""
|
|
329
|
+
Provides a scheduled update to the simulation time scale factor.
|
|
330
|
+
"""
|
|
331
|
+
|
|
332
|
+
time_scale_factor: float = Field(
|
|
333
|
+
..., description="Scenario seconds per wallclock second"
|
|
334
|
+
)
|
|
335
|
+
sim_update_time: datetime = Field(
|
|
336
|
+
..., description="Scenario time that the update will occur"
|
|
337
|
+
)
|
|
312
338
|
|
|
313
339
|
|
|
314
340
|
class ManagerConfig(BaseModel):
|
|
@@ -322,7 +348,7 @@ class ManagerConfig(BaseModel):
|
|
|
322
348
|
description="Time step for the simulation.",
|
|
323
349
|
)
|
|
324
350
|
time_scale_factor: float = Field(1.0, description="Time scale factor.")
|
|
325
|
-
time_scale_updates: List[
|
|
351
|
+
time_scale_updates: List[TimeScaleUpdateSchema] = Field(
|
|
326
352
|
default_factory=list, description="List of time scale updates."
|
|
327
353
|
)
|
|
328
354
|
time_status_step: Optional[timedelta] = Field(None, description="Time status step.")
|
|
@@ -339,6 +365,10 @@ class ManagerConfig(BaseModel):
|
|
|
339
365
|
shut_down_when_terminated: bool = Field(
|
|
340
366
|
False, description="Shut down when terminated."
|
|
341
367
|
)
|
|
368
|
+
is_scenario_time_step: bool = Field(
|
|
369
|
+
True,
|
|
370
|
+
description="If True, time_step is in scenario time and won't be scaled. If False, time_step is in wallclock time and will be scaled by the time scale factor.",
|
|
371
|
+
)
|
|
342
372
|
is_scenario_time_status_step: bool = Field(
|
|
343
373
|
True,
|
|
344
374
|
description="If True, time_status_step is in scenario time and won't be scaled. If False, time_status_step is in wallclock time and will be scaled by the time scale factor.",
|
|
@@ -348,6 +378,16 @@ class ManagerConfig(BaseModel):
|
|
|
348
378
|
def scale_time(cls, values):
|
|
349
379
|
time_scale_factor = values.get("time_scale_factor", 1.0)
|
|
350
380
|
|
|
381
|
+
if "time_step" in values and not values.get("is_scenario_time_step", True):
|
|
382
|
+
time_step = values["time_step"]
|
|
383
|
+
if isinstance(time_step, str):
|
|
384
|
+
hours, minutes, seconds = map(int, time_step.split(":"))
|
|
385
|
+
time_step = timedelta(hours=hours, minutes=minutes, seconds=seconds)
|
|
386
|
+
if isinstance(time_step, timedelta):
|
|
387
|
+
values["time_step"] = timedelta(
|
|
388
|
+
seconds=time_step.total_seconds() * time_scale_factor
|
|
389
|
+
)
|
|
390
|
+
|
|
351
391
|
if "time_status_step" in values and not values.get(
|
|
352
392
|
"is_scenario_time_status_step", True
|
|
353
393
|
):
|
|
@@ -427,7 +467,27 @@ class LoggerApplicationConfig(BaseModel):
|
|
|
427
467
|
timedelta(seconds=10), description="Time status step."
|
|
428
468
|
)
|
|
429
469
|
time_status_init: Optional[datetime] = Field(
|
|
430
|
-
datetime(
|
|
470
|
+
datetime.now(), description="Time status init."
|
|
471
|
+
)
|
|
472
|
+
shut_down_when_terminated: Optional[bool] = Field(
|
|
473
|
+
False, description="Shut down when terminated."
|
|
474
|
+
)
|
|
475
|
+
manager_app_name: Optional[str] = Field(
|
|
476
|
+
"manager", description="Manager application name."
|
|
477
|
+
)
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
class ApplicationConfig(BaseModel):
|
|
481
|
+
set_offset: Optional[bool] = Field(True, description="Set offset.")
|
|
482
|
+
time_scale_factor: Optional[float] = Field(1.0, description="Time scale factor.")
|
|
483
|
+
time_step: Optional[timedelta] = Field(
|
|
484
|
+
timedelta(seconds=1), description="Time step for swe_change."
|
|
485
|
+
)
|
|
486
|
+
time_status_step: Optional[timedelta] = Field(
|
|
487
|
+
timedelta(seconds=10), description="Time status step."
|
|
488
|
+
)
|
|
489
|
+
time_status_init: Optional[datetime] = Field(
|
|
490
|
+
datetime.now(), description="Time status init."
|
|
431
491
|
)
|
|
432
492
|
shut_down_when_terminated: Optional[bool] = Field(
|
|
433
493
|
False, description="Shut down when terminated."
|
|
@@ -438,7 +498,9 @@ class LoggerApplicationConfig(BaseModel):
|
|
|
438
498
|
|
|
439
499
|
|
|
440
500
|
class ExecConfig(BaseModel):
|
|
441
|
-
general: GeneralConfig
|
|
501
|
+
general: Optional[GeneralConfig] = Field(
|
|
502
|
+
None, description="General configuration for the execution."
|
|
503
|
+
)
|
|
442
504
|
manager: Optional[ManagerConfig] = Field(None, description="Manager configuration.")
|
|
443
505
|
managed_applications: Optional[Dict[str, ManagedApplicationConfig]] = Field(
|
|
444
506
|
default_factory=lambda: {"default": ManagedApplicationConfig()},
|
|
@@ -447,6 +509,9 @@ class ExecConfig(BaseModel):
|
|
|
447
509
|
logger_application: Optional[LoggerApplicationConfig] = Field(
|
|
448
510
|
None, description="Logger application configuration."
|
|
449
511
|
)
|
|
512
|
+
application: Optional[ApplicationConfig] = Field(
|
|
513
|
+
None, description="Application configuration."
|
|
514
|
+
)
|
|
450
515
|
|
|
451
516
|
|
|
452
517
|
class Config(BaseModel):
|
|
@@ -473,11 +538,11 @@ class ChannelConfig(BaseModel):
|
|
|
473
538
|
|
|
474
539
|
|
|
475
540
|
class Credentials(BaseModel):
|
|
476
|
-
username: str = Field(
|
|
477
|
-
password: str = Field(
|
|
478
|
-
client_id: Optional[str] = Field(
|
|
541
|
+
username: Optional[str] = Field("admin", description="Username for authentication.")
|
|
542
|
+
password: Optional[str] = Field("admin", description="Password for authentication.")
|
|
543
|
+
client_id: Optional[str] = Field(None, description="Client ID for authentication.")
|
|
479
544
|
client_secret_key: Optional[str] = Field(
|
|
480
|
-
|
|
545
|
+
None, description="Client secret key for authentication."
|
|
481
546
|
)
|
|
482
547
|
|
|
483
548
|
|
|
@@ -493,6 +558,9 @@ class SimulationConfig(BaseModel):
|
|
|
493
558
|
|
|
494
559
|
|
|
495
560
|
class RuntimeConfig(BaseModel):
|
|
561
|
+
wallclock_offset_properties: WallclockOffsetProperties = Field(
|
|
562
|
+
..., description="Properties for wallclock offset."
|
|
563
|
+
)
|
|
496
564
|
credentials: Credentials = Field(..., description="Credentials for authentication.")
|
|
497
565
|
server_configuration: Config = (
|
|
498
566
|
Field(..., description="Simulation configuration."),
|
|
@@ -500,3 +568,9 @@ class RuntimeConfig(BaseModel):
|
|
|
500
568
|
simulation_configuration: SimulationConfig = Field(
|
|
501
569
|
..., description="Simulation configuration."
|
|
502
570
|
)
|
|
571
|
+
application_configuration: Optional[Dict] = Field(
|
|
572
|
+
None, description="Application-specific, user-provided configuration."
|
|
573
|
+
)
|
|
574
|
+
yaml_file: Optional[str] = Field(
|
|
575
|
+
None, description="Path to the YAML file containing the configuration."
|
|
576
|
+
)
|
nost_tools/simulator.py
CHANGED
|
@@ -516,9 +516,7 @@ class Simulator(Observable):
|
|
|
516
516
|
Args:
|
|
517
517
|
wallclock_offset(:obj:`timedelta`): difference between system clock and trusted wallclock source
|
|
518
518
|
"""
|
|
519
|
-
if self._mode == Mode.
|
|
520
|
-
raise RuntimeError("Cannot set wallclock offset: simulator is executing")
|
|
521
|
-
elif self._mode == Mode.TERMINATING:
|
|
519
|
+
if self._mode == Mode.TERMINATING:
|
|
522
520
|
raise RuntimeError("Cannot set wallclock offset: simulator is terminating")
|
|
523
521
|
self._wallclock_offset = wallclock_offset
|
|
524
522
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nost_tools
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.0
|
|
4
4
|
Summary: Tools for Novel Observing Strategies Testbed (NOS-T) Applications
|
|
5
5
|
Author-email: "Paul T. Grogan" <paul.grogan@asu.edu>, "Emmanuel M. Gonzalez" <emmanuelgonzalez@asu.edu>
|
|
6
6
|
License-Expression: BSD-3-Clause
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
nost_tools/__init__.py,sha256=5DeYQxWXibC9y__BvjwL8YC8K4EYMZbAV6E75k5nuL8,870
|
|
2
|
+
nost_tools/application.py,sha256=gh48fokVifuhKyAoJylZ2mF_WHW90G9L8j99Ct7j2Pg,64257
|
|
3
|
+
nost_tools/application_utils.py,sha256=jiMzuuP6-47UlUO64HhwNvbl6uKvVnsksYgOw7CmxL4,9327
|
|
4
|
+
nost_tools/configuration.py,sha256=4WLs1BrHMMvVhSIpJfjVZe-zw04WygAzjiLX2pVXibY,13146
|
|
5
|
+
nost_tools/entity.py,sha256=JrSN5rz7h-N9zIQsaJOQVBIYPDfygacedks55fsq_QQ,2802
|
|
6
|
+
nost_tools/errors.py,sha256=0JcDlMEkZAya3-5c0rRozLuxp8qF58StG4JgRsaxfKU,344
|
|
7
|
+
nost_tools/logger_application.py,sha256=rxPBfyA7Zym5b_EsoSJvT9JWNIVWZX1a-4czFwCqaQ4,7217
|
|
8
|
+
nost_tools/managed_application.py,sha256=Xa6qGsNVrr_XNnpJt_-L5PVnpeUy7GXIf0p2aEI4dSE,11673
|
|
9
|
+
nost_tools/manager.py,sha256=KqFyE-vv9dPRuVdz_r1SAsPeWt9LwKOTF-a2cJId0rY,24321
|
|
10
|
+
nost_tools/observer.py,sha256=D64V0KTvHRPEqbB8q3BosJhoAlpBah2vyBlVbxWQR44,8161
|
|
11
|
+
nost_tools/publisher.py,sha256=omU8tb0AXnA6RfhYSh0vnXbJtrRo4ukx1J5ANl4bDLQ,5291
|
|
12
|
+
nost_tools/schemas.py,sha256=RZH9LCWSZT8hGteI_Cc5a_w_TyFYx0Kh5mBM02SObrk,20575
|
|
13
|
+
nost_tools/simulator.py,sha256=pWfMSarMCuInQTlvlJ5l53w5ZZP6jjyUtY8uWOkbe-4,20062
|
|
14
|
+
nost_tools-2.3.0.dist-info/licenses/LICENSE,sha256=aAMU-mTHTKpWkBsg9QhkhCQpEm3Gri7J_fVuJov8s3s,1539
|
|
15
|
+
nost_tools-2.3.0.dist-info/METADATA,sha256=6ovwTQnDSv0ODsIsMQcFI5-kN652WW8FYOYlwzg-SH8,4256
|
|
16
|
+
nost_tools-2.3.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
17
|
+
nost_tools-2.3.0.dist-info/top_level.txt,sha256=LNChUgrv2-wiym12O0r61kY83COjTpTiJ2Ly1Ca58A8,11
|
|
18
|
+
nost_tools-2.3.0.dist-info/RECORD,,
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
nost_tools/__init__.py,sha256=bNVX1vszl3KMnBrd1x53pAeZgUUwlPQCF0FuLotlc3Y,870
|
|
2
|
-
nost_tools/application.py,sha256=bPuRMBifppJ-qkDKqOqp13rW_LOjbM7JaXIozMyxmlw,60914
|
|
3
|
-
nost_tools/application_utils.py,sha256=_R39D26FYxgaO4uyTON24KXc4UQ4zAEDBZfEkHbEw64,9386
|
|
4
|
-
nost_tools/configuration.py,sha256=ikNpZi8aofhZzJRbJf4x46afbAnp8r5C7Yr50Rnn1Nc,11639
|
|
5
|
-
nost_tools/entity.py,sha256=JrSN5rz7h-N9zIQsaJOQVBIYPDfygacedks55fsq_QQ,2802
|
|
6
|
-
nost_tools/errors.py,sha256=0JcDlMEkZAya3-5c0rRozLuxp8qF58StG4JgRsaxfKU,344
|
|
7
|
-
nost_tools/logger_application.py,sha256=rxPBfyA7Zym5b_EsoSJvT9JWNIVWZX1a-4czFwCqaQ4,7217
|
|
8
|
-
nost_tools/managed_application.py,sha256=7393kCF9FeXlf_pSP_C-tAeNSW9_-smQH6pQgyHC6Cw,11576
|
|
9
|
-
nost_tools/manager.py,sha256=QSOcFqA3xpa84AuxvXV6134PZp8iQndNCq1w3P4tHUU,21315
|
|
10
|
-
nost_tools/observer.py,sha256=D64V0KTvHRPEqbB8q3BosJhoAlpBah2vyBlVbxWQR44,8161
|
|
11
|
-
nost_tools/publisher.py,sha256=omU8tb0AXnA6RfhYSh0vnXbJtrRo4ukx1J5ANl4bDLQ,5291
|
|
12
|
-
nost_tools/schemas.py,sha256=nlMG4gZH-hUdmvz5a2294wGVN9Km7yE80x40vRJJRu8,17670
|
|
13
|
-
nost_tools/simulator.py,sha256=ALnGDmnA_ga-1Lq-bVWi2vcrspgjS4vtuDE0jWsI7fE,20191
|
|
14
|
-
nost_tools-2.1.1.dist-info/licenses/LICENSE,sha256=aAMU-mTHTKpWkBsg9QhkhCQpEm3Gri7J_fVuJov8s3s,1539
|
|
15
|
-
nost_tools-2.1.1.dist-info/METADATA,sha256=fNbyKb9l5zQyeYZfYL9bfV5bfGHV1rF4z__FJ2leKfA,4256
|
|
16
|
-
nost_tools-2.1.1.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
|
|
17
|
-
nost_tools-2.1.1.dist-info/top_level.txt,sha256=LNChUgrv2-wiym12O0r61kY83COjTpTiJ2Ly1Ca58A8,11
|
|
18
|
-
nost_tools-2.1.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|