nost-tools 2.0.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.

@@ -0,0 +1,141 @@
1
+ """
2
+ Provides utility classes to help applications bind behavior to temporal events.
3
+ """
4
+
5
+ from abc import ABC, abstractmethod
6
+ from datetime import datetime, timedelta
7
+ from typing import TYPE_CHECKING
8
+
9
+ from .observer import Observer
10
+ from .simulator import Mode, Simulator
11
+
12
+ if TYPE_CHECKING:
13
+ from .application import Application
14
+
15
+
16
+ class ScenarioTimeIntervalPublisher(Observer, ABC):
17
+ """
18
+ Publishes messages at a regular interval (scenario time).
19
+
20
+ Provides the simulation with time status messages, also refered to as 'heartbeat messages',
21
+ or 'simulation time statuses'.
22
+
23
+ Attributes:
24
+ app (:obj:`Application`): application to publish messages
25
+ time_status_step (:obj:`timedelta`): scenario duration between time status messages
26
+ time_status_init (:obj:`datetime`): scenario time for first time status message
27
+ """
28
+
29
+ def __init__(
30
+ self,
31
+ app: "Application",
32
+ time_status_step: timedelta = None,
33
+ time_status_init: datetime = None,
34
+ ):
35
+ """
36
+ Initializes a new scenario time interval publisher.
37
+
38
+ Args:
39
+ app (:obj:`Application`): application to publish messages
40
+ time_status_step (:obj:`timedelta`): scenario duration between time status messages
41
+ time_status_init (:obj:`datetime`): scenario time for first time status message
42
+ """
43
+ self.app = app
44
+ self.time_status_step = time_status_step
45
+ self.time_status_init = time_status_init
46
+ self._next_time_status = None
47
+ # TODO: consider adding the `publish_message` callable as an argument rather than abstract method
48
+
49
+ @abstractmethod
50
+ def publish_message(self) -> None:
51
+ """
52
+ Publishes a message.
53
+ """
54
+ pass
55
+
56
+ def on_change(
57
+ self, source: object, property_name: str, old_value: object, new_value: object
58
+ ) -> None:
59
+ """
60
+ Publishes a message after a designated interval of scenario time.
61
+
62
+ Args:
63
+ source (object): observable that triggered the change
64
+ property_name (str): name of the changed property
65
+ old_value (obj): old value of the named property
66
+ new_value (obj): new value of the named property
67
+ """
68
+ if property_name == Simulator.PROPERTY_MODE and new_value == Mode.INITIALIZED:
69
+ if self.time_status_init is None:
70
+ self._next_time_status = self.app.simulator.get_init_time()
71
+ else:
72
+ self._next_time_status = self.time_status_init
73
+ elif property_name == Simulator.PROPERTY_TIME:
74
+ while self._next_time_status <= new_value:
75
+ self.publish_message()
76
+ if self.time_status_step is None:
77
+ self._next_time_status += self.app.simulator.get_time_step()
78
+ else:
79
+ self._next_time_status += self.time_status_step
80
+
81
+
82
+ class WallclockTimeIntervalPublisher(Observer, ABC):
83
+ """
84
+ Publishes messages at a regular interval (wallclock time).
85
+
86
+ Attributes:
87
+ app (:obj:`Application`): application to publish messages
88
+ time_status_step (:obj:`timedelta`): wallclock duration between time status messages
89
+ time_status_init (:obj:`datetime`): wallclock time for first time status message
90
+ """
91
+
92
+ def __init__(
93
+ self,
94
+ app: "Application",
95
+ time_status_step: timedelta = None,
96
+ time_status_init: datetime = None,
97
+ ):
98
+ """
99
+ Initializes a new wallclock time interval publisher.
100
+
101
+ Args:
102
+ app (:obj:`Application`): application to publish messages
103
+ time_status_step (:obj:`timedelta`): wallclock duration between time status messages
104
+ time_status_init (:obj:`datetime`): wallclock time for first time status message
105
+ """
106
+ self.app = app
107
+ self.time_status_step = time_status_step
108
+ self.time_status_init = time_status_init
109
+ self._next_time_status = None
110
+
111
+ @abstractmethod
112
+ def publish_message(self) -> None:
113
+ """
114
+ Publishes a message.
115
+ """
116
+ pass
117
+
118
+ def on_change(self, source, property_name, old_value, new_value):
119
+ """
120
+ Publishes a message after a designated interval of scenario time.
121
+
122
+ Args:
123
+ source (object): observable that triggered the change
124
+ property_name (str): name of the changed property
125
+ old_value (obj): old value of the named property
126
+ new_value (obj): new value of the named property
127
+ """
128
+ if property_name == Simulator.PROPERTY_MODE and new_value == Mode.INITIALIZED:
129
+ if self.time_status_init is None:
130
+ self._next_time_status = self.app.simulator.get_wallclock_time()
131
+ else:
132
+ self._next_time_status = self.time_status_init
133
+ elif property_name == Simulator.PROPERTY_TIME:
134
+ while self._next_time_status <= self.app.simulator.get_wallclock_time():
135
+ self.publish_message()
136
+ if self.time_status_step is None:
137
+ self._next_time_status += (
138
+ self.app.simulator.get_wallclock_time_step()
139
+ )
140
+ else:
141
+ self._next_time_status += self.time_status_step
nost_tools/schemas.py ADDED
@@ -0,0 +1,426 @@
1
+ """
2
+ Provides object models for common data structures.
3
+ """
4
+
5
+ from datetime import datetime, timedelta
6
+ from typing import Dict, List, Optional
7
+
8
+ from pydantic import BaseModel, Field, model_validator
9
+
10
+ from .simulator import Mode
11
+
12
+
13
+ class InitTaskingParameters(BaseModel):
14
+ """
15
+ Tasking parameters to initialize an execution.
16
+ """
17
+
18
+ sim_start_time: datetime = Field(
19
+ ..., description="Earliest possible scenario start time.", alias="simStartTime"
20
+ )
21
+ sim_stop_time: datetime = Field(
22
+ ..., description="Latest possible scenario end time.", alias="simStopTime"
23
+ )
24
+ required_apps: List[str] = Field(
25
+ [], description="List of required applications.", alias="requiredApps"
26
+ )
27
+
28
+
29
+ class InitCommand(BaseModel):
30
+ """
31
+ Command message to initialize an execution.
32
+ """
33
+
34
+ tasking_parameters: InitTaskingParameters = Field(
35
+ ...,
36
+ description="Tasking parameters for the initialize command.",
37
+ alias="taskingParameters",
38
+ )
39
+
40
+
41
+ class StartTaskingParameters(BaseModel):
42
+ """
43
+ Tasking parameters to start an execution.
44
+ """
45
+
46
+ start_time: Optional[datetime] = Field(
47
+ None,
48
+ description="Wallclock time at which to start execution.",
49
+ alias="startTime",
50
+ )
51
+ sim_start_time: Optional[datetime] = Field(
52
+ None,
53
+ description="Scenario time at which to start execution.",
54
+ alias="simStartTime",
55
+ )
56
+ sim_stop_time: Optional[datetime] = Field(
57
+ None,
58
+ description="Scenario time at which to stop execution.",
59
+ alias="simStopTime",
60
+ )
61
+ time_scaling_factor: float = Field(
62
+ 1.0,
63
+ gt=0,
64
+ description="Scenario seconds per wallclock second.",
65
+ alias="timeScalingFactor",
66
+ )
67
+
68
+
69
+ class StartCommand(BaseModel):
70
+ """
71
+ Command message to start an execution.
72
+ """
73
+
74
+ tasking_parameters: StartTaskingParameters = Field(
75
+ ...,
76
+ description="Tasking parameters for the start command.",
77
+ alias="taskingParameters",
78
+ )
79
+
80
+
81
+ class StopTaskingParameters(BaseModel):
82
+ """
83
+ Tasking parameters to stop an execution.
84
+ """
85
+
86
+ sim_stop_time: datetime = Field(
87
+ ...,
88
+ description="Scenario time at which to stop execution.",
89
+ alias="simStopTime",
90
+ )
91
+
92
+
93
+ class StopCommand(BaseModel):
94
+ """
95
+ Command message to stop an execution.
96
+ """
97
+
98
+ tasking_parameters: StopTaskingParameters = Field(
99
+ ...,
100
+ description="Tasking parameters for the stop command.",
101
+ alias="taskingParameters",
102
+ )
103
+
104
+
105
+ class UpdateTaskingParameters(BaseModel):
106
+ """
107
+ Tasking parameters to update an execution.
108
+ """
109
+
110
+ time_scaling_factor: float = Field(
111
+ ...,
112
+ gt=0,
113
+ description="Time scaling factor (scenario seconds per wallclock second).",
114
+ alias="timeScalingFactor",
115
+ )
116
+ sim_update_time: datetime = Field(
117
+ ...,
118
+ description="Scenario time at which to update the time scaling factor.",
119
+ alias="simUpdateTime",
120
+ )
121
+
122
+
123
+ class UpdateCommand(BaseModel):
124
+ """
125
+ Command message to update an execution.
126
+ """
127
+
128
+ tasking_parameters: UpdateTaskingParameters = Field(
129
+ ...,
130
+ description="Tasking parameters for the stop command.",
131
+ alias="taskingParameters",
132
+ )
133
+
134
+
135
+ class TimeStatusProperties(BaseModel):
136
+ """
137
+ Properties to report time status.
138
+ """
139
+
140
+ sim_time: datetime = Field(
141
+ ..., description="Current scenario time.", alias="simTime"
142
+ )
143
+ time: datetime = Field(..., description="Current wallclock time.")
144
+
145
+
146
+ class TimeStatus(BaseModel):
147
+ """
148
+ Message to report time status.
149
+ """
150
+
151
+ name: str = Field(
152
+ ..., description="Name of the application providing a time status."
153
+ )
154
+ description: Optional[str] = Field(
155
+ None, description="Description of the application providing a ready status."
156
+ )
157
+ properties: TimeStatusProperties = Field(
158
+ ..., description="Properties for the time status."
159
+ )
160
+
161
+
162
+ class ModeStatusProperties(BaseModel):
163
+ """
164
+ Properties to report mode status.
165
+ """
166
+
167
+ mode: Mode = Field(..., description="Current execution mode.")
168
+
169
+
170
+ class ModeStatus(BaseModel):
171
+ """
172
+ Message to report mode status.
173
+ """
174
+
175
+ name: str = Field(
176
+ ..., description="Name of the application providing a mode status."
177
+ )
178
+ description: Optional[str] = Field(
179
+ None, description="Description of the application providing a ready status."
180
+ )
181
+ properties: ModeStatusProperties = Field(
182
+ ..., description="Properties for the mode status."
183
+ )
184
+
185
+
186
+ class ReadyStatusProperties(BaseModel):
187
+ """
188
+ Properties to report ready status.
189
+ """
190
+
191
+ ready: bool = Field(True, description="True, if this application is ready.")
192
+
193
+
194
+ class ReadyStatus(BaseModel):
195
+ """
196
+ Message to report ready status.
197
+ """
198
+
199
+ name: str = Field(
200
+ ..., description="Name of the application providing a ready status."
201
+ )
202
+ description: Optional[str] = Field(
203
+ None, description="Description of the application providing a ready status."
204
+ )
205
+ properties: ReadyStatusProperties = Field(
206
+ ..., description="Properties for the ready status."
207
+ )
208
+
209
+
210
+ class InfoConfig(BaseModel):
211
+ title: Optional[str] = Field(None, description="Title of the simulation.")
212
+ version: Optional[str] = Field(None, description="Version of the simulation.")
213
+ description: Optional[str] = Field(
214
+ None, description="Description of the simulation."
215
+ )
216
+
217
+
218
+ class RabbitMQConfig(BaseModel):
219
+ keycloak_authentication: bool = Field(
220
+ False, description="Keycloak authentication for RabbitMQ."
221
+ )
222
+ host: str = Field("localhost", description="RabbitMQ host.")
223
+ port: int = Field(5672, description="RabbitMQ port.")
224
+ tls: bool = Field(False, description="RabbitMQ TLS/SSL.")
225
+ virtual_host: str = Field("/", description="RabbitMQ virtual host.")
226
+ message_expiration: str = Field(
227
+ "60000", description="RabbitMQ expiration, in milliseconds."
228
+ )
229
+ delivery_mode: int = Field(
230
+ 2, description="RabbitMQ delivery mode (1: non-persistent, 2: durable)."
231
+ )
232
+ content_type: str = Field(
233
+ "text/plain",
234
+ description="RabbitMQ MIME content type (application/json, text/plain, etc.).",
235
+ )
236
+ heartbeat: int = Field(30, description="RabbitMQ heartbeat interval, in seconds.")
237
+ connection_attempts: int = Field(
238
+ 3, description="RabbitMQ connection attempts before giving up."
239
+ )
240
+ retry_delay: int = Field(5, description="RabbitMQ retry delay, in seconds.")
241
+
242
+
243
+ class KeycloakConfig(BaseModel):
244
+ host: str = Field("localhost", description="Keycloak host.")
245
+ port: int = Field(8080, description="Keycloak port.")
246
+ realm: str = Field("master", description="Keycloak realm.")
247
+ tls: bool = Field(False, description="Keycloak TLS/SSL.")
248
+ token_refresh_interval: int = Field(
249
+ 60, description="Keycloak token refresh interval, in seconds."
250
+ )
251
+
252
+
253
+ class ServersConfig(BaseModel):
254
+ rabbitmq: RabbitMQConfig = Field(..., description="RabbitMQ configuration.")
255
+ keycloak: KeycloakConfig = Field(..., description="Keycloak configuration.")
256
+
257
+
258
+ class GeneralConfig(BaseModel):
259
+ prefix: str = Field("nost", description="Execution prefix.")
260
+
261
+
262
+ class ManagerConfig(BaseModel):
263
+ sim_start_time: Optional[datetime] = Field(
264
+ None, description="Simulation start time."
265
+ )
266
+ sim_stop_time: Optional[datetime] = Field(None, description="Simulation stop time.")
267
+ start_time: Optional[datetime] = Field(None, description="Execution start time.")
268
+ time_step: timedelta = Field(
269
+ timedelta(seconds=1),
270
+ description="Time step for the simulation.",
271
+ )
272
+ time_scale_factor: float = Field(1.0, description="Time scale factor.")
273
+ time_scale_updates: List[str] = Field(
274
+ default_factory=list, description="List of time scale updates."
275
+ )
276
+ time_status_step: Optional[timedelta] = Field(None, description="Time status step.")
277
+ time_status_init: Optional[datetime] = Field(None, description="Time status init.")
278
+ command_lead: timedelta = Field(
279
+ timedelta(seconds=0), description="Command lead time."
280
+ )
281
+ required_apps: List[str] = Field(
282
+ default_factory=list, description="List of required applications."
283
+ )
284
+ init_retry_delay_s: int = Field(5, description="Initial retry delay in seconds.")
285
+ init_max_retry: int = Field(5, description="Initial maximum retry attempts.")
286
+ set_offset: bool = Field(True, description="Set offset.")
287
+ shut_down_when_terminated: bool = Field(
288
+ False, description="Shut down when terminated."
289
+ )
290
+
291
+ @model_validator(mode="before")
292
+ def scale_time(cls, values):
293
+ time_scale_factor = values.get("time_scale_factor", 1.0)
294
+
295
+ if "time_status_step" in values:
296
+ time_status_step = values["time_status_step"]
297
+ if isinstance(time_status_step, str):
298
+ time_status_step = timedelta(
299
+ seconds=float(time_status_step.split(":")[-1])
300
+ )
301
+ if isinstance(time_status_step, timedelta):
302
+ values["time_status_step"] = timedelta(
303
+ seconds=time_status_step.total_seconds() * time_scale_factor
304
+ )
305
+
306
+ return values
307
+
308
+
309
+ class ManagedApplicationConfig(BaseModel):
310
+ time_scale_factor: float = Field(1.0, description="Time scale factor.")
311
+ time_step: timedelta = Field(
312
+ timedelta(seconds=1), description="Time step for swe_change."
313
+ )
314
+ set_offset: bool = Field(True, description="Set offset.")
315
+ time_status_step: timedelta = Field(None, description="Time status step.")
316
+ time_status_init: datetime = Field(None, description="Time status init.")
317
+ shut_down_when_terminated: bool = Field(
318
+ False, description="Shut down when terminated."
319
+ )
320
+ manager_app_name: str = Field("manager", description="Manager application name.")
321
+
322
+ @model_validator(mode="before")
323
+ def scale_time(cls, values):
324
+ time_scale_factor = values.get("time_scale_factor", 1.0)
325
+
326
+ if "time_step" in values:
327
+ time_step = values["time_step"]
328
+ if isinstance(time_step, str):
329
+ time_step = timedelta(seconds=float(time_step.split(":")[-1]))
330
+ if isinstance(time_step, timedelta):
331
+ values["time_step"] = timedelta(
332
+ seconds=time_step.total_seconds() * time_scale_factor
333
+ )
334
+
335
+ if "time_status_step" in values:
336
+ time_status_step = values["time_status_step"]
337
+ if isinstance(time_status_step, str):
338
+ time_status_step = timedelta(
339
+ seconds=float(time_status_step.split(":")[-1])
340
+ )
341
+ if isinstance(time_status_step, timedelta):
342
+ values["time_status_step"] = timedelta(
343
+ seconds=time_status_step.total_seconds() * time_scale_factor
344
+ )
345
+
346
+ return values
347
+
348
+
349
+ class LoggerApplicationConfig(BaseModel):
350
+ set_offset: Optional[bool] = Field(True, description="Set offset.")
351
+ time_scale_factor: Optional[float] = Field(1.0, description="Time scale factor.")
352
+ time_step: Optional[timedelta] = Field(
353
+ timedelta(seconds=1), description="Time step for swe_change."
354
+ )
355
+ time_status_step: Optional[timedelta] = Field(
356
+ timedelta(seconds=10), description="Time status step."
357
+ )
358
+ time_status_init: Optional[datetime] = Field(
359
+ datetime(2019, 3, 1, 0, 0, 0), description="Time status init."
360
+ )
361
+ shut_down_when_terminated: Optional[bool] = Field(
362
+ False, description="Shut down when terminated."
363
+ )
364
+ manager_app_name: Optional[str] = Field(
365
+ "manager", description="Manager application name."
366
+ )
367
+
368
+
369
+ class ExecConfig(BaseModel):
370
+ general: GeneralConfig
371
+ manager: ManagerConfig
372
+ managed_application: ManagedApplicationConfig
373
+ logger_application: LoggerApplicationConfig
374
+
375
+
376
+ class Config(BaseModel):
377
+ servers: ServersConfig = Field(..., description="Servers configuration.")
378
+ channels: Dict[str, Dict] = Field({}, description="Channels configuration.")
379
+ execution: ExecConfig = Field(..., description="Applications configuration.")
380
+
381
+
382
+ class ExchangeConfig(BaseModel):
383
+ name: str
384
+ type: str = "topic"
385
+ durable: bool = True
386
+ auto_delete: bool = False
387
+ vhost: str = "/"
388
+
389
+
390
+ class ChannelConfig(BaseModel):
391
+ app: str
392
+ address: str
393
+ exchange: str
394
+ durable: bool
395
+ auto_delete: bool
396
+ vhost: str
397
+
398
+
399
+ class Credentials(BaseModel):
400
+ username: str = Field(..., description="Username for authentication.")
401
+ password: str = Field(..., description="Password for authentication.")
402
+ client_id: Optional[str] = Field("", description="Client ID for authentication.")
403
+ client_secret_key: Optional[str] = Field(
404
+ "", description="Client secret key for authentication."
405
+ )
406
+
407
+
408
+ class SimulationConfig(BaseModel):
409
+ exchanges: Dict[str, Dict] = Field(
410
+ default_factory=dict, description="Dictionary of exchanges."
411
+ )
412
+ queues: List[Dict] = Field(default_factory=list, description="List of channels.")
413
+ execution_parameters: ExecConfig = Field(..., description="Execution parameters.")
414
+ predefined_exchanges_queues: bool = Field(
415
+ False, description="Predefined exchanges and queues."
416
+ )
417
+
418
+
419
+ class RuntimeConfig(BaseModel):
420
+ credentials: Credentials = Field(..., description="Credentials for authentication.")
421
+ server_configuration: Config = (
422
+ Field(..., description="Simulation configuration."),
423
+ )
424
+ simulation_configuration: SimulationConfig = Field(
425
+ ..., description="Simulation configuration."
426
+ )