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.
Files changed (44) hide show
  1. {opensmi_core-0.4 → opensmi_core-0.4.1}/PKG-INFO +1 -1
  2. {opensmi_core-0.4 → opensmi_core-0.4.1}/src/OpenSMI_Core.egg-info/PKG-INFO +1 -1
  3. opensmi_core-0.4.1/src/OpenSMI_Core.egg-info/scm_version.json +8 -0
  4. {opensmi_core-0.4 → opensmi_core-0.4.1}/src/opensmi/core/lifecycle_mixin.py +34 -27
  5. opensmi_core-0.4/src/OpenSMI_Core.egg-info/scm_version.json +0 -8
  6. {opensmi_core-0.4 → opensmi_core-0.4.1}/.github/workflows/api-docs.yml +0 -0
  7. {opensmi_core-0.4 → opensmi_core-0.4.1}/.github/workflows/python-publish.yml +0 -0
  8. {opensmi_core-0.4 → opensmi_core-0.4.1}/.gitignore +0 -0
  9. {opensmi_core-0.4 → opensmi_core-0.4.1}/.gitlab-ci.yml +0 -0
  10. {opensmi_core-0.4 → opensmi_core-0.4.1}/LICENSE +0 -0
  11. {opensmi_core-0.4 → opensmi_core-0.4.1}/LICENSES/MIT.txt +0 -0
  12. {opensmi_core-0.4 → opensmi_core-0.4.1}/README.md +0 -0
  13. {opensmi_core-0.4 → opensmi_core-0.4.1}/pyproject.toml +0 -0
  14. {opensmi_core-0.4 → opensmi_core-0.4.1}/setup.cfg +0 -0
  15. {opensmi_core-0.4 → opensmi_core-0.4.1}/src/OpenSMI_Core.egg-info/SOURCES.txt +0 -0
  16. {opensmi_core-0.4 → opensmi_core-0.4.1}/src/OpenSMI_Core.egg-info/dependency_links.txt +0 -0
  17. {opensmi_core-0.4 → opensmi_core-0.4.1}/src/OpenSMI_Core.egg-info/requires.txt +0 -0
  18. {opensmi_core-0.4 → opensmi_core-0.4.1}/src/OpenSMI_Core.egg-info/scm_file_list.json +0 -0
  19. {opensmi_core-0.4 → opensmi_core-0.4.1}/src/OpenSMI_Core.egg-info/top_level.txt +0 -0
  20. {opensmi_core-0.4 → opensmi_core-0.4.1}/src/opensmi/core/__init__.py +0 -0
  21. {opensmi_core-0.4 → opensmi_core-0.4.1}/src/opensmi/core/async_task_mixin.py +0 -0
  22. {opensmi_core-0.4 → opensmi_core-0.4.1}/src/opensmi/core/base_server.py +0 -0
  23. {opensmi_core-0.4 → opensmi_core-0.4.1}/src/opensmi/core/base_ua_object.py +0 -0
  24. {opensmi_core-0.4 → opensmi_core-0.4.1}/src/opensmi/core/config.py +0 -0
  25. {opensmi_core-0.4 → opensmi_core-0.4.1}/src/opensmi/core/enums.py +0 -0
  26. {opensmi_core-0.4 → opensmi_core-0.4.1}/src/opensmi/core/errors.py +0 -0
  27. {opensmi_core-0.4 → opensmi_core-0.4.1}/src/opensmi/core/frozen.py +0 -0
  28. {opensmi_core-0.4 → opensmi_core-0.4.1}/src/opensmi/core/hook.py +0 -0
  29. {opensmi_core-0.4 → opensmi_core-0.4.1}/src/opensmi/core/logging.py +0 -0
  30. {opensmi_core-0.4 → opensmi_core-0.4.1}/src/opensmi/core/meta.py +0 -0
  31. {opensmi_core-0.4 → opensmi_core-0.4.1}/src/opensmi/core/mixin_helpers.py +0 -0
  32. {opensmi_core-0.4 → opensmi_core-0.4.1}/src/opensmi/core/protocols.py +0 -0
  33. {opensmi_core-0.4 → opensmi_core-0.4.1}/src/opensmi/core/repr_mixin.py +0 -0
  34. {opensmi_core-0.4 → opensmi_core-0.4.1}/src/opensmi/core/signal.py +0 -0
  35. {opensmi_core-0.4 → opensmi_core-0.4.1}/src/opensmi/core/subscription_manager.py +0 -0
  36. {opensmi_core-0.4 → opensmi_core-0.4.1}/src/opensmi/core/ua.py +0 -0
  37. {opensmi_core-0.4 → opensmi_core-0.4.1}/src/opensmi/core/ua_enums.py +0 -0
  38. {opensmi_core-0.4 → opensmi_core-0.4.1}/src/opensmi/core/ua_node_util.py +0 -0
  39. {opensmi_core-0.4 → opensmi_core-0.4.1}/src/opensmi/core/unit_all.py +0 -0
  40. {opensmi_core-0.4 → opensmi_core-0.4.1}/src/opensmi/core/unit_base.py +0 -0
  41. {opensmi_core-0.4 → opensmi_core-0.4.1}/src/opensmi/core/unset.py +0 -0
  42. {opensmi_core-0.4 → opensmi_core-0.4.1}/tests/test_signal.py +0 -0
  43. {opensmi_core-0.4 → opensmi_core-0.4.1}/tests/test_unit.py +0 -0
  44. {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
@@ -0,0 +1,8 @@
1
+ {
2
+ "tag": "0.4.1",
3
+ "distance": 0,
4
+ "node": "gb332e932b41897204fcea659cd4b99c6cc4f228d",
5
+ "dirty": false,
6
+ "branch": "HEAD",
7
+ "node_date": "2026-10-13"
8
+ }
@@ -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 mixin: a plain class (does not inherit `LifecycleMixin`) with one or
10
- multiple ``@lifecycle``-decorated async-generator method(s). Code before yield is init;
11
- code after yield is shutdown.
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
- Composed class: the concrete class that inherits its resource mixins and
14
- `LifecycleMixin`. This is the only class that should inherit `LifecycleMixin`.
15
+ Resource mixin
16
+ A plain class (does not inherit `LifecycleMixin`) that provides one or more resources.
15
17
 
16
- Ordering: resources init in dependency order and shut down in the exact
17
- reverse order. Order is resolved once, in `LifecycleMixin.__init_subclass__`, from
18
- ``after`` / ``before`` arguments of ``@lifecycle``-decorated methods.
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 lifecycle resource.
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 @resource, no args
102
+ if _func is not None: # bare @lifecycle, no args
96
103
  return _decorator(_func)
97
- return _decorator # @resource(after=(...))
104
+ return _decorator # @lifecycle(after=(...))
98
105
 
99
106
 
100
107
  class LifecycleMixin:
101
- """Mixin managing the lifecycle of classes with ``@resource`` decorated methods."""
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 ExceptionGroup: If exceptions were caught during rollback.
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 isinstance(init_exception, CancelledError):
148
- get_logger(self, _LOGGER).error("Initialization cancelled")
149
- else:
150
- get_logger(self, _LOGGER).exception("Failed to initialize!")
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 ExceptionGroup(
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[type, ...] = getattr(func, _ATTR_LIFECYCLE_REQUIRES, ())
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[type, ...] = getattr(func, _ATTR_LIFECYCLE_AFTER, ())
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[type, ...] = getattr(func, _ATTR_LIFECYCLE_BEFORE, ())
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)
@@ -1,8 +0,0 @@
1
- {
2
- "tag": "0.4",
3
- "distance": 0,
4
- "node": "gf395f24046a13a5a9c4b6536f16e250aada34582",
5
- "dirty": false,
6
- "branch": "HEAD",
7
- "node_date": "2026-10-12"
8
- }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes