omlish 0.0.0.dev447__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 (265) hide show
  1. omlish/.omlish-manifests.json +12 -0
  2. omlish/README.md +199 -0
  3. omlish/__about__.py +21 -16
  4. omlish/argparse/all.py +17 -9
  5. omlish/argparse/cli.py +16 -3
  6. omlish/argparse/utils.py +21 -0
  7. omlish/asyncs/asyncio/rlock.py +110 -0
  8. omlish/asyncs/asyncio/sync.py +43 -0
  9. omlish/asyncs/asyncio/utils.py +2 -0
  10. omlish/asyncs/sync.py +25 -0
  11. omlish/bootstrap/_marshal.py +1 -1
  12. omlish/bootstrap/diag.py +12 -21
  13. omlish/bootstrap/main.py +2 -5
  14. omlish/bootstrap/sys.py +27 -28
  15. omlish/cexts/__init__.py +0 -0
  16. omlish/cexts/include/omlish/omlish.hh +1 -0
  17. omlish/collections/__init__.py +13 -1
  18. omlish/collections/attrregistry.py +210 -0
  19. omlish/collections/cache/impl.py +1 -0
  20. omlish/collections/identity.py +1 -0
  21. omlish/collections/mappings.py +28 -0
  22. omlish/collections/trie.py +5 -1
  23. omlish/collections/utils.py +77 -0
  24. omlish/concurrent/all.py +2 -1
  25. omlish/concurrent/futures.py +25 -0
  26. omlish/concurrent/threadlets.py +1 -1
  27. omlish/daemons/reparent.py +2 -3
  28. omlish/daemons/spawning.py +2 -3
  29. omlish/dataclasses/__init__.py +2 -0
  30. omlish/dataclasses/impl/api/classes/decorator.py +3 -0
  31. omlish/dataclasses/impl/api/classes/make.py +3 -0
  32. omlish/dataclasses/impl/concerns/repr.py +15 -2
  33. omlish/dataclasses/impl/configs.py +97 -37
  34. omlish/dataclasses/impl/generation/compilation.py +21 -19
  35. omlish/dataclasses/impl/generation/globals.py +1 -0
  36. omlish/dataclasses/impl/generation/ops.py +1 -0
  37. omlish/dataclasses/impl/generation/plans.py +2 -17
  38. omlish/dataclasses/impl/generation/processor.py +106 -25
  39. omlish/dataclasses/impl/processing/base.py +8 -0
  40. omlish/dataclasses/impl/processing/driving.py +19 -7
  41. omlish/dataclasses/specs.py +1 -0
  42. omlish/dataclasses/tools/modifiers.py +5 -0
  43. omlish/diag/_pycharm/runhack.py +1 -1
  44. omlish/diag/cmds/__init__.py +0 -0
  45. omlish/diag/{lslocks.py → cmds/lslocks.py} +6 -6
  46. omlish/diag/{lsof.py → cmds/lsof.py} +6 -6
  47. omlish/diag/{ps.py → cmds/ps.py} +6 -6
  48. omlish/diag/pycharm.py +16 -2
  49. omlish/diag/pydevd.py +58 -40
  50. omlish/diag/replserver/console.py +1 -1
  51. omlish/dispatch/__init__.py +18 -12
  52. omlish/dispatch/methods.py +50 -140
  53. omlish/dom/rendering.py +1 -1
  54. omlish/formats/dotenv.py +1 -1
  55. omlish/formats/json/stream/__init__.py +13 -0
  56. omlish/funcs/guard.py +225 -0
  57. omlish/graphs/dot/rendering.py +1 -1
  58. omlish/http/all.py +44 -4
  59. omlish/http/clients/asyncs.py +33 -27
  60. omlish/http/clients/base.py +17 -1
  61. omlish/http/clients/coro/__init__.py +0 -0
  62. omlish/http/clients/coro/sync.py +171 -0
  63. omlish/http/clients/default.py +208 -29
  64. omlish/http/clients/executor.py +56 -0
  65. omlish/http/clients/httpx.py +72 -11
  66. omlish/http/clients/middleware.py +181 -0
  67. omlish/http/clients/sync.py +33 -27
  68. omlish/http/clients/syncasync.py +49 -0
  69. omlish/http/clients/urllib.py +6 -3
  70. omlish/http/coro/client/connection.py +15 -6
  71. omlish/http/coro/io.py +2 -0
  72. omlish/http/flasky/__init__.py +40 -0
  73. omlish/http/flasky/_compat.py +2 -0
  74. omlish/http/flasky/api.py +82 -0
  75. omlish/http/flasky/app.py +203 -0
  76. omlish/http/flasky/cvs.py +59 -0
  77. omlish/http/flasky/requests.py +20 -0
  78. omlish/http/flasky/responses.py +23 -0
  79. omlish/http/flasky/routes.py +23 -0
  80. omlish/http/flasky/types.py +15 -0
  81. omlish/http/urls.py +67 -0
  82. omlish/inject/__init__.py +57 -29
  83. omlish/inject/_dataclasses.py +5148 -0
  84. omlish/inject/binder.py +11 -52
  85. omlish/inject/eagers.py +2 -0
  86. omlish/inject/elements.py +27 -0
  87. omlish/inject/helpers/__init__.py +0 -0
  88. omlish/inject/{utils.py → helpers/constfn.py} +3 -3
  89. omlish/inject/{tags.py → helpers/id.py} +2 -2
  90. omlish/inject/helpers/late.py +76 -0
  91. omlish/inject/{managed.py → helpers/managed.py} +10 -10
  92. omlish/inject/helpers/multis.py +143 -0
  93. omlish/inject/helpers/wrappers.py +54 -0
  94. omlish/inject/impl/elements.py +54 -21
  95. omlish/inject/impl/injector.py +29 -25
  96. omlish/inject/impl/inspect.py +10 -1
  97. omlish/inject/impl/maysync.py +3 -4
  98. omlish/inject/impl/multis.py +3 -0
  99. omlish/inject/impl/sync.py +3 -4
  100. omlish/inject/injector.py +31 -2
  101. omlish/inject/inspect.py +35 -0
  102. omlish/inject/maysync.py +2 -4
  103. omlish/inject/multis.py +8 -0
  104. omlish/inject/overrides.py +3 -3
  105. omlish/inject/privates.py +6 -0
  106. omlish/inject/providers.py +3 -2
  107. omlish/inject/sync.py +5 -4
  108. omlish/io/buffers.py +118 -0
  109. omlish/io/readers.py +29 -0
  110. omlish/iterators/transforms.py +2 -2
  111. omlish/lang/__init__.py +180 -97
  112. omlish/lang/_asyncs.cc +186 -0
  113. omlish/lang/asyncs.py +17 -0
  114. omlish/lang/casing.py +11 -0
  115. omlish/lang/contextmanagers.py +28 -4
  116. omlish/lang/functions.py +31 -22
  117. omlish/lang/imports/_capture.cc +11 -9
  118. omlish/lang/imports/capture.py +363 -170
  119. omlish/lang/imports/proxy.py +455 -152
  120. omlish/lang/lazyglobals.py +22 -9
  121. omlish/lang/params.py +17 -0
  122. omlish/lang/recursion.py +0 -1
  123. omlish/lang/sequences.py +124 -0
  124. omlish/lifecycles/README.md +30 -0
  125. omlish/lifecycles/__init__.py +87 -13
  126. omlish/lifecycles/_dataclasses.py +1388 -0
  127. omlish/lifecycles/base.py +178 -64
  128. omlish/lifecycles/contextmanagers.py +113 -4
  129. omlish/lifecycles/controller.py +150 -87
  130. omlish/lifecycles/injection.py +143 -0
  131. omlish/lifecycles/listeners.py +56 -0
  132. omlish/lifecycles/managed.py +142 -0
  133. omlish/lifecycles/manager.py +218 -93
  134. omlish/lifecycles/states.py +2 -0
  135. omlish/lifecycles/transitions.py +3 -0
  136. omlish/lifecycles/unwrap.py +57 -0
  137. omlish/lite/abstract.py +54 -24
  138. omlish/lite/asyncs.py +2 -2
  139. omlish/lite/attrops.py +2 -0
  140. omlish/lite/contextmanagers.py +4 -4
  141. omlish/lite/dataclasses.py +44 -0
  142. omlish/lite/maybes.py +8 -0
  143. omlish/lite/maysync.py +1 -5
  144. omlish/lite/pycharm.py +1 -1
  145. omlish/lite/typing.py +24 -0
  146. omlish/logs/_amalg.py +1 -1
  147. omlish/logs/all.py +25 -11
  148. omlish/logs/asyncs.py +73 -0
  149. omlish/logs/base.py +101 -12
  150. omlish/logs/contexts.py +4 -1
  151. omlish/logs/lists.py +125 -0
  152. omlish/logs/modules.py +19 -1
  153. omlish/logs/std/loggers.py +6 -1
  154. omlish/logs/std/noisy.py +11 -9
  155. omlish/logs/{standard.py → std/standard.py} +3 -4
  156. omlish/logs/utils.py +17 -2
  157. omlish/manifests/loading.py +2 -1
  158. omlish/marshal/__init__.py +33 -13
  159. omlish/marshal/_dataclasses.py +2774 -0
  160. omlish/marshal/base/configs.py +12 -0
  161. omlish/marshal/base/contexts.py +36 -21
  162. omlish/marshal/base/funcs.py +8 -11
  163. omlish/marshal/base/options.py +8 -0
  164. omlish/marshal/base/registries.py +146 -44
  165. omlish/marshal/base/types.py +40 -16
  166. omlish/marshal/composite/iterables.py +33 -20
  167. omlish/marshal/composite/literals.py +20 -18
  168. omlish/marshal/composite/mappings.py +36 -23
  169. omlish/marshal/composite/maybes.py +29 -19
  170. omlish/marshal/composite/newtypes.py +16 -16
  171. omlish/marshal/composite/optionals.py +14 -14
  172. omlish/marshal/composite/special.py +15 -15
  173. omlish/marshal/composite/unions/__init__.py +0 -0
  174. omlish/marshal/composite/unions/literals.py +93 -0
  175. omlish/marshal/composite/unions/primitives.py +103 -0
  176. omlish/marshal/factories/invalidate.py +18 -68
  177. omlish/marshal/factories/method.py +26 -0
  178. omlish/marshal/factories/moduleimport/factories.py +22 -65
  179. omlish/marshal/factories/multi.py +13 -25
  180. omlish/marshal/factories/recursive.py +42 -56
  181. omlish/marshal/factories/typecache.py +29 -74
  182. omlish/marshal/factories/typemap.py +42 -43
  183. omlish/marshal/objects/dataclasses.py +129 -106
  184. omlish/marshal/objects/marshal.py +18 -14
  185. omlish/marshal/objects/namedtuples.py +48 -42
  186. omlish/marshal/objects/unmarshal.py +19 -15
  187. omlish/marshal/polymorphism/marshal.py +9 -11
  188. omlish/marshal/polymorphism/metadata.py +16 -5
  189. omlish/marshal/polymorphism/standard.py +13 -1
  190. omlish/marshal/polymorphism/unions.py +15 -105
  191. omlish/marshal/polymorphism/unmarshal.py +9 -10
  192. omlish/marshal/singular/enums.py +14 -18
  193. omlish/marshal/standard.py +10 -6
  194. omlish/marshal/trivial/any.py +1 -1
  195. omlish/marshal/trivial/forbidden.py +21 -26
  196. omlish/metadata.py +23 -1
  197. omlish/os/forkhooks.py +4 -0
  198. omlish/os/pidfiles/pinning.py +2 -2
  199. omlish/reflect/__init__.py +43 -26
  200. omlish/reflect/ops.py +10 -1
  201. omlish/reflect/types.py +1 -0
  202. omlish/secrets/marshal.py +1 -1
  203. omlish/specs/jmespath/__init__.py +12 -3
  204. omlish/specs/jmespath/_dataclasses.py +2893 -0
  205. omlish/specs/jmespath/ast.py +1 -1
  206. omlish/specs/jsonrpc/__init__.py +13 -0
  207. omlish/specs/jsonrpc/_marshal.py +32 -23
  208. omlish/specs/jsonrpc/conns.py +10 -7
  209. omlish/specs/jsonschema/_marshal.py +1 -1
  210. omlish/specs/jsonschema/keywords/__init__.py +7 -0
  211. omlish/specs/jsonschema/keywords/_dataclasses.py +1644 -0
  212. omlish/specs/openapi/_marshal.py +31 -22
  213. omlish/sql/__init__.py +24 -5
  214. omlish/sql/{tabledefs/alchemy.py → alchemy/tabledefs.py} +2 -2
  215. omlish/sql/api/dbapi.py +1 -1
  216. omlish/sql/dbapi/__init__.py +15 -0
  217. omlish/sql/{dbapi.py → dbapi/drivers.py} +2 -2
  218. omlish/sql/queries/__init__.py +3 -0
  219. omlish/sql/queries/_marshal.py +2 -2
  220. omlish/sql/queries/rendering.py +1 -1
  221. omlish/sql/tabledefs/_marshal.py +1 -1
  222. omlish/subprocesses/base.py +4 -0
  223. omlish/subprocesses/editor.py +1 -1
  224. omlish/sync.py +155 -21
  225. omlish/term/alt.py +60 -0
  226. omlish/term/confirm.py +8 -8
  227. omlish/term/pager.py +235 -0
  228. omlish/term/terminfo.py +935 -0
  229. omlish/term/termstate.py +67 -0
  230. omlish/term/vt100/terminal.py +0 -3
  231. omlish/testing/pytest/plugins/asyncs/fixtures.py +4 -1
  232. omlish/testing/pytest/plugins/asyncs/plugin.py +2 -0
  233. omlish/testing/pytest/plugins/skips.py +2 -1
  234. omlish/testing/unittest/main.py +3 -3
  235. omlish/text/docwrap/__init__.py +3 -0
  236. omlish/text/docwrap/__main__.py +11 -0
  237. omlish/text/docwrap/api.py +83 -0
  238. omlish/text/docwrap/cli.py +91 -0
  239. omlish/text/docwrap/groups.py +84 -0
  240. omlish/text/docwrap/lists.py +167 -0
  241. omlish/text/docwrap/parts.py +146 -0
  242. omlish/text/docwrap/reflowing.py +106 -0
  243. omlish/text/docwrap/rendering.py +151 -0
  244. omlish/text/docwrap/utils.py +11 -0
  245. omlish/text/docwrap/wrapping.py +59 -0
  246. omlish/text/filecache.py +2 -2
  247. omlish/text/lorem.py +6 -0
  248. omlish/text/parts.py +2 -2
  249. omlish/text/textwrap.py +51 -0
  250. omlish/typedvalues/marshal.py +85 -59
  251. omlish/typedvalues/values.py +2 -1
  252. {omlish-0.0.0.dev447.dist-info → omlish-0.0.0.dev493.dist-info}/METADATA +36 -32
  253. {omlish-0.0.0.dev447.dist-info → omlish-0.0.0.dev493.dist-info}/RECORD +260 -199
  254. omlish/dataclasses/impl/generation/mangling.py +0 -18
  255. omlish/funcs/match.py +0 -227
  256. omlish/lifecycles/abstract.py +0 -86
  257. omlish/marshal/factories/match.py +0 -34
  258. omlish/marshal/factories/simple.py +0 -28
  259. /omlish/inject/{impl → helpers}/proxy.py +0 -0
  260. /omlish/inject/impl/{providers2.py → providersmap.py} +0 -0
  261. /omlish/sql/{abc.py → dbapi/abc.py} +0 -0
  262. {omlish-0.0.0.dev447.dist-info → omlish-0.0.0.dev493.dist-info}/WHEEL +0 -0
  263. {omlish-0.0.0.dev447.dist-info → omlish-0.0.0.dev493.dist-info}/entry_points.txt +0 -0
  264. {omlish-0.0.0.dev447.dist-info → omlish-0.0.0.dev493.dist-info}/licenses/LICENSE +0 -0
  265. {omlish-0.0.0.dev447.dist-info → omlish-0.0.0.dev493.dist-info}/top_level.txt +0 -0
