omlish 0.0.0.dev2__py3-none-any.whl → 0.0.0.dev4__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 -2
- omlish/__init__.py +8 -0
- omlish/argparse.py +4 -4
- omlish/asyncs/__init__.py +23 -2
- omlish/asyncs/anyio.py +13 -11
- omlish/asyncs/asyncs.py +1 -3
- omlish/asyncs/flavors.py +201 -0
- omlish/asyncs/futures.py +10 -9
- omlish/asyncs/trio_asyncio.py +41 -0
- omlish/c3.py +1 -1
- omlish/check.py +3 -3
- 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 +8 -8
- omlish/collections/cache/impl.py +24 -17
- omlish/collections/cache/types.py +1 -1
- omlish/collections/coerce.py +1 -1
- omlish/collections/frozen.py +6 -6
- omlish/collections/identity.py +3 -4
- omlish/collections/mappings.py +2 -2
- omlish/collections/ordered.py +7 -7
- omlish/collections/skiplist.py +1 -1
- omlish/collections/sorted.py +1 -1
- omlish/collections/treap.py +25 -0
- omlish/collections/treapmap.py +57 -5
- omlish/collections/unmodifiable.py +9 -9
- omlish/collections/utils.py +1 -1
- omlish/configs/flattening.py +7 -6
- omlish/configs/props.py +3 -3
- omlish/dataclasses/__init__.py +1 -1
- omlish/dataclasses/impl/__init__.py +17 -1
- omlish/dataclasses/impl/api.py +10 -11
- omlish/dataclasses/impl/as_.py +4 -4
- omlish/dataclasses/impl/exceptions.py +1 -1
- omlish/dataclasses/impl/fields.py +7 -7
- omlish/dataclasses/impl/frozen.py +2 -2
- omlish/dataclasses/impl/init.py +5 -5
- omlish/dataclasses/impl/internals.py +1 -1
- omlish/dataclasses/impl/metaclass.py +1 -1
- omlish/dataclasses/impl/order.py +1 -1
- omlish/dataclasses/impl/replace.py +1 -1
- omlish/dataclasses/impl/repr.py +4 -4
- omlish/dataclasses/impl/utils.py +6 -6
- omlish/defs.py +13 -17
- omlish/{procfs.py → diag/procfs.py} +22 -24
- omlish/diag/ps.py +47 -0
- omlish/{replserver → diag/replserver}/console.py +18 -20
- omlish/{replserver → diag/replserver}/server.py +8 -8
- omlish/dispatch/dispatch.py +5 -8
- omlish/dispatch/functions.py +1 -1
- omlish/dispatch/methods.py +4 -5
- omlish/docker.py +1 -1
- omlish/dynamic.py +10 -10
- omlish/fnpairs.py +400 -0
- omlish/graphs/trees.py +13 -13
- omlish/inject/__init__.py +7 -7
- omlish/inject/elements.py +1 -1
- omlish/inject/exceptions.py +7 -7
- omlish/inject/impl/elements.py +17 -5
- omlish/inject/impl/injector.py +12 -10
- omlish/inject/impl/inspect.py +2 -2
- omlish/inject/impl/scopes.py +12 -11
- omlish/inject/proxy.py +5 -5
- omlish/iterators.py +19 -24
- omlish/json.py +143 -6
- omlish/lang/__init__.py +13 -4
- omlish/lang/cached.py +2 -5
- omlish/lang/classes/__init__.py +2 -2
- omlish/lang/classes/abstract.py +2 -2
- omlish/lang/classes/restrict.py +14 -14
- omlish/lang/classes/simple.py +1 -1
- omlish/lang/classes/virtual.py +5 -5
- omlish/lang/clsdct.py +1 -1
- omlish/lang/cmp.py +2 -2
- omlish/lang/contextmanagers.py +12 -14
- omlish/lang/descriptors.py +16 -4
- omlish/lang/exceptions.py +1 -1
- omlish/lang/functions.py +58 -22
- omlish/lang/imports.py +22 -27
- omlish/lang/iterables.py +2 -2
- omlish/lang/maybes.py +1 -0
- omlish/lang/objects.py +15 -9
- omlish/lang/resolving.py +1 -1
- omlish/lang/strings.py +1 -1
- omlish/lang/sys.py +7 -0
- omlish/lang/typing.py +3 -3
- omlish/libc.py +9 -5
- 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 +3 -3
- omlish/marshal/exceptions.py +1 -1
- omlish/marshal/global_.py +10 -4
- omlish/marshal/objects.py +1 -2
- omlish/marshal/registries.py +3 -3
- omlish/marshal/utils.py +2 -2
- omlish/marshal/values.py +1 -1
- omlish/math.py +9 -9
- omlish/reflect.py +3 -3
- omlish/sql/__init__.py +9 -0
- omlish/sql/asyncs.py +148 -0
- omlish/stats.py +4 -5
- omlish/term.py +1 -1
- omlish/testing/pydevd.py +28 -6
- omlish/testing/pytest/inject/harness.py +1 -1
- omlish/testing/pytest/plugins/pydevd.py +1 -1
- omlish/testing/pytest/plugins/switches.py +1 -1
- omlish/text/delimit.py +3 -6
- {omlish-0.0.0.dev2.dist-info → omlish-0.0.0.dev4.dist-info}/METADATA +4 -1
- omlish-0.0.0.dev4.dist-info/RECORD +195 -0
- {omlish-0.0.0.dev2.dist-info → omlish-0.0.0.dev4.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-0.0.0.dev2.dist-info/RECORD +0 -193
- /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/sql/{_abcs.py → _abc.py} +0 -0
- {omlish-0.0.0.dev2.dist-info → omlish-0.0.0.dev4.dist-info}/LICENSE +0 -0
- {omlish-0.0.0.dev2.dist-info → omlish-0.0.0.dev4.dist-info}/top_level.txt +0 -0
omlish/dataclasses/impl/api.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import collections.abc
|
|
2
|
+
import contextlib
|
|
2
3
|
import dataclasses as dc
|
|
3
4
|
import keyword
|
|
4
5
|
import sys
|
|
@@ -18,13 +19,13 @@ from .params import ParamsExtras
|
|
|
18
19
|
MISSING = dc.MISSING
|
|
19
20
|
|
|
20
21
|
|
|
21
|
-
def field(
|
|
22
|
+
def field( # noqa
|
|
22
23
|
default=MISSING,
|
|
23
24
|
*,
|
|
24
25
|
default_factory=MISSING,
|
|
25
26
|
init=True,
|
|
26
|
-
repr=True,
|
|
27
|
-
hash=None,
|
|
27
|
+
repr=True, # noqa
|
|
28
|
+
hash=None, # noqa
|
|
28
29
|
compare=True,
|
|
29
30
|
metadata=None,
|
|
30
31
|
kw_only=MISSING,
|
|
@@ -66,12 +67,12 @@ def _strip_missing_values(d):
|
|
|
66
67
|
return {k: v for k, v in d.items() if v is not MISSING}
|
|
67
68
|
|
|
68
69
|
|
|
69
|
-
def dataclass(
|
|
70
|
+
def dataclass( # noqa
|
|
70
71
|
cls=None,
|
|
71
72
|
/,
|
|
72
73
|
*,
|
|
73
74
|
init=True,
|
|
74
|
-
repr=True,
|
|
75
|
+
repr=True, # noqa
|
|
75
76
|
eq=True,
|
|
76
77
|
order=False,
|
|
77
78
|
unsafe_hash=False,
|
|
@@ -135,14 +136,14 @@ def dataclass(
|
|
|
135
136
|
return wrap(cls)
|
|
136
137
|
|
|
137
138
|
|
|
138
|
-
def make_dataclass(
|
|
139
|
+
def make_dataclass( # noqa
|
|
139
140
|
cls_name,
|
|
140
141
|
fields,
|
|
141
142
|
*,
|
|
142
143
|
bases=(),
|
|
143
144
|
namespace=None,
|
|
144
145
|
init=True,
|
|
145
|
-
repr=True,
|
|
146
|
+
repr=True, # noqa
|
|
146
147
|
eq=True,
|
|
147
148
|
order=False,
|
|
148
149
|
unsafe_hash=False,
|
|
@@ -195,10 +196,8 @@ def make_dataclass(
|
|
|
195
196
|
try:
|
|
196
197
|
module = sys._getframemodulename(1) or '__main__' # type: ignore # noqa
|
|
197
198
|
except AttributeError:
|
|
198
|
-
|
|
199
|
+
with contextlib.suppress(AttributeError, ValueError):
|
|
199
200
|
module = sys._getframe(1).f_globals.get('__name__', '__main__') # noqa
|
|
200
|
-
except (AttributeError, ValueError):
|
|
201
|
-
pass
|
|
202
201
|
if module is not None:
|
|
203
202
|
cls.__module__ = module
|
|
204
203
|
|
|
@@ -225,7 +224,7 @@ class _ExtraParamsKwargs:
|
|
|
225
224
|
pass
|
|
226
225
|
|
|
227
226
|
|
|
228
|
-
def extra_params(
|
|
227
|
+
def extra_params( # noqa
|
|
229
228
|
*,
|
|
230
229
|
reorder=MISSING,
|
|
231
230
|
cache_hash=MISSING,
|
omlish/dataclasses/impl/as_.py
CHANGED
|
@@ -5,9 +5,9 @@ from .internals import is_dataclass_instance
|
|
|
5
5
|
from .internals import ATOMIC_TYPES
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
def asdict(obj, *, dict_factory=dict):
|
|
8
|
+
def asdict(obj, *, dict_factory=dict): # noqa
|
|
9
9
|
if not is_dataclass_instance(obj): # noqa
|
|
10
|
-
raise TypeError(
|
|
10
|
+
raise TypeError('asdict() should be called on dataclass instances')
|
|
11
11
|
return _asdict_inner(obj, dict_factory)
|
|
12
12
|
|
|
13
13
|
|
|
@@ -40,9 +40,9 @@ def _asdict_inner(obj, dict_factory):
|
|
|
40
40
|
return copy.deepcopy(obj)
|
|
41
41
|
|
|
42
42
|
|
|
43
|
-
def astuple(obj, *, tuple_factory=tuple):
|
|
43
|
+
def astuple(obj, *, tuple_factory=tuple): # noqa
|
|
44
44
|
if not is_dataclass_instance(obj):
|
|
45
|
-
raise TypeError(
|
|
45
|
+
raise TypeError('astuple() should be called on dataclass instances')
|
|
46
46
|
return _astuple_inner(obj, tuple_factory)
|
|
47
47
|
|
|
48
48
|
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
class
|
|
1
|
+
class CheckError(Exception):
|
|
2
2
|
pass
|
|
@@ -55,7 +55,7 @@ def preprocess_field(
|
|
|
55
55
|
if ft in (FieldType.CLASS, FieldType.INIT):
|
|
56
56
|
if f.default_factory is not MISSING:
|
|
57
57
|
raise TypeError(f'field {f.name} cannot have a default factory')
|
|
58
|
-
f._field_type = ft.value # type: ignore
|
|
58
|
+
f._field_type = ft.value # type: ignore # noqa
|
|
59
59
|
|
|
60
60
|
if ft in (FieldType.INSTANCE, FieldType.INIT):
|
|
61
61
|
if f.kw_only is MISSING:
|
|
@@ -88,7 +88,7 @@ def field_assign(
|
|
|
88
88
|
def field_init(
|
|
89
89
|
f: dc.Field,
|
|
90
90
|
frozen: bool,
|
|
91
|
-
locals: dict[str, ta.Any],
|
|
91
|
+
locals: dict[str, ta.Any], # noqa
|
|
92
92
|
self_name: str,
|
|
93
93
|
slots: bool,
|
|
94
94
|
) -> ta.Sequence[str]:
|
|
@@ -135,12 +135,12 @@ def field_init(
|
|
|
135
135
|
locals[default_name] = f.default
|
|
136
136
|
value = f.name
|
|
137
137
|
|
|
138
|
+
elif slots and f.default is not MISSING:
|
|
139
|
+
locals[default_name] = f.default
|
|
140
|
+
value = default_name
|
|
141
|
+
|
|
138
142
|
else:
|
|
139
|
-
|
|
140
|
-
locals[default_name] = f.default
|
|
141
|
-
value = default_name
|
|
142
|
-
else:
|
|
143
|
-
pass
|
|
143
|
+
pass
|
|
144
144
|
|
|
145
145
|
if value is not None and field_type(f) is not FieldType.INIT:
|
|
146
146
|
lines.append(field_assign(frozen, f.name, value, self_name, fx.override)) # noqa
|
|
@@ -10,9 +10,9 @@ from .utils import set_new_attribute
|
|
|
10
10
|
def frozen_get_del_attr(
|
|
11
11
|
cls: type,
|
|
12
12
|
fields: ta.Sequence[dc.Field],
|
|
13
|
-
globals: Namespace,
|
|
13
|
+
globals: Namespace, # noqa
|
|
14
14
|
) -> tuple[ta.Callable, ta.Callable]:
|
|
15
|
-
locals = {
|
|
15
|
+
locals = { # noqa
|
|
16
16
|
'cls': cls,
|
|
17
17
|
'FrozenInstanceError': dc.FrozenInstanceError,
|
|
18
18
|
}
|
omlish/dataclasses/impl/init.py
CHANGED
|
@@ -3,7 +3,7 @@ import inspect
|
|
|
3
3
|
import typing as ta
|
|
4
4
|
|
|
5
5
|
from ... import lang
|
|
6
|
-
from .exceptions import
|
|
6
|
+
from .exceptions import CheckError
|
|
7
7
|
from .fields import field_init
|
|
8
8
|
from .fields import field_type
|
|
9
9
|
from .fields import has_default
|
|
@@ -64,7 +64,7 @@ class InitBuilder:
|
|
|
64
64
|
fields: ta.Mapping[str, dc.Field],
|
|
65
65
|
has_post_init: bool,
|
|
66
66
|
self_name: str,
|
|
67
|
-
globals: Namespace,
|
|
67
|
+
globals: Namespace, # noqa
|
|
68
68
|
) -> None:
|
|
69
69
|
super().__init__()
|
|
70
70
|
|
|
@@ -86,7 +86,7 @@ class InitBuilder:
|
|
|
86
86
|
elif seen_default:
|
|
87
87
|
raise TypeError(f'non-default argument {f.name!r} follows default argument {seen_default.name!r}')
|
|
88
88
|
|
|
89
|
-
locals: dict[str, ta.Any] = {}
|
|
89
|
+
locals: dict[str, ta.Any] = {} # noqa
|
|
90
90
|
|
|
91
91
|
if self._info.params_extras.generic_init:
|
|
92
92
|
get_fty = lambda f: self._info.generic_replaced_field_annotations[f.name]
|
|
@@ -99,7 +99,7 @@ class InitBuilder:
|
|
|
99
99
|
'__dataclass_builtins_object__': object,
|
|
100
100
|
'__dataclass_builtins_isinstance__': isinstance,
|
|
101
101
|
'__dataclass_builtins_TypeError__': TypeError,
|
|
102
|
-
'__dataclass_CheckException__':
|
|
102
|
+
'__dataclass_CheckException__': CheckError,
|
|
103
103
|
})
|
|
104
104
|
|
|
105
105
|
body_lines: list[str] = []
|
|
@@ -143,7 +143,7 @@ class InitBuilder:
|
|
|
143
143
|
|
|
144
144
|
return create_fn(
|
|
145
145
|
'__init__',
|
|
146
|
-
[self._self_name
|
|
146
|
+
[self._self_name, *_init_params],
|
|
147
147
|
body_lines,
|
|
148
148
|
locals=locals,
|
|
149
149
|
globals=self._globals,
|
omlish/dataclasses/impl/order.py
CHANGED
|
@@ -11,7 +11,7 @@ from .utils import set_new_attribute
|
|
|
11
11
|
MISSING = dc.MISSING
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
def replace(obj, /, **changes):
|
|
14
|
+
def replace(obj, /, **changes): # noqa
|
|
15
15
|
if not is_dataclass_instance(obj):
|
|
16
16
|
raise TypeError('replace() should be called on dataclass instances')
|
|
17
17
|
return _replace(obj, **changes)
|
omlish/dataclasses/impl/repr.py
CHANGED
|
@@ -11,9 +11,9 @@ from .utils import set_new_attribute
|
|
|
11
11
|
|
|
12
12
|
def repr_fn(
|
|
13
13
|
fields: ta.Sequence[dc.Field],
|
|
14
|
-
globals: Namespace,
|
|
14
|
+
globals: Namespace, # noqa
|
|
15
15
|
) -> ta.Callable:
|
|
16
|
-
locals: dict[str, ta.Any] = {}
|
|
16
|
+
locals: dict[str, ta.Any] = {} # noqa
|
|
17
17
|
if any(get_field_extras(f).repr_fn is not None for f in fields):
|
|
18
18
|
lst: list[str] = []
|
|
19
19
|
for f in fields:
|
|
@@ -25,12 +25,12 @@ def repr_fn(
|
|
|
25
25
|
src = [
|
|
26
26
|
'l = []',
|
|
27
27
|
*lst,
|
|
28
|
-
'return f"{self.__class__.__qualname__}({
|
|
28
|
+
'return f"{self.__class__.__qualname__}({", ".join(l)})"',
|
|
29
29
|
]
|
|
30
30
|
else:
|
|
31
31
|
src = [
|
|
32
32
|
'return f"{self.__class__.__qualname__}(' +
|
|
33
|
-
', '.join([f
|
|
33
|
+
', '.join([f'{f.name}={{self.{f.name}!r}}' for f in fields]) +
|
|
34
34
|
')"',
|
|
35
35
|
]
|
|
36
36
|
fn = create_fn(
|
omlish/dataclasses/impl/utils.py
CHANGED
|
@@ -16,15 +16,15 @@ def create_fn(
|
|
|
16
16
|
args: ta.Sequence[str],
|
|
17
17
|
body: ta.Sequence[str],
|
|
18
18
|
*,
|
|
19
|
-
globals: Namespace | None = None,
|
|
20
|
-
locals: Namespace | None = None,
|
|
19
|
+
globals: Namespace | None = None, # noqa
|
|
20
|
+
locals: Namespace | None = None, # noqa
|
|
21
21
|
return_type: lang.Maybe[ta.Any] = lang.empty(),
|
|
22
22
|
) -> ta.Callable:
|
|
23
23
|
check.not_isinstance(args, str)
|
|
24
24
|
check.not_isinstance(body, str)
|
|
25
25
|
|
|
26
26
|
if locals is None:
|
|
27
|
-
locals = {}
|
|
27
|
+
locals = {} # noqa
|
|
28
28
|
return_annotation = ''
|
|
29
29
|
if return_type.present:
|
|
30
30
|
locals['__dataclass_return_type__'] = return_type()
|
|
@@ -43,7 +43,7 @@ def create_fn(
|
|
|
43
43
|
|
|
44
44
|
# TODO: https://github.com/python/cpython/commit/8945b7ff55b87d11c747af2dad0e3e4d631e62d6
|
|
45
45
|
class FuncBuilder:
|
|
46
|
-
def __init__(self, globals: Namespace) -> None:
|
|
46
|
+
def __init__(self, globals: Namespace) -> None: # noqa
|
|
47
47
|
super().__init__()
|
|
48
48
|
|
|
49
49
|
self.names: list[str] = []
|
|
@@ -59,7 +59,7 @@ class FuncBuilder:
|
|
|
59
59
|
args: ta.Sequence[str],
|
|
60
60
|
body: ta.Sequence[str],
|
|
61
61
|
*,
|
|
62
|
-
locals: Namespace | None = None,
|
|
62
|
+
locals: Namespace | None = None, # noqa
|
|
63
63
|
return_type: lang.Maybe[ta.Any] = lang.empty(),
|
|
64
64
|
overwrite_error: bool = False,
|
|
65
65
|
unconditional_add: bool = False,
|
|
@@ -91,7 +91,7 @@ class FuncBuilder:
|
|
|
91
91
|
body = textwrap.indent('\n'.join(body), ' ')
|
|
92
92
|
|
|
93
93
|
# Compute the text of the entire function, add it to the text we're generating.
|
|
94
|
-
deco_str =
|
|
94
|
+
deco_str = ' {decorator}\n' if decorator else ''
|
|
95
95
|
self.src.append(f'{deco_str} def {name}({args}){return_annotation}:\n{body}')
|
|
96
96
|
|
|
97
97
|
def add_fns_to_class(self, cls: type) -> None:
|
omlish/defs.py
CHANGED
|
@@ -4,6 +4,8 @@ class definitions. Should be used sparingly for methods not directly used by hum
|
|
|
4
4
|
remain @property's for type annotation, tool assistance, debugging, and otherwise, but these are still nice to have in
|
|
5
5
|
certain circumstances (the real-world alternative usually being simply not adding them).
|
|
6
6
|
"""
|
|
7
|
+
# ruff: noqa: ANN201
|
|
8
|
+
|
|
7
9
|
import abc
|
|
8
10
|
import functools
|
|
9
11
|
import operator
|
|
@@ -62,29 +64,23 @@ def build_attr_repr(obj, *, mro=False):
|
|
|
62
64
|
if mro:
|
|
63
65
|
attrs = [
|
|
64
66
|
attr
|
|
65
|
-
for ty in sorted(reversed(type(obj).__mro__), key=lambda _ty: _ty.__dict__.get('__repr_priority__', 0))
|
|
67
|
+
for ty in sorted(reversed(type(obj).__mro__), key=lambda _ty: _ty.__dict__.get('__repr_priority__', 0)) # noqa
|
|
66
68
|
for attr in ty.__dict__.get('__repr_attrs__', [])]
|
|
67
69
|
else:
|
|
68
70
|
attrs = obj.__repr_attrs__
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
hex(id(obj))[2:],
|
|
72
|
-
', '.join('%s=%s' % (attr, '<self>' if value is obj else _repr(value))
|
|
73
|
-
for attr in attrs for value in [getattr(obj, attr)]))
|
|
71
|
+
s = ', '.join(f'{a}={"<self>" if v is obj else _repr(v)}' for a in attrs for v in [getattr(obj, a)])
|
|
72
|
+
return f'{type(obj).__name__}@{hex(id(obj))[2:]}({s})'
|
|
74
73
|
|
|
75
74
|
|
|
76
75
|
@_repr_guard
|
|
77
76
|
def build_repr(obj, *attrs):
|
|
78
|
-
return '
|
|
79
|
-
type(obj).__name__,
|
|
80
|
-
hex(id(obj))[2:],
|
|
81
|
-
', '.join('%s=%r' % (attr, getattr(obj, attr)) for attr in attrs))
|
|
77
|
+
return f'{type(obj).__name__}@{hex(id(obj))[2:]}({", ".join(f"{attr}={getattr(obj, attr)!r}" for attr in attrs)})'
|
|
82
78
|
|
|
83
79
|
|
|
84
80
|
@_basic
|
|
85
81
|
@lang.cls_dct_fn()
|
|
86
|
-
def repr(cls_dct, *attrs, mro=False, priority=None):
|
|
87
|
-
def __repr__(self):
|
|
82
|
+
def repr(cls_dct, *attrs, mro=False, priority=None): # noqa
|
|
83
|
+
def __repr__(self): # noqa
|
|
88
84
|
return build_attr_repr(self, mro=mro)
|
|
89
85
|
|
|
90
86
|
cls_dct['__repr_attrs__'] = attrs
|
|
@@ -95,7 +91,7 @@ def repr(cls_dct, *attrs, mro=False, priority=None):
|
|
|
95
91
|
|
|
96
92
|
@lang.cls_dct_fn()
|
|
97
93
|
def bare_repr(cls_dct, *attrs):
|
|
98
|
-
def __repr__(self):
|
|
94
|
+
def __repr__(self): # noqa
|
|
99
95
|
return lang.attr_repr(self, *attrs)
|
|
100
96
|
|
|
101
97
|
cls_dct['__repr__'] = __repr__
|
|
@@ -103,7 +99,7 @@ def bare_repr(cls_dct, *attrs):
|
|
|
103
99
|
|
|
104
100
|
@lang.cls_dct_fn()
|
|
105
101
|
def name_repr(cls_dct):
|
|
106
|
-
def __repr__(self):
|
|
102
|
+
def __repr__(self): # noqa
|
|
107
103
|
return self.__name__
|
|
108
104
|
|
|
109
105
|
cls_dct['__repr__'] = __repr__
|
|
@@ -111,7 +107,7 @@ def name_repr(cls_dct):
|
|
|
111
107
|
|
|
112
108
|
@lang.cls_dct_fn()
|
|
113
109
|
def ne(cls_dct):
|
|
114
|
-
def __ne__(self, other):
|
|
110
|
+
def __ne__(self, other): # noqa
|
|
115
111
|
return not (self == other)
|
|
116
112
|
|
|
117
113
|
cls_dct['__ne__'] = __ne__
|
|
@@ -137,12 +133,12 @@ def no_order(cls_dct, *, raise_=None):
|
|
|
137
133
|
@_basic
|
|
138
134
|
@lang.cls_dct_fn()
|
|
139
135
|
def hash_eq(cls_dct, *attrs):
|
|
140
|
-
def __hash__(self):
|
|
136
|
+
def __hash__(self): # noqa
|
|
141
137
|
return hash(tuple(getattr(self, attr) for attr in attrs))
|
|
142
138
|
|
|
143
139
|
cls_dct['__hash__'] = __hash__
|
|
144
140
|
|
|
145
|
-
def __eq__(self, other):
|
|
141
|
+
def __eq__(self, other): # noqa
|
|
146
142
|
if type(other) is not type(self):
|
|
147
143
|
return False
|
|
148
144
|
for attr in attrs:
|
|
@@ -11,10 +11,10 @@ import struct
|
|
|
11
11
|
import sys
|
|
12
12
|
import typing as ta
|
|
13
13
|
|
|
14
|
-
from
|
|
15
|
-
from
|
|
16
|
-
from
|
|
17
|
-
from
|
|
14
|
+
from .. import iterators as it
|
|
15
|
+
from .. import json
|
|
16
|
+
from .. import lang
|
|
17
|
+
from .. import os as oos
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
log = logging.getLogger(__name__)
|
|
@@ -98,18 +98,18 @@ def _check_linux() -> None:
|
|
|
98
98
|
raise OSError
|
|
99
99
|
|
|
100
100
|
|
|
101
|
-
def get_process_stats(pid: PidLike = 'self') ->
|
|
101
|
+
def get_process_stats(pid: PidLike = 'self') -> list[str]:
|
|
102
102
|
"""http://man7.org/linux/man-pages/man5/proc.5.html -> /proc/[pid]/stat"""
|
|
103
103
|
|
|
104
104
|
_check_linux()
|
|
105
|
-
with open('/proc
|
|
105
|
+
with open(f'/proc/{pid}/stat') as f:
|
|
106
106
|
buf = f.read()
|
|
107
107
|
l, _, r = buf.rpartition(')')
|
|
108
108
|
pid, _, comm = l.partition('(')
|
|
109
|
-
return [pid.strip(), comm
|
|
109
|
+
return [pid.strip(), comm, *r.strip().split(' ')]
|
|
110
110
|
|
|
111
111
|
|
|
112
|
-
def get_process_chain(pid: PidLike = 'self') ->
|
|
112
|
+
def get_process_chain(pid: PidLike = 'self') -> list[tuple[int, str]]:
|
|
113
113
|
_check_linux()
|
|
114
114
|
lst = []
|
|
115
115
|
while pid:
|
|
@@ -144,7 +144,7 @@ def get_process_rss(pid: PidLike = 'self') -> int:
|
|
|
144
144
|
|
|
145
145
|
def set_process_oom_score_adj(score: str, pid: PidLike = 'self') -> None:
|
|
146
146
|
_check_linux()
|
|
147
|
-
with open('/proc
|
|
147
|
+
with open(f'/proc/{pid}/oom_score_adj', 'w') as f:
|
|
148
148
|
f.write(str(score))
|
|
149
149
|
|
|
150
150
|
|
|
@@ -160,11 +160,11 @@ MAP_LINE_RX = re.compile(
|
|
|
160
160
|
)
|
|
161
161
|
|
|
162
162
|
|
|
163
|
-
def get_process_maps(pid: PidLike = 'self', sharing: bool = False) -> ta.Iterator[
|
|
163
|
+
def get_process_maps(pid: PidLike = 'self', sharing: bool = False) -> ta.Iterator[dict[str, ta.Any]]:
|
|
164
164
|
"""http://man7.org/linux/man-pages/man5/proc.5.html -> /proc/[pid]/maps"""
|
|
165
165
|
|
|
166
166
|
_check_linux()
|
|
167
|
-
with open('/proc
|
|
167
|
+
with open(f'/proc/{pid}/{"smaps" if sharing else "maps"}') as map_file:
|
|
168
168
|
while True:
|
|
169
169
|
line = map_file.readline()
|
|
170
170
|
if not line:
|
|
@@ -210,14 +210,14 @@ PAGEMAP_KEYS = (
|
|
|
210
210
|
)
|
|
211
211
|
|
|
212
212
|
|
|
213
|
-
def get_process_range_pagemaps(start: int, end: int, pid: PidLike = 'self') -> ta.Iterable[
|
|
213
|
+
def get_process_range_pagemaps(start: int, end: int, pid: PidLike = 'self') -> ta.Iterable[dict[str, int]]:
|
|
214
214
|
"""https://www.kernel.org/doc/Documentation/vm/pagemap.txt"""
|
|
215
215
|
|
|
216
216
|
_check_linux()
|
|
217
217
|
offset = (start // oos.PAGE_SIZE) * 8
|
|
218
218
|
npages = ((end - start) // oos.PAGE_SIZE)
|
|
219
219
|
size = npages * 8
|
|
220
|
-
with open('/proc
|
|
220
|
+
with open(f'/proc/{pid}/pagemap', 'rb') as pagemap_file:
|
|
221
221
|
pagemap_file.seek(offset)
|
|
222
222
|
pagemap_buf = pagemap_file.read(size)
|
|
223
223
|
if not pagemap_buf:
|
|
@@ -237,14 +237,13 @@ def get_process_range_pagemaps(start: int, end: int, pid: PidLike = 'self') -> t
|
|
|
237
237
|
}
|
|
238
238
|
|
|
239
239
|
|
|
240
|
-
def get_process_pagemaps(pid: PidLike = 'self') -> ta.Iterable[
|
|
240
|
+
def get_process_pagemaps(pid: PidLike = 'self') -> ta.Iterable[dict[str, int]]:
|
|
241
241
|
_check_linux()
|
|
242
242
|
for m in get_process_maps(pid):
|
|
243
|
-
|
|
244
|
-
yield p
|
|
243
|
+
yield from get_process_range_pagemaps(m['address'], m['end_address'], pid)
|
|
245
244
|
|
|
246
245
|
|
|
247
|
-
def _dump_cmd(args):
|
|
246
|
+
def _dump_cmd(args: ta.Any) -> None:
|
|
248
247
|
total = 0
|
|
249
248
|
dirty_total = 0
|
|
250
249
|
for m in get_process_maps(args.pid, sharing=True):
|
|
@@ -264,7 +263,7 @@ def _dump_cmd(args):
|
|
|
264
263
|
sys.stdout.write('\n')
|
|
265
264
|
|
|
266
265
|
|
|
267
|
-
def _cmp_cmd(args):
|
|
266
|
+
def _cmp_cmd(args: ta.Any) -> None:
|
|
268
267
|
if len(args.pids) == 1:
|
|
269
268
|
[rpid] = args.pids
|
|
270
269
|
lpid = get_process_chain(rpid)[1][0]
|
|
@@ -273,12 +272,11 @@ def _cmp_cmd(args):
|
|
|
273
272
|
else:
|
|
274
273
|
raise TypeError('Invalid arguments')
|
|
275
274
|
|
|
276
|
-
def g(pid):
|
|
275
|
+
def g(pid: int) -> ta.Iterator[dict[str, int]]:
|
|
277
276
|
for m in get_process_maps(pid, sharing=True):
|
|
278
|
-
|
|
279
|
-
yield pm
|
|
277
|
+
yield from get_process_range_pagemaps(m['address'], m['end_address'], pid)
|
|
280
278
|
|
|
281
|
-
lpms, rpms =
|
|
279
|
+
lpms, rpms = (g(pid) for pid in (lpid, rpid))
|
|
282
280
|
|
|
283
281
|
l_pages = 0
|
|
284
282
|
r_pages = 0
|
|
@@ -289,7 +287,7 @@ def _cmp_cmd(args):
|
|
|
289
287
|
l_pages += 1
|
|
290
288
|
elif l is None and r is not None:
|
|
291
289
|
r_pages += 1
|
|
292
|
-
elif l['pfn'] != r['pfn']:
|
|
290
|
+
elif l['pfn'] != r['pfn']: # type: ignore
|
|
293
291
|
c_pages += 1
|
|
294
292
|
else:
|
|
295
293
|
continue
|
|
@@ -310,7 +308,7 @@ def _cmp_cmd(args):
|
|
|
310
308
|
sys.stdout.write('\n')
|
|
311
309
|
|
|
312
310
|
|
|
313
|
-
def _main():
|
|
311
|
+
def _main() -> None:
|
|
314
312
|
_check_linux()
|
|
315
313
|
|
|
316
314
|
arg_parser = argparse.ArgumentParser()
|
omlish/diag/ps.py
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import dataclasses as dc
|
|
2
|
+
import os
|
|
3
|
+
import subprocess
|
|
4
|
+
|
|
5
|
+
from .. import lang
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dc.dataclass(frozen=True)
|
|
9
|
+
class PsItem:
|
|
10
|
+
pid: int
|
|
11
|
+
ppid: int
|
|
12
|
+
cmd: str
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def get_ps_item(pid: int, timeout: lang.Timeout | None = None) -> PsItem:
|
|
16
|
+
timeout = lang.timeout(timeout)
|
|
17
|
+
out = subprocess.check_output(
|
|
18
|
+
['ps', '-o', 'pid=,ppid=,command=', str(int(pid))],
|
|
19
|
+
timeout=timeout.or_(None),
|
|
20
|
+
).decode().strip()
|
|
21
|
+
opid, _, rest = out.partition(' ')
|
|
22
|
+
ppid, _, cmd = rest.strip().partition(' ')
|
|
23
|
+
return PsItem(
|
|
24
|
+
int(opid),
|
|
25
|
+
int(ppid),
|
|
26
|
+
cmd.strip(),
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def get_ps_lineage(pid: int, timeout: lang.Timeout | None = None) -> list[PsItem]:
|
|
31
|
+
timeout = lang.timeout(timeout)
|
|
32
|
+
ret: list[PsItem] = []
|
|
33
|
+
while True:
|
|
34
|
+
cur = get_ps_item(pid, timeout)
|
|
35
|
+
if cur.ppid < 1:
|
|
36
|
+
break
|
|
37
|
+
ret.append(cur)
|
|
38
|
+
pid = cur.ppid
|
|
39
|
+
return ret
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _main() -> None:
|
|
43
|
+
print(get_ps_lineage(os.getpid()))
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
if __name__ == '__main__':
|
|
47
|
+
_main()
|
|
@@ -25,13 +25,13 @@ import traceback
|
|
|
25
25
|
import types
|
|
26
26
|
import typing as ta
|
|
27
27
|
|
|
28
|
-
from
|
|
28
|
+
from ... import check
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
log = logging.getLogger(__name__)
|
|
32
32
|
|
|
33
33
|
|
|
34
|
-
class
|
|
34
|
+
class DisconnectError(Exception):
|
|
35
35
|
pass
|
|
36
36
|
|
|
37
37
|
|
|
@@ -43,13 +43,13 @@ class InteractiveSocketConsole:
|
|
|
43
43
|
def __init__(
|
|
44
44
|
self,
|
|
45
45
|
conn: sock.socket,
|
|
46
|
-
locals: dict[str, ta.Any] | None = None,
|
|
46
|
+
locals: dict[str, ta.Any] | None = None, # noqa
|
|
47
47
|
filename: str = '<console>',
|
|
48
48
|
) -> None:
|
|
49
49
|
super().__init__()
|
|
50
50
|
|
|
51
51
|
if locals is None:
|
|
52
|
-
locals = {
|
|
52
|
+
locals = { # noqa
|
|
53
53
|
'__name__': '__console__',
|
|
54
54
|
'__doc__': None,
|
|
55
55
|
'__console__': self,
|
|
@@ -81,11 +81,9 @@ class InteractiveSocketConsole:
|
|
|
81
81
|
ps2 = getattr(sys, 'ps2', '... ')
|
|
82
82
|
|
|
83
83
|
if banner is None:
|
|
84
|
-
self.write(
|
|
85
|
-
'Python %s on %s\n%s\n(%s)\n' %
|
|
86
|
-
(sys.version, sys.platform, self.CPRT, self.__class__.__name__))
|
|
84
|
+
self.write(f'Python {sys.version} on {sys.platform}\n{self.CPRT}\n({self.__class__.__name__})\n')
|
|
87
85
|
elif banner:
|
|
88
|
-
self.write('
|
|
86
|
+
self.write(f'{banner!s}\n')
|
|
89
87
|
|
|
90
88
|
more = False
|
|
91
89
|
while True:
|
|
@@ -104,12 +102,12 @@ class InteractiveSocketConsole:
|
|
|
104
102
|
more = False
|
|
105
103
|
|
|
106
104
|
if exitmsg is None:
|
|
107
|
-
self.write('now exiting
|
|
105
|
+
self.write(f'now exiting {self.__class__.__name__}...\n')
|
|
108
106
|
|
|
109
107
|
elif exitmsg != '':
|
|
110
|
-
self.write('
|
|
108
|
+
self.write(f'{exitmsg}\n')
|
|
111
109
|
|
|
112
|
-
except
|
|
110
|
+
except DisconnectError:
|
|
113
111
|
pass
|
|
114
112
|
|
|
115
113
|
except OSError as oe:
|
|
@@ -133,7 +131,7 @@ class InteractiveSocketConsole:
|
|
|
133
131
|
while True:
|
|
134
132
|
b = self._conn.recv(1)
|
|
135
133
|
if not b:
|
|
136
|
-
raise
|
|
134
|
+
raise DisconnectError
|
|
137
135
|
if b == b'\n':
|
|
138
136
|
break
|
|
139
137
|
buf += b
|
|
@@ -228,20 +226,20 @@ class InteractiveSocketConsole:
|
|
|
228
226
|
last_tb = ei = None # type: ignore # noqa
|
|
229
227
|
|
|
230
228
|
def show_syntax_error(self, filename: str | None = None) -> None:
|
|
231
|
-
|
|
232
|
-
sys.last_type =
|
|
233
|
-
sys.last_value =
|
|
229
|
+
et, e, tb = sys.exc_info()
|
|
230
|
+
sys.last_type = et
|
|
231
|
+
sys.last_value = e
|
|
234
232
|
sys.last_traceback = tb
|
|
235
|
-
if filename and
|
|
233
|
+
if filename and et is SyntaxError:
|
|
236
234
|
# Work hard to stuff the correct filename in the exception
|
|
237
235
|
try:
|
|
238
|
-
msg, (dummy_filename, lineno, offset, line) =
|
|
236
|
+
msg, (dummy_filename, lineno, offset, line) = e.args # type: ignore
|
|
239
237
|
except ValueError:
|
|
240
238
|
# Not the format we expect; leave it alone
|
|
241
239
|
pass
|
|
242
240
|
else:
|
|
243
241
|
# Stuff in the right filename
|
|
244
|
-
|
|
245
|
-
sys.last_value =
|
|
246
|
-
lines = traceback.format_exception_only(
|
|
242
|
+
e = SyntaxError(msg, (filename, lineno, offset, line))
|
|
243
|
+
sys.last_value = e
|
|
244
|
+
lines = traceback.format_exception_only(et, e)
|
|
247
245
|
self.write(''.join(lines))
|