OpenSMI-Core 0.4__tar.gz → 0.4.1__tar.gz
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.
- {opensmi_core-0.4 → opensmi_core-0.4.1}/PKG-INFO +1 -1
- {opensmi_core-0.4 → opensmi_core-0.4.1}/src/OpenSMI_Core.egg-info/PKG-INFO +1 -1
- opensmi_core-0.4.1/src/OpenSMI_Core.egg-info/scm_version.json +8 -0
- {opensmi_core-0.4 → opensmi_core-0.4.1}/src/opensmi/core/lifecycle_mixin.py +34 -27
- opensmi_core-0.4/src/OpenSMI_Core.egg-info/scm_version.json +0 -8
- {opensmi_core-0.4 → opensmi_core-0.4.1}/.github/workflows/api-docs.yml +0 -0
- {opensmi_core-0.4 → opensmi_core-0.4.1}/.github/workflows/python-publish.yml +0 -0
- {opensmi_core-0.4 → opensmi_core-0.4.1}/.gitignore +0 -0
- {opensmi_core-0.4 → opensmi_core-0.4.1}/.gitlab-ci.yml +0 -0
- {opensmi_core-0.4 → opensmi_core-0.4.1}/LICENSE +0 -0
- {opensmi_core-0.4 → opensmi_core-0.4.1}/LICENSES/MIT.txt +0 -0
- {opensmi_core-0.4 → opensmi_core-0.4.1}/README.md +0 -0
- {opensmi_core-0.4 → opensmi_core-0.4.1}/pyproject.toml +0 -0
- {opensmi_core-0.4 → opensmi_core-0.4.1}/setup.cfg +0 -0
- {opensmi_core-0.4 → opensmi_core-0.4.1}/src/OpenSMI_Core.egg-info/SOURCES.txt +0 -0
- {opensmi_core-0.4 → opensmi_core-0.4.1}/src/OpenSMI_Core.egg-info/dependency_links.txt +0 -0
- {opensmi_core-0.4 → opensmi_core-0.4.1}/src/OpenSMI_Core.egg-info/requires.txt +0 -0
- {opensmi_core-0.4 → opensmi_core-0.4.1}/src/OpenSMI_Core.egg-info/scm_file_list.json +0 -0
- {opensmi_core-0.4 → opensmi_core-0.4.1}/src/OpenSMI_Core.egg-info/top_level.txt +0 -0
- {opensmi_core-0.4 → opensmi_core-0.4.1}/src/opensmi/core/__init__.py +0 -0
- {opensmi_core-0.4 → opensmi_core-0.4.1}/src/opensmi/core/async_task_mixin.py +0 -0
- {opensmi_core-0.4 → opensmi_core-0.4.1}/src/opensmi/core/base_server.py +0 -0
- {opensmi_core-0.4 → opensmi_core-0.4.1}/src/opensmi/core/base_ua_object.py +0 -0
- {opensmi_core-0.4 → opensmi_core-0.4.1}/src/opensmi/core/config.py +0 -0
- {opensmi_core-0.4 → opensmi_core-0.4.1}/src/opensmi/core/enums.py +0 -0
- {opensmi_core-0.4 → opensmi_core-0.4.1}/src/opensmi/core/errors.py +0 -0
- {opensmi_core-0.4 → opensmi_core-0.4.1}/src/opensmi/core/frozen.py +0 -0
- {opensmi_core-0.4 → opensmi_core-0.4.1}/src/opensmi/core/hook.py +0 -0
- {opensmi_core-0.4 → opensmi_core-0.4.1}/src/opensmi/core/logging.py +0 -0
- {opensmi_core-0.4 → opensmi_core-0.4.1}/src/opensmi/core/meta.py +0 -0
- {opensmi_core-0.4 → opensmi_core-0.4.1}/src/opensmi/core/mixin_helpers.py +0 -0
- {opensmi_core-0.4 → opensmi_core-0.4.1}/src/opensmi/core/protocols.py +0 -0
- {opensmi_core-0.4 → opensmi_core-0.4.1}/src/opensmi/core/repr_mixin.py +0 -0
- {opensmi_core-0.4 → opensmi_core-0.4.1}/src/opensmi/core/signal.py +0 -0
- {opensmi_core-0.4 → opensmi_core-0.4.1}/src/opensmi/core/subscription_manager.py +0 -0
- {opensmi_core-0.4 → opensmi_core-0.4.1}/src/opensmi/core/ua.py +0 -0
- {opensmi_core-0.4 → opensmi_core-0.4.1}/src/opensmi/core/ua_enums.py +0 -0
- {opensmi_core-0.4 → opensmi_core-0.4.1}/src/opensmi/core/ua_node_util.py +0 -0
- {opensmi_core-0.4 → opensmi_core-0.4.1}/src/opensmi/core/unit_all.py +0 -0
- {opensmi_core-0.4 → opensmi_core-0.4.1}/src/opensmi/core/unit_base.py +0 -0
- {opensmi_core-0.4 → opensmi_core-0.4.1}/src/opensmi/core/unset.py +0 -0
- {opensmi_core-0.4 → opensmi_core-0.4.1}/tests/test_signal.py +0 -0
- {opensmi_core-0.4 → opensmi_core-0.4.1}/tests/test_unit.py +0 -0
- {opensmi_core-0.4 → opensmi_core-0.4.1}/tools/auto_generate_units.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: OpenSMI-Core
|
|
3
|
-
Version: 0.4
|
|
3
|
+
Version: 0.4.1
|
|
4
4
|
Summary: Smart Machine Interface core functionality for server and client applications.
|
|
5
5
|
Author-email: Carsten Harms <Carsten.Harms@dfki.de>, Simon Jungbluth <Simon.Jungbluth@smartfactory.de>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: OpenSMI-Core
|
|
3
|
-
Version: 0.4
|
|
3
|
+
Version: 0.4.1
|
|
4
4
|
Summary: Smart Machine Interface core functionality for server and client applications.
|
|
5
5
|
Author-email: Carsten Harms <Carsten.Harms@dfki.de>, Simon Jungbluth <Simon.Jungbluth@smartfactory.de>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -5,23 +5,30 @@
|
|
|
5
5
|
"""Mixin for async lifecycle management for mixin-composed classes.
|
|
6
6
|
|
|
7
7
|
Concepts
|
|
8
|
+
========
|
|
8
9
|
|
|
9
|
-
Resource
|
|
10
|
-
|
|
11
|
-
code after yield is
|
|
10
|
+
Resource
|
|
11
|
+
A single `lifecycle`-decorated async-generator method. Code before ``yield`` initializes it;
|
|
12
|
+
code after ``yield`` shuts it down. A resource is the unit of ordering:
|
|
13
|
+
``after``, ``before`` and ``requires`` refer to resources, not classes.
|
|
12
14
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
+
Resource mixin
|
|
16
|
+
A plain class (does not inherit `LifecycleMixin`) that provides one or more resources.
|
|
15
17
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
18
|
+
Composed class
|
|
19
|
+
The concrete class that inherits its resource mixins and `LifecycleMixin`.
|
|
20
|
+
This is the only class that should inherit `LifecycleMixin`.
|
|
21
|
+
|
|
22
|
+
Ordering
|
|
23
|
+
Resources are initialized in dependency order and shut down in the exact reverse order. Order is
|
|
24
|
+
resolved once, in `LifecycleMixin.__init_subclass__`, from ``after`` / ``before``
|
|
25
|
+
arguments of `lifecycle`-decorated methods. ``requires`` is checked at the same point
|
|
26
|
+
but imposes no ordering.
|
|
19
27
|
"""
|
|
20
28
|
|
|
21
29
|
import graphlib
|
|
22
30
|
import inspect
|
|
23
31
|
import time
|
|
24
|
-
from asyncio import CancelledError
|
|
25
32
|
from collections.abc import AsyncGenerator, Callable
|
|
26
33
|
from typing import Final, Self, TypeVar, overload
|
|
27
34
|
|
|
@@ -47,7 +54,7 @@ _FuncOrFuncs = _Func | tuple[_Func, ...] | list[_Func]
|
|
|
47
54
|
def lifecycle(_func: _LifecycleFunc) -> _LifecycleFunc: ...
|
|
48
55
|
@overload
|
|
49
56
|
def lifecycle(
|
|
50
|
-
_func: None = None, *, after: _FuncOrFuncs = (), before: _FuncOrFuncs = ()
|
|
57
|
+
_func: None = None, *, after: _FuncOrFuncs = (), before: _FuncOrFuncs = (), requires: _FuncOrFuncs = ()
|
|
51
58
|
) -> Callable[[_LifecycleFunc], _LifecycleFunc]: ...
|
|
52
59
|
def lifecycle(
|
|
53
60
|
_func: _LifecycleFunc | None = None,
|
|
@@ -56,7 +63,7 @@ def lifecycle(
|
|
|
56
63
|
before: _FuncOrFuncs = (),
|
|
57
64
|
requires: _FuncOrFuncs = (),
|
|
58
65
|
) -> _LifecycleFunc | Callable[[_LifecycleFunc], _LifecycleFunc]:
|
|
59
|
-
"""Mark an async generator method as a
|
|
66
|
+
"""Mark an async generator method as a resource.
|
|
60
67
|
|
|
61
68
|
Code before ``yield`` runs during initialization; code after ``yield``
|
|
62
69
|
runs during shutdown, in reverse lifecycle order.
|
|
@@ -92,13 +99,13 @@ def lifecycle(
|
|
|
92
99
|
setattr(func, _ATTR_LIFECYCLE_REQUIRES, tuple(_normalize(requires)))
|
|
93
100
|
return func
|
|
94
101
|
|
|
95
|
-
if _func is not None: # bare @
|
|
102
|
+
if _func is not None: # bare @lifecycle, no args
|
|
96
103
|
return _decorator(_func)
|
|
97
|
-
return _decorator # @
|
|
104
|
+
return _decorator # @lifecycle(after=(...))
|
|
98
105
|
|
|
99
106
|
|
|
100
107
|
class LifecycleMixin:
|
|
101
|
-
"""Mixin managing the lifecycle of classes with
|
|
108
|
+
"""Mixin managing the lifecycle of classes with `lifecycle` decorated methods."""
|
|
102
109
|
|
|
103
110
|
__order: list[_Func] | None = None
|
|
104
111
|
__generators: dict[_Func, AsyncGenerator[None, None]] | None = None
|
|
@@ -124,7 +131,7 @@ class LifecycleMixin:
|
|
|
124
131
|
On failure, rollback anything already started, in reverse.
|
|
125
132
|
|
|
126
133
|
:raises BaseException: Reraised exception from failed resource initialization.
|
|
127
|
-
:raises
|
|
134
|
+
:raises BaseExceptionGroup: If exceptions were caught during rollback.
|
|
128
135
|
"""
|
|
129
136
|
if self.__state != LifecycleState.NEW:
|
|
130
137
|
return self
|
|
@@ -133,10 +140,12 @@ class LifecycleMixin:
|
|
|
133
140
|
self.__generators = {}
|
|
134
141
|
|
|
135
142
|
started: list[_Func] = []
|
|
143
|
+
current: _Func | None = None
|
|
136
144
|
time_start = time.monotonic()
|
|
137
145
|
try:
|
|
138
146
|
assert self.__order is not None
|
|
139
147
|
for func in self.__order:
|
|
148
|
+
current = func
|
|
140
149
|
get_logger(self, _LOGGER).debug("starting...", func=func.__qualname__)
|
|
141
150
|
agen = func(self)
|
|
142
151
|
await agen.__anext__() # run resource init (until it yields)
|
|
@@ -144,21 +153,19 @@ class LifecycleMixin:
|
|
|
144
153
|
started.append(func)
|
|
145
154
|
get_logger(self, _LOGGER).debug("started successfully", func=func.__qualname__)
|
|
146
155
|
except BaseException as init_exception:
|
|
147
|
-
if
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
156
|
+
if current is not None:
|
|
157
|
+
init_exception.add_note(
|
|
158
|
+
f"{type(self).__name__}: failed starting {current.__qualname__}; "
|
|
159
|
+
f"{len(started)} resource(s) had been started before it."
|
|
160
|
+
)
|
|
151
161
|
self.__state = LifecycleState.INITIALIZATION_FAILED
|
|
152
162
|
rollback_exceptions: list[Exception] = await self.__shutdown(started)
|
|
153
163
|
if rollback_exceptions:
|
|
154
164
|
msg = (
|
|
155
|
-
f"Resource failed to initialize, and rollback {len(rollback_exceptions)} "
|
|
165
|
+
f"Resource failed to initialize, and rollback of {len(rollback_exceptions)} "
|
|
156
166
|
"resource(s) failed to shut down cleanly."
|
|
157
167
|
)
|
|
158
|
-
raise
|
|
159
|
-
msg,
|
|
160
|
-
[init_exception, *rollback_exceptions], # pyright: ignore[reportArgumentType]
|
|
161
|
-
) from init_exception
|
|
168
|
+
raise BaseExceptionGroup(msg, [init_exception, *rollback_exceptions]) from init_exception
|
|
162
169
|
raise
|
|
163
170
|
|
|
164
171
|
self.__state = LifecycleState.INITIALIZED
|
|
@@ -256,7 +263,7 @@ def _build_edges(funcs: set[_Func]) -> dict[_Func, set[_Func]]:
|
|
|
256
263
|
|
|
257
264
|
for func in funcs:
|
|
258
265
|
# hard requirement: target must be present
|
|
259
|
-
required_funcs: tuple[
|
|
266
|
+
required_funcs: tuple[_Func, ...] = getattr(func, _ATTR_LIFECYCLE_REQUIRES, ())
|
|
260
267
|
for dep in required_funcs:
|
|
261
268
|
if dep not in funcs:
|
|
262
269
|
msg = (
|
|
@@ -266,12 +273,12 @@ def _build_edges(funcs: set[_Func]) -> dict[_Func, set[_Func]]:
|
|
|
266
273
|
raise TypeError(msg)
|
|
267
274
|
|
|
268
275
|
# soft hint: only wire the edge if the target actually ended up present
|
|
269
|
-
after_funcs: tuple[
|
|
276
|
+
after_funcs: tuple[_Func, ...] = getattr(func, _ATTR_LIFECYCLE_AFTER, ())
|
|
270
277
|
for after_func in after_funcs:
|
|
271
278
|
if after_func in funcs:
|
|
272
279
|
edges[func].add(after_func)
|
|
273
280
|
|
|
274
|
-
before_funcs: tuple[
|
|
281
|
+
before_funcs: tuple[_Func, ...] = getattr(func, _ATTR_LIFECYCLE_BEFORE, ())
|
|
275
282
|
for before_func in before_funcs:
|
|
276
283
|
if before_func in funcs:
|
|
277
284
|
edges[before_func].add(func)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|