@@ -4,160 +4,285 @@ from .. import check
4
4
  from .. import collections as col
5
5
  from .. import dataclasses as dc
6
6
  from .. import lang
7
- from .abstract import AbstractLifecycle
7
+ from .base import AnyLifecycle
8
+ from .base import AsyncLifecycle
8
9
  from .base import Lifecycle
10
+ from .base import as_async_lifecycle
11
+ from .controller import AnyLifecycleController
12
+ from .controller import AsyncLifecycleController
9
13
  from .controller import LifecycleController
14
+ from .managed import AsyncLifecycleManaged
15
+ from .managed import LifecycleManaged
10
16
  from .states import LifecycleState
11
17
  from .states import LifecycleStateError
12
18
  from .states import LifecycleStates
13
19
 
14
20
 
21
+ LifecycleControllerT = ta.TypeVar('LifecycleControllerT', bound=AnyLifecycleController)
22
+
23
+
15
24
  ##
16
25
 
17
26
 
18
- class LifecycleManager(AbstractLifecycle):
19
- @dc.dataclass(frozen=True)
20
- class Entry(lang.Final):
21
- controller: LifecycleController
22
- dependencies: ta.MutableSet['LifecycleManager.Entry'] = dc.field(default_factory=col.IdentitySet)
23
- dependents: ta.MutableSet['LifecycleManager.Entry'] = dc.field(default_factory=col.IdentitySet)
27
+ @dc.dataclass(frozen=True, eq=False)
28
+ class LifecycleManagerEntry(lang.Final, ta.Generic[LifecycleControllerT]):
29
+ controller: LifecycleControllerT
30
+
31
+ dependencies: ta.MutableSet['LifecycleManagerEntry'] = dc.field(default_factory=set)
32
+ dependents: ta.MutableSet['LifecycleManagerEntry'] = dc.field(default_factory=set)
33
+
24
34
 
35
+ #
36
+
37
+
38
+ @ta.final
39
+ class _InnerLifecycleManager(AsyncLifecycleManaged, lang.Final):
25
40
  def __init__(
26
41
  self,
27
- *,
28
- lock: lang.DefaultLockable = None,
42
+ get_state: ta.Callable[[], LifecycleState],
29
43
  ) -> None:
30
44
  super().__init__()
31
45
 
32
- self._lock = lang.default_lock(lock, False)
33
-
34
- self._entries_by_lifecycle: ta.MutableMapping[Lifecycle, LifecycleManager.Entry] = col.IdentityKeyDict()
35
-
36
- self._controller = LifecycleController(self._lifecycle, lock=self._lock)
46
+ self._get_state = get_state
37
47
 
38
- @property
39
- def controller(self) -> LifecycleController:
40
- return self._controller
48
+ self._entries_by_lifecycle: ta.MutableMapping[AnyLifecycle, LifecycleManagerEntry] = col.IdentityKeyDict()
41
49
 
42
- @property
43
- def state(self) -> LifecycleState:
44
- return self._controller.state
50
+ #
45
51
 
46
- @staticmethod
47
- def _get_controller(lifecycle: Lifecycle) -> LifecycleController:
48
- if isinstance(lifecycle, LifecycleController):
52
+ def _get_or_make_controller(self, lifecycle: AnyLifecycle) -> AnyLifecycleController:
53
+ if isinstance(lifecycle, (LifecycleController, AsyncLifecycleController)):
49
54
  return lifecycle
