omlish 0.0.0.dev5__py3-none-any.whl → 0.0.0.dev7__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/__about__.py +109 -5
- omlish/__init__.py +0 -8
- omlish/asyncs/__init__.py +9 -9
- omlish/asyncs/anyio.py +123 -19
- omlish/asyncs/asyncio.py +23 -0
- omlish/asyncs/asyncs.py +9 -6
- omlish/asyncs/bridge.py +316 -0
- omlish/asyncs/trio_asyncio.py +7 -3
- omlish/bootstrap.py +737 -0
- omlish/check.py +1 -1
- omlish/collections/__init__.py +5 -0
- omlish/collections/exceptions.py +2 -0
- omlish/collections/identity.py +7 -0
- omlish/collections/utils.py +38 -9
- omlish/configs/strings.py +96 -0
- omlish/dataclasses/__init__.py +16 -0
- omlish/dataclasses/impl/copy.py +30 -0
- omlish/dataclasses/impl/descriptors.py +95 -0
- omlish/dataclasses/impl/exceptions.py +6 -0
- omlish/dataclasses/impl/fields.py +24 -25
- omlish/dataclasses/impl/init.py +4 -2
- omlish/dataclasses/impl/main.py +2 -0
- omlish/dataclasses/impl/reflect.py +1 -1
- omlish/dataclasses/utils.py +67 -0
- omlish/{lang/datetimes.py → datetimes.py} +8 -4
- omlish/diag/__init__.py +4 -0
- omlish/diag/procfs.py +2 -2
- omlish/{testing → diag}/pydevd.py +35 -0
- omlish/diag/threads.py +131 -48
- omlish/dispatch/_dispatch2.py +65 -0
- omlish/dispatch/_dispatch3.py +104 -0
- omlish/docker.py +16 -1
- omlish/fnpairs.py +11 -4
- omlish/formats/__init__.py +0 -0
- omlish/{configs → formats}/dotenv.py +15 -24
- omlish/{json.py → formats/json.py} +2 -1
- omlish/formats/yaml.py +223 -0
- omlish/graphs/trees.py +1 -1
- omlish/http/asgi.py +2 -1
- omlish/http/collections.py +15 -0
- omlish/http/consts.py +22 -1
- omlish/http/sessions.py +10 -3
- omlish/inject/__init__.py +49 -17
- omlish/inject/binder.py +185 -5
- omlish/inject/bindings.py +3 -36
- omlish/inject/eagers.py +2 -8
- omlish/inject/elements.py +31 -10
- omlish/inject/exceptions.py +1 -1
- omlish/inject/impl/elements.py +37 -12
- omlish/inject/impl/injector.py +72 -25
- omlish/inject/impl/inspect.py +33 -5
- omlish/inject/impl/origins.py +77 -0
- omlish/inject/impl/{private.py → privates.py} +2 -2
- omlish/inject/impl/scopes.py +6 -2
- omlish/inject/injector.py +8 -4
- omlish/inject/inspect.py +18 -0
- omlish/inject/keys.py +8 -14
- omlish/inject/listeners.py +26 -0
- omlish/inject/managed.py +76 -10
- omlish/inject/multis.py +68 -18
- omlish/inject/origins.py +30 -0
- omlish/inject/overrides.py +5 -4
- omlish/inject/{private.py → privates.py} +6 -10
- omlish/inject/providers.py +12 -85
- omlish/inject/scopes.py +13 -6
- omlish/inject/types.py +3 -1
- omlish/inject/utils.py +18 -0
- omlish/iterators.py +69 -2
- omlish/lang/__init__.py +24 -9
- omlish/lang/cached.py +2 -2
- omlish/lang/classes/restrict.py +12 -1
- omlish/lang/classes/simple.py +18 -8
- omlish/lang/contextmanagers.py +13 -4
- omlish/lang/descriptors.py +132 -1
- omlish/lang/functions.py +8 -28
- omlish/lang/imports.py +67 -0
- omlish/lang/iterables.py +60 -1
- omlish/lang/maybes.py +3 -0
- omlish/lang/objects.py +38 -0
- omlish/lang/strings.py +25 -0
- omlish/lang/sys.py +9 -0
- omlish/lang/typing.py +42 -0
- omlish/lifecycles/__init__.py +34 -0
- omlish/lifecycles/abstract.py +43 -0
- omlish/lifecycles/base.py +51 -0
- omlish/lifecycles/contextmanagers.py +74 -0
- omlish/lifecycles/controller.py +116 -0
- omlish/lifecycles/manager.py +161 -0
- omlish/lifecycles/states.py +43 -0
- omlish/lifecycles/transitions.py +64 -0
- omlish/lite/__init__.py +1 -0
- omlish/lite/cached.py +18 -0
- omlish/lite/check.py +29 -0
- omlish/lite/contextmanagers.py +18 -0
- omlish/lite/json.py +30 -0
- omlish/lite/logs.py +52 -0
- omlish/lite/marshal.py +316 -0
- omlish/lite/reflect.py +49 -0
- omlish/lite/runtime.py +18 -0
- omlish/lite/secrets.py +19 -0
- omlish/lite/strings.py +25 -0
- omlish/lite/subprocesses.py +112 -0
- omlish/logs/configs.py +15 -2
- omlish/logs/formatters.py +7 -2
- omlish/marshal/__init__.py +32 -0
- omlish/marshal/any.py +5 -5
- omlish/marshal/base.py +27 -11
- omlish/marshal/base64.py +24 -9
- omlish/marshal/dataclasses.py +34 -28
- omlish/marshal/datetimes.py +74 -18
- omlish/marshal/enums.py +14 -8
- omlish/marshal/exceptions.py +11 -1
- omlish/marshal/factories.py +59 -74
- omlish/marshal/forbidden.py +35 -0
- omlish/marshal/global_.py +11 -4
- omlish/marshal/iterables.py +21 -24
- omlish/marshal/mappings.py +23 -26
- omlish/marshal/naming.py +4 -0
- omlish/marshal/numbers.py +51 -0
- omlish/marshal/objects.py +1 -0
- omlish/marshal/optionals.py +11 -12
- omlish/marshal/polymorphism.py +86 -21
- omlish/marshal/primitives.py +4 -5
- omlish/marshal/standard.py +13 -8
- omlish/marshal/uuids.py +4 -5
- omlish/matchfns.py +218 -0
- omlish/os.py +64 -0
- omlish/reflect/__init__.py +39 -0
- omlish/reflect/isinstance.py +38 -0
- omlish/reflect/ops.py +84 -0
- omlish/reflect/subst.py +110 -0
- omlish/reflect/types.py +275 -0
- omlish/secrets/__init__.py +23 -0
- omlish/secrets/crypto.py +132 -0
- omlish/secrets/marshal.py +70 -0
- omlish/secrets/openssl.py +207 -0
- omlish/secrets/passwords.py +120 -0
- omlish/secrets/secrets.py +299 -0
- omlish/secrets/subprocesses.py +42 -0
- omlish/sql/dbs.py +7 -6
- omlish/sql/duckdb.py +136 -0
- omlish/sql/exprs.py +12 -0
- omlish/sql/secrets.py +10 -0
- omlish/sql/sqlean.py +17 -0
- omlish/term.py +2 -2
- omlish/testing/pytest/__init__.py +3 -2
- omlish/testing/pytest/inject/harness.py +3 -3
- omlish/testing/pytest/marks.py +4 -7
- omlish/testing/pytest/plugins/__init__.py +1 -0
- omlish/testing/pytest/plugins/asyncs.py +136 -0
- omlish/testing/pytest/plugins/pydevd.py +1 -1
- omlish/testing/pytest/plugins/switches.py +54 -19
- omlish/text/glyphsplit.py +97 -0
- omlish-0.0.0.dev7.dist-info/METADATA +50 -0
- omlish-0.0.0.dev7.dist-info/RECORD +268 -0
- {omlish-0.0.0.dev5.dist-info → omlish-0.0.0.dev7.dist-info}/WHEEL +1 -1
- omlish/reflect.py +0 -355
- omlish-0.0.0.dev5.dist-info/METADATA +0 -34
- omlish-0.0.0.dev5.dist-info/RECORD +0 -212
- /omlish/{asyncs/futures.py → concurrent.py} +0 -0
- /omlish/{configs → formats}/props.py +0 -0
- {omlish-0.0.0.dev5.dist-info → omlish-0.0.0.dev7.dist-info}/LICENSE +0 -0
- {omlish-0.0.0.dev5.dist-info → omlish-0.0.0.dev7.dist-info}/top_level.txt +0 -0
omlish/__about__.py
CHANGED
|
@@ -1,6 +1,110 @@
|
|
|
1
|
-
|
|
2
|
-
__url__ = 'https://github.com/wrmsr/omlish'
|
|
3
|
-
__license__ = 'BSD-3-Clause'
|
|
4
|
-
__requires_python__ = '>=3.12'
|
|
1
|
+
__version__ = '0.0.0.dev7'
|
|
5
2
|
|
|
6
|
-
|
|
3
|
+
|
|
4
|
+
#
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ProjectBase:
|
|
8
|
+
name: str | None = None
|
|
9
|
+
authors = [{'name': 'wrmsr'}]
|
|
10
|
+
urls = {'source': 'https://github.com/wrmsr/omlish'}
|
|
11
|
+
license = {'text': 'BSD-3-Clause'}
|
|
12
|
+
requires_python = '>=3.12'
|
|
13
|
+
|
|
14
|
+
version = __version__
|
|
15
|
+
|
|
16
|
+
classifiers = [
|
|
17
|
+
'License :: OSI Approved :: BSD License',
|
|
18
|
+
'Development Status :: 2 - Pre-Alpha',
|
|
19
|
+
'Intended Audience :: Developers',
|
|
20
|
+
|
|
21
|
+
'Operating System :: OS Independent',
|
|
22
|
+
'Operating System :: POSIX',
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Project(ProjectBase):
|
|
27
|
+
name = 'omlish'
|
|
28
|
+
description = 'omlish'
|
|
29
|
+
|
|
30
|
+
optional_dependencies = {
|
|
31
|
+
'async': [
|
|
32
|
+
'anyio >= 4.4',
|
|
33
|
+
'sniffio >= 1.3',
|
|
34
|
+
|
|
35
|
+
'greenlet >= 3; python_version < "3.13"',
|
|
36
|
+
|
|
37
|
+
'trio >= 0.26',
|
|
38
|
+
'trio-asyncio >= 0.15; python_version < "3.13"',
|
|
39
|
+
],
|
|
40
|
+
|
|
41
|
+
'compression': [
|
|
42
|
+
'lz4 >= 4',
|
|
43
|
+
'python-snappy >= 0.7; python_version < "3.13"',
|
|
44
|
+
'zstd >= 1.5',
|
|
45
|
+
],
|
|
46
|
+
|
|
47
|
+
'formats': [
|
|
48
|
+
'orjson > 3.10',
|
|
49
|
+
'cloudpickle >= 3',
|
|
50
|
+
'pyyaml >= 5',
|
|
51
|
+
],
|
|
52
|
+
|
|
53
|
+
'http': [
|
|
54
|
+
'httpx[http2] >= 0.27',
|
|
55
|
+
],
|
|
56
|
+
|
|
57
|
+
'misc': [
|
|
58
|
+
'jinja2 >= 3.1',
|
|
59
|
+
'psutil >= 6',
|
|
60
|
+
'wrapt >= 1.14',
|
|
61
|
+
],
|
|
62
|
+
|
|
63
|
+
'secrets': [
|
|
64
|
+
'cryptography >= 43',
|
|
65
|
+
],
|
|
66
|
+
|
|
67
|
+
'sql': [
|
|
68
|
+
'sqlalchemy >= 2; python_version >= "3.13"',
|
|
69
|
+
'sqlalchemy[asyncio] >= 2; python_version < "3.13"',
|
|
70
|
+
|
|
71
|
+
'pg8000 >= 1.31',
|
|
72
|
+
'pymysql >= 1.1',
|
|
73
|
+
|
|
74
|
+
'aiomysql >= 0.2',
|
|
75
|
+
'aiosqlite >= 0.20',
|
|
76
|
+
'asyncpg >= 0.29; python_version < "3.13"',
|
|
77
|
+
],
|
|
78
|
+
|
|
79
|
+
'sqlx': [
|
|
80
|
+
"sqlean.py >= 3.45; python_version < '3.13'",
|
|
81
|
+
|
|
82
|
+
'duckdb >= 1',
|
|
83
|
+
],
|
|
84
|
+
|
|
85
|
+
'testing': [
|
|
86
|
+
'pytest >= 8',
|
|
87
|
+
],
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
#
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class SetuptoolsBase:
|
|
95
|
+
manifest_in = [
|
|
96
|
+
'global-exclude **/conftest.py',
|
|
97
|
+
]
|
|
98
|
+
|
|
99
|
+
include_package_data = False
|
|
100
|
+
|
|
101
|
+
find_packages = {
|
|
102
|
+
'exclude': ['*.tests', '*.tests.*'],
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class Setuptools(SetuptoolsBase):
|
|
107
|
+
find_packages = {
|
|
108
|
+
'include': ['omlish', 'omlish.*'],
|
|
109
|
+
'exclude': [*SetuptoolsBase.find_packages['exclude']],
|
|
110
|
+
}
|
omlish/__init__.py
CHANGED
omlish/asyncs/__init__.py
CHANGED
|
@@ -6,6 +6,15 @@ from .asyncs import ( # noqa
|
|
|
6
6
|
syncable_iterable,
|
|
7
7
|
)
|
|
8
8
|
|
|
9
|
+
from .bridge import ( # noqa
|
|
10
|
+
a_to_s,
|
|
11
|
+
is_in_bridge,
|
|
12
|
+
s_to_a,
|
|
13
|
+
s_to_a_await,
|
|
14
|
+
trivial_a_to_s,
|
|
15
|
+
trivial_s_to_a,
|
|
16
|
+
)
|
|
17
|
+
|
|
9
18
|
from .flavors import ( # noqa
|
|
10
19
|
ContextManagerAdapter,
|
|
11
20
|
Flavor,
|
|
@@ -24,12 +33,3 @@ from .flavors import ( # noqa
|
|
|
24
33
|
mark_trio,
|
|
25
34
|
with_adapter_loop,
|
|
26
35
|
)
|
|
27
|
-
|
|
28
|
-
from .futures import ( # noqa
|
|
29
|
-
FutureError,
|
|
30
|
-
FutureTimeoutError,
|
|
31
|
-
ImmediateExecutor,
|
|
32
|
-
new_thread_or_immediate_executor,
|
|
33
|
-
wait_dependent_futures,
|
|
34
|
-
wait_futures,
|
|
35
|
-
)
|
omlish/asyncs/anyio.py
CHANGED
|
@@ -13,11 +13,23 @@ lookit:
|
|
|
13
13
|
- https://github.com/M-o-a-T/aevent
|
|
14
14
|
- https://github.com/florimondmanca/aiometer
|
|
15
15
|
- https://github.com/sanitizers/octomachinery/blob/b36c3d3d49da813ac46e361424132955a4e99ac8/octomachinery/utils/asynctools.py
|
|
16
|
+
|
|
17
|
+
==
|
|
18
|
+
|
|
19
|
+
async def killer(shutdown: anyio.Event, sleep_s: float) -> None:
|
|
20
|
+
log.warning('Killing in %d seconds', sleep_s)
|
|
21
|
+
await anyio.sleep(sleep_s)
|
|
22
|
+
log.warning('Killing')
|
|
23
|
+
shutdown.set()
|
|
24
|
+
|
|
16
25
|
""" # noqa
|
|
26
|
+
import signal
|
|
17
27
|
import typing as ta
|
|
18
28
|
|
|
29
|
+
import anyio.abc
|
|
19
30
|
import anyio.streams.memory
|
|
20
31
|
import anyio.streams.stapled
|
|
32
|
+
import sniffio
|
|
21
33
|
|
|
22
34
|
from .. import check
|
|
23
35
|
from .. import lang
|
|
@@ -26,6 +38,9 @@ from .. import lang
|
|
|
26
38
|
T = ta.TypeVar('T')
|
|
27
39
|
|
|
28
40
|
|
|
41
|
+
##
|
|
42
|
+
|
|
43
|
+
|
|
29
44
|
async def anyio_eof_to_empty(fn: ta.Callable[..., ta.Awaitable[T]], *args: ta.Any, **kwargs: ta.Any) -> T | bytes:
|
|
30
45
|
try:
|
|
31
46
|
return await fn(*args, **kwargs)
|
|
@@ -33,6 +48,85 @@ async def anyio_eof_to_empty(fn: ta.Callable[..., ta.Awaitable[T]], *args: ta.An
|
|
|
33
48
|
return b''
|
|
34
49
|
|
|
35
50
|
|
|
51
|
+
async def gather(*fns: ta.Callable[..., ta.Awaitable[T]], take_first: bool = False) -> list[lang.Maybe[T]]:
|
|
52
|
+
results: list[lang.Maybe[T]] = [lang.empty()] * len(fns)
|
|
53
|
+
|
|
54
|
+
async def inner(fn, i):
|
|
55
|
+
results[i] = lang.just(await fn())
|
|
56
|
+
if take_first:
|
|
57
|
+
tg.cancel_scope.cancel()
|
|
58
|
+
|
|
59
|
+
async with anyio.create_task_group() as tg:
|
|
60
|
+
for i, fn in enumerate(fns):
|
|
61
|
+
tg.start_soon(inner, fn, i)
|
|
62
|
+
|
|
63
|
+
return results
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
async def first(*fns: ta.Callable[..., ta.Awaitable[T]], **kwargs: ta.Any) -> list[lang.Maybe[T]]:
|
|
67
|
+
return await gather(*fns, take_first=True, **kwargs)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
##
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def get_current_task() -> anyio.TaskInfo | None:
|
|
74
|
+
try:
|
|
75
|
+
return anyio.get_current_task()
|
|
76
|
+
except sniffio.AsyncLibraryNotFoundError:
|
|
77
|
+
return None
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
#
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
BackendTask: ta.TypeAlias = ta.Union[ # noqa
|
|
84
|
+
# asyncio.tasks.Task,
|
|
85
|
+
# trio.lowlevel.Task,
|
|
86
|
+
ta.Any,
|
|
87
|
+
]
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _is_class_named(obj: ta.Any, m: str, n: str) -> bool:
|
|
91
|
+
cls = obj.__class__
|
|
92
|
+
return cls.__module__ == m and cls.__name__ == n
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def get_backend_task(at: anyio.TaskInfo) -> BackendTask | None:
|
|
96
|
+
if _is_class_named(at, 'anyio._backends._asyncio', 'AsyncIOTaskInfo'):
|
|
97
|
+
# https://github.com/agronholm/anyio/blob/8907964926a24461840eee0925d3f355e729f15d/src/anyio/_backends/_asyncio.py#L1846 # noqa
|
|
98
|
+
# weakref.ref
|
|
99
|
+
obj = at._task() # type: ignore # noqa
|
|
100
|
+
if obj is not None and not (
|
|
101
|
+
_is_class_named(obj, '_asyncio', 'Task') or
|
|
102
|
+
_is_class_named(obj, 'asyncio.tasks', 'Task')
|
|
103
|
+
):
|
|
104
|
+
raise TypeError(obj)
|
|
105
|
+
return obj
|
|
106
|
+
|
|
107
|
+
elif _is_class_named(at, 'anyio._backends._trio', 'TrioTaskInfo'):
|
|
108
|
+
# https://github.com/agronholm/anyio/blob/8907964926a24461840eee0925d3f355e729f15d/src/anyio/_backends/_trio.py#L850 # noqa
|
|
109
|
+
# weakref.proxy
|
|
110
|
+
# https://stackoverflow.com/a/62144308 :|
|
|
111
|
+
obj = at._task.__repr__.__self__ # type: ignore # noqa
|
|
112
|
+
if obj is not None and not _is_class_named(obj, 'trio.lowlevel', 'Task'):
|
|
113
|
+
raise TypeError(obj)
|
|
114
|
+
return obj
|
|
115
|
+
|
|
116
|
+
else:
|
|
117
|
+
raise TypeError(at)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def get_current_backend_task() -> BackendTask | None:
|
|
121
|
+
if (at := get_current_task()) is not None:
|
|
122
|
+
return get_backend_task(at)
|
|
123
|
+
else:
|
|
124
|
+
return None
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
##
|
|
128
|
+
|
|
129
|
+
|
|
36
130
|
def split_memory_object_streams(
|
|
37
131
|
*args: anyio.create_memory_object_stream[T],
|
|
38
132
|
) -> tuple[
|
|
@@ -72,25 +166,6 @@ def staple_memory_object_stream2[T](max_buffer_size: float = 0) -> anyio.streams
|
|
|
72
166
|
)
|
|
73
167
|
|
|
74
168
|
|
|
75
|
-
async def gather(*fns: ta.Callable[..., ta.Awaitable[T]], take_first: bool = False) -> list[lang.Maybe[T]]:
|
|
76
|
-
results: list[lang.Maybe[T]] = [lang.empty()] * len(fns)
|
|
77
|
-
|
|
78
|
-
async def inner(fn, i):
|
|
79
|
-
results[i] = lang.just(await fn())
|
|
80
|
-
if take_first:
|
|
81
|
-
tg.cancel_scope.cancel()
|
|
82
|
-
|
|
83
|
-
async with anyio.create_task_group() as tg:
|
|
84
|
-
for i, fn in enumerate(fns):
|
|
85
|
-
tg.start_soon(inner, fn, i)
|
|
86
|
-
|
|
87
|
-
return results
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
async def first(*fns: ta.Callable[..., ta.Awaitable[T]], **kwargs: ta.Any) -> list[lang.Maybe[T]]:
|
|
91
|
-
return await gather(*fns, take_first=True, **kwargs)
|
|
92
|
-
|
|
93
|
-
|
|
94
169
|
##
|
|
95
170
|
|
|
96
171
|
|
|
@@ -150,3 +225,32 @@ class LazyFn(ta.Generic[T]):
|
|
|
150
225
|
self._v = lang.just(await self._fn())
|
|
151
226
|
await self._once.do(do)
|
|
152
227
|
return self._v.must()
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
##
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
async def install_shutdown_signal_handler(
|
|
234
|
+
tg: anyio.abc.TaskGroup,
|
|
235
|
+
event: anyio.Event | None = None,
|
|
236
|
+
*,
|
|
237
|
+
signals: ta.Iterable[int] = (signal.SIGINT, signal.SIGTERM),
|
|
238
|
+
echo: bool = False,
|
|
239
|
+
) -> ta.Callable[..., ta.Awaitable[None]] | None:
|
|
240
|
+
if event is None:
|
|
241
|
+
event = anyio.Event()
|
|
242
|
+
|
|
243
|
+
async def _handler(*, task_status=anyio.TASK_STATUS_IGNORED):
|
|
244
|
+
with anyio.open_signal_receiver(*signals) as it: # type: ignore
|
|
245
|
+
task_status.started()
|
|
246
|
+
async for signum in it:
|
|
247
|
+
if echo:
|
|
248
|
+
if signum == signal.SIGINT:
|
|
249
|
+
print('Ctrl+C pressed!')
|
|
250
|
+
else:
|
|
251
|
+
print('Terminated!')
|
|
252
|
+
event.set()
|
|
253
|
+
return
|
|
254
|
+
|
|
255
|
+
await tg.start(_handler)
|
|
256
|
+
return event.wait
|
omlish/asyncs/asyncio.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
+
import contextlib
|
|
2
3
|
import functools
|
|
3
4
|
import typing as ta
|
|
4
5
|
|
|
@@ -17,3 +18,25 @@ def asyncio_once(fn: CallableT) -> CallableT:
|
|
|
17
18
|
return await future
|
|
18
19
|
|
|
19
20
|
return ta.cast(CallableT, inner)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def get_real_current_loop() -> asyncio.AbstractEventLoop | None:
|
|
24
|
+
return asyncio.get_event_loop_policy()._local._loop # type: ignore # noqa
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def drain_tasks(loop=None):
|
|
28
|
+
if loop is None:
|
|
29
|
+
loop = get_real_current_loop()
|
|
30
|
+
|
|
31
|
+
while loop._ready or loop._scheduled: # noqa
|
|
32
|
+
loop._run_once() # noqa
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@contextlib.contextmanager
|
|
36
|
+
def draining_asyncio_tasks() -> ta.Iterator[None]:
|
|
37
|
+
loop = get_real_current_loop()
|
|
38
|
+
try:
|
|
39
|
+
yield
|
|
40
|
+
finally:
|
|
41
|
+
if loop is not None:
|
|
42
|
+
drain_tasks(loop) # noqa
|
omlish/asyncs/asyncs.py
CHANGED
|
@@ -5,10 +5,10 @@ TODO:
|
|
|
5
5
|
517 ns ± 13.2 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
|
|
6
6
|
- injected io provider - sync vs greenlet aio trampolined
|
|
7
7
|
- push/pull bridge?
|
|
8
|
+
- move to lang
|
|
8
9
|
|
|
9
10
|
https://github.com/sqlalchemy/sqlalchemy/blob/1e75c189da721395bc8c2d899c722a5b9a170404/lib/sqlalchemy/util/_concurrency_py3k.py#L83
|
|
10
11
|
"""
|
|
11
|
-
import contextlib
|
|
12
12
|
import functools
|
|
13
13
|
import typing as ta
|
|
14
14
|
|
|
@@ -16,8 +16,7 @@ import typing as ta
|
|
|
16
16
|
T = ta.TypeVar('T')
|
|
17
17
|
|
|
18
18
|
|
|
19
|
-
def sync_await(fn: ta.Callable[..., T], *args, **kwargs) -> T:
|
|
20
|
-
ret: ta.Any
|
|
19
|
+
def sync_await(fn: ta.Callable[..., T], *args: ta.Any, **kwargs: ta.Any) -> T:
|
|
21
20
|
ret = missing = object()
|
|
22
21
|
|
|
23
22
|
async def gate():
|
|
@@ -25,13 +24,17 @@ def sync_await(fn: ta.Callable[..., T], *args, **kwargs) -> T:
|
|
|
25
24
|
ret = await fn(*args, **kwargs) # type: ignore
|
|
26
25
|
|
|
27
26
|
cr = gate()
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
try:
|
|
28
|
+
try:
|
|
30
29
|
cr.send(None)
|
|
30
|
+
except StopIteration:
|
|
31
|
+
pass
|
|
31
32
|
if ret is missing or cr.cr_await is not None or cr.cr_running:
|
|
32
33
|
raise TypeError('Not terminated')
|
|
34
|
+
finally:
|
|
35
|
+
cr.close()
|
|
33
36
|
|
|
34
|
-
return
|
|
37
|
+
return ret # type: ignore
|
|
35
38
|
|
|
36
39
|
|
|
37
40
|
def sync_list(fn: ta.Callable[..., ta.AsyncIterator[T]], *args, **kwargs) -> list[T]:
|