omlish 0.0.0.dev484__py3-none-any.whl → 0.0.0.dev493__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 omlish might be problematic. Click here for more details.

Files changed (62) hide show
  1. omlish/README.md +199 -0
  2. omlish/__about__.py +8 -3
  3. omlish/dataclasses/impl/generation/plans.py +2 -17
  4. omlish/dataclasses/impl/generation/processor.py +2 -2
  5. omlish/dataclasses/impl/processing/driving.py +13 -1
  6. omlish/diag/_pycharm/runhack.py +1 -1
  7. omlish/inject/__init__.py +19 -11
  8. omlish/inject/_dataclasses.py +1485 -1323
  9. omlish/inject/binder.py +7 -4
  10. omlish/inject/eagers.py +2 -0
  11. omlish/inject/helpers/late.py +76 -0
  12. omlish/inject/{managed.py → helpers/managed.py} +10 -10
  13. omlish/inject/impl/elements.py +7 -4
  14. omlish/inject/impl/injector.py +10 -7
  15. omlish/inject/inspect.py +1 -1
  16. omlish/lang/__init__.py +2 -0
  17. omlish/lifecycles/README.md +30 -0
  18. omlish/lifecycles/__init__.py +87 -13
  19. omlish/lifecycles/_dataclasses.py +1388 -0
  20. omlish/lifecycles/base.py +178 -64
  21. omlish/lifecycles/contextmanagers.py +113 -4
  22. omlish/lifecycles/controller.py +150 -87
  23. omlish/lifecycles/injection.py +143 -0
  24. omlish/lifecycles/listeners.py +56 -0
  25. omlish/lifecycles/managed.py +142 -0
  26. omlish/lifecycles/manager.py +218 -93
  27. omlish/lifecycles/states.py +2 -0
  28. omlish/lifecycles/transitions.py +3 -0
  29. omlish/lifecycles/unwrap.py +57 -0
  30. omlish/lite/typing.py +18 -0
  31. omlish/logs/_amalg.py +1 -1
  32. omlish/logs/all.py +25 -11
  33. omlish/logs/asyncs.py +73 -0
  34. omlish/logs/base.py +101 -12
  35. omlish/logs/contexts.py +4 -1
  36. omlish/logs/lists.py +125 -0
  37. omlish/logs/modules.py +19 -1
  38. omlish/logs/std/loggers.py +6 -1
  39. omlish/logs/std/noisy.py +11 -9
  40. omlish/logs/{standard.py → std/standard.py} +3 -4
  41. omlish/logs/utils.py +16 -1
  42. omlish/marshal/_dataclasses.py +781 -781
  43. omlish/reflect/__init__.py +43 -26
  44. omlish/reflect/ops.py +10 -1
  45. omlish/specs/jmespath/_dataclasses.py +559 -559
  46. omlish/specs/jsonschema/keywords/_dataclasses.py +220 -220
  47. omlish/sql/__init__.py +24 -5
  48. omlish/sql/api/dbapi.py +1 -1
  49. omlish/sql/dbapi/__init__.py +15 -0
  50. omlish/sql/{dbapi.py → dbapi/drivers.py} +2 -2
  51. omlish/sql/queries/__init__.py +3 -0
  52. omlish/testing/pytest/plugins/asyncs/plugin.py +2 -0
  53. omlish/text/docwrap/cli.py +5 -0
  54. {omlish-0.0.0.dev484.dist-info → omlish-0.0.0.dev493.dist-info}/METADATA +8 -5
  55. {omlish-0.0.0.dev484.dist-info → omlish-0.0.0.dev493.dist-info}/RECORD +61 -51
  56. omlish/lifecycles/abstract.py +0 -86
  57. /omlish/inject/{impl → helpers}/proxy.py +0 -0
  58. /omlish/sql/{abc.py → dbapi/abc.py} +0 -0
  59. {omlish-0.0.0.dev484.dist-info → omlish-0.0.0.dev493.dist-info}/WHEEL +0 -0
  60. {omlish-0.0.0.dev484.dist-info → omlish-0.0.0.dev493.dist-info}/entry_points.txt +0 -0
  61. {omlish-0.0.0.dev484.dist-info → omlish-0.0.0.dev493.dist-info}/licenses/LICENSE +0 -0
  62. {omlish-0.0.0.dev484.dist-info → omlish-0.0.0.dev493.dist-info}/top_level.txt +0 -0
