omlish 0.0.0.dev3__py3-none-any.whl → 0.0.0.dev5__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 +1 -1
- omlish/__init__.py +8 -0
- omlish/asyncs/__init__.py +18 -0
- omlish/asyncs/anyio.py +66 -0
- omlish/asyncs/flavors.py +227 -0
- omlish/asyncs/trio_asyncio.py +47 -0
- omlish/c3.py +1 -1
- omlish/cached.py +1 -2
- omlish/collections/__init__.py +4 -1
- omlish/collections/cache/impl.py +1 -1
- omlish/collections/indexed.py +1 -1
- omlish/collections/utils.py +38 -6
- omlish/configs/__init__.py +5 -0
- omlish/configs/classes.py +53 -0
- omlish/configs/dotenv.py +586 -0
- omlish/configs/props.py +589 -49
- omlish/dataclasses/impl/api.py +1 -1
- omlish/dataclasses/impl/as_.py +1 -1
- omlish/dataclasses/impl/fields.py +1 -0
- omlish/dataclasses/impl/init.py +1 -1
- omlish/dataclasses/impl/main.py +1 -0
- omlish/dataclasses/impl/metaclass.py +6 -1
- omlish/dataclasses/impl/order.py +1 -1
- omlish/dataclasses/impl/reflect.py +15 -2
- omlish/defs.py +1 -1
- omlish/diag/procfs.py +29 -1
- omlish/diag/procstats.py +32 -0
- omlish/diag/replserver/console.py +3 -3
- omlish/diag/replserver/server.py +6 -5
- omlish/diag/threads.py +86 -0
- omlish/docker.py +19 -0
- omlish/dynamic.py +2 -2
- omlish/fnpairs.py +121 -24
- omlish/graphs/dags.py +113 -0
- omlish/graphs/domination.py +268 -0
- omlish/graphs/trees.py +2 -2
- omlish/http/__init__.py +25 -0
- omlish/http/asgi.py +131 -0
- omlish/http/consts.py +31 -4
- omlish/http/cookies.py +194 -0
- omlish/http/dates.py +70 -0
- omlish/http/encodings.py +6 -0
- omlish/http/json.py +273 -0
- omlish/http/sessions.py +197 -0
- omlish/inject/__init__.py +8 -2
- omlish/inject/bindings.py +3 -3
- omlish/inject/exceptions.py +3 -3
- omlish/inject/impl/elements.py +46 -25
- omlish/inject/impl/injector.py +8 -5
- omlish/inject/impl/multis.py +74 -0
- omlish/inject/impl/providers.py +19 -39
- omlish/inject/{proxy.py → impl/proxy.py} +2 -2
- omlish/inject/impl/scopes.py +4 -2
- omlish/inject/injector.py +1 -0
- omlish/inject/keys.py +3 -9
- omlish/inject/multis.py +70 -0
- omlish/inject/providers.py +23 -23
- omlish/inject/scopes.py +7 -3
- omlish/inject/types.py +0 -8
- omlish/iterators.py +13 -0
- omlish/json.py +138 -1
- omlish/lang/__init__.py +8 -0
- omlish/lang/classes/restrict.py +1 -1
- omlish/lang/classes/virtual.py +2 -2
- omlish/lang/contextmanagers.py +64 -0
- omlish/lang/datetimes.py +6 -5
- omlish/lang/functions.py +10 -0
- omlish/lang/imports.py +11 -2
- omlish/lang/sys.py +7 -0
- omlish/lang/typing.py +1 -0
- omlish/logs/utils.py +1 -1
- omlish/marshal/datetimes.py +1 -1
- omlish/reflect.py +8 -2
- omlish/sql/__init__.py +9 -0
- omlish/sql/asyncs.py +148 -0
- omlish/sync.py +70 -0
- omlish/term.py +6 -1
- omlish/testing/pydevd.py +2 -0
- omlish/testing/pytest/__init__.py +5 -0
- omlish/testing/pytest/helpers.py +0 -24
- omlish/testing/pytest/inject/harness.py +1 -1
- omlish/testing/pytest/marks.py +48 -0
- omlish/testing/pytest/plugins/__init__.py +2 -0
- omlish/testing/pytest/plugins/managermarks.py +60 -0
- omlish/testing/testing.py +10 -0
- omlish/text/delimit.py +4 -0
- {omlish-0.0.0.dev3.dist-info → omlish-0.0.0.dev5.dist-info}/METADATA +4 -1
- {omlish-0.0.0.dev3.dist-info → omlish-0.0.0.dev5.dist-info}/RECORD +91 -70
- {omlish-0.0.0.dev3.dist-info → omlish-0.0.0.dev5.dist-info}/WHEEL +1 -1
- {omlish-0.0.0.dev3.dist-info → omlish-0.0.0.dev5.dist-info}/LICENSE +0 -0
- {omlish-0.0.0.dev3.dist-info → omlish-0.0.0.dev5.dist-info}/top_level.txt +0 -0
omlish/reflect.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""
|
|
2
2
|
TODO:
|
|
3
|
+
- callables..
|
|
3
4
|
- uniform collection isinstance - items() for mappings, iter() for other
|
|
4
5
|
- also check instance type in isinstance not just items lol
|
|
5
6
|
- ta.Generic in mro causing trouble - omit? no longer 1:1
|
|
@@ -7,12 +8,13 @@ TODO:
|
|
|
7
8
|
- cache __hash__ in Generic/Union
|
|
8
9
|
"""
|
|
9
10
|
import collections.abc
|
|
10
|
-
import typing as ta
|
|
11
11
|
import types
|
|
12
|
+
import typing as ta
|
|
12
13
|
|
|
13
14
|
from . import c3
|
|
14
15
|
from . import lang
|
|
15
16
|
|
|
17
|
+
|
|
16
18
|
if ta.TYPE_CHECKING:
|
|
17
19
|
from .collections import cache
|
|
18
20
|
else:
|
|
@@ -25,6 +27,7 @@ _NONE_TYPE_FROZENSET: frozenset['Type'] = frozenset([_NoneType])
|
|
|
25
27
|
|
|
26
28
|
|
|
27
29
|
_GenericAlias = ta._GenericAlias # type: ignore # noqa
|
|
30
|
+
# _CallableGenericAlias = ta._CallableGenericAlias # type: ignore # noqa
|
|
28
31
|
_SpecialGenericAlias = ta._SpecialGenericAlias # type: ignore # noqa
|
|
29
32
|
_UnionGenericAlias = ta._UnionGenericAlias # type: ignore # noqa
|
|
30
33
|
|
|
@@ -155,7 +158,10 @@ def type_(obj: ta.Any) -> Type:
|
|
|
155
158
|
if isinstance(obj, ta.NewType): # noqa
|
|
156
159
|
return NewType(oty)
|
|
157
160
|
|
|
158
|
-
if
|
|
161
|
+
if (
|
|
162
|
+
oty is _GenericAlias or
|
|
163
|
+
oty is ta.GenericAlias # type: ignore # noqa
|
|
164
|
+
):
|
|
159
165
|
origin = ta.get_origin(obj)
|
|
160
166
|
args = ta.get_args(obj)
|
|
161
167
|
if origin is ta.Generic:
|
omlish/sql/__init__.py
CHANGED
omlish/sql/asyncs.py
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TODO:
|
|
3
|
+
- Maysync impls?
|
|
4
|
+
- base Protocol so adapters and real sa impls can be used interchangeably (if in asyncio ctx)?
|
|
5
|
+
"""
|
|
6
|
+
import contextlib
|
|
7
|
+
import typing as ta
|
|
8
|
+
|
|
9
|
+
import sqlalchemy as sa
|
|
10
|
+
import sqlalchemy.ext.asyncio as saa
|
|
11
|
+
|
|
12
|
+
from .. import asyncs as au
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
T = ta.TypeVar('T')
|
|
16
|
+
P = ta.ParamSpec('P')
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
AsyncEngineLike = ta.Union[saa.AsyncEngine, 'AsyncEngine']
|
|
20
|
+
AsyncConnectionLike = ta.Union[saa.AsyncConnection, 'AsyncConnection']
|
|
21
|
+
AsyncTransactionLike = ta.Union[saa.AsyncTransaction, 'AsyncTransaction']
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class AsyncTransaction:
|
|
25
|
+
def __init__(self, underlying: saa.AsyncTransaction) -> None:
|
|
26
|
+
super().__init__()
|
|
27
|
+
self._underlying = underlying
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def underlying(self) -> saa.AsyncTransaction:
|
|
31
|
+
return self._underlying
|
|
32
|
+
|
|
33
|
+
##
|
|
34
|
+
|
|
35
|
+
@au.mark_asyncio
|
|
36
|
+
async def close(self) -> None:
|
|
37
|
+
await au.from_asyncio(self._underlying.close)()
|
|
38
|
+
|
|
39
|
+
@au.mark_asyncio
|
|
40
|
+
async def rollback(self) -> None:
|
|
41
|
+
await au.from_asyncio(self._underlying.rollback)()
|
|
42
|
+
|
|
43
|
+
@au.mark_asyncio
|
|
44
|
+
async def commit(self) -> None:
|
|
45
|
+
await au.from_asyncio(self._underlying.commit)()
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class AsyncConnection:
|
|
49
|
+
def __init__(self, underlying: saa.AsyncConnection) -> None:
|
|
50
|
+
super().__init__()
|
|
51
|
+
self._underlying = underlying
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def underlying(self) -> saa.AsyncConnection:
|
|
55
|
+
return self._underlying
|
|
56
|
+
|
|
57
|
+
##
|
|
58
|
+
|
|
59
|
+
@contextlib.asynccontextmanager
|
|
60
|
+
@au.mark_asyncio
|
|
61
|
+
async def begin(self) -> ta.AsyncIterator[AsyncTransaction]:
|
|
62
|
+
async with au.from_asyncio_context(self._underlying.begin()) as u:
|
|
63
|
+
yield AsyncTransaction(u)
|
|
64
|
+
|
|
65
|
+
@au.mark_asyncio
|
|
66
|
+
async def execute(
|
|
67
|
+
self,
|
|
68
|
+
statement: ta.Any,
|
|
69
|
+
*args: ta.Any,
|
|
70
|
+
**kwargs: ta.Any,
|
|
71
|
+
) -> sa.CursorResult[ta.Any]:
|
|
72
|
+
return await au.from_asyncio(self._underlying.execute)(statement, *args, **kwargs)
|
|
73
|
+
|
|
74
|
+
@au.mark_asyncio
|
|
75
|
+
async def run_sync(
|
|
76
|
+
self,
|
|
77
|
+
fn: ta.Callable[ta.Concatenate[sa.Connection, P], T],
|
|
78
|
+
*args: P.args,
|
|
79
|
+
**kwargs: P.kwargs,
|
|
80
|
+
) -> T:
|
|
81
|
+
return await au.from_asyncio(self._underlying.run_sync)(fn, *args, **kwargs)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class AsyncEngine:
|
|
85
|
+
def __init__(self, underlying: saa.AsyncEngine) -> None:
|
|
86
|
+
super().__init__()
|
|
87
|
+
self._underlying = underlying
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
def underlying(self) -> saa.AsyncEngine:
|
|
91
|
+
return self._underlying
|
|
92
|
+
|
|
93
|
+
##
|
|
94
|
+
|
|
95
|
+
@contextlib.asynccontextmanager
|
|
96
|
+
@au.mark_asyncio
|
|
97
|
+
async def connect(self) -> ta.AsyncIterator[AsyncConnection]:
|
|
98
|
+
async with au.from_asyncio_context(self._underlying.connect()) as u:
|
|
99
|
+
yield AsyncConnection(u)
|
|
100
|
+
|
|
101
|
+
@au.mark_asyncio
|
|
102
|
+
async def dispose(self, close: bool = True) -> None:
|
|
103
|
+
await au.from_asyncio(self._underlying.dispose)(close)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
##
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@ta.overload
|
|
110
|
+
def async_adapt(obj: AsyncEngine) -> AsyncEngine:
|
|
111
|
+
...
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@ta.overload
|
|
115
|
+
def async_adapt(obj: AsyncConnection) -> AsyncConnection:
|
|
116
|
+
...
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
@ta.overload
|
|
120
|
+
def async_adapt(obj: AsyncTransaction) -> AsyncTransaction:
|
|
121
|
+
...
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@ta.overload
|
|
125
|
+
def async_adapt(obj: saa.AsyncEngine) -> AsyncEngine:
|
|
126
|
+
...
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
@ta.overload
|
|
130
|
+
def async_adapt(obj: saa.AsyncConnection) -> AsyncConnection:
|
|
131
|
+
...
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
@ta.overload
|
|
135
|
+
def async_adapt(obj: saa.AsyncTransaction) -> AsyncTransaction:
|
|
136
|
+
...
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def async_adapt(obj: ta.Any) -> ta.Any:
|
|
140
|
+
if isinstance(obj, (AsyncEngine, AsyncConnection, AsyncTransaction)):
|
|
141
|
+
return obj
|
|
142
|
+
if isinstance(obj, saa.AsyncTransaction):
|
|
143
|
+
return AsyncTransaction(obj)
|
|
144
|
+
if isinstance(obj, saa.AsyncConnection):
|
|
145
|
+
return AsyncConnection(obj)
|
|
146
|
+
if isinstance(obj, saa.AsyncEngine):
|
|
147
|
+
return AsyncEngine(obj)
|
|
148
|
+
raise TypeError(obj)
|
omlish/sync.py
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TODO:
|
|
3
|
+
- sync (lol) w/ asyncs.anyio
|
|
4
|
+
- atomics
|
|
5
|
+
"""
|
|
6
|
+
import threading
|
|
7
|
+
import typing as ta
|
|
8
|
+
|
|
9
|
+
from . import lang
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
T = ta.TypeVar('T')
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Once:
|
|
16
|
+
def __init__(self) -> None:
|
|
17
|
+
super().__init__()
|
|
18
|
+
self._done = False
|
|
19
|
+
self._lock = threading.Lock()
|
|
20
|
+
|
|
21
|
+
def do(self, fn: ta.Callable[[], None]) -> bool:
|
|
22
|
+
if self._done:
|
|
23
|
+
return False
|
|
24
|
+
with self._lock:
|
|
25
|
+
if self._done:
|
|
26
|
+
return False # type: ignore
|
|
27
|
+
try:
|
|
28
|
+
fn()
|
|
29
|
+
finally:
|
|
30
|
+
self._done = True
|
|
31
|
+
return True
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class Lazy(ta.Generic[T]):
|
|
35
|
+
def __init__(self) -> None:
|
|
36
|
+
super().__init__()
|
|
37
|
+
self._once = Once()
|
|
38
|
+
self._v: lang.Maybe[T] = lang.empty()
|
|
39
|
+
|
|
40
|
+
def peek(self) -> lang.Maybe[T]:
|
|
41
|
+
return self._v
|
|
42
|
+
|
|
43
|
+
def set(self, v: T) -> None:
|
|
44
|
+
self._v = lang.just(v)
|
|
45
|
+
|
|
46
|
+
def get(self, fn: ta.Callable[[], T]) -> T:
|
|
47
|
+
def do():
|
|
48
|
+
self._v = lang.just(fn())
|
|
49
|
+
self._once.do(do)
|
|
50
|
+
return self._v.must()
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class LazyFn(ta.Generic[T]):
|
|
54
|
+
def __init__(self, fn: ta.Callable[[], T]) -> None:
|
|
55
|
+
super().__init__()
|
|
56
|
+
self._fn = fn
|
|
57
|
+
self._once = Once()
|
|
58
|
+
self._v: lang.Maybe[T] = lang.empty()
|
|
59
|
+
|
|
60
|
+
def peek(self) -> lang.Maybe[T]:
|
|
61
|
+
return self._v
|
|
62
|
+
|
|
63
|
+
def set(self, v: T) -> None:
|
|
64
|
+
self._v = lang.just(v)
|
|
65
|
+
|
|
66
|
+
def get(self) -> T:
|
|
67
|
+
def do():
|
|
68
|
+
self._v = lang.just(self._fn())
|
|
69
|
+
self._once.do(do)
|
|
70
|
+
return self._v.must()
|
omlish/term.py
CHANGED
|
@@ -213,8 +213,13 @@ def main() -> None:
|
|
|
213
213
|
|
|
214
214
|
sys.stdout.write(SGR(SGRs.RESET))
|
|
215
215
|
sys.stdout.write(BG8(15) + ' ')
|
|
216
|
-
for i in
|
|
216
|
+
for i in range(20):
|
|
217
217
|
sys.stdout.write(BG8(i) + ' ')
|
|
218
|
+
sys.stdout.write('\n')
|
|
219
|
+
for i in range(256):
|
|
220
|
+
if i % 12 == 0:
|
|
221
|
+
sys.stdout.write('\n')
|
|
222
|
+
sys.stdout.write(BG8(i + 16) + ' ')
|
|
218
223
|
sys.stdout.write(SGR(SGRs.RESET) + '\n')
|
|
219
224
|
|
|
220
225
|
|
omlish/testing/pydevd.py
CHANGED
omlish/testing/pytest/helpers.py
CHANGED
|
@@ -1,28 +1,4 @@
|
|
|
1
1
|
import contextlib
|
|
2
|
-
import shutil
|
|
3
|
-
import sys
|
|
4
|
-
import typing as ta
|
|
5
|
-
|
|
6
|
-
import pytest
|
|
7
|
-
|
|
8
|
-
from ..testing import can_import
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
def skip_if_cant_import(module: str, *args, **kwargs):
|
|
12
|
-
return pytest.mark.skipif(not can_import(module, *args, **kwargs), reason=f'requires import {module}')
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
def skip_if_not_on_path(exe: str):
|
|
16
|
-
return pytest.mark.skipif(shutil.which(exe) is None, reason=f'requires exe on path {exe}')
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
def skip_if_python_version_less_than(num: ta.Sequence[int]):
|
|
20
|
-
return pytest.mark.skipif(sys.version_info < tuple(num), reason=f'python version {tuple(sys.version_info)} < {tuple(num)}') # noqa
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
def skip_if_not_single():
|
|
24
|
-
# [resolve_collection_argument(a) for a in session.config.args]
|
|
25
|
-
raise NotImplementedError
|
|
26
2
|
|
|
27
3
|
|
|
28
4
|
@contextlib.contextmanager
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import shutil
|
|
2
|
+
import sys
|
|
3
|
+
import sysconfig
|
|
4
|
+
import typing as ta
|
|
5
|
+
|
|
6
|
+
import pytest
|
|
7
|
+
|
|
8
|
+
from ... import lang # noqa
|
|
9
|
+
from ..testing import can_import
|
|
10
|
+
from .plugins.managermarks import ManagerMark # noqa
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
if ta.TYPE_CHECKING:
|
|
14
|
+
import asyncio
|
|
15
|
+
else:
|
|
16
|
+
asyncio = lang.proxy_import('asyncio')
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def skip_if_cant_import(module: str, *args, **kwargs):
|
|
20
|
+
return pytest.mark.skipif(not can_import(module, *args, **kwargs), reason=f'requires import {module}')
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def skip_if_not_on_path(exe: str):
|
|
24
|
+
return pytest.mark.skipif(shutil.which(exe) is None, reason=f'requires exe on path {exe}')
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def skip_if_python_version_less_than(num: ta.Sequence[int]):
|
|
28
|
+
return pytest.mark.skipif(sys.version_info < tuple(num), reason=f'python version {tuple(sys.version_info)} < {tuple(num)}') # noqa
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def skip_if_not_single():
|
|
32
|
+
# FIXME
|
|
33
|
+
# [resolve_collection_argument(a) for a in session.config.args]
|
|
34
|
+
raise NotImplementedError
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def skip_if_nogil():
|
|
38
|
+
return pytest.mark.skipif(sysconfig.get_config_var('Py_GIL_DISABLED'), reason='requires gil build')
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class drain_asyncio(ManagerMark): # noqa
|
|
42
|
+
def __call__(self, item: pytest.Function) -> ta.Iterator[None]:
|
|
43
|
+
loop = asyncio.get_event_loop()
|
|
44
|
+
try:
|
|
45
|
+
yield
|
|
46
|
+
finally:
|
|
47
|
+
while loop._ready or loop._scheduled: # type: ignore # noqa
|
|
48
|
+
loop._run_once() # type: ignore # noqa
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
import contextlib
|
|
3
|
+
import typing as ta
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from .... import lang
|
|
8
|
+
from ._registry import register
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ManagerMark(lang.Abstract):
|
|
12
|
+
def __init_subclass__(cls, **kwargs):
|
|
13
|
+
super().__init_subclass__(**kwargs)
|
|
14
|
+
|
|
15
|
+
@abc.abstractmethod
|
|
16
|
+
def __call__(self, item: pytest.Function) -> ta.Iterator[None]:
|
|
17
|
+
raise NotImplementedError
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _deep_subclasses(cls):
|
|
21
|
+
ret = set()
|
|
22
|
+
|
|
23
|
+
def rec(cur):
|
|
24
|
+
for nxt in cur.__subclasses__():
|
|
25
|
+
if nxt not in ret:
|
|
26
|
+
ret.add(nxt)
|
|
27
|
+
rec(nxt)
|
|
28
|
+
|
|
29
|
+
rec(cls)
|
|
30
|
+
return ret
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@register
|
|
34
|
+
class ManagerMarksPlugin:
|
|
35
|
+
|
|
36
|
+
@lang.cached_function
|
|
37
|
+
def mark_classes(self) -> ta.Mapping[str, type[ManagerMark]]:
|
|
38
|
+
return {
|
|
39
|
+
cls.__name__: cls
|
|
40
|
+
for cls in _deep_subclasses(ManagerMark)
|
|
41
|
+
if not lang.is_abstract_class(cls)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
def pytest_configure(self, config):
|
|
45
|
+
for n in self.mark_classes():
|
|
46
|
+
config.addinivalue_line(
|
|
47
|
+
'markers',
|
|
48
|
+
f'{n}: mark to manage {n}',
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
@pytest.hookimpl(hookwrapper=True)
|
|
52
|
+
def pytest_runtest_call(self, item):
|
|
53
|
+
with contextlib.ExitStack() as es:
|
|
54
|
+
for n, cls in self.mark_classes().items():
|
|
55
|
+
if (m := item.get_closest_marker(n)) is None:
|
|
56
|
+
continue
|
|
57
|
+
inst = cls(*m.args, **m.kwargs)
|
|
58
|
+
es.enter_context(contextlib.contextmanager(inst)(item))
|
|
59
|
+
|
|
60
|
+
yield
|
omlish/testing/testing.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import functools
|
|
2
2
|
import importlib
|
|
3
3
|
import os
|
|
4
|
+
import sys
|
|
4
5
|
import threading
|
|
5
6
|
import time
|
|
6
7
|
import traceback
|
|
@@ -100,3 +101,12 @@ def xfail(fn):
|
|
|
100
101
|
traceback.print_exc()
|
|
101
102
|
|
|
102
103
|
return inner
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def raise_in_thread(thr: threading.Thread, exc: BaseException | type[BaseException]) -> None:
|
|
107
|
+
if sys.implementation.name != 'cpython':
|
|
108
|
+
raise RuntimeError(sys.implementation.name)
|
|
109
|
+
|
|
110
|
+
# https://github.com/python/cpython/blob/37ba7531a59a0a2b240a86f7e2adfb1b1cd8ac0c/Lib/test/test_threading.py#L182
|
|
111
|
+
import ctypes as ct
|
|
112
|
+
ct.pythonapi.PyThreadState_SetAsyncExc(ct.c_ulong(thr.ident), ct.py_object(exc)) # type: ignore
|
omlish/text/delimit.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: omlish
|
|
3
|
-
Version: 0.0.0.
|
|
3
|
+
Version: 0.0.0.dev5
|
|
4
4
|
Summary: omlish
|
|
5
5
|
Author: wrmsr
|
|
6
6
|
License: BSD-3-Clause
|
|
@@ -24,6 +24,9 @@ Provides-Extra: sql
|
|
|
24
24
|
Requires-Dist: sqlalchemy ; extra == 'sql'
|
|
25
25
|
Provides-Extra: test
|
|
26
26
|
Requires-Dist: pytest ; extra == 'test'
|
|
27
|
+
Provides-Extra: trio
|
|
28
|
+
Requires-Dist: trio ; extra == 'trio'
|
|
29
|
+
Requires-Dist: trio-asyncio ; extra == 'trio'
|
|
27
30
|
Provides-Extra: wrapt
|
|
28
31
|
Requires-Dist: wrapt ; extra == 'wrapt'
|
|
29
32
|
Provides-Extra: yaml
|