50
- # elif isinstance(lifecycle, AbstractLifecycle):
51
- # return lifecycle.lifecycle_controller
55
+
52
56
  elif isinstance(lifecycle, Lifecycle):
53
57
  return LifecycleController(lifecycle)
58
+
59
+ elif isinstance(lifecycle, AsyncLifecycle):
60
+ return AsyncLifecycleController(lifecycle)
61
+
54
62
  else:
55
63
  raise TypeError(lifecycle)
56
64
 
57
- def _add_internal(self, lifecycle: Lifecycle, dependencies: ta.Iterable[Lifecycle]) -> Entry:
58
- check.state(self.state < LifecycleStates.STOPPING and not self.state.is_failed)
65
+ def _add_internal(self, lifecycle: AnyLifecycle, dependencies: ta.Iterable[AnyLifecycle]) -> LifecycleManagerEntry:
66
+ check.state(self._get_state() < LifecycleStates.STOPPING and not self._get_state().is_failed)
59
67
 
60
- check.isinstance(lifecycle, Lifecycle)
68
+ check.isinstance(lifecycle, (Lifecycle, AsyncLifecycle))
61
69
  try:
62
70
  entry = self._entries_by_lifecycle[lifecycle]
63
71
  except KeyError:
64
- controller = self._get_controller(lifecycle)
65
- entry = self._entries_by_lifecycle[lifecycle] = LifecycleManager.Entry(controller)
72
+ controller = self._get_or_make_controller(lifecycle)
73
+ entry = self._entries_by_lifecycle[lifecycle] = LifecycleManagerEntry(controller)
66
74
 
67
75
  for dep in dependencies:
68
- check.isinstance(dep, Lifecycle)
76
+ check.isinstance(dep, (Lifecycle, AsyncLifecycle))
69
77
  dep_entry = self._add_internal(dep, [])
70
78
  entry.dependencies.add(dep_entry)
71
79
  dep_entry.dependents.add(entry)
72
80
 
73
81
  return entry
74
82
 
75
- def add(
83
+ async def add(
76
84
  self,
77
- lifecycle: Lifecycle,
78
- dependencies: ta.Iterable[Lifecycle] = (),
79
- ) -> Entry:
80
- check.state(self.state < LifecycleStates.STOPPING and not self.state.is_failed)
81
-
82
- with self._lock():
83
- entry = self._add_internal(lifecycle, dependencies)
84
-
85
- if self.state >= LifecycleStates.CONSTRUCTING:
86
- def rec(e):
87
- if e.controller.state < LifecycleStates.CONSTRUCTED:
88
- for dep in e.dependencies:
89
- rec(dep)
90
- e.controller.lifecycle_construct()
91
- rec(entry)
92
-
93
- if self.state >= LifecycleStates.STARTING:
94
- def rec(e):
95
- if e.controller.state < LifecycleStates.STARTED:
96
- for dep in e.dependencies:
97
- rec(dep)
98
- e.controller.lifecycle_start()
99
- rec(entry)
100
-
101
- return entry
85
+ lifecycle: AnyLifecycle,
86
+ dependencies: ta.Iterable[AnyLifecycle] = (),
87
+ ) -> LifecycleManagerEntry:
88
+ check.state(self._get_state() < LifecycleStates.STOPPING and not self._get_state().is_failed)
89
+
90
+ entry = self._add_internal(lifecycle, dependencies)
91
+
92
+ if self._get_state() >= LifecycleStates.CONSTRUCTING:
93
+ async def rec(e):
94
+ if e.controller.state < LifecycleStates.CONSTRUCTED:
95
+ for dep in e.dependencies:
96
+ await rec(dep)
97
+
98
+ await as_async_lifecycle(e.controller).lifecycle_construct()
99
+
100
+ await rec(entry)
101
+
102
+ if self._get_state() >= LifecycleStates.STARTING:
103
+ async def rec(e):
104
+ if e.controller.state < LifecycleStates.STARTED:
105
+ for dep in e.dependencies:
106
+ await rec(dep)
107
+
108
+ await as_async_lifecycle(e.controller).lifecycle_start()
109
+
110
+ await rec(entry)
111
+
112
+ return entry
102
113
 
103
114
  ##
104
115
 
105
116
  @ta.override
106
- def _lifecycle_construct(self) -> None:
107
- def rec(entry: LifecycleManager.Entry) -> None:
108
- for dep in entry.dependencies:
109
- rec(dep)
117
+ async def _lifecycle_construct(self) -> None:
118
+ async def rec(e: LifecycleManagerEntry) -> None:
119
+ for dep in e.dependencies:
120
+ await rec(dep)
110
121
 
111
- if entry.controller.state.is_failed:
112
- raise LifecycleStateError(entry.controller)
122
+ if e.controller.state.is_failed:
123
+ raise LifecycleStateError(e.controller)
113
124
 
114
- if entry.controller.state < LifecycleStates.CONSTRUCTED:
115
- entry.controller.lifecycle_construct()
125
+ if e.controller.state < LifecycleStates.CONSTRUCTED:
126
+ await as_async_lifecycle(e.controller).lifecycle_construct()
116
127
 
117
128
  for entry in self._entries_by_lifecycle.values():
118
- rec(entry)
129
+ await rec(entry)
119
130
 
120
131
  @ta.override
121
- def _lifecycle_start(self) -> None:
122
- def rec(entry: LifecycleManager.Entry) -> None:
123
- for dep in entry.dependencies:
124
- rec(dep)
132
+ async def _lifecycle_start(self) -> None:
133
+ async def rec(e: LifecycleManagerEntry) -> None:
134
+ for dep in e.dependencies:
135
+ await rec(dep)
125
136
 
126
- if entry.controller.state.is_failed:
127
- raise LifecycleStateError(entry.controller)
137
+ if e.controller.state.is_failed:
138
+ raise LifecycleStateError(e.controller)
128
139
 
129
- if entry.controller.state < LifecycleStates.CONSTRUCTED:
130
- entry.controller.lifecycle_construct()
140
+ if e.controller.state < LifecycleStates.CONSTRUCTED:
141
+ await as_async_lifecycle(e.controller).lifecycle_construct()
131
142
 
132
- if entry.controller.state < LifecycleStates.STARTED:
133
- entry.controller.lifecycle_start()
143
+ if e.controller.state < LifecycleStates.STARTED:
144
+ await as_async_lifecycle(e.controller).lifecycle_start()
134
145
 
135
146
  for entry in self._entries_by_lifecycle.values():
136
- rec(entry)
147
+ await rec(entry)
137
148
 
138
149
  @ta.override
139
- def _lifecycle_stop(self) -> None:
140
- def rec(entry: LifecycleManager.Entry) -> None:
141
- for dep in entry.dependents:
142
- rec(dep)
150
+ async def _lifecycle_stop(self) -> None:
151
+ async def rec(e: LifecycleManagerEntry) -> None:
152
+ for dep in e.dependents:
153
+ await rec(dep)
143
154
 
144
- if entry.controller.state.is_failed:
145
- raise LifecycleStateError(entry.controller)
155
+ if e.controller.state.is_failed:
156
+ raise LifecycleStateError(e.controller)
146
157
 
147
- if entry.controller.state is LifecycleStates.STARTED:
148
- entry.controller.lifecycle_stop()
158
+ if e.controller.state is LifecycleStates.STARTED:
159
+ await as_async_lifecycle(e.controller).lifecycle_stop()
149
160
 
150
161
  for entry in self._entries_by_lifecycle.values():
151
- rec(entry)
162
+ await rec(entry)
152
163
 
153
164
  @ta.override
154
- def _lifecycle_destroy(self) -> None:
155
- def rec(entry: LifecycleManager.Entry) -> None:
156
- for dep in entry.dependents:
157
- rec(dep)
165
+ async def _lifecycle_destroy(self) -> None:
166
+ async def rec(e: LifecycleManagerEntry) -> None:
167
+ for dep in e.dependents:
168
+ await rec(dep)
158
169
 
159
- if entry.controller.state < LifecycleStates.DESTROYED:
160
- entry.controller.lifecycle_destroy()
170
+ if e.controller.state < LifecycleStates.DESTROYED:
171
+ await as_async_lifecycle(e.controller).lifecycle_destroy()
161
172
 
162
173
  for entry in self._entries_by_lifecycle.values():
163
- rec(entry)
174
+ await rec(entry)
175
+
176
+
177
+ ##
178
+
179
+
180
+ class LifecycleManager(LifecycleManaged, lang.Final):
181
+ def __init__(
182
+ self,
183
+ *,
184
+ lock: lang.DefaultLockable = None,
185
+ ) -> None:
186
+ super().__init__()
187
+
188
+ self._lock = lang.default_lock(lock, None)
189
+
190
+ self._inner = _InnerLifecycleManager(lambda: self._inner_controller.state)
191
+
192
+ self._inner_controller = AsyncLifecycleController(
193
+ self._inner._lifecycle, # noqa
194
+ lock=lambda: lang.SyncToAsyncContextManager(self._lock()),
195
+ )
196
+
197
+ #
198
+
199
+ @property
200
+ def state(self) -> LifecycleState:
201
+ return self._inner_controller.state
202
+
203
+ #
204
+
205
+ def add(
206
+ self,
207
+ lifecycle: Lifecycle,
208
+ dependencies: ta.Iterable[Lifecycle] = (),
209
+ ) -> LifecycleManagerEntry[LifecycleController]:
210
+ return lang.sync_await(self._inner.add(
211
+ check.isinstance(lifecycle, Lifecycle),
212
+ [check.isinstance(d, Lifecycle) for d in dependencies],
213
+ ))
214
+
215
+ #
216
+
217
+ @ta.override
218
+ def _lifecycle_construct(self) -> None:
219
+ return lang.sync_await(self._inner_controller.lifecycle_construct())
220
+
221
+ @ta.override
222
+ def _lifecycle_start(self) -> None:
223
+ return lang.sync_await(self._inner_controller.lifecycle_start())
224
+
225
+ @ta.override
226
+ def _lifecycle_stop(self) -> None:
227
+ return lang.sync_await(self._inner_controller.lifecycle_stop())
228
+
229
+ @ta.override
230
+ def _lifecycle_destroy(self) -> None:
231
+ return lang.sync_await(self._inner_controller.lifecycle_destroy())
232
+
233
+
234
+ #
235
+
236
+
237
+ class AsyncLifecycleManager(AsyncLifecycleManaged, lang.Final):
238
+ def __init__(
239
+ self,
240
+ *,
241
+ lock: lang.DefaultAsyncLockable = None,
242
+ ) -> None:
243
+ super().__init__()
244
+
245
+ self._lock = lang.default_async_lock(lock, None)
246
+
247
+ self._inner = _InnerLifecycleManager(lambda: self._inner_controller.state)
248
+
249
+ self._inner_controller = AsyncLifecycleController(
250
+ self._inner._lifecycle, # noqa
251
+ lock=self._lock,
252
+ )
253
+
254
+ #
255
+
256
+ @property
257
+ def state(self) -> LifecycleState:
258
+ return self._inner_controller.state
259
+
260
+ #
261
+
262
+ async def add(
263
+ self,
264
+ lifecycle: AnyLifecycle,
265
+ dependencies: ta.Iterable[AnyLifecycle] = (),
266
+ ) -> LifecycleManagerEntry[AnyLifecycleController]:
267
+ return await self._inner.add(
268
+ lifecycle,
269
+ dependencies,
270
+ )
271
+
272
+ #
273
+
274
+ @ta.override
275
+ async def _lifecycle_construct(self) -> None:
276
+ return await self._inner_controller.lifecycle_construct()
277
+
278
+ @ta.override
279
+ async def _lifecycle_start(self) -> None:
280
+ return await self._inner_controller.lifecycle_start()
281
+
282
+ @ta.override
283
+ async def _lifecycle_stop(self) -> None:
284
+ return await self._inner_controller.lifecycle_stop()
285
+
286
+ @ta.override
287
+ async def _lifecycle_destroy(self) -> None:
288
+ return await self._inner_controller.lifecycle_destroy()
@@ -1,4 +1,5 @@
1
1
  import functools