omlish/inject/binder.py CHANGED
@@ -88,7 +88,7 @@ def bind(
88
88
  in_: Scope | None = None,
89
89
  singleton: bool = False,
90
90
 
91
- eager: bool = False,
91
+ eager: bool | int = False,
92
92
  expose: bool = False,
93
93
  ) -> Element | Elements:
94
94
  if obj is None or obj is inspect.Parameter.empty:
@@ -117,7 +117,10 @@ def bind(
117
117
  elif _is_fn(obj) and not has_to:
118
118
  sig = _inspect.signature(obj)
119
119
  ty = rfl.type_(sig.return_annotation)
120
- to_fn = obj
120
+ if inspect.iscoroutinefunction(obj):
121
+ to_async_fn = obj
122
+ else:
123
+ to_fn = obj
121
124
  key = Key(ty)
122
125
  else:
123
126
  if to_const is not None:
@@ -180,8 +183,8 @@ def bind(
180
183
 
181
184
  elements: list[Element] = [binding]
182
185
 
183
- if eager:
184
- elements.append(Eager(key))
186
+ if eager is not False:
187
+ elements.append(Eager(key, priority=eager if isinstance(eager, int) else 0))
185
188
  if expose:
186
189
  elements.append(Expose(key))
187
190
 
omlish/inject/eagers.py CHANGED
@@ -16,3 +16,5 @@ from .keys import Key
16
16
  @dc.extra_class_params(cache_hash=True)
17
17
  class Eager(Element, lang.Final):
18
18
  key: Key = dc.xfield(coerce=check.of_isinstance(Key))
19
+
20
+ priority: int = dc.xfield(0, kw_only=True)
@@ -0,0 +1,76 @@
1
+ """
2
+ A minor tidiness for hiding refs to [Async]Injector in circ-dep workarounds. Unfortunately unsuitable outside of
3
+ injection modules due to it itself being in here (as user code shouldn't reference injector guts), but at least it's
4
+ something.
5
+ """
6
+ import abc
7
+ import functools
8
+ import typing as ta
9
+
10
+ from ... import lang
11
+ from ... import reflect as rfl
12
+ from ..binder import bind
13
+ from ..elements import Elements
14
+ from ..elements import as_elements
15
+ from ..injector import AsyncInjector
16
+ from ..inspect import KwargsTarget
17
+ from ..keys import Key
18
+ from ..keys import as_key
19
+ from ..sync import Injector
20
+
21
+
22
+ T = ta.TypeVar('T')
23
+
24
+
25
+ ##
26
+
27
+
28
+ class Late(lang.Abstract, ta.Generic[T]):
29
+ @abc.abstractmethod
30
+ def __call__(self) -> T:
31
+ raise NotImplementedError
32
+
33
+
34
+ class AsyncLate(lang.Abstract, ta.Generic[T]):
35
+ @abc.abstractmethod
36
+ def __call__(self) -> ta.Awaitable[T]:
37
+ raise NotImplementedError
38
+
39
+
40
+ #
41
+
42
+
43
+ def _bind_late(
44
+ injector_cls: type,
45
+ late_cls: ta.Any,
46
+ inner: ta.Any,
47
+ outer: ta.Any | None = None,
48
+ ) -> Elements:
49
+ inner_key = as_key(inner)
50
+
51
+ outer_key: Key
52
+ outer_fac: ta.Callable[[ta.Any], ta.Any]
53
+ if outer is None:
54
+ inner_ann = rfl.to_annotation(inner_key.ty)
55
+ outer_ann = late_cls[inner_ann]
56
+ outer_key = Key(rfl.type_(outer_ann))
57
+ outer_fac = lang.identity
58
+ else:
59
+ outer_key = as_key(outer)
60
+ outer_cls = rfl.get_concrete_type(outer_key.ty)
61
+ outer_fac = outer_cls # type: ignore[assignment]
62
+
63
+ return as_elements(
64
+ bind(outer_key, to_fn=KwargsTarget.of( # noqa
65
+ lambda i: outer_fac(functools.partial(i.provide, inner_key)),
66
+ i=injector_cls,
67
+ )),
68
+ )
69
+
70
+
71
+ def bind_late(inner: ta.Any, outer: ta.Any | None = None) -> Elements:
72
+ return _bind_late(Injector, Late, inner, outer)
73
+
74
+
75
+ def bind_async_late(inner: ta.Any, outer: ta.Any | None = None) -> Elements:
76
+ return _bind_late(AsyncInjector, AsyncLate, inner, outer)
@@ -5,20 +5,20 @@ TODO:
5
5
  import contextlib
6
6
  import typing as ta
7
7
 
8
- from .. import lang
9
- from .binder import bind
10
- from .elements import Elemental
11
- from .impl.inspect import build_kwargs_target
8
+ from ... import lang
9
+ from ..binder import bind
10
+ from ..elements import Elemental
11
+ from ..impl.inspect import build_kwargs_target
12
12
 
13
13
 
14
14
  if ta.TYPE_CHECKING:
15
- from . import injector as _injector
16
- from . import maysync as _maysync
17
- from . import sync as _sync
15
+ from .. import injector as _injector
16
+ from .. import maysync as _maysync
17
+ from .. import sync as _sync
18
18
  else:
19
- _injector = lang.proxy_import('.injector', __package__)
20
- _maysync = lang.proxy_import('.maysync', __package__)
21
- _sync = lang.proxy_import('.sync', __package__)
19
+ _injector = lang.proxy_import('..injector', __package__)
20
+ _maysync = lang.proxy_import('..maysync', __package__)
21
+ _sync = lang.proxy_import('..sync', __package__)
22
22
 
23
23
 
24
24
  T = ta.TypeVar('T')
@@ -214,13 +214,16 @@ class ElementCollection(CollectedElements, lang.Final):
214
214
  return [sb.scope for sb in self.elements_of_type(ScopeBinding)]
215
215
 
216
216
  @lang.cached_function
217
- def eager_keys_by_scope(self) -> ta.Mapping[Scope, ta.Sequence[Key]]:
217
+ def sorted_eager_keys_by_scope(self) -> ta.Mapping[Scope, ta.Sequence[Key]]:
218
218
  bim = self.binding_impl_map()
219
- ret: dict[Scope, list[Key]] = {}
219
+ dct: dict[Scope, list[Eager]] = {}
220
220
  for e in self.elements_of_type(Eager):
221
221
  bi = bim[e.key]
222
- ret.setdefault(bi.scope, []).append(e.key)
223
- return ret
222
+ dct.setdefault(bi.scope, []).append(e)
223
+ return {
224
+ sc: tuple(eg.key for eg in sorted(egs, key=lambda eg: eg.priority))
225
+ for sc, egs in dct.items()
226
+ }
224
227
 
225
228
 
226
229
  ##
@@ -12,6 +12,9 @@ TODO:
12
12
  - multiple live request scopes on single injector - use private injectors?
13
13
  - more listeners - UnboundKeyListener
14
14
  - lazy parent listener chain cache thing
15
+ - https://github.com/7mind/izumi-chibi-ts
16
+ - Axis tagging for conditional bindings (e.g., dev vs prod implementations)
17
+ - Fail-fast validation with circular and missing dependency detection
15
18
  """
16
19
  import contextlib
17
20
  import functools
@@ -73,14 +76,14 @@ class AsyncInjectorImpl(AsyncInjector, lang.Final):
73
76
 
74
77
  self._bim = ec.binding_impl_map()
75
78
 
76
- self._ekbs = ec.eager_keys_by_scope()
79
+ self._ekbs = ec.sorted_eager_keys_by_scope()
77
80
 
78
- self._pls: tuple[ProvisionListener, ...] = tuple(
79
- b.listener # type: ignore[attr-defined]
80
- for b in itertools.chain(
81
- ec.elements_of_type(ProvisionListenerBinding),
82
- p._pls if p is not None else (), # noqa
83
- )
81
+ self._pls: tuple[ProvisionListener, ...] = (
82
+ *(
83
+ b.listener
84
+ for b in ec.elements_of_type(ProvisionListenerBinding)
85
+ ),
86
+ *(p._pls if p is not None else []), # noqa
84
87
  )
85
88
 
86
89
  self._root: AsyncInjectorImpl = p._root if p is not None else self # noqa
omlish/inject/inspect.py CHANGED
@@ -58,7 +58,7 @@ class KwargsTarget(ta.NamedTuple):
58
58
 
59
59
  return cls(
60
60
  obj,
61
- [*kws, *kw_kwargs],
61
+ (*kws, *kw_kwargs),
62
62
  )
63
63
 
64
64
 
omlish/lang/__init__.py CHANGED
@@ -621,6 +621,8 @@ with _auto_proxy_init(globals(), update_exports=True):
621
621
  Func2,
622
622
  Func3,
623
623
 
624
+ CachedFunc0,
625
+
624
626
  typing_annotations_attr,
625
627
  )
626
628
 
@@ -0,0 +1,30 @@
1
+ Lifecycle management, inspired by [governator](https://github.com/Netflix/governator/tree/master/governator-core/).
2
+
3
+ A central idea is to 'hide the guts' of a given class's lifecycle management from code interacting with it.
4
+
5
+ The main classes are as follows:
6
+
7
+ - Lifecycle / AsyncLifecycle - the code 'managed by' the rest of the lifecycle machinery. In general, user code will
8
+ hide its implementations of these from the code that otherwise interacts with it. Being the lowest level / unit of
9
+ management callbacks, it intentionally provides no additional machinery, existing solely as a skeleton of methods
10
+ which will be called by lifecycle internals - this is to reduce friction with more functional / less OO code. However,
11
+ it still chooses to be a class with multiple nullary methods, rather than a more functional-style single unary free
12
+ function: the operations present in each method are generally intended to have no overlap in practice, and their
13
+ explicit division is a conscious choice.
14
+
15
+ - LifecycleManaged / AsyncLifecycleManaged - a mixin which can be used to add lifecycle management behavior to a class.
16
+ This removes the need for manual subclassing of Lifecycle / AsyncLifecycle, providing private '\_lifecycle_<state>'
17
+ callback methods (with default no-op implementations) which subclasses may override.
18
+
19
+ - LifecycleListener / AsyncLifecycleListener - callback interfaces whose methods will be called when a lifecycle object
20
+ goes through a lifecycle state transition.
21
+
22
+ - LifecycleController / AsyncLifecycleController - these classes run the state machine for any single Lifecycle /
23
+ AsyncLifecycle instance. They are also responsible for maintaining a registry of lifecycle listeners and calling their
24
+ methods as necessary. Unlike user code, these classes are openly subclasses of Lifecycle / AsyncLifecycle, allowing
25
+ them to be called as application state dictates - they will internally ensure correct state transitions.
26
+
27
+ - LifecycleManager / AsyncLifecycleManager - these classes are responsible for construction and operation of (acyclic)
28
+ graphs of lifecycle objects. They will ensure that, as necessary according to registered dependencies, lifecycle
29
+ objects are started in the correct order, and that they are stopped in the correct order. This class is itself a
30
+ LifecycleManaged / AsyncLifecycleManaged. Notably, AsyncLifecycleManager can also manage sync Lifecycles.
@@ -1,43 +1,71 @@
1
- from .abstract import ( # noqa
2
- AbstractLifecycle,
1
+ from .. import dataclasses as _dc
3
2
 
4
- AbstractAsyncLifecycle,
3
+
4
+ _dc.init_package(
5
+ globals(),
6
+ codegen=True,
5
7
  )
6
8
 
9
+
10
+ ##
11
+
12
+
7
13
  from .base import ( # noqa
8
- AnyLifecycleCallback,
14
+ Lifecycle,
15
+ AsyncLifecycle,
9
16
  AnyLifecycle,
10
- AnyCallbackLifecycle,
17
+ ANY_LIFECYCLE_TYPES,
11
18
 
12
- LifecycleCallback,
13
- CallbackLifecycle,
14
- Lifecycle,
19
+ sync_to_async_lifecycle,
20
+ as_async_lifecycle,
21
+ async_to_sync_lifecycle,
22
+ as_sync_lifecycle,
15
23
 
16
- AsyncLifecycleCallback,
24
+ CallbackLifecycle,
17
25
  CallbackAsyncLifecycle,
18
- AsyncLifecycle,
19
26
  )
20
27
 
21
28
  from .contextmanagers import ( # noqa
22
29
  ContextManagerLifecycle,
30
+ AsyncContextManagerLifecycle,
31
+
23
32
  LifecycleContextManager,
33
+ AsyncLifecycleContextManager,
34
+
35
+ lifecycle_context_manage,
36
+ async_lifecycle_context_manage,
24
37
  )
25
38
 
26
39
  from .controller import ( # noqa
27
- AnyLifecycleListener,
40
+ LifecycleController,
41
+ AsyncLifecycleController,
28
42
  AnyLifecycleController,
43
+ ANY_LIFECYCLE_CONTROLLER_TYPES,
44
+ )
29
45
 
30
- LifecycleController,
46
+ from .listeners import ( # noqa
31
47
  LifecycleListener,
48
+ AsyncLifecycleListener,
49
+ AnyLifecycleListener,
50
+ ANY_LIFECYCLE_LISTENER_TYPES,
51
+ )
52
+
53
+ from .managed import ( # noqa
54
+ LifecycleManaged,
55
+ AsyncLifecycleManaged,
56
+ AnyLifecycleManaged,
57
+ ANY_LIFECYCLE_MANAGED_TYPES,
32
58
  )
33
59
 
34
60
  from .manager import ( # noqa
61
+ LifecycleManagerEntry,
35
62
  LifecycleManager,
63
+ AsyncLifecycleManager,
36
64
  )
37
65
 
38
66
  from .states import ( # noqa
39
- LifecycleState,
40
67
  LifecycleStateError,
68
+ LifecycleState,
41
69
  LifecycleStates,
42
70
  )
43
71
 
@@ -45,3 +73,49 @@ from .transitions import ( # noqa
45
73
  LifecycleTransition,
46
74
  LifecycleTransitions,
47
75
  )
76
+
77
+ from .unwrap import ( # noqa
78
+ unwrap_lifecycle,
79
+ unwrap_async_lifecycle,
80
+ unwrap_any_lifecycle,
81
+ )
82
+
83
+
84
+ ##
85
+
86
+
87
+ from .. import lang as _lang # noqa
88
+
89
+
90
+ with _lang.auto_proxy_init(globals()):
91
+ ##
92
+
93
+ from .injection import ( # noqa
94
+ bind_lifecycle_registrar,
95
+ bind_async_lifecycle_registrar,
96
+
97
+ bind_managed_lifecycle_manager,
98
+ bind_async_managed_lifecycle_manager,
99
+ )
100
+
101
+
102
+ ##
103
+
104
+
105
+ NEW = LifecycleStates.NEW
106
+
107
+ CONSTRUCTING = LifecycleStates.CONSTRUCTING
108
+ FAILED_CONSTRUCTING = LifecycleStates.FAILED_CONSTRUCTING
109
+ CONSTRUCTED = LifecycleStates.CONSTRUCTED
110
+
111
+ STARTING = LifecycleStates.STARTING
112
+ FAILED_STARTING = LifecycleStates.FAILED_STARTING
113
+ STARTED = LifecycleStates.STARTED
114
+
115
+ STOPPING = LifecycleStates.STOPPING
116
+ FAILED_STOPPING = LifecycleStates.FAILED_STOPPING
117
+ STOPPED = LifecycleStates.STOPPED
118
+
119
+ DESTROYING = LifecycleStates.DESTROYING
120
+ FAILED_DESTROYING = LifecycleStates.FAILED_DESTROYING
121
+ DESTROYED = LifecycleStates.DESTROYED