nost-tools 2.1.1__py3-none-any.whl → 2.2.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 CHANGED
@@ -1,4 +1,4 @@
1
- __version__ = "2.1.1"
1
+ __version__ = "2.2.0"
2
2
 
3
3
  from .application import Application
4
4
  from .application_utils import ConnectionConfig, ModeStatusObserver, TimeStatusPublisher
nost_tools/application.py CHANGED
@@ -239,7 +239,7 @@ class Application:
239
239
  self.config = config
240
240
  parameters = getattr(
241
241
  self.config.rc.simulation_configuration.execution_parameters,
242
- self.app_name,
242
+ "application",
243
243
  None,
244
244
  )
245
245
  self.set_offset = parameters.set_offset
@@ -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
- # Declare the topic exchange
257
- if self.app.channel.is_open and self.app.connection.is_open:
258
- self.app.send_message(
259
- app_name=self.app.app_name,
260
- app_topics="status.mode",
261
- payload=status.model_dump_json(by_alias=True, exclude_none=True),
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
+ )
@@ -25,6 +25,7 @@ from .schemas import (
25
25
 
26
26
  logger = logging.getLogger(__name__)
27
27
 
28
+
28
29
  class ConnectionConfig:
29
30
  """Connection configuration.
30
31
 
@@ -59,6 +60,7 @@ class ConnectionConfig:
59
60
  virtual_host: str = None,
60
61
  is_tls: bool = True,
61
62
  yaml_file: str = None,
63
+ app_name: str = None,
62
64
  ):
63
65
  """
64
66
  Initializes a new connection configuration.
@@ -75,6 +77,7 @@ class ConnectionConfig:
75
77
  virtual_host (str): RabbitMQ virtual host
76
78
  is_tls (bool): True, if the connection uses TLS
77
79
  yaml_file (str): Path to the YAML configuration file
80
+ app_name (str): Name of the application to get specific configuration for
78
81
  """
79
82
  self.username = username
80
83
  self.password = password
@@ -93,6 +96,8 @@ class ConnectionConfig:
93
96
  self.yaml_file = yaml_file
94
97
  self.unique_exchanges = {}
95
98
  self.channel_configs = []
99
+ self.app_name = app_name
100
+ self.app_specific = None
96
101
 
97
102
  self.create_connection_config()
98
103
 
@@ -208,6 +213,29 @@ class ConnectionConfig:
208
213
  except ValidationError as err:
209
214
  raise EnvironmentVariableError(f"Invalid environment variables: {err}")
210
215
 
216
+ def get_app_specific_config(self, app_name):
217
+ """
218
+ Get application-specific configuration from execution.managed_applications if available.
219
+
220
+ Args:
221
+ app_name (str): Name of the application
222
+
223
+ Returns:
224
+ dict: Application-specific configuration parameters if available, otherwise None.
225
+ """
226
+ if not os.path.exists(self.yaml_file):
227
+ raise ConfigurationError("Couldn't load config file (not found)")
228
+
229
+ with open(self.yaml_file, "r", encoding="utf-8") as f:
230
+ yaml_data = yaml.safe_load(f)
231
+
232
+ try:
233
+ return yaml_data["execution"]["managed_applications"][app_name][
234
+ "configuration_parameters"
235
+ ]
236
+ except:
237
+ return None
238
+
211
239
  def load_yaml_config_file(self):
212
240
  """
213
241
  Loads a YAML configuration file and returns the parsed data.
@@ -243,6 +271,9 @@ class ConnectionConfig:
243
271
  ), "Application names do not match the channels defined in the configuration file."
244
272
  except ConfigAssertionError as e:
245
273
  raise ValueError(f"Assertion error: {e}")
274
+ # Load app-specific configuration if app_name is provided
275
+ if self.app_name:
276
+ self.app_specific = self.get_app_specific_config(self.app_name)
246
277
  else:
247
278
  try:
248
279
  self.yaml_config = Config(
@@ -301,4 +332,5 @@ class ConnectionConfig:
301
332
  credentials=self.credentials_config,
302
333
  server_configuration=server_config,
303
334
  simulation_configuration=self.simulation_config,
335
+ application_configuration=self.app_specific,
304
336
  )
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,
@@ -128,6 +129,69 @@ class Manager(Application):
128
129
  f"Heartbeat check: {remaining:.2f} seconds remaining in sleep"
129
130
  )
130
131
 
132
+ def start_up(
133
+ self,
134
+ prefix: str,
135
+ config: ConnectionConfig,
136
+ set_offset: bool = None,
137
+ time_status_step: timedelta = None,
138
+ time_status_init: datetime = None,
139
+ shut_down_when_terminated: bool = None,
140
+ time_step: timedelta = None,
141
+ manager_app_name: str = None,
142
+ ) -> None:
143
+ """
144
+ Starts up the application by connecting to message broker, starting a background event loop,
145
+ subscribing to manager events, and registering callback functions.
146
+
147
+ Args:
148
+ prefix (str): execution namespace (prefix)
149
+ config (:obj:`ConnectionConfig`): connection configuration
150
+ set_offset (bool): True, if the system clock offset shall be set using a NTP request prior to execution
151
+ time_status_step (:obj:`timedelta`): scenario duration between time status messages
152
+ time_status_init (:obj:`datetime`): scenario time for first time status message
153
+ shut_down_when_terminated (bool): True, if the application should shut down when the simulation is terminated
154
+ time_step (:obj:`timedelta`): scenario time step used in execution (Default: 1 second)
155
+ manager_app_name (str): manager application name (Default: manager)
156
+ """
157
+ if (
158
+ set_offset is not None
159
+ and time_status_step is not None
160
+ and time_status_init is not None
161
+ and shut_down_when_terminated is not None
162
+ and time_step is not None
163
+ and manager_app_name is not None
164
+ ):
165
+ self.set_offset = set_offset
166
+ self.time_status_step = time_status_step
167
+ self.time_status_init = time_status_init
168
+ self.shut_down_when_terminated = shut_down_when_terminated
169
+ self.time_step = time_step
170
+ self.manager_app_name = manager_app_name
171
+ else:
172
+ self.config = config
173
+ parameters = (
174
+ self.config.rc.simulation_configuration.execution_parameters.manager
175
+ )
176
+ self.set_offset = parameters.set_offset
177
+ self.time_status_step = parameters.time_status_step
178
+ self.time_status_init = parameters.time_status_init
179
+ self.shut_down_when_terminated = parameters.shut_down_when_terminated
180
+ self.time_step = parameters.time_step
181
+
182
+ # start up base application
183
+ super().start_up(
184
+ prefix,
185
+ config,
186
+ self.set_offset,
187
+ self.time_status_step,
188
+ self.time_status_init,
189
+ self.shut_down_when_terminated,
190
+ )
191
+
192
+ # Establish the exchange
193
+ self.establish_exchange()
194
+
131
195
  def execute_test_plan(self, *args, **kwargs) -> None:
132
196
  """
133
197
  Starts the test plan execution in a background thread.
@@ -218,7 +282,16 @@ class Manager(Application):
218
282
  "No configuration runtime. Please provide simulation start and stop times."
219
283
  )
220
284
 
221
- self.establish_exchange()
285
+ # Convert TimeScaleUpdateSchema objects to TimeScaleUpdate objects
286
+ converted_updates = []
287
+ for update_schema in parameters.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(
nost_tools/schemas.py CHANGED
@@ -311,6 +311,19 @@ class GeneralConfig(BaseModel):
311
311
  prefix: str = Field("nost", description="Execution prefix.")
312
312
 
313
313
 
314
+ class TimeScaleUpdateSchema(BaseModel):
315
+ """
316
+ Provides a scheduled update to the simulation time scale factor.
317
+ """
318
+
319
+ time_scale_factor: float = Field(
320
+ ..., description="Scenario seconds per wallclock second"
321
+ )
322
+ sim_update_time: datetime = Field(
323
+ ..., description="Scenario time that the update will occur"
324
+ )
325
+
326
+
314
327
  class ManagerConfig(BaseModel):
315
328
  sim_start_time: Optional[datetime] = Field(
316
329
  None, description="Simulation start time."
@@ -322,7 +335,7 @@ class ManagerConfig(BaseModel):
322
335
  description="Time step for the simulation.",
323
336
  )
324
337
  time_scale_factor: float = Field(1.0, description="Time scale factor.")
325
- time_scale_updates: List[str] = Field(
338
+ time_scale_updates: List[TimeScaleUpdateSchema] = Field(
326
339
  default_factory=list, description="List of time scale updates."
327
340
  )
328
341
  time_status_step: Optional[timedelta] = Field(None, description="Time status step.")
@@ -437,6 +450,26 @@ class LoggerApplicationConfig(BaseModel):
437
450
  )
438
451
 
439
452
 
453
+ class ApplicationConfig(BaseModel):
454
+ set_offset: Optional[bool] = Field(True, description="Set offset.")
455
+ time_scale_factor: Optional[float] = Field(1.0, description="Time scale factor.")
456
+ time_step: Optional[timedelta] = Field(
457
+ timedelta(seconds=1), description="Time step for swe_change."
458
+ )
459
+ time_status_step: Optional[timedelta] = Field(
460
+ timedelta(seconds=10), description="Time status step."
461
+ )
462
+ time_status_init: Optional[datetime] = Field(
463
+ datetime.now(), description="Time status init."
464
+ )
465
+ shut_down_when_terminated: Optional[bool] = Field(
466
+ False, description="Shut down when terminated."
467
+ )
468
+ manager_app_name: Optional[str] = Field(
469
+ "manager", description="Manager application name."
470
+ )
471
+
472
+
440
473
  class ExecConfig(BaseModel):
441
474
  general: GeneralConfig
442
475
  manager: Optional[ManagerConfig] = Field(None, description="Manager configuration.")
@@ -447,6 +480,9 @@ class ExecConfig(BaseModel):
447
480
  logger_application: Optional[LoggerApplicationConfig] = Field(
448
481
  None, description="Logger application configuration."
449
482
  )
483
+ application: Optional[ApplicationConfig] = Field(
484
+ None, description="Application configuration."
485
+ )
450
486
 
451
487
 
452
488
  class Config(BaseModel):
@@ -500,3 +536,6 @@ class RuntimeConfig(BaseModel):
500
536
  simulation_configuration: SimulationConfig = Field(
501
537
  ..., description="Simulation configuration."
502
538
  )
539
+ application_configuration: Optional[Dict] = Field(
540
+ None, description="Application-specific, user-provided configuration."
541
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nost_tools
3
- Version: 2.1.1
3
+ Version: 2.2.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=4AGyjwpOcyyQSwtFNemFAQYyZyoRUJbH5dEkJ75xxCI,870
2
+ nost_tools/application.py,sha256=1msGzjDbPNY5Bv5OPu0pUctBEpXVBCkfBzjpF8G6skg,60914
3
+ nost_tools/application_utils.py,sha256=jiMzuuP6-47UlUO64HhwNvbl6uKvVnsksYgOw7CmxL4,9327
4
+ nost_tools/configuration.py,sha256=DBsISmhtMiWUw2PC4CfvIcfPOh_ayujFt5gif_2PvOI,12852
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=SrGu3TX8cQnvSPzJC5W5izZPJe9beQPQIKyT7PHwepQ,24485
10
+ nost_tools/observer.py,sha256=D64V0KTvHRPEqbB8q3BosJhoAlpBah2vyBlVbxWQR44,8161
11
+ nost_tools/publisher.py,sha256=omU8tb0AXnA6RfhYSh0vnXbJtrRo4ukx1J5ANl4bDLQ,5291
12
+ nost_tools/schemas.py,sha256=VAwHuIxVVbf-Te_CWkZ6iNvJwRvr9PYJRRD2dt0nMJ4,19068
13
+ nost_tools/simulator.py,sha256=ALnGDmnA_ga-1Lq-bVWi2vcrspgjS4vtuDE0jWsI7fE,20191
14
+ nost_tools-2.2.0.dist-info/licenses/LICENSE,sha256=aAMU-mTHTKpWkBsg9QhkhCQpEm3Gri7J_fVuJov8s3s,1539
15
+ nost_tools-2.2.0.dist-info/METADATA,sha256=Mvx484RUws2yo8PdAZPnCIgx0l2ssODBeJkQucB-Xuk,4256
16
+ nost_tools-2.2.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
17
+ nost_tools-2.2.0.dist-info/top_level.txt,sha256=LNChUgrv2-wiym12O0r61kY83COjTpTiJ2Ly1Ca58A8,11
18
+ nost_tools-2.2.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.8.0)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -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,,