omlish 0.0.0.dev447__py3-none-any.whl → 0.0.0.dev484__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 (226) hide show
  1. omlish/.omlish-manifests.json +12 -0
  2. omlish/__about__.py +15 -15
  3. omlish/argparse/all.py +17 -9
  4. omlish/argparse/cli.py +16 -3
  5. omlish/argparse/utils.py +21 -0
  6. omlish/asyncs/asyncio/rlock.py +110 -0
  7. omlish/asyncs/asyncio/sync.py +43 -0
  8. omlish/asyncs/asyncio/utils.py +2 -0
  9. omlish/asyncs/sync.py +25 -0
  10. omlish/bootstrap/_marshal.py +1 -1
  11. omlish/bootstrap/diag.py +12 -21
  12. omlish/bootstrap/main.py +2 -5
  13. omlish/bootstrap/sys.py +27 -28
  14. omlish/cexts/__init__.py +0 -0
  15. omlish/cexts/include/omlish/omlish.hh +1 -0
  16. omlish/collections/__init__.py +13 -1
  17. omlish/collections/attrregistry.py +210 -0
  18. omlish/collections/cache/impl.py +1 -0
  19. omlish/collections/identity.py +1 -0
  20. omlish/collections/mappings.py +28 -0
  21. omlish/collections/trie.py +5 -1
  22. omlish/collections/utils.py +77 -0
  23. omlish/concurrent/all.py +2 -1
  24. omlish/concurrent/futures.py +25 -0
  25. omlish/concurrent/threadlets.py +1 -1
  26. omlish/daemons/reparent.py +2 -3
  27. omlish/daemons/spawning.py +2 -3
  28. omlish/dataclasses/__init__.py +2 -0
  29. omlish/dataclasses/impl/api/classes/decorator.py +3 -0
  30. omlish/dataclasses/impl/api/classes/make.py +3 -0
  31. omlish/dataclasses/impl/concerns/repr.py +15 -2
  32. omlish/dataclasses/impl/configs.py +97 -37
  33. omlish/dataclasses/impl/generation/compilation.py +21 -19
  34. omlish/dataclasses/impl/generation/globals.py +1 -0
  35. omlish/dataclasses/impl/generation/ops.py +1 -0
  36. omlish/dataclasses/impl/generation/processor.py +105 -24
  37. omlish/dataclasses/impl/processing/base.py +8 -0
  38. omlish/dataclasses/impl/processing/driving.py +8 -8
  39. omlish/dataclasses/specs.py +1 -0
  40. omlish/dataclasses/tools/modifiers.py +5 -0
  41. omlish/diag/cmds/__init__.py +0 -0
  42. omlish/diag/{lslocks.py → cmds/lslocks.py} +6 -6
  43. omlish/diag/{lsof.py → cmds/lsof.py} +6 -6
  44. omlish/diag/{ps.py → cmds/ps.py} +6 -6
  45. omlish/diag/pycharm.py +16 -2
  46. omlish/diag/pydevd.py +58 -40
  47. omlish/diag/replserver/console.py +1 -1
  48. omlish/dispatch/__init__.py +18 -12
  49. omlish/dispatch/methods.py +50 -140
  50. omlish/dom/rendering.py +1 -1
  51. omlish/formats/dotenv.py +1 -1
  52. omlish/formats/json/stream/__init__.py +13 -0
  53. omlish/funcs/guard.py +225 -0
  54. omlish/graphs/dot/rendering.py +1 -1
  55. omlish/http/all.py +44 -4
  56. omlish/http/clients/asyncs.py +33 -27
  57. omlish/http/clients/base.py +17 -1
  58. omlish/http/clients/coro/__init__.py +0 -0
  59. omlish/http/clients/coro/sync.py +171 -0
  60. omlish/http/clients/default.py +208 -29
  61. omlish/http/clients/executor.py +56 -0
  62. omlish/http/clients/httpx.py +72 -11
  63. omlish/http/clients/middleware.py +181 -0
  64. omlish/http/clients/sync.py +33 -27
  65. omlish/http/clients/syncasync.py +49 -0
  66. omlish/http/clients/urllib.py +6 -3
  67. omlish/http/coro/client/connection.py +15 -6
  68. omlish/http/coro/io.py +2 -0
  69. omlish/http/flasky/__init__.py +40 -0
  70. omlish/http/flasky/_compat.py +2 -0
  71. omlish/http/flasky/api.py +82 -0
  72. omlish/http/flasky/app.py +203 -0
  73. omlish/http/flasky/cvs.py +59 -0
  74. omlish/http/flasky/requests.py +20 -0
  75. omlish/http/flasky/responses.py +23 -0
  76. omlish/http/flasky/routes.py +23 -0
  77. omlish/http/flasky/types.py +15 -0
  78. omlish/http/urls.py +67 -0
  79. omlish/inject/__init__.py +38 -18
  80. omlish/inject/_dataclasses.py +4986 -0
  81. omlish/inject/binder.py +4 -48
  82. omlish/inject/elements.py +27 -0
  83. omlish/inject/helpers/__init__.py +0 -0
  84. omlish/inject/{utils.py → helpers/constfn.py} +3 -3
  85. omlish/inject/{tags.py → helpers/id.py} +2 -2
  86. omlish/inject/helpers/multis.py +143 -0
  87. omlish/inject/helpers/wrappers.py +54 -0
  88. omlish/inject/impl/elements.py +47 -17
  89. omlish/inject/impl/injector.py +20 -19
  90. omlish/inject/impl/inspect.py +10 -1
  91. omlish/inject/impl/maysync.py +3 -4
  92. omlish/inject/impl/multis.py +3 -0
  93. omlish/inject/impl/sync.py +3 -4
  94. omlish/inject/injector.py +31 -2
  95. omlish/inject/inspect.py +35 -0
  96. omlish/inject/maysync.py +2 -4
  97. omlish/inject/multis.py +8 -0
  98. omlish/inject/overrides.py +3 -3
  99. omlish/inject/privates.py +6 -0
  100. omlish/inject/providers.py +3 -2
  101. omlish/inject/sync.py +5 -4
  102. omlish/io/buffers.py +118 -0
  103. omlish/io/readers.py +29 -0
  104. omlish/iterators/transforms.py +2 -2
  105. omlish/lang/__init__.py +178 -97
  106. omlish/lang/_asyncs.cc +186 -0
  107. omlish/lang/asyncs.py +17 -0
  108. omlish/lang/casing.py +11 -0
  109. omlish/lang/contextmanagers.py +28 -4
  110. omlish/lang/functions.py +31 -22
  111. omlish/lang/imports/_capture.cc +11 -9
  112. omlish/lang/imports/capture.py +363 -170
  113. omlish/lang/imports/proxy.py +455 -152
  114. omlish/lang/lazyglobals.py +22 -9
  115. omlish/lang/params.py +17 -0
  116. omlish/lang/recursion.py +0 -1
  117. omlish/lang/sequences.py +124 -0
  118. omlish/lite/abstract.py +54 -24
  119. omlish/lite/asyncs.py +2 -2
  120. omlish/lite/attrops.py +2 -0
  121. omlish/lite/contextmanagers.py +4 -4
  122. omlish/lite/dataclasses.py +44 -0
  123. omlish/lite/maybes.py +8 -0
  124. omlish/lite/maysync.py +1 -5
  125. omlish/lite/pycharm.py +1 -1
  126. omlish/lite/typing.py +6 -0
  127. omlish/logs/all.py +1 -1
  128. omlish/logs/utils.py +1 -1
  129. omlish/manifests/loading.py +2 -1
  130. omlish/marshal/__init__.py +33 -13
  131. omlish/marshal/_dataclasses.py +2774 -0
  132. omlish/marshal/base/configs.py +12 -0
  133. omlish/marshal/base/contexts.py +36 -21
  134. omlish/marshal/base/funcs.py +8 -11
  135. omlish/marshal/base/options.py +8 -0
  136. omlish/marshal/base/registries.py +146 -44
  137. omlish/marshal/base/types.py +40 -16
  138. omlish/marshal/composite/iterables.py +33 -20
  139. omlish/marshal/composite/literals.py +20 -18
  140. omlish/marshal/composite/mappings.py +36 -23
  141. omlish/marshal/composite/maybes.py +29 -19
  142. omlish/marshal/composite/newtypes.py +16 -16
  143. omlish/marshal/composite/optionals.py +14 -14
  144. omlish/marshal/composite/special.py +15 -15
  145. omlish/marshal/composite/unions/__init__.py +0 -0
  146. omlish/marshal/composite/unions/literals.py +93 -0
  147. omlish/marshal/composite/unions/primitives.py +103 -0
  148. omlish/marshal/factories/invalidate.py +18 -68
  149. omlish/marshal/factories/method.py +26 -0
  150. omlish/marshal/factories/moduleimport/factories.py +22 -65
  151. omlish/marshal/factories/multi.py +13 -25
  152. omlish/marshal/factories/recursive.py +42 -56
  153. omlish/marshal/factories/typecache.py +29 -74
  154. omlish/marshal/factories/typemap.py +42 -43
  155. omlish/marshal/objects/dataclasses.py +129 -106
  156. omlish/marshal/objects/marshal.py +18 -14
  157. omlish/marshal/objects/namedtuples.py +48 -42
  158. omlish/marshal/objects/unmarshal.py +19 -15
  159. omlish/marshal/polymorphism/marshal.py +9 -11
  160. omlish/marshal/polymorphism/metadata.py +16 -5
  161. omlish/marshal/polymorphism/standard.py +13 -1
  162. omlish/marshal/polymorphism/unions.py +15 -105
  163. omlish/marshal/polymorphism/unmarshal.py +9 -10
  164. omlish/marshal/singular/enums.py +14 -18
  165. omlish/marshal/standard.py +10 -6
  166. omlish/marshal/trivial/any.py +1 -1
  167. omlish/marshal/trivial/forbidden.py +21 -26
  168. omlish/metadata.py +23 -1
  169. omlish/os/forkhooks.py +4 -0
  170. omlish/os/pidfiles/pinning.py +2 -2
  171. omlish/reflect/types.py +1 -0
  172. omlish/secrets/marshal.py +1 -1
  173. omlish/specs/jmespath/__init__.py +12 -3
  174. omlish/specs/jmespath/_dataclasses.py +2893 -0
  175. omlish/specs/jmespath/ast.py +1 -1
  176. omlish/specs/jsonrpc/__init__.py +13 -0
  177. omlish/specs/jsonrpc/_marshal.py +32 -23
  178. omlish/specs/jsonrpc/conns.py +10 -7
  179. omlish/specs/jsonschema/_marshal.py +1 -1
  180. omlish/specs/jsonschema/keywords/__init__.py +7 -0
  181. omlish/specs/jsonschema/keywords/_dataclasses.py +1644 -0
  182. omlish/specs/openapi/_marshal.py +31 -22
  183. omlish/sql/{tabledefs/alchemy.py → alchemy/tabledefs.py} +2 -2
  184. omlish/sql/queries/_marshal.py +2 -2
  185. omlish/sql/queries/rendering.py +1 -1
  186. omlish/sql/tabledefs/_marshal.py +1 -1
  187. omlish/subprocesses/base.py +4 -0
  188. omlish/subprocesses/editor.py +1 -1
  189. omlish/sync.py +155 -21
  190. omlish/term/alt.py +60 -0
  191. omlish/term/confirm.py +8 -8
  192. omlish/term/pager.py +235 -0
  193. omlish/term/terminfo.py +935 -0
  194. omlish/term/termstate.py +67 -0
  195. omlish/term/vt100/terminal.py +0 -3
  196. omlish/testing/pytest/plugins/asyncs/fixtures.py +4 -1
  197. omlish/testing/pytest/plugins/skips.py +2 -1
  198. omlish/testing/unittest/main.py +3 -3
  199. omlish/text/docwrap/__init__.py +3 -0
  200. omlish/text/docwrap/__main__.py +11 -0
  201. omlish/text/docwrap/api.py +83 -0
  202. omlish/text/docwrap/cli.py +86 -0
  203. omlish/text/docwrap/groups.py +84 -0
  204. omlish/text/docwrap/lists.py +167 -0
  205. omlish/text/docwrap/parts.py +146 -0
  206. omlish/text/docwrap/reflowing.py +106 -0
  207. omlish/text/docwrap/rendering.py +151 -0
  208. omlish/text/docwrap/utils.py +11 -0
  209. omlish/text/docwrap/wrapping.py +59 -0
  210. omlish/text/filecache.py +2 -2
  211. omlish/text/lorem.py +6 -0
  212. omlish/text/parts.py +2 -2
  213. omlish/text/textwrap.py +51 -0
  214. omlish/typedvalues/marshal.py +85 -59
  215. omlish/typedvalues/values.py +2 -1
  216. {omlish-0.0.0.dev447.dist-info → omlish-0.0.0.dev484.dist-info}/METADATA +29 -28
  217. {omlish-0.0.0.dev447.dist-info → omlish-0.0.0.dev484.dist-info}/RECORD +222 -171
  218. omlish/dataclasses/impl/generation/mangling.py +0 -18
  219. omlish/funcs/match.py +0 -227
  220. omlish/marshal/factories/match.py +0 -34
  221. omlish/marshal/factories/simple.py +0 -28
  222. /omlish/inject/impl/{providers2.py → providersmap.py} +0 -0
  223. {omlish-0.0.0.dev447.dist-info → omlish-0.0.0.dev484.dist-info}/WHEEL +0 -0
  224. {omlish-0.0.0.dev447.dist-info → omlish-0.0.0.dev484.dist-info}/entry_points.txt +0 -0
  225. {omlish-0.0.0.dev447.dist-info → omlish-0.0.0.dev484.dist-info}/licenses/LICENSE +0 -0
  226. {omlish-0.0.0.dev447.dist-info → omlish-0.0.0.dev484.dist-info}/top_level.txt +0 -0