2
+ import typing as ta
2
3
 
3
4
  from .. import check
4
5
  from .. import dataclasses as dc
@@ -12,6 +13,7 @@ class LifecycleStateError(Exception):
12
13
  pass
13
14
 
14
15
 
16
+ @ta.final
15
17
  @dc.dataclass(frozen=True, eq=False)
16
18
  @functools.total_ordering
17
19
  class LifecycleState(lang.Final):
@@ -1,3 +1,5 @@
1
+ import typing as ta
2
+
1
3
  from .. import check
2
4
  from .. import dataclasses as dc
3
5
  from .. import lang
@@ -8,6 +10,7 @@ from .states import LifecycleStates
8
10
  ##
9
11
 
10
12
 
13
+ @ta.final
11
14
  @dc.dataclass(frozen=True)
12
15
  class LifecycleTransition(lang.Final):
13
16
  old: frozenset[LifecycleState]
@@ -0,0 +1,57 @@
1
+ import functools
2
+ import typing as ta
3
+
4
+ from .base import AsyncLifecycle
5
+ from .base import Lifecycle
6
+ from .managed import AsyncLifecycleManaged
7
+ from .managed import LifecycleManaged
8
+
9
+
10
+ ##
11
+
12
+
13
+ @functools.singledispatch
14
+ def unwrap_lifecycle(obj: ta.Any) -> Lifecycle | None:
15
+ return None
16
+
17
+
18
+ @unwrap_lifecycle.register
19
+ def _(obj: Lifecycle) -> Lifecycle:
20
+ return obj
21
+
22
+
23
+ @unwrap_lifecycle.register
24
+ def _(obj: LifecycleManaged) -> Lifecycle:
25
+ return obj._lifecycle # noqa
26
+
27
+
28
+ #
29
+
30
+
31
+ @functools.singledispatch
32
+ def unwrap_async_lifecycle(obj: ta.Any) -> AsyncLifecycle | None:
33
+ return None
34
+
35
+
36
+ @unwrap_async_lifecycle.register
37
+ def _(obj: AsyncLifecycle) -> AsyncLifecycle:
38
+ return obj
39
+
40
+
41
+ @unwrap_async_lifecycle.register
42
+ def _(obj: AsyncLifecycleManaged) -> AsyncLifecycle:
43
+ return obj._lifecycle # noqa
44
+
45
+
46
+ #
47
+
48
+
49
+ def unwrap_any_lifecycle(obj: ta.Any) -> Lifecycle | AsyncLifecycle | None:
50
+ if (lc := unwrap_lifecycle(obj)) is not None:
51
+ return lc
52
+
53
+ elif (alc := unwrap_async_lifecycle(obj)) is not None:
54
+ return alc
55
+
56
+ else:
57
+ return None
omlish/lite/abstract.py CHANGED
@@ -3,6 +3,9 @@ import abc
3
3
  import typing as ta
4
4
 
5
5
 
6
+ T = ta.TypeVar('T')
7
+
8
+
6
9
  ##
7
10
 
8
11
 
@@ -14,25 +17,49 @@ def is_abstract_method(obj: ta.Any) -> bool:
14
17
  return bool(getattr(obj, _IS_ABSTRACT_METHOD_ATTR, False))
15
18
 
16
19
 
17
- def update_abstracts(cls, *, force=False):
20
+ def compute_abstract_methods(cls: type) -> ta.FrozenSet[str]:
21
+ # ~> https://github.com/python/cpython/blob/f3476c6507381ca860eec0989f53647b13517423/Modules/_abc.c#L358
22
+
23
+ # Stage 1: direct abstract methods
24
+
25
+ abstracts = {
26
+ a
27
+ # Get items as a list to avoid mutation issues during iteration
28
+ for a, v in list(cls.__dict__.items())
29
+ if is_abstract_method(v)
30
+ }
31
+
32
+ # Stage 2: inherited abstract methods
33
+
34
+ for base in cls.__bases__:
35
+ # Get __abstractmethods__ from base if it exists
36
+ if (base_abstracts := getattr(base, _ABSTRACT_METHODS_ATTR, None)) is None:
37
+ continue
38
+
39
+ # Iterate over abstract methods in base
40
+ for key in base_abstracts:
41
+ # Check if this class has an attribute with this name
42
+ try:
43
+ value = getattr(cls, key)
44
+ except AttributeError:
45
+ # Attribute not found in this class, skip
46
+ continue
47
+
48
+ # Check if it's still abstract
49
+ if is_abstract_method(value):
50
+ abstracts.add(key)
51
+
52
+ return frozenset(abstracts)
53
+
54
+
55
+ def update_abstracts(cls: ta.Type[T], *, force: bool = False) -> ta.Type[T]:
18
56
  if not force and not hasattr(cls, _ABSTRACT_METHODS_ATTR):
