cgse-common 2024.1.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.
egse/state.py ADDED
@@ -0,0 +1,173 @@
1
+ import abc
2
+ import logging
3
+ import textwrap
4
+ from pathlib import Path
5
+ from typing import Optional
6
+
7
+ from egse.decorators import borg
8
+ from egse.decorators import deprecate
9
+ from egse.setup import Setup
10
+
11
+ LOGGER = logging.getLogger(__name__)
12
+
13
+
14
+ class StateError(Exception):
15
+ pass
16
+
17
+
18
+ class UnknownStateError(StateError):
19
+ pass
20
+
21
+
22
+ class IllegalStateTransition(StateError):
23
+ pass
24
+
25
+
26
+ class NotImplementedTransition(StateError):
27
+ pass
28
+
29
+
30
+ class ConnectionStateInterface(abc.ABC):
31
+ """
32
+ A class used to enforce the implementation of the _connection_ interface
33
+ to model the state of a (network) connection.
34
+
35
+ Subclasses only need to implement those methods that are applicable to
36
+ their state.
37
+
38
+ """
39
+
40
+ # This class is to enforce the implementation of the interface on both the
41
+ # model, i.e. the Proxy, and the State class. At the same time, it will allow
42
+ # the State subclasses to implement only those methods that are applicable
43
+ # in their state.
44
+
45
+ @abc.abstractmethod
46
+ def connect(self, proxy):
47
+ pass
48
+
49
+ @abc.abstractmethod
50
+ def disconnect(self, proxy):
51
+ pass
52
+
53
+ @abc.abstractmethod
54
+ def reconnect(self, proxy):
55
+ pass
56
+
57
+
58
+ @borg
59
+ class _GlobalState:
60
+ """
61
+ This class implements global state that is shared between instances of this class.
62
+ """
63
+
64
+ # TODO (rik): turn command sequence into a class and move add_, clear_ and get_ to that class
65
+
66
+ def __init__(self):
67
+ self._dry_run = False
68
+ self._command_sequence = []
69
+ self._setup: Optional[Setup] = None
70
+
71
+ def __call__(self, *args, **kwargs):
72
+ return self
73
+
74
+ @property
75
+ def dry_run(self):
76
+ return self._dry_run
77
+
78
+ @dry_run.setter
79
+ def dry_run(self, flag: bool):
80
+ self._dry_run = flag
81
+
82
+ def add_command(self, cmd):
83
+ self._command_sequence.append(cmd)
84
+
85
+ def get_command_sequence(self):
86
+ return self._command_sequence
87
+
88
+ def clear_command_sequence(self):
89
+ self._command_sequence.clear()
90
+
91
+ @property
92
+ def setup(self) -> Optional[Setup]:
93
+ """
94
+ Returns the currently active Setup from the configuration manager. Please note that each call
95
+ to this property sends a request to the configuration manager to return its current Setup. If
96
+ you are accessing information from the Setup in a loop or function that is called often, save
97
+ the Setup into a local variable before proceeding.
98
+
99
+ Returns:
100
+ The currently active Setup or None (when the configuration manager is not reachable).
101
+ """
102
+ return self.load_setup()
103
+
104
+ # This function should be the standard function to reload a setup from the configuration manager
105
+ # Since we have no proper CM yet, the function loads from the default setup.yaml file.
106
+ # But what happens then in other parts of the system, where e.g. the PM has also the 'rights'
107
+ # to call load_setup() on the CM_CS?
108
+
109
+ def load_setup(self) -> Optional[Setup]:
110
+ """
111
+ Loads the currently active Setup from the Configuration manager. The current Setup is the Setup
112
+ that is defined and loaded in the Configuration manager. When the configuration manager is not
113
+ reachable, None will be returned and a warning will be logged.
114
+
115
+ Since the GlobalState should reflect the configuration of the test, it can only load the current
116
+ Setup from the configuration manager. If you need to work with different Setups, work with the `Setup`
117
+ class and the Configuration Manager directly.
118
+
119
+ Returns:
120
+ The currently active Setup or None.
121
+ """
122
+ from egse.confman import ConfigurationManagerProxy
123
+ from egse.confman import is_configuration_manager_active
124
+
125
+ if is_configuration_manager_active():
126
+ with ConfigurationManagerProxy() as cm_proxy:
127
+ self._setup = cm_proxy.get_setup()
128
+ else:
129
+ LOGGER.warning(textwrap.dedent(
130
+ f"""\
131
+ Could not reach the Configuration Manager to request the Setup, returning the current local Setup.
132
+
133
+ Check if the Configuration Manager is running and why it can not be consulted. When it's
134
+ back on-line, do a 'load_setup()'.
135
+ """
136
+ ))
137
+
138
+ return self._setup
139
+
140
+ # FIXME:
141
+ # These two 'private' methods are still called in plato-test-scripts, so until that is fixed,
142
+ # leave the methods here
143
+
144
+ @deprecate(reason="this is a private function", alternative="reload_setup()")
145
+ def _reload_setup(self):
146
+ self._setup = Setup.from_yaml_file()
147
+ return self._setup
148
+
149
+ @deprecate(reason="this is a private function", alternative="reload_setup_from()")
150
+ def _reload_setup_from(self, filename: Path):
151
+ """Used by the unit tests to load a predefined setup."""
152
+ self._setup = Setup.from_yaml_file(filename=filename)
153
+ return self.setup
154
+
155
+
156
+ GlobalState = _GlobalState()
157
+
158
+ __all__ = [
159
+ "GlobalState",
160
+ ]
161
+
162
+ if __name__ == "__main__":
163
+
164
+ from rich import print
165
+
166
+ print(textwrap.dedent(
167
+ f"""\
168
+ GlobalState info:
169
+ Setup loaded: {GlobalState.setup.get_id()}
170
+ Dry run: {'ON' if GlobalState.dry_run else 'OFF'}
171
+ Command Sequence: {GlobalState.get_command_sequence()} \
172
+ """)
173
+ )