@@ -359,5 +359,17 @@
359
359
  "module": "omlish.specs.jmespath.__main__"
360
360
  }
361
361
  }
362
+ },
363
+ {
364
+ "module": ".text.docwrap.__main__",
365
+ "attr": "_CLI_MODULE",
366
+ "file": "omlish/text/docwrap/__main__.py",
367
+ "line": 1,
368
+ "value": {
369
+ "!omdev.cli.types.CliModule": {
370
+ "name": "docwrap",
371
+ "module": "omlish.text.docwrap.__main__"
372
+ }
373
+ }
362
374
  }
363
375
  ]
omlish/__about__.py CHANGED
@@ -1,5 +1,5 @@
1
- __version__ = '0.0.0.dev447'
2
- __revision__ = 'b7728880793e311f2b0080bb2b0ad2508b86e7b3'
1
+ __version__ = '0.0.0.dev484'
2
+ __revision__ = '7c8d11bd7ee674238104cc5940ee4971dbf70a29'
3
3
 
4
4
 
5
5
  #
@@ -36,12 +36,12 @@ class Project(ProjectBase):
36
36
 
37
37
  optional_dependencies = {
38
38
  'async': [
39
- 'anyio ~= 4.10',
39
+ 'anyio ~= 4.11',
40
40
  'sniffio ~= 1.3',
41
41
 
42
- 'greenlet ~= 3.2',
42
+ 'greenlet ~= 3.3',
43
43
 
44
- 'trio ~= 0.31',
44
+ 'trio ~= 0.32',
45
45
  'trio-asyncio ~= 0.15',
46
46
  ],
47
47
 
@@ -53,7 +53,7 @@ class Project(ProjectBase):
53
53
 
54
54
  'zstandard ~= 0.25; python_version < "3.14"',
55
55
 
56
- 'brotli ~= 1.1',
56
+ 'brotli ~= 1.2',
57
57
  ],
