nost-tools 2.0.0__py3-none-any.whl → 2.0.1__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/entity.py CHANGED
@@ -1,73 +1,73 @@
1
- """
2
- Provides a base class to maintain state variables during scenario execution.
3
- """
4
-
5
- import logging
6
- from datetime import datetime, timedelta
7
-
8
- from .observer import Observable
9
-
10
- logger = logging.getLogger(__name__)
11
-
12
-
13
- class Entity(Observable):
14
- """
15
- A base entity that maintains its own clock (time) during scenario execution.
16
-
17
- Notifies observers of changes to one observable property
18
- * `time`: current scenario time
19
-
20
- Attributes:
21
- name (str): The entity name (optional)
22
- """
23
-
24
- PROPERTY_TIME = "time"
25
-
26
- def __init__(self, name: str = None):
27
- """
28
- Initializes a new entity.
29
-
30
- Args:
31
- name (str): name of the entity (default: None)
32
- """
33
- super().__init__()
34
- self.name = name
35
- self._init_time = self._time = self._next_time = None
36
-
37
- def initialize(self, init_time: datetime) -> None:
38
- """
39
- Initializes the entity at a designated initial scenario time.
40
-
41
- Args:
42
- init_time (:obj:`datetime`): initial scenario time
43
- """
44
- self._init_time = self._time = self._next_time = init_time
45
-
46
- def tick(self, time_step: timedelta) -> None:
47
- """
48
- Computes the next state transition following an elapsed scenario duration (time step).
49
-
50
- Args:
51
- time_step (:obj:`timedelta`): elapsed scenario duration
52
- """
53
- self._next_time = self._time + time_step
54
-
55
- def tock(self) -> None:
56
- """
57
- Commits the state transition pre-computed in `tick` and notifies observers of changes.
58
- """
59
- # update the time
60
- if self._time != self._next_time:
61
- prev_time = self._time
62
- self._time = self._next_time
63
- logger.debug(f"Entity {self.name} updated time to {self._time}.")
64
- self.notify_observers(self.PROPERTY_TIME, prev_time, self._time)
65
-
66
- def get_time(self) -> datetime:
67
- """
68
- Retrieves the current scenario time.
69
-
70
- Returns:
71
- :obj:`datetime`: current scenario time
72
- """
73
- return self._time
1
+ """
2
+ Provides a base class to maintain state variables during scenario execution.
3
+ """
4
+
5
+ import logging
6
+ from datetime import datetime, timedelta
7
+
8
+ from .observer import Observable
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ class Entity(Observable):
14
+ """
15
+ A base entity that maintains its own clock (time) during scenario execution.
16
+
17
+ Notifies observers of changes to one observable property
18
+ * `time`: current scenario time
19
+
20
+ Attributes:
21
+ name (str): The entity name (optional)
22
+ """
23
+
24
+ PROPERTY_TIME = "time"
25
+
26
+ def __init__(self, name: str = None):
27
+ """
28
+ Initializes a new entity.
29
+
30
+ Args:
31
+ name (str): name of the entity (default: None)
32
+ """
33
+ super().__init__()
34
+ self.name = name
35
+ self._init_time = self._time = self._next_time = None
36
+
37
+ def initialize(self, init_time: datetime) -> None:
38
+ """
39
+ Initializes the entity at a designated initial scenario time.
40
+
41
+ Args:
42
+ init_time (:obj:`datetime`): initial scenario time
43
+ """
44
+ self._init_time = self._time = self._next_time = init_time
45
+
46
+ def tick(self, time_step: timedelta) -> None:
47
+ """
48
+ Computes the next state transition following an elapsed scenario duration (time step).
49
+
50
+ Args:
51
+ time_step (:obj:`timedelta`): elapsed scenario duration
52
+ """
53
+ self._next_time = self._time + time_step
54
+
55
+ def tock(self) -> None:
56
+ """
57
+ Commits the state transition pre-computed in `tick` and notifies observers of changes.
58
+ """
59
+ # update the time
60
+ if self._time != self._next_time:
61
+ prev_time = self._time
62
+ self._time = self._next_time
63
+ logger.debug(f"Entity {self.name} updated time to {self._time}.")
64
+ self.notify_observers(self.PROPERTY_TIME, prev_time, self._time)
65
+
66
+ def get_time(self) -> datetime:
67
+ """
68
+ Retrieves the current scenario time.
69
+
70
+ Returns:
71
+ :obj:`datetime`: current scenario time
72
+ """
73
+ return self._time
nost_tools/errors.py CHANGED
@@ -1,14 +1,14 @@
1
- """
2
- Provides object models for common data structures.
3
- """
4
-
5
- class ConfigurationError(Exception):
6
- """Configuration error"""
7
-
8
-
9
- class EnvironmentVariableError(Exception):
10
- """Environment variable error"""
11
-
12
-
13
- class ConfigAssertionError(Exception): # Renamed to avoid shadowing built-in
14
- """Assertion error for configuration validation"""
1
+ """
2
+ Provides object models for common data structures.
3
+ """
4
+
5
+ class ConfigurationError(Exception):
6
+ """Configuration error"""
7
+
8
+
9
+ class EnvironmentVariableError(Exception):
10
+ """Environment variable error"""
11
+
12
+
13
+ class ConfigAssertionError(Exception): # Renamed to avoid shadowing built-in
14
+ """Assertion error for configuration validation"""
@@ -1,192 +1,192 @@
1
- """
2
- Provides a base logger application that subscribes and writes all messages to file.
3
- """
4
-
5
- import logging
6
- import os
7
- from datetime import datetime, timedelta
8
-
9
- from .application import Application
10
- from .configuration import ConnectionConfig
11
-
12
- logger = logging.getLogger(__name__)
13
-
14
-
15
- class LoggerApplication(Application):
16
- """
17
- Logger NOS-T Application.
18
-
19
- This object class defines the basic functionality for a NOS-T application
20
- that subscribes to a specified topic and logs all messages to file.
21
-
22
- Attributes:
23
- prefix (str): The test run namespace (prefix)
24
- simulator (:obj:`Simulator`): Application simulator defined in the *Simulator* class
25
- app_name (str): Logger application name (default: logger)
26
- app_description (str): Logger application description (optional)
27
- log_app (str): Application name to be logged (default: "+")
28
- log_topic (str): Topic to be logged (default: "#")
29
- log_dir (str): Directory to write log files (default: ".")
30
- log_file (:obj:`File`): Current log file
31
- """
32
-
33
- def __init__(self, app_name: str = "logger", app_description: str = None):
34
- """
35
- Initializes a new logging application.
36
-
37
- Args:
38
- app_name (str): application name (default: "logger")
39
- app_description (str): application description (optional)
40
- """
41
- super().__init__(app_name, app_description)
42
- self.log_topic = None
43
- self.log_app = None
44
- self.log_dir = None
45
- self.log_file = None
46
-
47
- def start_up(
48
- self,
49
- prefix: str,
50
- config: ConnectionConfig,
51
- set_offset: bool = None,
52
- time_status_step: timedelta = None,
53
- time_status_init: datetime = None,
54
- shut_down_when_terminated: bool = None,
55
- time_step: timedelta = None,
56
- manager_app_name: str = None,
57
- log_app: str = "+",
58
- log_topic: str = "#",
59
- log_dir: str = ".",
60
- ) -> None:
61
- """
62
- Starts up the logger application by connecting to message broker,
63
- starting a background event loop, subscribing to manager events, and
64
- registering callback functions.
65
-
66
- Args:
67
- prefix (str): The test run namespace (prefix)
68
- config (:obj:`ConnectionConfig`): The connection configuration
69
- set_offset (bool): True, if the system clock offset shall be set
70
- time_status_step (:obj:`timedelta`): Time interval for status messages
71
- time_status_init (:obj:`datetime`): Initial time for status messages
72
- shut_down_when_terminated (bool): True, if the application shall shut down when terminated
73
- time_step (:obj:`timedelta`): Time step for the application
74
- manager_app_name (str): Manager application name
75
- log_app (str): Application name to be logged (default: "+")
76
- log_topic (str): Topic to be logged (default: "#")
77
- log_dir (str): Directory to write log files (default: ".")
78
- """
79
- if (
80
- set_offset is not None
81
- and time_status_step is not None
82
- and time_status_init is not None
83
- and shut_down_when_terminated is not None
84
- and time_step is not None
85
- and manager_app_name is not None
86
- ):
87
- self.set_offset = set_offset
88
- self.time_status_step = time_status_step
89
- self.time_status_init = time_status_init
90
- self.shut_down_when_terminated = shut_down_when_terminated
91
- self.time_step = time_step
92
- self.manager_app_name = manager_app_name
93
- else:
94
- self.config = config
95
- parameters = getattr(
96
- self.config.rc.simulation_configuration.execution_parameters,
97
- "logger_application",
98
- None,
99
- )
100
- self.set_offset = parameters.set_offset
101
- self.time_status_step = parameters.time_status_step
102
- self.time_status_init = parameters.time_status_init
103
- self.shut_down_when_terminated = parameters.shut_down_when_terminated
104
-
105
- self.log_app = log_app
106
- self.log_topic = log_topic
107
- self.log_dir = log_dir
108
-
109
- # Create log directory if it doesn't exist
110
- os.makedirs(self.log_dir, exist_ok=True)
111
-
112
- # Open log file now
113
- self._open_log_file()
114
-
115
- # Start up base application
116
- super().start_up(
117
- prefix,
118
- config,
119
- self.set_offset,
120
- self.time_status_step,
121
- self.time_status_init,
122
- self.shut_down_when_terminated,
123
- )
124
-
125
- # Add callback for specified topic(s)
126
- self.add_message_callback(self.log_app, self.log_topic, self.on_log_message)
127
-
128
- logger.info(f"Logger {self.app_name} started up and listening for messages.")
129
-
130
- def shut_down(self) -> None:
131
- """
132
- Shuts down the application by stopping the background event loop
133
- and disconnecting from the message broker.
134
- """
135
- # Close the log file if it's open
136
- self._close_log_file()
137
-
138
- # Shut down base application
139
- super().shut_down()
140
-
141
- def _open_log_file(self) -> None:
142
- """
143
- Opens a new log file for writing messages.
144
- """
145
- if self.log_file is not None:
146
- self._close_log_file()
147
-
148
- ts = (
149
- str(self.simulator.get_wallclock_time()).replace(" ", "T").replace(":", "-")
150
- )
151
- log_filename = os.path.join(self.log_dir, f"{ts}.log")
152
- self.log_file = open(log_filename, "a")
153
- self.log_file.write(f"Timestamp,Topic,Payload\n")
154
- logger.info(f"Logger {self.app_name} opened file {self.log_file.name}.")
155
-
156
- def _close_log_file(self) -> None:
157
- """
158
- Closes the current log file if it's open.
159
- """
160
- if self.log_file is not None:
161
- self.log_file.close()
162
- logger.info(f"Logger {self.app_name} closed file {self.log_file.name}.")
163
- self.log_file = None
164
-
165
- def on_log_message(self, ch, method, properties, body):
166
- """
167
- Callback function to log a message received by the logger application.
168
-
169
- Args:
170
- ch: The channel object
171
- method: The method frame
172
- properties: The message properties
173
- body: The message body
174
- """
175
- if self.log_file is not None:
176
- try:
177
- routing_key = method.routing_key
178
- payload = body.decode("utf-8") if isinstance(body, bytes) else str(body)
179
-
180
- logger.debug(f"Logger {self.app_name} logging message: {payload}")
181
-
182
- timestamp = self.simulator.get_wallclock_time()
183
- self.log_file.write(f"{timestamp},{routing_key},{payload}\n")
184
- self.log_file.flush() # Ensure data is written immediately
185
- except Exception as e:
186
- logger.error(f"Error logging message: {e}")
187
- else:
188
- # If log file isn't open, try to reopen it
189
- self._open_log_file()
190
- logger.error(f"Logger {self.app_name} had to reopen log file.")
191
- # Try to log the message again
192
- self.on_log_message(ch, method, properties, body)
1
+ """
2
+ Provides a base logger application that subscribes and writes all messages to file.
3
+ """
4
+
5
+ import logging
6
+ import os
7
+ from datetime import datetime, timedelta
8
+
9
+ from .application import Application
10
+ from .configuration import ConnectionConfig
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ class LoggerApplication(Application):
16
+ """
17
+ Logger NOS-T Application.
18
+
19
+ This object class defines the basic functionality for a NOS-T application
20
+ that subscribes to a specified topic and logs all messages to file.
21
+
22
+ Attributes:
23
+ prefix (str): The test run namespace (prefix)
24
+ simulator (:obj:`Simulator`): Application simulator defined in the *Simulator* class
25
+ app_name (str): Logger application name (default: logger)
26
+ app_description (str): Logger application description (optional)
27
+ log_app (str): Application name to be logged (default: "+")
28
+ log_topic (str): Topic to be logged (default: "#")
29
+ log_dir (str): Directory to write log files (default: ".")
30
+ log_file (:obj:`File`): Current log file
31
+ """
32
+
33
+ def __init__(self, app_name: str = "logger", app_description: str = None):
34
+ """
35
+ Initializes a new logging application.
36
+
37
+ Args:
38
+ app_name (str): application name (default: "logger")
39
+ app_description (str): application description (optional)
40
+ """
41
+ super().__init__(app_name, app_description)
42
+ self.log_topic = None
43
+ self.log_app = None
44
+ self.log_dir = None
45
+ self.log_file = None
46
+
47
+ def start_up(
48
+ self,
49
+ prefix: str,
50
+ config: ConnectionConfig,
51
+ set_offset: bool = None,
52
+ time_status_step: timedelta = None,
53
+ time_status_init: datetime = None,
54
+ shut_down_when_terminated: bool = None,
55
+ time_step: timedelta = None,
56
+ manager_app_name: str = None,
57
+ log_app: str = "+",
58
+ log_topic: str = "#",
59
+ log_dir: str = ".",
60
+ ) -> None:
61
+ """
62
+ Starts up the logger application by connecting to message broker,
63
+ starting a background event loop, subscribing to manager events, and
64
+ registering callback functions.
65
+
66
+ Args:
67
+ prefix (str): The test run namespace (prefix)
68
+ config (:obj:`ConnectionConfig`): The connection configuration
69
+ set_offset (bool): True, if the system clock offset shall be set
70
+ time_status_step (:obj:`timedelta`): Time interval for status messages
71
+ time_status_init (:obj:`datetime`): Initial time for status messages
72
+ shut_down_when_terminated (bool): True, if the application shall shut down when terminated
73
+ time_step (:obj:`timedelta`): Time step for the application
74
+ manager_app_name (str): Manager application name
75
+ log_app (str): Application name to be logged (default: "+")
76
+ log_topic (str): Topic to be logged (default: "#")
77
+ log_dir (str): Directory to write log files (default: ".")
78
+ """
79
+ if (
80
+ set_offset is not None
81
+ and time_status_step is not None
82
+ and time_status_init is not None
83
+ and shut_down_when_terminated is not None
84
+ and time_step is not None
85
+ and manager_app_name is not None
86
+ ):
87
+ self.set_offset = set_offset
88
+ self.time_status_step = time_status_step
89
+ self.time_status_init = time_status_init
90
+ self.shut_down_when_terminated = shut_down_when_terminated
91
+ self.time_step = time_step
92
+ self.manager_app_name = manager_app_name
93
+ else:
94
+ self.config = config
95
+ parameters = getattr(
96
+ self.config.rc.simulation_configuration.execution_parameters,
97
+ "logger_application",
98
+ None,
99
+ )
100
+ self.set_offset = parameters.set_offset
101
+ self.time_status_step = parameters.time_status_step
102
+ self.time_status_init = parameters.time_status_init
103
+ self.shut_down_when_terminated = parameters.shut_down_when_terminated
104
+
105
+ self.log_app = log_app
106
+ self.log_topic = log_topic
107
+ self.log_dir = log_dir
108
+
109
+ # Create log directory if it doesn't exist
110
+ os.makedirs(self.log_dir, exist_ok=True)
111
+
112
+ # Open log file now
113
+ self._open_log_file()
114
+
115
+ # Start up base application
116
+ super().start_up(
117
+ prefix,
118
+ config,
119
+ self.set_offset,
120
+ self.time_status_step,
121
+ self.time_status_init,
122
+ self.shut_down_when_terminated,
123
+ )
124
+
125
+ # Add callback for specified topic(s)
126
+ self.add_message_callback(self.log_app, self.log_topic, self.on_log_message)
127
+
128
+ logger.info(f"Logger {self.app_name} started up and listening for messages.")
129
+
130
+ def shut_down(self) -> None:
131
+ """
132
+ Shuts down the application by stopping the background event loop
133
+ and disconnecting from the message broker.
134
+ """
135
+ # Close the log file if it's open
136
+ self._close_log_file()
137
+
138
+ # Shut down base application
139
+ super().shut_down()
140
+
141
+ def _open_log_file(self) -> None:
142
+ """
143
+ Opens a new log file for writing messages.
144
+ """
145
+ if self.log_file is not None:
146
+ self._close_log_file()
147
+
148
+ ts = (
149
+ str(self.simulator.get_wallclock_time()).replace(" ", "T").replace(":", "-")
150
+ )
151
+ log_filename = os.path.join(self.log_dir, f"{ts}.log")
152
+ self.log_file = open(log_filename, "a")
153
+ self.log_file.write(f"Timestamp,Topic,Payload\n")
154
+ logger.info(f"Logger {self.app_name} opened file {self.log_file.name}.")
155
+
156
+ def _close_log_file(self) -> None:
157
+ """
158
+ Closes the current log file if it's open.
159
+ """
160
+ if self.log_file is not None:
161
+ self.log_file.close()
162
+ logger.info(f"Logger {self.app_name} closed file {self.log_file.name}.")
163
+ self.log_file = None
164
+
165
+ def on_log_message(self, ch, method, properties, body):
166
+ """
167
+ Callback function to log a message received by the logger application.
168
+
169
+ Args:
170
+ ch: The channel object
171
+ method: The method frame
172
+ properties: The message properties
173
+ body: The message body
174
+ """
175
+ if self.log_file is not None:
176
+ try:
177
+ routing_key = method.routing_key
178
+ payload = body.decode("utf-8") if isinstance(body, bytes) else str(body)
179
+
180
+ logger.debug(f"Logger {self.app_name} logging message: {payload}")
181
+
182
+ timestamp = self.simulator.get_wallclock_time()
183
+ self.log_file.write(f"{timestamp},{routing_key},{payload}\n")
184
+ self.log_file.flush() # Ensure data is written immediately
185
+ except Exception as e:
186
+ logger.error(f"Error logging message: {e}")
187
+ else:
188
+ # If log file isn't open, try to reopen it
189
+ self._open_log_file()
190
+ logger.error(f"Logger {self.app_name} had to reopen log file.")
191
+ # Try to log the message again
192
+ self.on_log_message(ch, method, properties, body)