omlish 0.0.0.dev1__py3-none-any.whl → 0.0.0.dev3__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 +2 -3
- omlish/argparse.py +8 -8
- omlish/asyncs/__init__.py +2 -2
- omlish/asyncs/anyio.py +64 -1
- omlish/asyncs/asyncs.py +1 -3
- omlish/asyncs/futures.py +16 -15
- omlish/c3.py +5 -5
- omlish/check.py +8 -8
- omlish/collections/__init__.py +98 -63
- omlish/collections/_abc.py +2 -0
- omlish/collections/_io_abc.py +4 -2
- omlish/collections/cache/__init__.py +1 -1
- omlish/collections/cache/descriptor.py +12 -12
- omlish/collections/cache/impl.py +27 -20
- omlish/collections/cache/types.py +1 -1
- omlish/collections/coerce.py +44 -44
- omlish/collections/frozen.py +9 -9
- omlish/collections/identity.py +4 -5
- omlish/collections/mappings.py +5 -5
- omlish/collections/ordered.py +8 -8
- omlish/collections/skiplist.py +7 -7
- omlish/collections/sorted.py +4 -4
- omlish/collections/treap.py +42 -17
- omlish/collections/treapmap.py +59 -7
- omlish/collections/unmodifiable.py +25 -24
- omlish/collections/utils.py +1 -1
- omlish/configs/flattening.py +8 -7
- omlish/configs/props.py +3 -3
- omlish/dataclasses/__init__.py +1 -1
- omlish/dataclasses/impl/__init__.py +18 -0
- omlish/dataclasses/impl/api.py +15 -24
- omlish/dataclasses/impl/as_.py +4 -4
- omlish/dataclasses/impl/exceptions.py +1 -1
- omlish/dataclasses/impl/fields.py +8 -8
- omlish/dataclasses/impl/frozen.py +2 -2
- omlish/dataclasses/impl/init.py +6 -6
- omlish/dataclasses/impl/internals.py +16 -1
- omlish/dataclasses/impl/main.py +4 -4
- omlish/dataclasses/impl/metaclass.py +2 -2
- omlish/dataclasses/impl/metadata.py +1 -1
- omlish/dataclasses/impl/order.py +2 -2
- omlish/dataclasses/impl/params.py +4 -38
- omlish/dataclasses/impl/reflect.py +1 -7
- omlish/dataclasses/impl/replace.py +1 -1
- omlish/dataclasses/impl/repr.py +24 -6
- omlish/dataclasses/impl/simple.py +2 -2
- omlish/dataclasses/impl/slots.py +2 -2
- omlish/dataclasses/impl/utils.py +7 -7
- omlish/defs.py +13 -17
- omlish/diag/procfs.py +334 -0
- omlish/diag/ps.py +47 -0
- omlish/{replserver → diag/replserver}/console.py +26 -28
- omlish/{replserver → diag/replserver}/server.py +12 -12
- omlish/dispatch/dispatch.py +14 -16
- omlish/dispatch/functions.py +1 -1
- omlish/dispatch/methods.py +6 -7
- omlish/docker.py +8 -6
- omlish/dynamic.py +13 -13
- omlish/fnpairs.py +311 -0
- omlish/graphs/dot/items.py +1 -1
- omlish/graphs/trees.py +25 -31
- omlish/inject/__init__.py +7 -7
- omlish/inject/elements.py +2 -2
- omlish/inject/exceptions.py +8 -8
- omlish/inject/impl/elements.py +4 -4
- omlish/inject/impl/injector.py +6 -6
- omlish/inject/impl/inspect.py +3 -3
- omlish/inject/impl/scopes.py +9 -9
- omlish/inject/injector.py +1 -1
- omlish/inject/providers.py +2 -2
- omlish/inject/proxy.py +5 -5
- omlish/iterators.py +62 -26
- omlish/json.py +7 -6
- omlish/lang/__init__.py +172 -112
- omlish/lang/cached.py +15 -10
- omlish/lang/classes/__init__.py +35 -24
- omlish/lang/classes/abstract.py +3 -3
- omlish/lang/classes/restrict.py +14 -14
- omlish/lang/classes/simple.py +2 -2
- omlish/lang/classes/virtual.py +5 -5
- omlish/lang/clsdct.py +2 -2
- omlish/lang/cmp.py +2 -2
- omlish/lang/contextmanagers.py +31 -25
- omlish/lang/datetimes.py +1 -1
- omlish/lang/descriptors.py +51 -6
- omlish/lang/exceptions.py +2 -0
- omlish/lang/functions.py +101 -35
- omlish/lang/imports.py +25 -30
- omlish/lang/iterables.py +2 -2
- omlish/lang/maybes.py +2 -1
- omlish/lang/objects.py +17 -11
- omlish/lang/resolving.py +1 -1
- omlish/lang/strings.py +1 -1
- omlish/lang/timeouts.py +53 -0
- omlish/lang/typing.py +5 -5
- omlish/libc.py +15 -11
- omlish/logs/_abc.py +5 -1
- omlish/logs/filters.py +2 -0
- omlish/logs/formatters.py +6 -2
- omlish/logs/utils.py +1 -1
- omlish/marshal/base.py +9 -9
- omlish/marshal/dataclasses.py +2 -2
- omlish/marshal/enums.py +2 -2
- omlish/marshal/exceptions.py +1 -1
- omlish/marshal/factories.py +10 -10
- omlish/marshal/global_.py +10 -4
- omlish/marshal/iterables.py +2 -2
- omlish/marshal/mappings.py +2 -2
- omlish/marshal/objects.py +1 -2
- omlish/marshal/optionals.py +4 -4
- omlish/marshal/polymorphism.py +4 -4
- omlish/marshal/registries.py +3 -3
- omlish/marshal/standard.py +6 -6
- omlish/marshal/utils.py +3 -3
- omlish/marshal/values.py +1 -1
- omlish/math.py +9 -9
- omlish/os.py +13 -4
- omlish/reflect.py +5 -15
- omlish/sql/__init__.py +0 -0
- omlish/sql/_abc.py +65 -0
- omlish/sql/dbs.py +90 -0
- omlish/stats.py +7 -8
- omlish/term.py +1 -1
- omlish/testing/pydevd.py +30 -12
- omlish/testing/pytest/inject/__init__.py +7 -0
- omlish/testing/pytest/inject/harness.py +24 -2
- omlish/testing/pytest/plugins/__init__.py +1 -1
- omlish/testing/pytest/plugins/pydevd.py +12 -0
- omlish/testing/pytest/plugins/switches.py +3 -3
- omlish/testing/testing.py +5 -5
- omlish/text/delimit.py +3 -6
- omlish/text/parts.py +3 -3
- omlish-0.0.0.dev3.dist-info/METADATA +31 -0
- omlish-0.0.0.dev3.dist-info/RECORD +191 -0
- {omlish-0.0.0.dev1.dist-info → omlish-0.0.0.dev3.dist-info}/WHEEL +1 -1
- omlish/lang/classes/test/test_abstract.py +0 -89
- omlish/lang/classes/test/test_restrict.py +0 -71
- omlish/lang/classes/test/test_simple.py +0 -58
- omlish/lang/classes/test/test_virtual.py +0 -72
- omlish/testing/pytest/plugins/pycharm.py +0 -54
- omlish-0.0.0.dev1.dist-info/METADATA +0 -17
- omlish-0.0.0.dev1.dist-info/RECORD +0 -187
- /omlish/{lang/classes/test → diag}/__init__.py +0 -0
- /omlish/{replserver → diag/replserver}/__init__.py +0 -0
- /omlish/{replserver → diag/replserver}/__main__.py +0 -0
- {omlish-0.0.0.dev1.dist-info → omlish-0.0.0.dev3.dist-info}/LICENSE +0 -0
- {omlish-0.0.0.dev1.dist-info → omlish-0.0.0.dev3.dist-info}/top_level.txt +0 -0
omlish/marshal/registries.py
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import abc
|
|
2
1
|
import dataclasses as dc
|
|
3
2
|
import threading
|
|
4
3
|
import typing as ta
|
|
5
4
|
|
|
6
5
|
from .. import check
|
|
6
|
+
from .. import lang
|
|
7
7
|
from .. import reflect as rfl
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
class RegistryItem(
|
|
10
|
+
class RegistryItem(lang.Abstract):
|
|
11
11
|
pass
|
|
12
12
|
|
|
13
13
|
|
|
@@ -31,7 +31,7 @@ class Registry:
|
|
|
31
31
|
super().__init__()
|
|
32
32
|
self._mtx = threading.Lock()
|
|
33
33
|
self._dct: dict[rfl.Type, _TypeRegistry] = {}
|
|
34
|
-
self._ps: ta.Sequence[
|
|
34
|
+
self._ps: ta.Sequence[Registry] = []
|
|
35
35
|
|
|
36
36
|
def register(self, rty: rfl.Type, *items: RegistryItem) -> 'Registry':
|
|
37
37
|
check.isinstance(rty, rfl.TYPES)
|
omlish/marshal/standard.py
CHANGED
|
@@ -47,9 +47,9 @@ def new_standard_marshaler_factory() -> MarshalerFactory:
|
|
|
47
47
|
return TypeCacheFactory( # noqa
|
|
48
48
|
RecursiveMarshalerFactory(
|
|
49
49
|
CompositeFactory(
|
|
50
|
-
*STANDARD_MARSHALER_FACTORIES
|
|
51
|
-
)
|
|
52
|
-
)
|
|
50
|
+
*STANDARD_MARSHALER_FACTORIES,
|
|
51
|
+
),
|
|
52
|
+
),
|
|
53
53
|
)
|
|
54
54
|
|
|
55
55
|
|
|
@@ -74,7 +74,7 @@ def new_standard_unmarshaler_factory() -> UnmarshalerFactory:
|
|
|
74
74
|
return TypeCacheFactory( # noqa
|
|
75
75
|
RecursiveUnmarshalerFactory(
|
|
76
76
|
CompositeFactory(
|
|
77
|
-
*STANDARD_UNMARSHALER_FACTORIES
|
|
78
|
-
)
|
|
79
|
-
)
|
|
77
|
+
*STANDARD_UNMARSHALER_FACTORIES,
|
|
78
|
+
),
|
|
79
|
+
),
|
|
80
80
|
)
|
omlish/marshal/utils.py
CHANGED
|
@@ -5,7 +5,7 @@ T = ta.TypeVar('T')
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
class _Proxy(ta.Generic[T]):
|
|
8
|
-
__obj:
|
|
8
|
+
__obj: T | None = None
|
|
9
9
|
|
|
10
10
|
@property
|
|
11
11
|
def _obj(self) -> T:
|
|
@@ -19,5 +19,5 @@ class _Proxy(ta.Generic[T]):
|
|
|
19
19
|
self.__obj = obj
|
|
20
20
|
|
|
21
21
|
@classmethod
|
|
22
|
-
def _new(cls):
|
|
23
|
-
return (p := cls()), p._set_obj
|
|
22
|
+
def _new(cls) -> tuple[ta.Any, ta.Callable[[ta.Any], None]]:
|
|
23
|
+
return (p := cls()), p._set_obj # noqa
|
omlish/marshal/values.py
CHANGED
omlish/math.py
CHANGED
|
@@ -66,7 +66,7 @@ class FixedWidthInt(int):
|
|
|
66
66
|
|
|
67
67
|
MASK: ta.ClassVar[int]
|
|
68
68
|
|
|
69
|
-
def __init_subclass__(cls
|
|
69
|
+
def __init_subclass__(cls) -> None:
|
|
70
70
|
super().__init_subclass__()
|
|
71
71
|
|
|
72
72
|
if not isinstance(cls.BITS, int):
|
|
@@ -82,13 +82,13 @@ class FixedWidthInt(int):
|
|
|
82
82
|
cls.MASK = (1 << cls.BITS) - 1
|
|
83
83
|
|
|
84
84
|
@classmethod
|
|
85
|
-
def clamp(cls, value):
|
|
85
|
+
def clamp(cls, value: int) -> int:
|
|
86
86
|
return ((value - cls.MIN) & cls.MASK) + cls.MIN
|
|
87
87
|
|
|
88
|
-
def __new__(cls, value
|
|
89
|
-
return super().__new__(cls, cls.clamp(value))
|
|
88
|
+
def __new__(cls, value: int) -> 'FixedWidthInt':
|
|
89
|
+
return super().__new__(cls, cls.clamp(value)) # noqa
|
|
90
90
|
|
|
91
|
-
SCALAR_PROXY_METHODS =
|
|
91
|
+
SCALAR_PROXY_METHODS = frozenset([
|
|
92
92
|
'__abs__',
|
|
93
93
|
'__add__',
|
|
94
94
|
'__and__',
|
|
@@ -117,12 +117,12 @@ class FixedWidthInt(int):
|
|
|
117
117
|
'__sub__',
|
|
118
118
|
'__truediv__',
|
|
119
119
|
'__xor__',
|
|
120
|
-
|
|
120
|
+
])
|
|
121
121
|
|
|
122
|
-
TUPLE_PROXY_METHODS =
|
|
122
|
+
TUPLE_PROXY_METHODS = frozenset([
|
|
123
123
|
'__divmod__',
|
|
124
124
|
'__rdivmod__',
|
|
125
|
-
|
|
125
|
+
])
|
|
126
126
|
|
|
127
127
|
for _proxy_name in SCALAR_PROXY_METHODS:
|
|
128
128
|
locals()[_proxy_name] = _gen_scalar_proxy_method(_proxy_name)
|
|
@@ -130,7 +130,7 @@ class FixedWidthInt(int):
|
|
|
130
130
|
locals()[_proxy_name] = _gen_tuple_proxy_method(_proxy_name)
|
|
131
131
|
del _proxy_name
|
|
132
132
|
|
|
133
|
-
def __repr__(self):
|
|
133
|
+
def __repr__(self) -> str:
|
|
134
134
|
return f'{self.__class__.__name__}({int(self)})'
|
|
135
135
|
|
|
136
136
|
|
omlish/os.py
CHANGED
|
@@ -1,14 +1,23 @@
|
|
|
1
1
|
import contextlib
|
|
2
|
+
import resource
|
|
2
3
|
import shutil
|
|
3
4
|
import tempfile
|
|
4
5
|
import typing as ta
|
|
5
6
|
|
|
6
7
|
|
|
8
|
+
PAGE_SIZE = resource.getpagesize()
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def round_to_page_size(sz: int) -> int:
|
|
12
|
+
sz += PAGE_SIZE - 1
|
|
13
|
+
return sz - (sz % PAGE_SIZE)
|
|
14
|
+
|
|
15
|
+
|
|
7
16
|
@contextlib.contextmanager
|
|
8
17
|
def tmp_dir(
|
|
9
|
-
root_dir:
|
|
18
|
+
root_dir: str | None = None,
|
|
10
19
|
cleanup: bool = True,
|
|
11
|
-
**kwargs: ta.Any
|
|
20
|
+
**kwargs: ta.Any,
|
|
12
21
|
) -> ta.Iterator[str]:
|
|
13
22
|
path = tempfile.mkdtemp(dir=root_dir, **kwargs)
|
|
14
23
|
try:
|
|
@@ -20,9 +29,9 @@ def tmp_dir(
|
|
|
20
29
|
|
|
21
30
|
@contextlib.contextmanager
|
|
22
31
|
def tmp_file(
|
|
23
|
-
root_dir:
|
|
32
|
+
root_dir: str | None = None,
|
|
24
33
|
cleanup: bool = True,
|
|
25
|
-
**kwargs: ta.Any
|
|
34
|
+
**kwargs: ta.Any,
|
|
26
35
|
) -> ta.Iterator[tempfile._TemporaryFileWrapper]: # noqa
|
|
27
36
|
with tempfile.NamedTemporaryFile(dir=root_dir, delete=False, **kwargs) as f:
|
|
28
37
|
try:
|
omlish/reflect.py
CHANGED
|
@@ -21,7 +21,7 @@ else:
|
|
|
21
21
|
|
|
22
22
|
_NoneType = types.NoneType # type: ignore
|
|
23
23
|
|
|
24
|
-
_NONE_TYPE_FROZENSET:
|
|
24
|
+
_NONE_TYPE_FROZENSET: frozenset['Type'] = frozenset([_NoneType])
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
_GenericAlias = ta._GenericAlias # type: ignore # noqa
|
|
@@ -63,16 +63,6 @@ _KNOWN_SPECIAL_TYPE_VARS = tuple(
|
|
|
63
63
|
##
|
|
64
64
|
|
|
65
65
|
|
|
66
|
-
try:
|
|
67
|
-
from types import get_original_bases # type: ignore
|
|
68
|
-
except ImportError:
|
|
69
|
-
def get_original_bases(cls, /):
|
|
70
|
-
try:
|
|
71
|
-
return cls.__dict__.get('__orig_bases__', cls.__bases__)
|
|
72
|
-
except AttributeError:
|
|
73
|
-
raise TypeError(f'Expected an instance of type, not {type(cls).__name__!r}') from None
|
|
74
|
-
|
|
75
|
-
|
|
76
66
|
def get_params(obj: ta.Any) -> tuple[ta.TypeVar, ...]:
|
|
77
67
|
if isinstance(obj, type):
|
|
78
68
|
if issubclass(obj, ta.Generic): # type: ignore
|
|
@@ -109,7 +99,7 @@ Type = ta.Union[
|
|
|
109
99
|
|
|
110
100
|
|
|
111
101
|
class Union(ta.NamedTuple):
|
|
112
|
-
args:
|
|
102
|
+
args: frozenset[Type]
|
|
113
103
|
|
|
114
104
|
@property
|
|
115
105
|
def is_optional(self) -> bool:
|
|
@@ -131,7 +121,7 @@ class Generic(ta.NamedTuple):
|
|
|
131
121
|
# params2: tuple[ta.TypeVar, ...] # map[int, V] = (V,) | map[T, T] = (T,)
|
|
132
122
|
obj: ta.Any
|
|
133
123
|
|
|
134
|
-
def __repr__(self):
|
|
124
|
+
def __repr__(self) -> str:
|
|
135
125
|
return (
|
|
136
126
|
f'{self.__class__.__name__}('
|
|
137
127
|
f'cls={self.cls.__name__}, '
|
|
@@ -218,7 +208,7 @@ def get_underlying(nt: NewType) -> Type:
|
|
|
218
208
|
return type_(nt.obj.__supertype__) # noqa
|
|
219
209
|
|
|
220
210
|
|
|
221
|
-
def get_concrete_type(ty: Type) ->
|
|
211
|
+
def get_concrete_type(ty: Type) -> type | None:
|
|
222
212
|
if isinstance(ty, type):
|
|
223
213
|
return ty
|
|
224
214
|
if isinstance(ty, Generic):
|
|
@@ -299,7 +289,7 @@ class GenericSubstitution:
|
|
|
299
289
|
if (cty := get_concrete_type(ty)) is not None:
|
|
300
290
|
rpl = get_type_var_replacements(ty)
|
|
301
291
|
ret: list[Type] = []
|
|
302
|
-
for b in get_original_bases(cty):
|
|
292
|
+
for b in types.get_original_bases(cty):
|
|
303
293
|
bty = type_(b)
|
|
304
294
|
if isinstance(bty, Generic) and isinstance(b, type):
|
|
305
295
|
# FIXME: throws away relative types, but can't use original vars as they're class-contextual
|
omlish/sql/__init__.py
ADDED
|
File without changes
|
omlish/sql/_abc.py
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import typing as ta
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
DBAPITypeCode: ta.TypeAlias = ta.Any | None
|
|
5
|
+
|
|
6
|
+
DBAPIColumnDescription: ta.TypeAlias = tuple[
|
|
7
|
+
str,
|
|
8
|
+
DBAPITypeCode,
|
|
9
|
+
int | None,
|
|
10
|
+
int | None,
|
|
11
|
+
int | None,
|
|
12
|
+
int | None,
|
|
13
|
+
bool | None,
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class DBAPIConnection(ta.Protocol):
|
|
18
|
+
def close(self) -> object: ...
|
|
19
|
+
|
|
20
|
+
def commit(self) -> object: ...
|
|
21
|
+
|
|
22
|
+
# optional:
|
|
23
|
+
# def rollback(self) -> ta.Any: ...
|
|
24
|
+
|
|
25
|
+
def cursor(self) -> 'DBAPICursor': ...
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class DBAPICursor(ta.Protocol):
|
|
29
|
+
@property
|
|
30
|
+
def description(self) -> ta.Sequence[DBAPIColumnDescription] | None: ...
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def rowcount(self) -> int: ...
|
|
34
|
+
|
|
35
|
+
# optional:
|
|
36
|
+
# def callproc(self, procname: str, parameters: Sequence[ta.Any] = ...) -> Sequence[ta.Any]: ...
|
|
37
|
+
|
|
38
|
+
def close(self) -> object: ...
|
|
39
|
+
|
|
40
|
+
def execute(
|
|
41
|
+
self,
|
|
42
|
+
operation: str,
|
|
43
|
+
parameters: ta.Sequence[ta.Any] | ta.Mapping[str, ta.Any] = ...,
|
|
44
|
+
) -> object: ...
|
|
45
|
+
|
|
46
|
+
def executemany(
|
|
47
|
+
self,
|
|
48
|
+
operation: str,
|
|
49
|
+
seq_of_parameters: ta.Sequence[ta.Sequence[ta.Any]],
|
|
50
|
+
) -> object: ...
|
|
51
|
+
|
|
52
|
+
def fetchone(self) -> ta.Sequence[ta.Any] | None: ...
|
|
53
|
+
|
|
54
|
+
def fetchmany(self, size: int = ...) -> ta.Sequence[ta.Sequence[ta.Any]]: ...
|
|
55
|
+
|
|
56
|
+
def fetchall(self) -> ta.Sequence[ta.Sequence[ta.Any]]: ...
|
|
57
|
+
|
|
58
|
+
# optional:
|
|
59
|
+
# def nextset(self) -> None | Literal[True]: ...
|
|
60
|
+
|
|
61
|
+
arraysize: int
|
|
62
|
+
|
|
63
|
+
def setinputsizes(self, sizes: ta.Sequence[DBAPITypeCode | int | None]) -> object: ...
|
|
64
|
+
|
|
65
|
+
def setoutputsize(self, size: int, column: int = ...) -> object: ...
|
omlish/sql/dbs.py
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import typing as ta
|
|
2
|
+
import urllib.parse
|
|
3
|
+
|
|
4
|
+
from .. import dataclasses as dc
|
|
5
|
+
from .. import lang
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
##
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dc.dataclass(frozen=True, kw_only=True)
|
|
12
|
+
class DbType:
|
|
13
|
+
name: str
|
|
14
|
+
dialect_name: str
|
|
15
|
+
|
|
16
|
+
default_port: int | None = None
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class DbTypes(lang.Namespace):
|
|
20
|
+
MYSQL = DbType(
|
|
21
|
+
name='mysql',
|
|
22
|
+
dialect_name='mysql',
|
|
23
|
+
default_port=3306,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
POSTGRES = DbType(
|
|
27
|
+
name='postgres',
|
|
28
|
+
dialect_name='postgresql',
|
|
29
|
+
default_port=5432,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
SQLITE = DbType(
|
|
33
|
+
name='sqlite',
|
|
34
|
+
dialect_name='sqlite',
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
##
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class DbLoc(lang.Abstract):
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@dc.dataclass(frozen=True)
|
|
46
|
+
class UrlDbLoc(DbLoc, lang.Final):
|
|
47
|
+
url: str
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@dc.dataclass(frozen=True)
|
|
51
|
+
class HostDbLoc(DbLoc, lang.Final):
|
|
52
|
+
host: str
|
|
53
|
+
port: int | None = None
|
|
54
|
+
|
|
55
|
+
username: str | None = None
|
|
56
|
+
password: str | None = dc.xfield(default=None, repr_fn=lambda pw: '...' if pw is not None else None)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
##
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@dc.dataclass(frozen=True)
|
|
63
|
+
class DbSpec:
|
|
64
|
+
name: str
|
|
65
|
+
type: DbType
|
|
66
|
+
loc: DbLoc
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
##
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def rebuild_url(url: str, fn: ta.Callable[[urllib.parse.ParseResult], urllib.parse.ParseResult]) -> str:
|
|
73
|
+
if '://' in url:
|
|
74
|
+
engine, _, url = url.partition('://')
|
|
75
|
+
url = 'sql://' + url
|
|
76
|
+
else:
|
|
77
|
+
engine = None
|
|
78
|
+
parsed = urllib.parse.urlparse(url)
|
|
79
|
+
parsed = fn(parsed)
|
|
80
|
+
if engine is not None and parsed.scheme == 'sql':
|
|
81
|
+
parsed = parsed._replace(scheme=engine)
|
|
82
|
+
return urllib.parse.urlunparse(parsed) # noqa
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def set_url_engine(url: str, engine: str) -> str:
|
|
86
|
+
return rebuild_url(url, lambda parsed: parsed._replace(scheme=engine)) # noqa
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def set_url_database(url: str, database: str) -> str:
|
|
90
|
+
return rebuild_url(url, lambda parsed: parsed._replace(path='/' + database)) # noqa
|
omlish/stats.py
CHANGED
|
@@ -5,6 +5,7 @@ TODO:
|
|
|
5
5
|
"""
|
|
6
6
|
import bisect
|
|
7
7
|
import collections
|
|
8
|
+
import contextlib
|
|
8
9
|
import dataclasses as dc
|
|
9
10
|
import math
|
|
10
11
|
import operator
|
|
@@ -215,7 +216,7 @@ class Stats(ta.Sequence[float]):
|
|
|
215
216
|
else:
|
|
216
217
|
bins = [float(x) for x in bins]
|
|
217
218
|
if self.min < bins[0]:
|
|
218
|
-
bins = [self.min
|
|
219
|
+
bins = [self.min, *bins]
|
|
219
220
|
|
|
220
221
|
round_factor = 10.0 ** bin_digits
|
|
221
222
|
bins = [math.floor(b * round_factor) / round_factor for b in bins]
|
|
@@ -251,7 +252,7 @@ class SamplingHistogram:
|
|
|
251
252
|
sample_percentiles: list['SamplingHistogram.Percentile']
|
|
252
253
|
|
|
253
254
|
DEFAULT_SIZE = 1000
|
|
254
|
-
DEFAULT_PERCENTILES =
|
|
255
|
+
DEFAULT_PERCENTILES = (0.5, 0.75, 0.9, 0.95, 0.99)
|
|
255
256
|
|
|
256
257
|
def __init__(
|
|
257
258
|
self,
|
|
@@ -272,10 +273,10 @@ class SamplingHistogram:
|
|
|
272
273
|
|
|
273
274
|
self._percentile_pos_list = [self._calc_percentile_pos(p, self._size) for p in self._percentiles]
|
|
274
275
|
|
|
275
|
-
self._ring: list[
|
|
276
|
+
self._ring: list[SamplingHistogram.Entry | None] = [None] * size
|
|
276
277
|
self._ring_pos = 0
|
|
277
278
|
|
|
278
|
-
self._sample: list[
|
|
279
|
+
self._sample: list[SamplingHistogram.Entry | None] = [None] * size
|
|
279
280
|
self._sample_pos_queue = list(reversed(range(size)))
|
|
280
281
|
|
|
281
282
|
def add(self, value: float) -> None:
|
|
@@ -291,10 +292,8 @@ class SamplingHistogram:
|
|
|
291
292
|
|
|
292
293
|
sample_pos = None
|
|
293
294
|
if self._sample_pos_queue:
|
|
294
|
-
|
|
295
|
+
with contextlib.suppress(IndexError):
|
|
295
296
|
sample_pos = self._sample_pos_queue.pop()
|
|
296
|
-
except IndexError:
|
|
297
|
-
pass
|
|
298
297
|
if sample_pos is None:
|
|
299
298
|
sample_pos = random.randrange(0, self._size)
|
|
300
299
|
self._sample[sample_pos] = entry
|
|
@@ -303,7 +302,7 @@ class SamplingHistogram:
|
|
|
303
302
|
def _calc_percentile_pos(p: float, sz: int) -> int:
|
|
304
303
|
return int(round((p * sz) - 1))
|
|
305
304
|
|
|
306
|
-
def _calc_percentiles(self, entries: list[
|
|
305
|
+
def _calc_percentiles(self, entries: list[Entry | None]) -> list[Percentile]:
|
|
307
306
|
entries = list(filter(None, entries))
|
|
308
307
|
sz = len(entries)
|
|
309
308
|
if not sz:
|
omlish/term.py
CHANGED
omlish/testing/pydevd.py
CHANGED
|
@@ -5,8 +5,27 @@ an already-debugging PyCharm instance to debug PySpark jobs.
|
|
|
5
5
|
|
|
6
6
|
TODO:
|
|
7
7
|
- https://www.jetbrains.com/help/pycharm/remote-debugging-with-product.html#
|
|
8
|
-
-
|
|
9
|
-
|
|
8
|
+
- PyCharm.app/Contents/plugins/python/helpers/pydev/_pydevd_bundle/pydevd_constants.py -> USE_LOW_IMPACT_MONITORING
|
|
9
|
+
|
|
10
|
+
==
|
|
11
|
+
|
|
12
|
+
https://www.jetbrains.com/help/pycharm/remote-debugging-with-product.html#remote-debug-config ->
|
|
13
|
+
|
|
14
|
+
pycharm_port = 43251
|
|
15
|
+
pycharm_version = '241.18034.82'
|
|
16
|
+
buf = textwrap.dedent(f'''
|
|
17
|
+
import subprocess
|
|
18
|
+
import sys
|
|
19
|
+
subprocess.check_call([sys.executable, '-mpip', 'install', f'pydevd-pycharm~={pycharm_version}'])
|
|
20
|
+
|
|
21
|
+
import pydevd_pycharm # noqa
|
|
22
|
+
pydevd_pycharm.settrace(
|
|
23
|
+
'docker.for.mac.localhost',
|
|
24
|
+
port={pycharm_port},
|
|
25
|
+
stdoutToServer=True,
|
|
26
|
+
stderrToServer=True,
|
|
27
|
+
)
|
|
28
|
+
''') + '\n' * 2 + buf
|
|
10
29
|
"""
|
|
11
30
|
import json
|
|
12
31
|
import os
|
|
@@ -40,14 +59,14 @@ def is_debugger_call(hoist: int = 0, walk: int = 2) -> bool:
|
|
|
40
59
|
return False
|
|
41
60
|
|
|
42
61
|
|
|
43
|
-
class
|
|
62
|
+
class DebuggerCallForbiddenError(Exception):
|
|
44
63
|
pass
|
|
45
64
|
|
|
46
65
|
|
|
47
66
|
def forbid_debugger_call(hoist: int = 0) -> None:
|
|
48
67
|
# FIXME: only reentrant?
|
|
49
68
|
if not ALLOW_DEBUGGER_CALLS and is_debugger_call(hoist + 1):
|
|
50
|
-
raise
|
|
69
|
+
raise DebuggerCallForbiddenError
|
|
51
70
|
|
|
52
71
|
|
|
53
72
|
##
|
|
@@ -62,7 +81,7 @@ def silence_subprocess_check() -> None:
|
|
|
62
81
|
return
|
|
63
82
|
|
|
64
83
|
new_tb = lang.proxy_import('traceback')
|
|
65
|
-
new_tb.print_exc = lambda *a, **k: None # type: ignore
|
|
84
|
+
new_tb.print_exc = lambda *a, **k: None # type: ignore # noqa
|
|
66
85
|
pydev_monkey.traceback = new_tb
|
|
67
86
|
|
|
68
87
|
|
|
@@ -87,7 +106,7 @@ def patch_for_trio_asyncio() -> None:
|
|
|
87
106
|
|
|
88
107
|
|
|
89
108
|
@lang.cached_function
|
|
90
|
-
def _pydevd() ->
|
|
109
|
+
def _pydevd() -> types.ModuleType | None:
|
|
91
110
|
try:
|
|
92
111
|
return __import__('pydevd')
|
|
93
112
|
except ImportError:
|
|
@@ -98,7 +117,7 @@ def is_present() -> bool:
|
|
|
98
117
|
return _pydevd() is not None
|
|
99
118
|
|
|
100
119
|
|
|
101
|
-
def get_setup() ->
|
|
120
|
+
def get_setup() -> dict | None:
|
|
102
121
|
if is_present():
|
|
103
122
|
return _pydevd().SetupHolder.setup
|
|
104
123
|
else:
|
|
@@ -154,8 +173,8 @@ def save_args() -> None:
|
|
|
154
173
|
|
|
155
174
|
def maybe_reexec(
|
|
156
175
|
*,
|
|
157
|
-
file:
|
|
158
|
-
module:
|
|
176
|
+
file: str | None = None,
|
|
177
|
+
module: str | None = None,
|
|
159
178
|
silence: bool = False,
|
|
160
179
|
) -> None:
|
|
161
180
|
if ARGS_ENV_VAR not in os.environ:
|
|
@@ -188,9 +207,8 @@ def maybe_reexec(
|
|
|
188
207
|
"""))
|
|
189
208
|
file = bootstrap_path
|
|
190
209
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
raise ValueError
|
|
210
|
+
elif file is None:
|
|
211
|
+
raise ValueError
|
|
194
212
|
|
|
195
213
|
args = [sys.executable]
|
|
196
214
|
args.extend(json.loads(os.environ[ARGS_ENV_VAR]))
|
|
@@ -81,7 +81,7 @@ class Harness:
|
|
|
81
81
|
|
|
82
82
|
def __getitem__(
|
|
83
83
|
self,
|
|
84
|
-
target:
|
|
84
|
+
target: inj.Key[T] | type[T],
|
|
85
85
|
) -> T:
|
|
86
86
|
return check.not_none(self._inj)[target]
|
|
87
87
|
|
|
@@ -141,7 +141,7 @@ class HarnessPlugin:
|
|
|
141
141
|
with harness._pytest_scope_manager(PytestScope.CLASS, request): # noqa
|
|
142
142
|
yield
|
|
143
143
|
|
|
144
|
-
@pytest.fixture(scope='function', autouse=True)
|
|
144
|
+
@pytest.fixture(scope='function', autouse=True) # noqa
|
|
145
145
|
def _harness_scope_listener_function(self, harness, request):
|
|
146
146
|
with harness._pytest_scope_manager(PytestScope.FUNCTION, request): # noqa
|
|
147
147
|
yield
|
|
@@ -153,6 +153,28 @@ class HarnessPlugin:
|
|
|
153
153
|
_HARNESS_ELEMENTS_LIST: list[inj.Elements] = []
|
|
154
154
|
|
|
155
155
|
|
|
156
|
+
def register(*args: inj.Element | inj.Elements) -> None:
|
|
157
|
+
_HARNESS_ELEMENTS_LIST.append(inj.as_elements(*args))
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
TypeT = ta.TypeVar('TypeT', bound=type)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def bind(
|
|
164
|
+
scope: PytestScope | str = PytestScope.SESSION,
|
|
165
|
+
*,
|
|
166
|
+
eager: bool = False,
|
|
167
|
+
) -> ta.Callable[[TypeT], TypeT]:
|
|
168
|
+
def inner(cls):
|
|
169
|
+
pts = scope if isinstance(scope, PytestScope) else PytestScope[check.isinstance(scope, str).upper()]
|
|
170
|
+
check.isinstance(cls, type)
|
|
171
|
+
register(inj.as_elements(
|
|
172
|
+
inj.in_(cls, _SCOPES_BY_PYTEST_SCOPE[pts]),
|
|
173
|
+
))
|
|
174
|
+
return cls
|
|
175
|
+
return inner
|
|
176
|
+
|
|
177
|
+
|
|
156
178
|
@pytest.fixture(scope='session', autouse=True)
|
|
157
179
|
def harness() -> ta.Generator[Harness, None, None]:
|
|
158
180
|
with Harness(inj.as_elements(*_HARNESS_ELEMENTS_LIST)).activate() as h:
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from ... import pydevd as opd
|
|
2
|
+
from ._registry import register
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@register
|
|
6
|
+
class PydevdPlugin:
|
|
7
|
+
|
|
8
|
+
def pytest_collection(self, session):
|
|
9
|
+
setup = opd.get_setup()
|
|
10
|
+
if setup is not None:
|
|
11
|
+
if hasattr(session.config, '_env_timeout'):
|
|
12
|
+
session.config._env_timeout = None # noqa
|
|
@@ -13,7 +13,7 @@ from .... import collections as col
|
|
|
13
13
|
from ._registry import register
|
|
14
14
|
|
|
15
15
|
|
|
16
|
-
Configable =
|
|
16
|
+
Configable = pytest.FixtureRequest | pytest.Config
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
SWITCHES = col.OrderedSet([
|
|
@@ -31,13 +31,13 @@ def _get_obj_config(obj: Configable) -> pytest.Config:
|
|
|
31
31
|
raise TypeError(obj)
|
|
32
32
|
|
|
33
33
|
|
|
34
|
-
def is_disabled(obj:
|
|
34
|
+
def is_disabled(obj: Configable | None, name: str) -> bool:
|
|
35
35
|
check.isinstance(name, str)
|
|
36
36
|
check.in_(name, SWITCHES)
|
|
37
37
|
return obj is not None and _get_obj_config(obj).getoption(f'--no-{name}')
|
|
38
38
|
|
|
39
39
|
|
|
40
|
-
def skip_if_disabled(obj:
|
|
40
|
+
def skip_if_disabled(obj: Configable | None, name: str) -> None:
|
|
41
41
|
if is_disabled(obj, name):
|
|
42
42
|
pytest.skip(f'{name} disabled')
|
|
43
43
|
|