58
58
 
59
59
  'diag': [
@@ -79,11 +79,11 @@ class Project(ProjectBase):
79
79
  ],
80
80
 
81
81
  'misc': [
82
- 'wrapt ~= 1.17',
82
+ 'wrapt ~= 2.0',
83
83
  ],
84
84
 
85
85
  'secrets': [
86
- 'cryptography ~= 45.0',
86
+ 'cryptography ~= 46.0',
87
87
  ],
88
88
 
89
89
  'sqlalchemy': [
@@ -92,18 +92,18 @@ class Project(ProjectBase):
92
92
 
93
93
  'sqldrivers': [
94
94
  'pg8000 ~= 1.31',
95
- # 'psycopg2 ~= 2.9',
95
+ # 'psycopg2 ~= 3.3',
96
96
  # 'psycopg ~= 3.2',
97
97
 
98
98
  'pymysql ~= 1.1',
99
- # 'mysql-connector-python ~= 9.3',
99
+ # 'mysql-connector-python ~= 9.5',
100
100
  # 'mysqlclient ~= 2.2',
101
101
 
102
- 'aiomysql ~= 0.2',
102
+ 'aiomysql ~= 0.3',
103
103
  'aiosqlite ~= 0.21',
104
- 'asyncpg ~= 0.30',
104
+ 'asyncpg ~= 0.31',
105
105
 
106
- 'apsw ~= 3.50',
106
+ 'apsw ~= 3.51',
107
107
 
108
108
  'sqlean.py ~= 3.50',
109
109
 
@@ -117,7 +117,7 @@ class Project(ProjectBase):
117
117
  ],
118
118
 
119
119
  'testing': [
120
- 'pytest ~= 8.4',
120
+ 'pytest ~= 9.0',
121
121
  ],
122
122
  }