19
57
  # Per stdlib: We check for __abstractmethods__ here because cls might by a C implementation or a python
20
58
  # implementation (especially during testing), and we want to handle both cases.
21
59
  return cls
22
60
 
23
- abstracts: ta.Set[str] = set()
24
-
25
- for scls in cls.__bases__:
26
- for name in getattr(scls, _ABSTRACT_METHODS_ATTR, ()):
27
- value = getattr(cls, name, None)
28
- if getattr(value, _IS_ABSTRACT_METHOD_ATTR, False):
29
- abstracts.add(name)
30
-
31
- for name, value in cls.__dict__.items():
32
- if getattr(value, _IS_ABSTRACT_METHOD_ATTR, False):
33
- abstracts.add(name)
34
-
35
- setattr(cls, _ABSTRACT_METHODS_ATTR, frozenset(abstracts))
61
+ abstracts = compute_abstract_methods(cls)
62
+ setattr(cls, _ABSTRACT_METHODS_ATTR, abstracts)
36
63
  return cls
37
64
 
38
65
 
@@ -86,23 +113,26 @@ class Abstract:
86
113
  super().__init_subclass__(**kwargs)
87
114
 
88
115
  if not (Abstract in cls.__bases__ or abc.ABC in cls.__bases__):
89
- ams = {a: cls for a, o in cls.__dict__.items() if is_abstract_method(o)}
90
-
91
- seen = set(cls.__dict__)
92
- for b in cls.__bases__:
93
- ams.update({a: b for a in set(getattr(b, _ABSTRACT_METHODS_ATTR, [])) - seen}) # noqa
94
- seen.update(dir(b))
116
+ if ams := compute_abstract_methods(cls):
117
+ amd = {
118
+ a: mcls
119
+ for mcls in cls.__mro__[::-1]
120
+ for a in ams
121
+ if a in mcls.__dict__
122
+ }
95
123
 
96
- if ams:
97
124
  raise AbstractTypeError(
98
125
  f'Cannot subclass abstract class {cls.__name__} with abstract methods: ' +
99
126
  ', '.join(sorted([
100
127
  '.'.join([
101
- *([m] if (m := getattr(c, '__module__')) else []),
102
- getattr(c, '__qualname__', getattr(c, '__name__')),
128
+ *([
129
+ *([m] if (m := getattr(c, '__module__')) else []),
130
+ getattr(c, '__qualname__', getattr(c, '__name__')),
131
+ ] if c is not None else '?'),
103
132
  a,
104
133
  ])
105
- for a, c in ams.items()
134
+ for a in ams
135
+ for c in [amd.get(a)]
106
136
  ])),
107
137
  )
108
138
 
omlish/lite/asyncs.py CHANGED
@@ -50,12 +50,12 @@ def sync_await(aw: ta.Awaitable[T]) -> T:
50
50
 
51
51
  ret = missing = object()
52
52
 
53
- async def gate():
53
+ async def thunk():
54
54
  nonlocal ret
55
55
 
56
56
  ret = await aw
57
57
 
58
- cr = gate()
58
+ cr = thunk()
59
59
  try:
60
60
  try:
61
61
  cr.send(None)
omlish/lite/attrops.py CHANGED
@@ -6,6 +6,8 @@ TODO:
6
6
  - per-attr repr transform / filter
7
7
  - __ne__ ? cases where it still matters
8
8
  - ordering ?
9
+ - repr_filter: ta.Union[ta.Callable[[ta.Any], ta.Optional[str]], ta.Literal['not_none', 'truthy']]] ?
10
+ - unify repr/repr_fn/repr_filter
9
11
  """
10
12
  import types # noqa
11
13
  import typing as ta
@@ -54,7 +54,7 @@ class ExitStacked:
54
54
  es.__enter__()
55
55
  try:
56
56
  self._enter_contexts()
57
- except Exception: # noqa
57
+ except BaseException: # noqa
58
58
  es.__exit__(*sys.exc_info())
59
59
  raise
60
60
  return self
@@ -65,7 +65,7 @@ class ExitStacked:
65
65
  return None
66
66
  try:
67
67
  self._exit_contexts()
68
- except Exception: # noqa
68
+ except BaseException: # noqa
69
69
  es.__exit__(*sys.exc_info())
70
70
  raise
71
71
  return es.__exit__(exc_type, exc_val, exc_tb)
@@ -113,7 +113,7 @@ class AsyncExitStacked:
113
113
  await es.__aenter__()
114
114
  try:
115
115
  await self._async_enter_contexts()
116
- except Exception: # noqa
116
+ except BaseException: # noqa
117
117
  await es.__aexit__(*sys.exc_info())
118
118
  raise
119
119
  return self
@@ -124,7 +124,7 @@ class AsyncExitStacked:
124
124
  return None
125
125
  try:
126
126
  await self._async_exit_contexts()
127
- except Exception: # noqa
127
+ except BaseException: # noqa
128
128
  await es.__aexit__(*sys.exc_info())
129
129
  raise
130
130
  return await es.__aexit__(exc_type, exc_val, exc_tb)