omlish 0.0.0.dev4__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 +1 -1
- omlish/asyncs/__init__.py +1 -4
- omlish/asyncs/anyio.py +66 -0
- omlish/asyncs/flavors.py +27 -1
- omlish/asyncs/trio_asyncio.py +24 -18
- 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/fnpairs.py +26 -18
- 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 +33 -24
- omlish/inject/impl/injector.py +1 -0
- 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 +1 -0
- 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 +2 -1
- omlish/lang/__init__.py +4 -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/typing.py +1 -0
- omlish/logs/utils.py +1 -1
- omlish/marshal/datetimes.py +1 -1
- omlish/reflect.py +8 -2
- omlish/sync.py +70 -0
- omlish/term.py +6 -1
- 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.dev4.dist-info → omlish-0.0.0.dev5.dist-info}/METADATA +1 -1
- {omlish-0.0.0.dev4.dist-info → omlish-0.0.0.dev5.dist-info}/RECORD +86 -69
- {omlish-0.0.0.dev4.dist-info → omlish-0.0.0.dev5.dist-info}/WHEEL +1 -1
- {omlish-0.0.0.dev4.dist-info → omlish-0.0.0.dev5.dist-info}/LICENSE +0 -0
- {omlish-0.0.0.dev4.dist-info → omlish-0.0.0.dev5.dist-info}/top_level.txt +0 -0
omlish/__about__.py
CHANGED
omlish/__init__.py
CHANGED
omlish/asyncs/__init__.py
CHANGED
|
@@ -22,6 +22,7 @@ from .flavors import ( # noqa
|
|
|
22
22
|
mark_asyncio,
|
|
23
23
|
mark_flavor,
|
|
24
24
|
mark_trio,
|
|
25
|
+
with_adapter_loop,
|
|
25
26
|
)
|
|
26
27
|
|
|
27
28
|
from .futures import ( # noqa
|
|
@@ -32,7 +33,3 @@ from .futures import ( # noqa
|
|
|
32
33
|
wait_dependent_futures,
|
|
33
34
|
wait_futures,
|
|
34
35
|
)
|
|
35
|
-
|
|
36
|
-
from .trio_asyncio import ( # noqa
|
|
37
|
-
with_trio_asyncio_loop,
|
|
38
|
-
)
|
omlish/asyncs/anyio.py
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
"""
|
|
2
|
+
TODO:
|
|
3
|
+
- bane
|
|
4
|
+
- owned lock
|
|
5
|
+
- async once
|
|
6
|
+
|
|
2
7
|
lookit:
|
|
3
8
|
- https://github.com/davidbrochart/sqlite-anyio/blob/a3ba4c6ef0535b14a5a60071fcd6ed565a514963/sqlite_anyio/sqlite.py
|
|
4
9
|
- https://github.com/rafalkrupinski/ratelimit-anyio/blob/2910a8a3d6fa54ed17ee6ba457686c9f7a4c4beb/src/ratelimit_anyio/__init__.py
|
|
@@ -84,3 +89,64 @@ async def gather(*fns: ta.Callable[..., ta.Awaitable[T]], take_first: bool = Fal
|
|
|
84
89
|
|
|
85
90
|
async def first(*fns: ta.Callable[..., ta.Awaitable[T]], **kwargs: ta.Any) -> list[lang.Maybe[T]]:
|
|
86
91
|
return await gather(*fns, take_first=True, **kwargs)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
##
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class Once:
|
|
98
|
+
def __init__(self) -> None:
|
|
99
|
+
super().__init__()
|
|
100
|
+
self._done = False
|
|
101
|
+
self._lock = anyio.Lock()
|
|
102
|
+
|
|
103
|
+
async def do(self, fn: ta.Callable[[], ta.Awaitable[None]]) -> bool:
|
|
104
|
+
if self._done:
|
|
105
|
+
return False
|
|
106
|
+
async with self._lock:
|
|
107
|
+
if self._done:
|
|
108
|
+
return False # type: ignore
|
|
109
|
+
try:
|
|
110
|
+
await fn()
|
|
111
|
+
finally:
|
|
112
|
+
self._done = True
|
|
113
|
+
return True
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class Lazy(ta.Generic[T]):
|
|
117
|
+
def __init__(self) -> None:
|
|
118
|
+
super().__init__()
|
|
119
|
+
self._once = Once()
|
|
120
|
+
self._v: lang.Maybe[T] = lang.empty()
|
|
121
|
+
|
|
122
|
+
def peek(self) -> lang.Maybe[T]:
|
|
123
|
+
return self._v
|
|
124
|
+
|
|
125
|
+
def set(self, v: T) -> None:
|
|
126
|
+
self._v = lang.just(v)
|
|
127
|
+
|
|
128
|
+
async def get(self, fn: ta.Callable[[], ta.Awaitable[T]]) -> T:
|
|
129
|
+
async def do():
|
|
130
|
+
self._v = lang.just(await fn())
|
|
131
|
+
await self._once.do(do)
|
|
132
|
+
return self._v.must()
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class LazyFn(ta.Generic[T]):
|
|
136
|
+
def __init__(self, fn: ta.Callable[[], ta.Awaitable[T]]) -> None:
|
|
137
|
+
super().__init__()
|
|
138
|
+
self._fn = fn
|
|
139
|
+
self._once = Once()
|
|
140
|
+
self._v: lang.Maybe[T] = lang.empty()
|
|
141
|
+
|
|
142
|
+
def peek(self) -> lang.Maybe[T]:
|
|
143
|
+
return self._v
|
|
144
|
+
|
|
145
|
+
def set(self, v: T) -> None:
|
|
146
|
+
self._v = lang.just(v)
|
|
147
|
+
|
|
148
|
+
async def get(self) -> T:
|
|
149
|
+
async def do():
|
|
150
|
+
self._v = lang.just(await self._fn())
|
|
151
|
+
await self._once.do(do)
|
|
152
|
+
return self._v.must()
|
omlish/asyncs/flavors.py
CHANGED
|
@@ -7,10 +7,13 @@ TODO:
|
|
|
7
7
|
import abc
|
|
8
8
|
import dataclasses as dc
|
|
9
9
|
import enum
|
|
10
|
+
import functools
|
|
10
11
|
import typing as ta
|
|
11
12
|
|
|
12
13
|
from .. import lang
|
|
13
14
|
from .trio_asyncio import check_trio_asyncio
|
|
15
|
+
from .trio_asyncio import with_trio_asyncio_loop
|
|
16
|
+
|
|
14
17
|
|
|
15
18
|
if ta.TYPE_CHECKING:
|
|
16
19
|
import sniffio
|
|
@@ -79,7 +82,7 @@ def _get_module_flavor(p: str) -> Flavor | None:
|
|
|
79
82
|
return pf
|
|
80
83
|
|
|
81
84
|
|
|
82
|
-
def get_flavor(obj: ta.Any, default:
|
|
85
|
+
def get_flavor(obj: ta.Any, default: Flavor | type[_MISSING] | None = _MISSING) -> Flavor:
|
|
83
86
|
u = lang.unwrap_func(obj)
|
|
84
87
|
|
|
85
88
|
try:
|
|
@@ -100,6 +103,29 @@ def get_flavor(obj: ta.Any, default: ta.Union[Flavor, type[_MISSING], None] = _M
|
|
|
100
103
|
##
|
|
101
104
|
|
|
102
105
|
|
|
106
|
+
def with_adapter_loop(*, wait=False):
|
|
107
|
+
def outer(fn):
|
|
108
|
+
@functools.wraps(fn)
|
|
109
|
+
async def inner(*args, **kwargs):
|
|
110
|
+
cur_lib = sniffio.current_async_library()
|
|
111
|
+
|
|
112
|
+
if cur_lib == 'asyncio':
|
|
113
|
+
await fn(*args, **kwargs)
|
|
114
|
+
|
|
115
|
+
elif cur_lib == 'trio':
|
|
116
|
+
await with_trio_asyncio_loop(wait=wait)(fn)(*args, **kwargs)
|
|
117
|
+
|
|
118
|
+
else:
|
|
119
|
+
raise RuntimeError(f'Unknown async library: {cur_lib}')
|
|
120
|
+
|
|
121
|
+
return inner
|
|
122
|
+
|
|
123
|
+
return outer
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
##
|
|
127
|
+
|
|
128
|
+
|
|
103
129
|
class Adapter(lang.Abstract):
|
|
104
130
|
_FROM_METHODS_BY_FLAVOR: ta.ClassVar[ta.Mapping[Flavor, str]] = {
|
|
105
131
|
Flavor.ANYIO: 'from_anyio',
|
omlish/asyncs/trio_asyncio.py
CHANGED
|
@@ -3,12 +3,15 @@ import typing as ta
|
|
|
3
3
|
|
|
4
4
|
from .. import lang
|
|
5
5
|
|
|
6
|
+
|
|
6
7
|
if ta.TYPE_CHECKING:
|
|
7
8
|
import asyncio
|
|
9
|
+
|
|
8
10
|
import sniffio
|
|
9
11
|
import trio_asyncio
|
|
10
12
|
else:
|
|
11
13
|
asyncio = lang.proxy_import('asyncio')
|
|
14
|
+
|
|
12
15
|
sniffio = lang.proxy_import('sniffio')
|
|
13
16
|
trio_asyncio = lang.proxy_import('trio_asyncio')
|
|
14
17
|
|
|
@@ -18,24 +21,27 @@ def check_trio_asyncio() -> None:
|
|
|
18
21
|
raise RuntimeError('trio_asyncio loop not running')
|
|
19
22
|
|
|
20
23
|
|
|
21
|
-
def with_trio_asyncio_loop(
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
def with_trio_asyncio_loop(*, wait=False):
|
|
25
|
+
def outer(fn):
|
|
26
|
+
@functools.wraps(fn)
|
|
27
|
+
async def inner(*args, **kwargs):
|
|
28
|
+
if trio_asyncio.current_loop.get() is not None:
|
|
29
|
+
await fn(*args, **kwargs)
|
|
30
|
+
return
|
|
27
31
|
|
|
28
|
-
|
|
29
|
-
|
|
32
|
+
if sniffio.current_async_library() != 'trio':
|
|
33
|
+
raise RuntimeError('trio loop not running')
|
|
30
34
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
35
|
+
loop: asyncio.BaseEventLoop
|
|
36
|
+
async with trio_asyncio.open_loop() as loop:
|
|
37
|
+
try:
|
|
38
|
+
await fn(*args, **kwargs)
|
|
39
|
+
finally:
|
|
40
|
+
if wait:
|
|
41
|
+
# FIXME: lol
|
|
42
|
+
while asyncio.all_tasks(loop):
|
|
43
|
+
await asyncio.sleep(.2)
|
|
44
|
+
|
|
45
|
+
return inner
|
|
40
46
|
|
|
41
|
-
return
|
|
47
|
+
return outer
|
omlish/c3.py
CHANGED
|
@@ -144,7 +144,7 @@ def compose_mro(
|
|
|
144
144
|
|
|
145
145
|
# Remove entries which are strict bases of other entries (they will end up in the MRO anyway.
|
|
146
146
|
def is_strict_base(typ):
|
|
147
|
-
for other in types:
|
|
147
|
+
for other in types: # noqa
|
|
148
148
|
if typ != other and typ in (get_mro(other) or []):
|
|
149
149
|
return True
|
|
150
150
|
return False
|
omlish/cached.py
CHANGED
omlish/collections/__init__.py
CHANGED
omlish/collections/cache/impl.py
CHANGED
omlish/collections/indexed.py
CHANGED
omlish/collections/utils.py
CHANGED
|
@@ -12,6 +12,9 @@ K = ta.TypeVar('K')
|
|
|
12
12
|
V = ta.TypeVar('V')
|
|
13
13
|
|
|
14
14
|
|
|
15
|
+
##
|
|
16
|
+
|
|
17
|
+
|
|
15
18
|
def mut_toposort(data: dict[T, set[T]]) -> ta.Iterator[set[T]]:
|
|
16
19
|
for k, v in data.items():
|
|
17
20
|
v.discard(k)
|
|
@@ -31,6 +34,9 @@ def toposort(data: ta.Mapping[T, ta.AbstractSet[T]]) -> ta.Iterator[set[T]]:
|
|
|
31
34
|
return mut_toposort({k: set(v) for k, v in data.items()})
|
|
32
35
|
|
|
33
36
|
|
|
37
|
+
##
|
|
38
|
+
|
|
39
|
+
|
|
34
40
|
def partition(items: ta.Iterable[T], pred: ta.Callable[[T], bool]) -> tuple[list[T], list[T]]:
|
|
35
41
|
t: list[T] = []
|
|
36
42
|
f: list[T] = []
|
|
@@ -54,13 +60,36 @@ def unique(it: ta.Iterable[T], *, identity: bool = False) -> list[T]:
|
|
|
54
60
|
return ret
|
|
55
61
|
|
|
56
62
|
|
|
57
|
-
def
|
|
58
|
-
|
|
59
|
-
for k, v in
|
|
60
|
-
if k in
|
|
63
|
+
def unique_map(kvs: ta.Iterable[tuple[K, V]], *, identity: bool = False) -> ta.MutableMapping[K, V]:
|
|
64
|
+
d: ta.MutableMapping[K, V] = IdentityKeyDict() if identity else {}
|
|
65
|
+
for k, v in kvs:
|
|
66
|
+
if k in d:
|
|
61
67
|
raise KeyError(k)
|
|
62
|
-
|
|
63
|
-
return
|
|
68
|
+
d[k] = v
|
|
69
|
+
return d
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def unique_map_by(fn: ta.Callable[[V], K], vs: ta.Iterable[V], *, identity: bool = False) -> ta.MutableMapping[K, V]:
|
|
73
|
+
return unique_map(((fn(v), v) for v in vs), identity=identity)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def multi_map(kvs: ta.Iterable[tuple[K, V]], *, identity: bool = False) -> ta.MutableMapping[K, list[V]]:
|
|
77
|
+
d: ta.MutableMapping[K, list[V]] = IdentityKeyDict() if identity else {}
|
|
78
|
+
l: list[V]
|
|
79
|
+
for k, v in kvs:
|
|
80
|
+
try:
|
|
81
|
+
l = d[k]
|
|
82
|
+
except KeyError:
|
|
83
|
+
l = d[k] = []
|
|
84
|
+
l.append(v)
|
|
85
|
+
return d
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def multi_map_by(fn: ta.Callable[[V], K], vs: ta.Iterable[V], *, identity: bool = False) -> ta.MutableMapping[K, list[V]]: # noqa
|
|
89
|
+
return multi_map(((fn(v), v) for v in vs), identity=identity)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
##
|
|
64
93
|
|
|
65
94
|
|
|
66
95
|
def all_equal(it: ta.Iterable[T]) -> bool:
|
|
@@ -81,6 +110,9 @@ def all_not_equal(it: ta.Iterable[T]) -> bool:
|
|
|
81
110
|
return True
|
|
82
111
|
|
|
83
112
|
|
|
113
|
+
##
|
|
114
|
+
|
|
115
|
+
|
|
84
116
|
def key_cmp(fn: ta.Callable[[K, K], int]) -> ta.Callable[[tuple[K, V], tuple[K, V]], int]:
|
|
85
117
|
return lambda t0, t1: fn(t0[0], t1[0])
|
|
86
118
|
|
omlish/configs/__init__.py
CHANGED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import typing as ta
|
|
2
|
+
import weakref
|
|
3
|
+
|
|
4
|
+
from .. import check
|
|
5
|
+
from .. import dataclasses as dc
|
|
6
|
+
from .. import lang
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Config(
|
|
10
|
+
dc.Data,
|
|
11
|
+
lang.Abstract,
|
|
12
|
+
frozen=True,
|
|
13
|
+
reorder=True,
|
|
14
|
+
confer=frozenset([
|
|
15
|
+
'frozen',
|
|
16
|
+
'reorder',
|
|
17
|
+
'confer',
|
|
18
|
+
]),
|
|
19
|
+
):
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
ConfigT = ta.TypeVar('ConfigT', bound='Config')
|
|
24
|
+
|
|
25
|
+
_CONFIG_CLS_MAP: ta.MutableMapping[type[Config], type['Configurable']] = weakref.WeakValueDictionary()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class Configurable(ta.Generic[ConfigT], lang.Abstract):
|
|
29
|
+
|
|
30
|
+
# FIXME: https://github.com/python/mypy/issues/5144
|
|
31
|
+
Config: ta.ClassVar[type[ConfigT]] # type: ignore # noqa
|
|
32
|
+
|
|
33
|
+
def __init_subclass__(cls, **kwargs: ta.Any) -> None:
|
|
34
|
+
super().__init_subclass__(**kwargs)
|
|
35
|
+
|
|
36
|
+
cfg_cls = check.issubclass(cls.__dict__['Config'], Config)
|
|
37
|
+
check.not_in(cfg_cls, _CONFIG_CLS_MAP)
|
|
38
|
+
_CONFIG_CLS_MAP[cfg_cls] = cls
|
|
39
|
+
|
|
40
|
+
def __init__(self, config: ConfigT) -> None:
|
|
41
|
+
super().__init__()
|
|
42
|
+
|
|
43
|
+
self._config: ConfigT = check.isinstance(config, self.Config)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def get_impl(cfg: type[Config] | Config) -> type[Configurable]:
|
|
47
|
+
if isinstance(cfg, type):
|
|
48
|
+
cfg_cls = check.issubclass(cfg, Config) # noqa
|
|
49
|
+
elif isinstance(cfg, Config):
|
|
50
|
+
cfg_cls = type(cfg)
|
|
51
|
+
else:
|
|
52
|
+
raise TypeError(cfg)
|
|
53
|
+
return _CONFIG_CLS_MAP[cfg_cls]
|