123
123
 
@@ -185,7 +185,7 @@ class SetuptoolsBase:
185
185
 
186
186
 
187
187
  class Setuptools(SetuptoolsBase):
188
- cexts = True
188
+ cext = True
189
189
 
190
190
  find_packages = {
191
191
  'include': [Project.name, f'{Project.name}.*'],
omlish/argparse/all.py CHANGED
@@ -1,17 +1,25 @@
1
1
  # ruff: noqa: I001
2
2
  import argparse
3
3
 
4
- from .cli import ( # noqa
5
- ArgparseArg as Arg,
6
- argparse_arg as arg,
7
- argparse_arg_ as arg_,
4
+ from .. import lang as _lang
8
5
 
9
- ArgparseCmdFn as CmdFn,
10
- ArgparseCmd as Cmd,
11
- argparse_cmd as cmd,
12
6
 
13
- ArgparseCli as Cli,
14
- )
7
+ with _lang.auto_proxy_init(globals()):
8
+ from .cli import ( # noqa
9
+ ArgparseArg as Arg,
10
+ argparse_arg as arg,
11
+ argparse_arg_ as arg_,
12
+
13
+ ArgparseCmdFn as CmdFn,
14
+ ArgparseCmd as Cmd,
15
+ argparse_cmd as cmd,
16
+
17
+ ArgparseCli as Cli,
18
+ )
19
+
20
+ from .utils import ( # noqa
21
+ NoExitArgumentParser,
22
+ )
15
23
 
16
24
 
17
25
  ##
omlish/argparse/cli.py CHANGED
@@ -10,6 +10,7 @@ TODO:
10
10
  - pre-run, post-run hooks
11
11
  - exitstack?
12
12
  - suggestion - difflib.get_close_matches
13
+ - add_argument_group - group kw on ArgparseKwarg?
13
14
  """
14
15
  import argparse
15
16
  import dataclasses as dc
@@ -36,6 +37,7 @@ ArgparseCmdFn = ta.Callable[[], ta.Optional[int]] # ta.TypeAlias
36
37
  class ArgparseArg:
37
38
  args: ta.Sequence[ta.Any]
38
39
  kwargs: ta.Mapping[str, ta.Any]
40
+ group: ta.Optional[str] = None
39
41
  dest: ta.Optional[str] = None
40
42
 
41
43
  def __get__(self, instance, owner=None):
@@ -45,7 +47,11 @@ class ArgparseArg:
45
47
 
46
48
 
47
49
  def argparse_arg(*args, **kwargs) -> ArgparseArg:
48
- return ArgparseArg(args, kwargs)
50
+ return ArgparseArg(
51
+ args=args,
52
+ group=kwargs.pop('group', None),
53
+ kwargs=kwargs,
54
+ )
49
55
 
50
56
 
51
57
  def argparse_arg_(*args, **kwargs) -> ta.Any:
@@ -215,6 +221,10 @@ class ArgparseCli:
215
221
  subparser.set_defaults(_cmd=obj)
216
222
 
217
223
  elif isinstance(obj, ArgparseArg):
224
+ if obj.group is not None:
225
+ # FIXME: add_argument_group
226
+ raise NotImplementedError
227
+
218
228
  if att in anns:
219
229
  ann_kwargs = _get_argparse_arg_ann_kwargs(anns[att])
220
230
  obj.kwargs = {**ann_kwargs, **obj.kwargs}
@@ -260,7 +270,7 @@ class ArgparseCli:
260
270
 
261
271
  if self._unknown_args and not (cmd is not None and cmd.accepts_unknown):
262
272
  msg = f'unrecognized arguments: {" ".join(self._unknown_args)}'
263
- if (parser := self.get_parser()).exit_on_error:
273
+ if (parser := self.get_parser()).exit_on_error: # noqa
264
274
  parser.error(msg)
265
275
  else:
266
276
  raise argparse.ArgumentError(None, msg)
@@ -280,7 +290,10 @@ class ArgparseCli:
280
290
  return fn()
281
291
 
282
292
  def cli_run_and_exit(self) -> ta.NoReturn:
283
- sys.exit(rc if isinstance(rc := self.cli_run(), int) else 0)
293
+ rc = self.cli_run()
294
+ if not isinstance(rc, int):
295
+ rc = 0
296
+ raise SystemExit(rc)
284
297
 
285
298
  def __call__(self, *, exit: bool = False) -> ta.Optional[int]: # noqa
286
299
  if exit:
@@ -0,0 +1,21 @@
1
+ import argparse
2
+ import typing as ta
3
+
4
+
5
+ ##
6
+
7
+
8
+ class NoExitArgumentParser(argparse.ArgumentParser):
9
+ def __init__(
10
+ self,
11
+ *args: ta.Any,
12
+ exit_on_error: bool = False,
13
+ **kwargs: ta.Any,
14
+ ) -> None:
15
+ if exit_on_error:
16
+ raise TypeError('exit_on_error=True not supported')
17
+
18
+ super().__init__(*args, exit_on_error=False, **kwargs) # type: ignore[misc]
19
+
20
+ def exit(self, status=0, message=None):
21
+ raise TypeError('NoExitArgumentParser.exit() not supported')
@@ -0,0 +1,110 @@
1
+ # MIT License
2
+ #
3
+ # Copyright (c) 2023 Joshua George Albert
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
6
+ # documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
7
+ # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
8
+ # persons to whom the Software is furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
11
+ # Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
14
+ # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
15
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
16
+ # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
17
+ #
18
+ # https://github.com/Joshuaalbert/FairAsyncRLock/blob/7c3ed4e08892638dba262eaf0b1a7b1fad4b2608/fair_async_rlock/fair_async_rlock.py
19
+ import asyncio
20
+ import collections
21
+ import typing as ta
22
+
23
+
24
+ ##
25
+
26
+
27
+ class AsyncioRLock:
28
+ def __init__(self) -> None:
29
+ super().__init__()
30
+
31
+ self._owner: asyncio.Task | None = None
32
+ self._count = 0
33
+ self._owner_transfer = False
34
+ self._queue: collections.deque[asyncio.Event] = collections.deque()
35
+
36
+ def is_owner(self, task: asyncio.Task | None = None) -> bool:
37
+ if task is None:
38
+ task = asyncio.current_task()
39
+ return self._owner == task
40
+
41
+ def is_locked(self) -> bool:
42
+ return self._owner is not None
43
+
44
+ async def acquire(self) -> None:
45
+ me = asyncio.current_task()
46
+
47
+ # If the lock is reentrant, acquire it immediately
48
+ if self.is_owner(task=me):
49
+ self._count += 1
50
+ return
51
+
52
+ # If the lock is free (and ownership not in midst of transfer), acquire it immediately
53
+ if not self._count and not self._owner_transfer:
54
+ self._owner = me
55
+ self._count = 1
56
+ return
57
+
58
+ # Create an event for this task, to notify when it's ready for acquire
59
+ event = asyncio.Event()
60
+ self._queue.append(event)
61
+
62
+ # Wait for the lock to be free, then acquire
63
+ try:
64
+ await event.wait()
65
+ self._owner_transfer = False
66
+ self._owner = me
67
+ self._count = 1
68
+
69
+ except asyncio.CancelledError:
70
+ try: # if in queue, then cancelled before release
71
+ self._queue.remove(event)
72
+
73
+ except ValueError: # otherwise, release happened, this was next, and we simulate passing on
74
+ self._owner_transfer = False
75
+ self._owner = me
76
+ self._count = 1
77
+ self._current_task_release()
78
+
79
+ raise
80
+
81
+ def _current_task_release(self) -> None:
82
+ self._count -= 1
83
+ if not self._count:
84
+ self._owner = None
85
+
86
+ if self._queue:
87
+ # Wake up the next task in the queue
88
+ event = self._queue.popleft()
89
+ event.set()
90
+
91
+ # Setting this here prevents another task getting lock until owner transfer.
92
+ self._owner_transfer = True
93
+
94
+ def release(self) -> None:
95
+ me = asyncio.current_task()
96
+
97
+ if self._owner is None:
98
+ raise RuntimeError(f'Cannot release un-acquired lock. {me} tried to release.')
99
+
100
+ if not self.is_owner(task=me):
101
+ raise RuntimeError(f'Cannot release foreign lock. {me} tried to unlock {self._owner}.')
102
+
103
+ self._current_task_release()
104
+
105
+ async def __aenter__(self) -> ta.Self:
106
+ await self.acquire()
107
+ return self
108
+
109
+ async def __aexit__(self, exc_type, exc, tb):
110
+ self.release()
@@ -0,0 +1,43 @@
1
+ # ruff: noqa: UP045
2
+ # @omlish-lite
3
+ import asyncio
4
+ import typing as ta
5
+
6
+ from ...sync import SyncBufferRelay
7
+ from ..sync import AsyncBufferRelay
8
+
9
+
10
+ T = ta.TypeVar('T')
11
+
12
+
13
+ ##
14
+
15
+
16
+ @ta.final
17
+ class AsyncioBufferRelay(AsyncBufferRelay[T]):
18
+ def __init__(
19
+ self,
20
+ *,
21
+ event: ta.Optional[asyncio.Event] = None,
22
+ loop: ta.Optional[ta.Any] = None,
23
+ ) -> None:
24
+ if event is None:
25
+ event = asyncio.Event()
26
+ self._event = event
27
+ if loop is None:
28
+ loop = asyncio.get_running_loop()
29
+ self._loop = loop
30
+
31
+ self._relay: SyncBufferRelay[T] = SyncBufferRelay(
32
+ wake_fn=lambda: loop.call_soon_threadsafe(event.set), # type: ignore[arg-type]
33
+ )
34
+
35
+ def push(self, *vs: T) -> None:
36
+ self._relay.push(*vs)
37
+
38
+ def swap(self) -> ta.Sequence[T]:
39
+ return self._relay.swap()
40
+
41
+ async def wait(self) -> None:
42
+ await self._event.wait()
43
+ self._event.clear()
@@ -76,6 +76,8 @@ async def asyncio_wait_concurrent(
76
76
  if isinstance(concurrency, asyncio.Semaphore):
77
77
  semaphore = concurrency
78
78
  elif isinstance(concurrency, int):
79
+ if concurrency < 1:
80
+ raise ValueError(f'Concurrency must be >= 1, got {concurrency}')
79
81
  semaphore = asyncio.Semaphore(concurrency)
80
82
  else:
81
83
  raise TypeError(concurrency)
omlish/asyncs/sync.py ADDED
@@ -0,0 +1,25 @@
1
+ # @omlish-lite
2
+ import abc
3
+ import typing as ta
4
+
5
+ from ..lite.abstract import Abstract
6
+
7
+
8
+ T = ta.TypeVar('T')
9
+
10
+
11
+ ##
12
+
13
+
14
+ class AsyncBufferRelay(Abstract, ta.Generic[T]):
15
+ @abc.abstractmethod
16
+ def push(self, *vs: T) -> None:
17
+ raise NotImplementedError
18
+
19
+ @abc.abstractmethod
20
+ def swap(self) -> ta.Sequence[T]:
21
+ raise NotImplementedError
22
+
23
+ @abc.abstractmethod
24
+ async def wait(self) -> None:
25
+ raise NotImplementedError
@@ -8,7 +8,7 @@ from .harness import BOOTSTRAP_TYPES_BY_NAME
8
8
 
9
9
 
10
10
  @lang.static_init
11
- def _install_standard_marshalling() -> None:
11
+ def _install_standard_marshaling() -> None:
12
12
  cfgs_poly = msh.Polymorphism(
13
13
  Bootstrap.Config,
14
14
  [msh.Impl(b.Config, n) for n, b in BOOTSTRAP_TYPES_BY_NAME.items()],
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,