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
omlish/bootstrap/diag.py CHANGED
@@ -13,23 +13,14 @@ from .base import ContextBootstrap
13
13
  from .base import SimpleBootstrap
14
14
 
15
15
 
16
- if ta.TYPE_CHECKING:
16
+ with lang.auto_proxy_import(globals()):
17
17
  import cProfile # noqa
18
18
  import pstats
19
19
 
20
- from ..diag import debug as ddbg
21
- from ..diag import pycharm as diagpc
22
- from ..diag import replserver as diagrs
23
- from ..diag import threads as diagt
24
-
25
- else:
26
- cProfile = lang.proxy_import('cProfile') # noqa
27
- pstats = lang.proxy_import('pstats')
28
-
29
- ddbg = lang.proxy_import('..diag.debug', __package__)
30
- diagpc = lang.proxy_import('..diag.pycharm', __package__)
31
- diagrs = lang.proxy_import('..diag.replserver', __package__)
32
- diagt = lang.proxy_import('..diag.threads', __package__)
20
+ from ..diag import debug as d_debug
21
+ from ..diag import pycharm as d_pycharm
22
+ from ..diag import replserver
23
+ from ..diag import threads as d_threads
33
24
 
34
25
 
35
26
  ##
@@ -110,7 +101,7 @@ class ThreadDumpBootstrap(ContextBootstrap['ThreadDumpBootstrap.Config']):
110
101
  @contextlib.contextmanager
111
102
  def enter(self) -> ta.Iterator[None]:
112
103
  if self._config.interval_s:
113
- tdt = diagt.create_thread_dump_thread(
104
+ tdt = d_threads.create_thread_dump_thread(
114
105
  interval_s=self._config.interval_s,
115
106
  start=True,
116
107
  nodaemon=self._config.nodaemon,
@@ -119,7 +110,7 @@ class ThreadDumpBootstrap(ContextBootstrap['ThreadDumpBootstrap.Config']):
119
110
  tdt = None
120
111
 
121
112
  if self._config.on_sigquit:
122
- dump_threads_str = diagt.dump_threads_str
113
+ dump_threads_str = d_threads.dump_threads_str
123
114
 
124
115
  def handler(signum, frame):
125
116
  print(dump_threads_str(), file=sys.stderr)
@@ -153,7 +144,7 @@ class TimebombBootstrap(ContextBootstrap['TimebombBootstrap.Config']):
153
144
  yield
154
145
  return
155
146
 
156
- tbt = diagt.create_timebomb_thread(
147
+ tbt = d_threads.create_timebomb_thread(
157
148
  self._config.delay_s,
158
149
  start=True,
159
150
  )
@@ -173,8 +164,8 @@ class PycharmBootstrap(SimpleBootstrap['PycharmBootstrap.Config']):
173
164
 
174
165
  def run(self) -> None:
175
166
  if self._config.debug is not None:
176
- prd = diagpc.PycharmRemoteDebugger.parse(self._config.debug)
177
- diagpc.pycharm_remote_debugger_attach(prd)
167
+ prd = d_pycharm.PycharmRemoteDebugger.parse(self._config.debug)
168
+ d_pycharm.pycharm_remote_debugger_attach(prd)
178
169
 
179
170
 
180
171
  ##
@@ -190,7 +181,7 @@ class ReplServerBootstrap(ContextBootstrap['ReplServerBootstrap.Config']):
190
181
  if self._config.path is None:
191
182
  return
192
183
 
193
- with diagrs.ReplServer(diagrs.ReplServer.Config(
184
+ with replserver.ReplServer(replserver.ReplServer.Config(
194
185
  path=self._config.path,
195
186
  )) as rs:
196
187
  thread = threading.Thread(target=rs.run, name='replserver')
@@ -212,5 +203,5 @@ class DebugBootstrap(ContextBootstrap['DebugBootstrap.Config']):
212
203
  if not self._config.enable:
213
204
  return
214
205
 
215
- with ddbg.debugging_on_exception():
206
+ with d_debug.debugging_on_exception():
216
207
  yield
omlish/bootstrap/main.py CHANGED
@@ -1,9 +1,9 @@
1
+ # ruff: noqa: UP006 UP007 UP045
1
2
  """
2
3
  TODO:
3
4
  - -x / --exec - os.exec entrypoint
4
5
  - refuse to install non-exec-relevant Bootstraps when chosen
5
6
  """
6
- # ruff: noqa: UP006 UP007 UP045
7
7
  import argparse
8
8
  import dataclasses as dc
9
9
  import io
@@ -19,12 +19,9 @@ from .harness import BOOTSTRAP_TYPES_BY_NAME
19
19
  from .harness import bootstrap
20
20
 
21
21
 
22
- if ta.TYPE_CHECKING:
22
+ with lang.auto_proxy_import(globals()):
23
23
  import runpy
24
24
 
25
- else:
26
- runpy = lang.proxy_import('runpy')
27
-
28
25
 
29
26
  ##
30
27
 
omlish/bootstrap/sys.py CHANGED
@@ -2,14 +2,10 @@
2
2
  import contextlib
3
3
  import dataclasses as dc
4
4
  import enum
5
- import faulthandler
6
5
  import gc
7
6
  import importlib
8
7
  import logging
9
8
  import os
10
- import pwd
11
- import resource
12
- import signal
13
9
  import sys
14
10
  import typing as ta
15
11
 
@@ -19,18 +15,17 @@ from .base import ContextBootstrap
19
15
  from .base import SimpleBootstrap
20
16
 
21
17
 
22
- if ta.TYPE_CHECKING:
18
+ with lang.auto_proxy_import(globals()):
19
+ import faulthandler
20
+ import pwd
21
+ import resource
22
+ import signal
23
+
23
24
  from .. import libc
24
25
  from ..formats import dotenv
25
26
  from ..logs import all as logs
26
27
  from ..os.pidfiles import pidfile
27
28
 
28
- else:
29
- libc = lang.proxy_import('..libc', __package__)
30
- logs = lang.proxy_import('..logs', __package__)
31
- dotenv = lang.proxy_import('..formats.dotenv', __package__)
32
- pidfile = lang.proxy_import('..os.pidfiles.pidfile', __package__)
33
-
34
29
 
35
30
  ##
36
31
 
@@ -184,14 +179,16 @@ class FaulthandlerBootstrap(ContextBootstrap['FaulthandlerBootstrap.Config']):
184
179
  ##
185
180
 
186
181
 
187
- SIGNALS_BY_NAME = {
188
- a[len('SIG'):]: v # noqa
189
- for a in dir(signal)
190
- if a.startswith('SIG')
191
- and not a.startswith('SIG_')
192
- and a == a.upper()
193
- and isinstance((v := getattr(signal, a)), int)
194
- }
182
+ @lang.cached_function
183
+ def signals_by_name() -> ta.Mapping[str, int]:
184
+ return {
185
+ a[len('SIG'):]: v # noqa
186
+ for a in dir(signal)
187
+ if a.startswith('SIG')
188
+ and not a.startswith('SIG_')
189
+ and a == a.upper()
190
+ and isinstance((v := getattr(signal, a)), int)
191
+ }
195
192
 
196
193
 
197
194
  class PrctlBootstrap(SimpleBootstrap['PrctlBootstrap.Config']):
@@ -208,20 +205,22 @@ class PrctlBootstrap(SimpleBootstrap['PrctlBootstrap.Config']):
208
205
  if isinstance(self._config.deathsig, int):
209
206
  sig = self._config.deathsig
210
207
  else:
211
- sig = SIGNALS_BY_NAME[self._config.deathsig.upper()]
208
+ sig = signals_by_name()[self._config.deathsig.upper()]
212
209
  libc.prctl(libc.PR_SET_PDEATHSIG, sig, 0, 0, 0, 0)
213
210
 
214
211
 
215
212
  ##
216
213
 
217
214
 
218
- RLIMITS_BY_NAME = {
219
- a[len('RLIMIT_'):]: v # noqa
220
- for a in dir(resource)
221
- if a.startswith('RLIMIT_')
222
- and a == a.upper()
223
- and isinstance((v := getattr(resource, a)), int)
224
- }
215
+ @lang.cached_function
216
+ def rlimits_by_name() -> ta.Mapping[str, int]:
217
+ return {
218
+ a[len('RLIMIT_'):]: v # noqa
219
+ for a in dir(resource)
220
+ if a.startswith('RLIMIT_')
221
+ and a == a.upper()
222
+ and isinstance((v := getattr(resource, a)), int)
223
+ }
225
224
 
226
225
 
227
226
  class RlimitBootstrap(ContextBootstrap['RlimitBootstrap.Config']):
@@ -240,7 +239,7 @@ class RlimitBootstrap(ContextBootstrap['RlimitBootstrap.Config']):
240
239
 
241
240
  prev = {}
242
241
  for k, (s, h) in self._config.limits.items():
243
- i = RLIMITS_BY_NAME[k.upper()]
242
+ i = rlimits_by_name()[k.upper()]
244
243
  prev[i] = resource.getrlimit(i)
245
244
  resource.setrlimit(i, (or_infin(s), or_infin(h)))
246
245
 
File without changes
@@ -0,0 +1 @@
1
+ #pragma once
@@ -6,12 +6,21 @@ from .. import lang as _lang
6
6
  with _lang.auto_proxy_init(globals()):
7
7
  ##
8
8
 
9
+ from .attrregistry import ( # noqa
10
+ AttrRegistry,
11
+
12
+ AttrRegistryCache,
13
+ SimpleAttrRegistryCache,
14
+ )
15
+
9
16
  from .bimap import ( # noqa
10
17
  BiMap,
11
18
 
12
19
  make_bi_map,
13
20
  )
14
21
 
22
+ from . import cache # noqa
23
+
15
24
  from .coerce import ( # noqa
16
25
  abs_set,
17
26
  abs_set_of,
@@ -64,9 +73,10 @@ with _lang.auto_proxy_init(globals()):
64
73
  from . import kv # noqa
65
74
 
66
75
  from .mappings import ( # noqa
76
+ DynamicTypeMap,
67
77
  MissingDict,
68
78
  TypeMap,
69
- DynamicTypeMap,
79
+ dict_factory,
70
80
  guarded_map_update,
71
81
  multikey_dict,
72
82
  )
@@ -145,6 +155,8 @@ with _lang.auto_proxy_init(globals()):
145
155
  key_cmp,
146
156
  make_map,
147
157
  make_map_by,
158
+ map_keys,
159
+ map_values,
148
160
  multi_map,
149
161
  multi_map_by,
150
162
  partition,
@@ -0,0 +1,210 @@
1
+ """
2
+ TODO:
3
+ - lock?
4
+ """
5
+ import dataclasses as dc
6
+ import typing as ta
7
+ import weakref
8
+
9
+ from .. import check
10
+ from .. import lang
11
+ from .mappings import dict_factory
12
+
13
+
14
+ T = ta.TypeVar('T')
15
+ K = ta.TypeVar('K')
16
+ V = ta.TypeVar('V')
17
+
18
+
19
+ ##
20
+
21
+
22
+ class AttrRegistry(ta.Generic[K, V]):
23
+ """
24
+ MRO-honoring class member registry. There are many ways to do this, and this one is attr name based: a class is
25
+ considered to have objects registered to it based on whether or not they are accessible by a non-shadowed,
26
+ MRO-resolved named attribute on that class.
27
+
28
+ Care must be taken when overriding registered objects from superclasses in subclasses - shadowing the attribute name
29
+ of the superclass member will not automatically register the new member with the same name to the registry - it must
30
+ be explicitly registered itself. This is a feature, allowing for selective de-registration of objects in subclasses
31
+ via name shadowing.
32
+ """
33
+
34
+ def __init__(
35
+ self,
36
+ *,
37
+ requires_override: bool = False,
38
+ is_override: ta.Callable[[ta.Any], bool] | None = None,
39
+ forbid_duplicates: bool = False,
40
+ identity: bool = False,
41
+ weak: bool = False,
42
+ ) -> None:
43
+ super().__init__()
44
+
45
+ self._requires_override = requires_override
46
+ self._is_override = is_override
47
+ self._forbid_duplicates = forbid_duplicates
48
+ self._identity = identity
49
+ self._weak = weak
50
+
51
+ self._objs: ta.MutableMapping[K, V] = dict_factory(identity=identity, weak=weak)()
52
+ self._invalidate_callbacks: list[ta.Callable[[], None]] = []
53
+
54
+ def add_invalidate_callback(self, callback: ta.Callable[[], None]) -> None:
55
+ self._invalidate_callbacks.append(callback)
56
+
57
+ @ta.overload
58
+ def register(self, obj: K, val: V) -> None:
59
+ ...
60
+
61
+ @ta.overload
62
+ def register(self, val: V) -> ta.Callable[[T], T]:
63
+ ...
64
+
65
+ def register(self, *args):
66
+ def inner(obj, val):
67
+ check.not_in(obj, self._objs)
68
+
69
+ self._objs[obj] = val
70
+
71
+ for iv in self._invalidate_callbacks:
72
+ iv()
73
+
74
+ return obj
75
+
76
+ if len(args) == 1:
77
+ return lambda obj: inner(obj, args[0])
78
+ elif len(args) == 2:
79
+ inner = inner(*args)
80
+ return None
81
+ else:
82
+ raise TypeError(args)
83
+
84
+ def _lookup(self, obj: ta.Any) -> lang.Maybe[V]:
85
+ if not self._identity:
86
+ try:
87
+ hash(obj)
88
+ except TypeError:
89
+ return lang.empty()
90
+
91
+ try:
92
+ val = self._objs[obj]
93
+ except KeyError:
94
+ return lang.empty()
95
+ else:
96
+ return lang.just(val)
97
+
98
+ @dc.dataclass()
99
+ class DuplicatesForbiddenError(Exception):
100
+ owner_cls: type
101
+ instance_cls: type
102
+ att: str
103
+ ex_att: str
104
+
105
+ def collect(self, instance_cls: type, owner_cls: type | None = None) -> dict[str, tuple[K, V]]:
106
+ if owner_cls is None:
107
+ owner_cls = instance_cls
108
+
109
+ mro = instance_cls.__mro__[-2::-1]
110
+ try:
111
+ mro_pos = mro.index(owner_cls)
112
+ except ValueError:
113
+ raise TypeError(f'Owner class {owner_cls} not in mro of instance class {instance_cls}') from None
114
+
115
+ mro_dct: dict[str, list[tuple[type, ta.Any]]] = {}
116
+ for cur_cls in mro[:mro_pos + 1]:
117
+ for att, obj in cur_cls.__dict__.items():
118
+ if att not in mro_dct:
119
+ if not self._lookup(obj).present:
120
+ continue
121
+
122
+ try:
123
+ lst = mro_dct[att]
124
+ except KeyError:
125
+ lst = mro_dct[att] = []
126
+ lst.append((cur_cls, obj))
127
+
128
+ #
129
+
130
+ seen: ta.MutableMapping[ta.Any, str] | None = None
131
+ if self._forbid_duplicates:
132
+ seen = dict_factory(identity=self._identity)()
133
+
134
+ out: dict[str, tuple[K, V]] = {}
135
+ for att, lst in mro_dct.items():
136
+ if not lst:
137
+ raise RuntimeError
138
+ _, obj = lst[-1]
139
+
140
+ if len(lst) > 1:
141
+ if self._requires_override and not (self._is_override or lang.is_override)(obj):
142
+ raise lang.RequiresOverrideError(
143
+ att,
144
+ instance_cls,
145
+ lst[-1][0],
146
+ lst[0][0],
147
+ )
148
+
149
+ if not (mv := self._lookup(obj)).present:
150
+ continue
151
+
152
+ if seen is not None:
153
+ try:
154
+ ex_att = seen[obj]
155
+ except KeyError:
156
+ pass
157
+ else:
158
+ raise AttrRegistry.DuplicatesForbiddenError(owner_cls, instance_cls, att, ex_att) # noqa
159
+ seen[obj] = att
160
+
161
+ out[att] = (obj, mv.must())
162
+
163
+ return out
164
+
165
+
166
+ ##
167
+
168
+
169
+ class AttrRegistryCache(ta.Generic[K, V, T]):
170
+ def __init__(
171
+ self,
172
+ registry: AttrRegistry[K, V],
173
+ prepare: ta.Callable[[type, dict[str, tuple[K, V]]], T],
174
+ ) -> None:
175
+ super().__init__()
176
+
177
+ self._registry = registry
178
+ self._prepare = prepare
179
+
180
+ self._cache: dict[ta.Any, T] = {}
181
+
182
+ def cache_remove(k, self_ref=weakref.ref(self)):
183
+ if (ref_self := self_ref()) is not None:
184
+ cache = ref_self._cache # noqa
185
+ try:
186
+ del cache[k]
187
+ except KeyError:
188
+ pass
189
+
190
+ self._cache_remove = cache_remove
191
+
192
+ registry.add_invalidate_callback(self._cache.clear)
193
+
194
+ def get(self, instance_cls: type) -> T:
195
+ cls_ref = weakref.ref(instance_cls)
196
+ try:
197
+ return self._cache[cls_ref]
198
+ except KeyError:
199
+ pass
200
+ del cls_ref
201
+
202
+ collected = self._registry.collect(instance_cls)
203
+ out = self._prepare(instance_cls, collected)
204
+ self._cache[weakref.ref(instance_cls, self._cache_remove)] = out
205
+ return out
206
+
207
+
208
+ class SimpleAttrRegistryCache(AttrRegistryCache[K, V, dict[str, tuple[K, V]]], ta.Generic[K, V]):
209
+ def __init__(self, registry: AttrRegistry[K, V]) -> None:
210
+ super().__init__(registry, lambda _, dct: dct)
@@ -41,6 +41,7 @@ def LRU(cache: 'Cache') -> None: # noqa
41
41
  cache._kill(cache._root.lru_next) # type: ignore # noqa
42
42
 
43
43
 
44
+ # aka FIFO
44
45
  def LRI(cache: 'Cache') -> None: # noqa
45
46
  cache._kill(cache._root.ins_next) # type: ignore # noqa
46
47
 
@@ -105,6 +105,7 @@ class IdentityWeakKeyDictionary(ta.MutableMapping[K, V]):
105
105
 
106
106
  TODO:
107
107
  - audit for freethreaded
108
+ - leans on stdlib impls so shouldn't matter?
108
109
  """
109
110
 
110
111
  def __init__(self, *args: ta.Any, **kwargs: ta.Any) -> None:
@@ -1,6 +1,12 @@
1
1
  import typing as ta
2
2
  import weakref
3
3
 
4
+ from .. import lang
5
+
6
+
7
+ with lang.auto_proxy_import(globals()):
8
+ from . import identity as _identity
9
+
4
10
 
5
11
  T = ta.TypeVar('T')
6
12
  K = ta.TypeVar('K')
@@ -63,6 +69,9 @@ class TypeMap(ta.Generic[T]):
63
69
  def __iter__(self) -> ta.Iterator[T]:
64
70
  return iter(self._items)
65
71
 
72
+ def __contains__(self, ty: type[T]) -> bool:
73
+ return ty in self._items
74
+
66
75
  def get(self, ty: type[T]) -> T | None:
67
76
  return self._dct.get(ty)
68
77
 
@@ -138,3 +147,22 @@ class MissingDict(dict[K, V]):
138
147
  def __missing__(self, key):
139
148
  v = self[key] = self._missing_fn(key)
140
149
  return v
150
+
151
+
152
+ ##
153
+
154
+
155
+ def dict_factory[K, V](
156
+ *,
157
+ identity: bool = False,
158
+ weak: bool = False,
159
+ ) -> ta.Callable[..., ta.MutableMapping[K, V]]:
160
+ if identity:
161
+ if weak:
162
+ return _identity.IdentityWeakKeyDictionary
163
+ else:
164
+ return _identity.IdentityKeyDict
165
+ elif weak:
166
+ return weakref.WeakKeyDictionary
167
+ else:
168
+ return dict
@@ -37,12 +37,16 @@ class Trie(ta.MutableMapping[ta.Sequence[K], V], ta.Generic[K, V]):
37
37
  def children(self) -> ta.Mapping[K2, 'Trie.Node[K2, V2]']:
38
38
  return self._children
39
39
 
40
- def __init__(self) -> None:
40
+ def __init__(self, items: ta.Iterable[tuple[ta.Iterable[K], V]] | None = None) -> None:
41
41
  super().__init__()
42
42
 
43
43
  self._len = 0
44
44
  self._root: Trie.Node[K, V] = Trie.Node()
45
45
 
46
+ if items is not None:
47
+ for k, v in items:
48
+ self[k] = v
49
+
46
50
  @property
47
51
  def root(self) -> Node[K, V]:
48
52
  return self._root
@@ -7,7 +7,9 @@ from .identity import IdentitySet
7
7
 
8
8
  T = ta.TypeVar('T')
9
9
  K = ta.TypeVar('K')
10
+ K2 = ta.TypeVar('K2')
10
11
  V = ta.TypeVar('V')
12
+ V2 = ta.TypeVar('V2')
11
13
 
12
14
 
13
15
  ##
@@ -135,6 +137,81 @@ def make_map_by(
135
137
  ##
136
138
 
137
139
 
140
+ @ta.overload
141
+ def map_keys(
142
+ fn: ta.Callable[[K], K2],
143
+ dct: ta.Mapping[K, V] | ta.Iterable[tuple[K, V]],
144
+ *,
145
+ identity: ta.Literal[False] = False,
146
+ strict: bool = False,
147
+ ) -> dict[K2, V]:
148
+ return {}
149
+
150
+
151
+ @ta.overload
152
+ def map_keys(
153
+ fn: ta.Callable[[K], K2],
154
+ dct: ta.Mapping[K, V] | ta.Iterable[tuple[K, V]],
155
+ *,
156
+ identity: bool = False,
157
+ strict: bool = False,
158
+ ) -> ta.MutableMapping[K2, V]:
159
+ return {}
160
+
161
+
162
+ def map_keys(
163
+ fn,
164
+ dct,
165
+ *,
166
+ identity=False,
167
+ strict=False,
168
+ ):
169
+ return make_map(
170
+ ((fn(k), v) for k, v in (dct.items() if isinstance(dct, ta.Mapping) else dct)),
171
+ identity=identity,
172
+ strict=strict,
173
+ )
174
+
175
+
176
+ @ta.overload
177
+ def map_values(
178
+ fn: ta.Callable[[V], V2],
179
+ dct: ta.Mapping[K, V] | ta.Iterable[tuple[K, V]],
180
+ *,
181
+ identity: ta.Literal[False] = False,
182
+ strict: bool = False,
183
+ ) -> dict[K, V2]:
184
+ return {}
185
+
186
+
187
+ @ta.overload
188
+ def map_values(
189
+ fn: ta.Callable[[V], V2],
190
+ dct: ta.Mapping[K, V] | ta.Iterable[tuple[K, V]],
191
+ *,
192
+ identity: bool = False,
193
+ strict: bool = False,
194
+ ) -> ta.MutableMapping[K2, V]:
195
+ return {}
196
+
197
+
198
+ def map_values(
199
+ fn,
200
+ dct,
201
+ *,
202
+ identity=False,
203
+ strict=False,
204
+ ):
205
+ return make_map(
206
+ ((k, fn(v)) for k, v in (dct.items() if isinstance(dct, ta.Mapping) else dct)),
207
+ identity=identity,
208
+ strict=strict,
209
+ )
210
+
211
+
212
+ ##
213
+
214
+
138
215
  @ta.overload
139
216
  def multi_map(
140
217
  kvs: ta.Iterable[tuple[K, V]],
omlish/concurrent/all.py CHANGED
@@ -12,6 +12,7 @@ with _lang.auto_proxy_init(globals()):
12
12
  from .futures import ( # noqa
13
13
  FutureError,
14
14
  FutureTimeoutError,
15
- wait_futures,
15
+ wait_all_futures_or_raise,
16
16
  wait_dependent_futures,
17
+ wait_futures,
17
18
  )
@@ -48,6 +48,13 @@ def wait_futures(
48
48
  raise_exceptions: bool = False,
49
49
  cancel_on_exception: bool = False,
50
50
  ) -> bool:
51
+ # TODO:
52
+ # - raise_exceptions 'at_end', ExceptionGroup
53
+ # - more responsive than tick_interval_s - semaphore, add callbacks to each fut to decrement, sleep with a timed
54
+ # wait on sem
55
+ # - cancel_on_timeout
56
+ # - obviate wait_all_futures_or_raise
57
+
51
58
  start = time.time()
52
59
 
53
60
  not_done = set(futures)
@@ -72,6 +79,24 @@ def wait_futures(
72
79
  return False
73
80
 
74
81
 
82
+ def wait_all_futures_or_raise(futures: ta.Sequence[cf.Future]) -> None:
83
+ done, not_done = cf.wait(futures, return_when=cf.ALL_COMPLETED)
84
+ if not_done:
85
+ raise RuntimeError(f'Not all futures finished: {not_done}')
86
+
87
+ excs: list[Exception] = []
88
+ for f in done:
89
+ try:
90
+ f.result()
91
+ except Exception as e: # noqa
92
+ excs.append(e)
93
+
94
+ if len(excs) == 1:
95
+ raise excs[0]
96
+ elif excs:
97
+ raise ExceptionGroup('One or more futures failed', excs)
98
+
99
+
75
100
  def wait_dependent_futures(
76
101
  executor: cf.Executor,
77
102
  dependency_sets_by_fn: ta.Mapping[ta.Callable, ta.AbstractSet[ta.Callable]],