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.
- omlish/.omlish-manifests.json +12 -0
- omlish/__about__.py +15 -15
- omlish/argparse/all.py +17 -9
- omlish/argparse/cli.py +16 -3
- omlish/argparse/utils.py +21 -0
- omlish/asyncs/asyncio/rlock.py +110 -0
- omlish/asyncs/asyncio/sync.py +43 -0
- omlish/asyncs/asyncio/utils.py +2 -0
- omlish/asyncs/sync.py +25 -0
- omlish/bootstrap/_marshal.py +1 -1
- omlish/bootstrap/diag.py +12 -21
- omlish/bootstrap/main.py +2 -5
- omlish/bootstrap/sys.py +27 -28
- omlish/cexts/__init__.py +0 -0
- omlish/cexts/include/omlish/omlish.hh +1 -0
- omlish/collections/__init__.py +13 -1
- omlish/collections/attrregistry.py +210 -0
- omlish/collections/cache/impl.py +1 -0
- omlish/collections/identity.py +1 -0
- omlish/collections/mappings.py +28 -0
- omlish/collections/trie.py +5 -1
- omlish/collections/utils.py +77 -0
- omlish/concurrent/all.py +2 -1
- omlish/concurrent/futures.py +25 -0
- omlish/concurrent/threadlets.py +1 -1
- omlish/daemons/reparent.py +2 -3
- omlish/daemons/spawning.py +2 -3
- omlish/dataclasses/__init__.py +2 -0
- omlish/dataclasses/impl/api/classes/decorator.py +3 -0
- omlish/dataclasses/impl/api/classes/make.py +3 -0
- omlish/dataclasses/impl/concerns/repr.py +15 -2
- omlish/dataclasses/impl/configs.py +97 -37
- omlish/dataclasses/impl/generation/compilation.py +21 -19
- omlish/dataclasses/impl/generation/globals.py +1 -0
- omlish/dataclasses/impl/generation/ops.py +1 -0
- omlish/dataclasses/impl/generation/processor.py +105 -24
- omlish/dataclasses/impl/processing/base.py +8 -0
- omlish/dataclasses/impl/processing/driving.py +8 -8
- omlish/dataclasses/specs.py +1 -0
- omlish/dataclasses/tools/modifiers.py +5 -0
- omlish/diag/cmds/__init__.py +0 -0
- omlish/diag/{lslocks.py → cmds/lslocks.py} +6 -6
- omlish/diag/{lsof.py → cmds/lsof.py} +6 -6
- omlish/diag/{ps.py → cmds/ps.py} +6 -6
- omlish/diag/pycharm.py +16 -2
- omlish/diag/pydevd.py +58 -40
- omlish/diag/replserver/console.py +1 -1
- omlish/dispatch/__init__.py +18 -12
- omlish/dispatch/methods.py +50 -140
- omlish/dom/rendering.py +1 -1
- omlish/formats/dotenv.py +1 -1
- omlish/formats/json/stream/__init__.py +13 -0
- omlish/funcs/guard.py +225 -0
- omlish/graphs/dot/rendering.py +1 -1
- omlish/http/all.py +44 -4
- omlish/http/clients/asyncs.py +33 -27
- omlish/http/clients/base.py +17 -1
- omlish/http/clients/coro/__init__.py +0 -0
- omlish/http/clients/coro/sync.py +171 -0
- omlish/http/clients/default.py +208 -29
- omlish/http/clients/executor.py +56 -0
- omlish/http/clients/httpx.py +72 -11
- omlish/http/clients/middleware.py +181 -0
- omlish/http/clients/sync.py +33 -27
- omlish/http/clients/syncasync.py +49 -0
- omlish/http/clients/urllib.py +6 -3
- omlish/http/coro/client/connection.py +15 -6
- omlish/http/coro/io.py +2 -0
- omlish/http/flasky/__init__.py +40 -0
- omlish/http/flasky/_compat.py +2 -0
- omlish/http/flasky/api.py +82 -0
- omlish/http/flasky/app.py +203 -0
- omlish/http/flasky/cvs.py +59 -0
- omlish/http/flasky/requests.py +20 -0
- omlish/http/flasky/responses.py +23 -0
- omlish/http/flasky/routes.py +23 -0
- omlish/http/flasky/types.py +15 -0
- omlish/http/urls.py +67 -0
- omlish/inject/__init__.py +38 -18
- omlish/inject/_dataclasses.py +4986 -0
- omlish/inject/binder.py +4 -48
- omlish/inject/elements.py +27 -0
- omlish/inject/helpers/__init__.py +0 -0
- omlish/inject/{utils.py → helpers/constfn.py} +3 -3
- omlish/inject/{tags.py → helpers/id.py} +2 -2
- omlish/inject/helpers/multis.py +143 -0
- omlish/inject/helpers/wrappers.py +54 -0
- omlish/inject/impl/elements.py +47 -17
- omlish/inject/impl/injector.py +20 -19
- omlish/inject/impl/inspect.py +10 -1
- omlish/inject/impl/maysync.py +3 -4
- omlish/inject/impl/multis.py +3 -0
- omlish/inject/impl/sync.py +3 -4
- omlish/inject/injector.py +31 -2
- omlish/inject/inspect.py +35 -0
- omlish/inject/maysync.py +2 -4
- omlish/inject/multis.py +8 -0
- omlish/inject/overrides.py +3 -3
- omlish/inject/privates.py +6 -0
- omlish/inject/providers.py +3 -2
- omlish/inject/sync.py +5 -4
- omlish/io/buffers.py +118 -0
- omlish/io/readers.py +29 -0
- omlish/iterators/transforms.py +2 -2
- omlish/lang/__init__.py +178 -97
- omlish/lang/_asyncs.cc +186 -0
- omlish/lang/asyncs.py +17 -0
- omlish/lang/casing.py +11 -0
- omlish/lang/contextmanagers.py +28 -4
- omlish/lang/functions.py +31 -22
- omlish/lang/imports/_capture.cc +11 -9
- omlish/lang/imports/capture.py +363 -170
- omlish/lang/imports/proxy.py +455 -152
- omlish/lang/lazyglobals.py +22 -9
- omlish/lang/params.py +17 -0
- omlish/lang/recursion.py +0 -1
- omlish/lang/sequences.py +124 -0
- omlish/lite/abstract.py +54 -24
- omlish/lite/asyncs.py +2 -2
- omlish/lite/attrops.py +2 -0
- omlish/lite/contextmanagers.py +4 -4
- omlish/lite/dataclasses.py +44 -0
- omlish/lite/maybes.py +8 -0
- omlish/lite/maysync.py +1 -5
- omlish/lite/pycharm.py +1 -1
- omlish/lite/typing.py +6 -0
- omlish/logs/all.py +1 -1
- omlish/logs/utils.py +1 -1
- omlish/manifests/loading.py +2 -1
- omlish/marshal/__init__.py +33 -13
- omlish/marshal/_dataclasses.py +2774 -0
- omlish/marshal/base/configs.py +12 -0
- omlish/marshal/base/contexts.py +36 -21
- omlish/marshal/base/funcs.py +8 -11
- omlish/marshal/base/options.py +8 -0
- omlish/marshal/base/registries.py +146 -44
- omlish/marshal/base/types.py +40 -16
- omlish/marshal/composite/iterables.py +33 -20
- omlish/marshal/composite/literals.py +20 -18
- omlish/marshal/composite/mappings.py +36 -23
- omlish/marshal/composite/maybes.py +29 -19
- omlish/marshal/composite/newtypes.py +16 -16
- omlish/marshal/composite/optionals.py +14 -14
- omlish/marshal/composite/special.py +15 -15
- omlish/marshal/composite/unions/__init__.py +0 -0
- omlish/marshal/composite/unions/literals.py +93 -0
- omlish/marshal/composite/unions/primitives.py +103 -0
- omlish/marshal/factories/invalidate.py +18 -68
- omlish/marshal/factories/method.py +26 -0
- omlish/marshal/factories/moduleimport/factories.py +22 -65
- omlish/marshal/factories/multi.py +13 -25
- omlish/marshal/factories/recursive.py +42 -56
- omlish/marshal/factories/typecache.py +29 -74
- omlish/marshal/factories/typemap.py +42 -43
- omlish/marshal/objects/dataclasses.py +129 -106
- omlish/marshal/objects/marshal.py +18 -14
- omlish/marshal/objects/namedtuples.py +48 -42
- omlish/marshal/objects/unmarshal.py +19 -15
- omlish/marshal/polymorphism/marshal.py +9 -11
- omlish/marshal/polymorphism/metadata.py +16 -5
- omlish/marshal/polymorphism/standard.py +13 -1
- omlish/marshal/polymorphism/unions.py +15 -105
- omlish/marshal/polymorphism/unmarshal.py +9 -10
- omlish/marshal/singular/enums.py +14 -18
- omlish/marshal/standard.py +10 -6
- omlish/marshal/trivial/any.py +1 -1
- omlish/marshal/trivial/forbidden.py +21 -26
- omlish/metadata.py +23 -1
- omlish/os/forkhooks.py +4 -0
- omlish/os/pidfiles/pinning.py +2 -2
- omlish/reflect/types.py +1 -0
- omlish/secrets/marshal.py +1 -1
- omlish/specs/jmespath/__init__.py +12 -3
- omlish/specs/jmespath/_dataclasses.py +2893 -0
- omlish/specs/jmespath/ast.py +1 -1
- omlish/specs/jsonrpc/__init__.py +13 -0
- omlish/specs/jsonrpc/_marshal.py +32 -23
- omlish/specs/jsonrpc/conns.py +10 -7
- omlish/specs/jsonschema/_marshal.py +1 -1
- omlish/specs/jsonschema/keywords/__init__.py +7 -0
- omlish/specs/jsonschema/keywords/_dataclasses.py +1644 -0
- omlish/specs/openapi/_marshal.py +31 -22
- omlish/sql/{tabledefs/alchemy.py → alchemy/tabledefs.py} +2 -2
- omlish/sql/queries/_marshal.py +2 -2
- omlish/sql/queries/rendering.py +1 -1
- omlish/sql/tabledefs/_marshal.py +1 -1
- omlish/subprocesses/base.py +4 -0
- omlish/subprocesses/editor.py +1 -1
- omlish/sync.py +155 -21
- omlish/term/alt.py +60 -0
- omlish/term/confirm.py +8 -8
- omlish/term/pager.py +235 -0
- omlish/term/terminfo.py +935 -0
- omlish/term/termstate.py +67 -0
- omlish/term/vt100/terminal.py +0 -3
- omlish/testing/pytest/plugins/asyncs/fixtures.py +4 -1
- omlish/testing/pytest/plugins/skips.py +2 -1
- omlish/testing/unittest/main.py +3 -3
- omlish/text/docwrap/__init__.py +3 -0
- omlish/text/docwrap/__main__.py +11 -0
- omlish/text/docwrap/api.py +83 -0
- omlish/text/docwrap/cli.py +86 -0
- omlish/text/docwrap/groups.py +84 -0
- omlish/text/docwrap/lists.py +167 -0
- omlish/text/docwrap/parts.py +146 -0
- omlish/text/docwrap/reflowing.py +106 -0
- omlish/text/docwrap/rendering.py +151 -0
- omlish/text/docwrap/utils.py +11 -0
- omlish/text/docwrap/wrapping.py +59 -0
- omlish/text/filecache.py +2 -2
- omlish/text/lorem.py +6 -0
- omlish/text/parts.py +2 -2
- omlish/text/textwrap.py +51 -0
- omlish/typedvalues/marshal.py +85 -59
- omlish/typedvalues/values.py +2 -1
- {omlish-0.0.0.dev447.dist-info → omlish-0.0.0.dev484.dist-info}/METADATA +29 -28
- {omlish-0.0.0.dev447.dist-info → omlish-0.0.0.dev484.dist-info}/RECORD +222 -171
- omlish/dataclasses/impl/generation/mangling.py +0 -18
- omlish/funcs/match.py +0 -227
- omlish/marshal/factories/match.py +0 -34
- omlish/marshal/factories/simple.py +0 -28
- /omlish/inject/impl/{providers2.py → providersmap.py} +0 -0
- {omlish-0.0.0.dev447.dist-info → omlish-0.0.0.dev484.dist-info}/WHEEL +0 -0
- {omlish-0.0.0.dev447.dist-info → omlish-0.0.0.dev484.dist-info}/entry_points.txt +0 -0
- {omlish-0.0.0.dev447.dist-info → omlish-0.0.0.dev484.dist-info}/licenses/LICENSE +0 -0
- {omlish-0.0.0.dev447.dist-info → omlish-0.0.0.dev484.dist-info}/top_level.txt +0 -0
omlish/.omlish-manifests.json
CHANGED
|
@@ -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.
|
|
2
|
-
__revision__ = '
|
|
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.
|
|
39
|
+
'anyio ~= 4.11',
|
|
40
40
|
'sniffio ~= 1.3',
|
|
41
41
|
|
|
42
|
-
'greenlet ~= 3.
|
|
42
|
+
'greenlet ~= 3.3',
|
|
43
43
|
|
|
44
|
-
'trio ~= 0.
|
|
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.
|
|
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 ~=
|
|
82
|
+
'wrapt ~= 2.0',
|
|
83
83
|
],
|
|
84
84
|
|
|
85
85
|
'secrets': [
|
|
86
|
-
'cryptography ~=
|
|
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 ~=
|
|
95
|
+
# 'psycopg2 ~= 3.3',
|
|
96
96
|
# 'psycopg ~= 3.2',
|
|
97
97
|
|
|
98
98
|
'pymysql ~= 1.1',
|
|
99
|
-
# 'mysql-connector-python ~= 9.
|
|
99
|
+
# 'mysql-connector-python ~= 9.5',
|
|
100
100
|
# 'mysqlclient ~= 2.2',
|
|
101
101
|
|
|
102
|
-
'aiomysql ~= 0.
|
|
102
|
+
'aiomysql ~= 0.3',
|
|
103
103
|
'aiosqlite ~= 0.21',
|
|
104
|
-
'asyncpg ~= 0.
|
|
104
|
+
'asyncpg ~= 0.31',
|
|
105
105
|
|
|
106
|
-
'apsw ~= 3.
|
|
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 ~=
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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:
|
omlish/argparse/utils.py
ADDED
|
@@ -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()
|
omlish/asyncs/asyncio/utils.py
CHANGED
|
@@ -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
|
omlish/bootstrap/_marshal.py
CHANGED
|
@@ -8,7 +8,7 @@ from .harness import BOOTSTRAP_TYPES_BY_NAME
|
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
@lang.static_init
|
|
11
|
-
def
|
|
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
|
-
|
|
16
|
+
with lang.auto_proxy_import(globals()):
|
|
17
17
|
import cProfile # noqa
|
|
18
18
|
import pstats
|
|
19
19
|
|
|
20
|
-
from ..diag import debug as
|
|
21
|
-
from ..diag import pycharm as
|
|
22
|
-
from ..diag import replserver
|
|
23
|
-
from ..diag import threads as
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
177
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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 =
|
|
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
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
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 =
|
|
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
|
|
omlish/cexts/__init__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#pragma once
|
omlish/collections/__init__.py
CHANGED
|
@@ -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
|
-
|
|
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,
|