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.

Files changed (124) hide show
  1. omlish/__about__.py +1 -2
  2. omlish/__init__.py +8 -0
  3. omlish/argparse.py +4 -4
  4. omlish/asyncs/__init__.py +23 -2
  5. omlish/asyncs/anyio.py +13 -11
  6. omlish/asyncs/asyncs.py +1 -3
  7. omlish/asyncs/flavors.py +201 -0
  8. omlish/asyncs/futures.py +10 -9
  9. omlish/asyncs/trio_asyncio.py +41 -0
  10. omlish/c3.py +1 -1
  11. omlish/check.py +3 -3
  12. omlish/collections/_abc.py +2 -0
  13. omlish/collections/_io_abc.py +4 -2
  14. omlish/collections/cache/__init__.py +1 -1
  15. omlish/collections/cache/descriptor.py +8 -8
  16. omlish/collections/cache/impl.py +24 -17
  17. omlish/collections/cache/types.py +1 -1
  18. omlish/collections/coerce.py +1 -1
  19. omlish/collections/frozen.py +6 -6
  20. omlish/collections/identity.py +3 -4
  21. omlish/collections/mappings.py +2 -2
  22. omlish/collections/ordered.py +7 -7
  23. omlish/collections/skiplist.py +1 -1
  24. omlish/collections/sorted.py +1 -1
  25. omlish/collections/treap.py +25 -0
  26. omlish/collections/treapmap.py +57 -5
  27. omlish/collections/unmodifiable.py +9 -9
  28. omlish/collections/utils.py +1 -1
  29. omlish/configs/flattening.py +7 -6
  30. omlish/configs/props.py +3 -3
  31. omlish/dataclasses/__init__.py +1 -1
  32. omlish/dataclasses/impl/__init__.py +17 -1
  33. omlish/dataclasses/impl/api.py +10 -11
  34. omlish/dataclasses/impl/as_.py +4 -4
  35. omlish/dataclasses/impl/exceptions.py +1 -1
  36. omlish/dataclasses/impl/fields.py +7 -7
  37. omlish/dataclasses/impl/frozen.py +2 -2
  38. omlish/dataclasses/impl/init.py +5 -5
  39. omlish/dataclasses/impl/internals.py +1 -1
  40. omlish/dataclasses/impl/metaclass.py +1 -1
  41. omlish/dataclasses/impl/order.py +1 -1
  42. omlish/dataclasses/impl/replace.py +1 -1
  43. omlish/dataclasses/impl/repr.py +4 -4
  44. omlish/dataclasses/impl/utils.py +6 -6
  45. omlish/defs.py +13 -17
  46. omlish/{procfs.py → diag/procfs.py} +22 -24
  47. omlish/diag/ps.py +47 -0
  48. omlish/{replserver → diag/replserver}/console.py +18 -20
  49. omlish/{replserver → diag/replserver}/server.py +8 -8
  50. omlish/dispatch/dispatch.py +5 -8
  51. omlish/dispatch/functions.py +1 -1
  52. omlish/dispatch/methods.py +4 -5
  53. omlish/docker.py +1 -1
  54. omlish/dynamic.py +10 -10
  55. omlish/fnpairs.py +400 -0
  56. omlish/graphs/trees.py +13 -13
  57. omlish/inject/__init__.py +7 -7
  58. omlish/inject/elements.py +1 -1
  59. omlish/inject/exceptions.py +7 -7
  60. omlish/inject/impl/elements.py +17 -5
  61. omlish/inject/impl/injector.py +12 -10
  62. omlish/inject/impl/inspect.py +2 -2
  63. omlish/inject/impl/scopes.py +12 -11
  64. omlish/inject/proxy.py +5 -5
  65. omlish/iterators.py +19 -24
  66. omlish/json.py +143 -6
  67. omlish/lang/__init__.py +13 -4
  68. omlish/lang/cached.py +2 -5
  69. omlish/lang/classes/__init__.py +2 -2
  70. omlish/lang/classes/abstract.py +2 -2
  71. omlish/lang/classes/restrict.py +14 -14
  72. omlish/lang/classes/simple.py +1 -1
  73. omlish/lang/classes/virtual.py +5 -5
  74. omlish/lang/clsdct.py +1 -1
  75. omlish/lang/cmp.py +2 -2
  76. omlish/lang/contextmanagers.py +12 -14
  77. omlish/lang/descriptors.py +16 -4
  78. omlish/lang/exceptions.py +1 -1
  79. omlish/lang/functions.py +58 -22
  80. omlish/lang/imports.py +22 -27
  81. omlish/lang/iterables.py +2 -2
  82. omlish/lang/maybes.py +1 -0
  83. omlish/lang/objects.py +15 -9
  84. omlish/lang/resolving.py +1 -1
  85. omlish/lang/strings.py +1 -1
  86. omlish/lang/sys.py +7 -0
  87. omlish/lang/typing.py +3 -3
  88. omlish/libc.py +9 -5
  89. omlish/logs/_abc.py +5 -1
  90. omlish/logs/filters.py +2 -0
  91. omlish/logs/formatters.py +6 -2
  92. omlish/logs/utils.py +1 -1
  93. omlish/marshal/base.py +3 -3
  94. omlish/marshal/exceptions.py +1 -1
  95. omlish/marshal/global_.py +10 -4
  96. omlish/marshal/objects.py +1 -2
  97. omlish/marshal/registries.py +3 -3
  98. omlish/marshal/utils.py +2 -2
  99. omlish/marshal/values.py +1 -1
  100. omlish/math.py +9 -9
  101. omlish/reflect.py +3 -3
  102. omlish/sql/__init__.py +9 -0
  103. omlish/sql/asyncs.py +148 -0
  104. omlish/stats.py +4 -5
  105. omlish/term.py +1 -1
  106. omlish/testing/pydevd.py +28 -6
  107. omlish/testing/pytest/inject/harness.py +1 -1
  108. omlish/testing/pytest/plugins/pydevd.py +1 -1
  109. omlish/testing/pytest/plugins/switches.py +1 -1
  110. omlish/text/delimit.py +3 -6
  111. {omlish-0.0.0.dev2.dist-info → omlish-0.0.0.dev4.dist-info}/METADATA +4 -1
  112. omlish-0.0.0.dev4.dist-info/RECORD +195 -0
  113. {omlish-0.0.0.dev2.dist-info → omlish-0.0.0.dev4.dist-info}/WHEEL +1 -1
  114. omlish/lang/classes/test/test_abstract.py +0 -89
  115. omlish/lang/classes/test/test_restrict.py +0 -71
  116. omlish/lang/classes/test/test_simple.py +0 -58
  117. omlish/lang/classes/test/test_virtual.py +0 -72
  118. omlish-0.0.0.dev2.dist-info/RECORD +0 -193
  119. /omlish/{lang/classes/test → diag}/__init__.py +0 -0
  120. /omlish/{replserver → diag/replserver}/__init__.py +0 -0
  121. /omlish/{replserver → diag/replserver}/__main__.py +0 -0
  122. /omlish/sql/{_abcs.py → _abc.py} +0 -0
  123. {omlish-0.0.0.dev2.dist-info → omlish-0.0.0.dev4.dist-info}/LICENSE +0 -0
  124. {omlish-0.0.0.dev2.dist-info → omlish-0.0.0.dev4.dist-info}/top_level.txt +0 -0
@@ -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
- try:
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,
@@ -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("asdict() should be called on dataclass instances")
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("astuple() should be called on dataclass instances")
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 CheckException(Exception):
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
- if slots and f.default is not MISSING:
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
  }
@@ -3,7 +3,7 @@ import inspect
3
3
  import typing as ta
4
4
 
5
5
  from ... import lang
6
- from .exceptions import CheckException
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__': 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] + _init_params,
146
+ [self._self_name, *_init_params],
147
147
  body_lines,
148
148
  locals=locals,
149
149
  globals=self._globals,
@@ -42,7 +42,7 @@ is_dataclass_instance = dc._is_dataclass_instance # type: ignore # noqa
42
42
  ##
43
43
 
44
44
 
45
- ATOMIC_TYPES: ta.FrozenSet[type]
45
+ ATOMIC_TYPES: frozenset[type]
46
46
 
47
47
  if hasattr(dc, '_ATOMIC_TYPES'):
48
48
  ATOMIC_TYPES = getattr(dc, '_ATOMIC_TYPES')
@@ -97,7 +97,7 @@ class Data(metaclass=DataMeta):
97
97
  spi = super().__post_init__ # type: ignore # noqa
98
98
  except AttributeError:
99
99
  if args or kwargs:
100
- raise TypeError(args, kwargs)
100
+ raise TypeError(args, kwargs) from None
101
101
  else:
102
102
  spi(*args, **kwargs)
103
103
 
@@ -12,7 +12,7 @@ def cmp_fn(
12
12
  op: str,
13
13
  self_tuple: str,
14
14
  other_tuple: str,
15
- globals: Namespace,
15
+ globals: Namespace, # noqa
16
16
  ) -> ta.Callable:
17
17
  return create_fn(
18
18
  name,
@@ -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)
@@ -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__}({\", \".join(l)})"',
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"{f.name}={{self.{f.name}!r}}" for f in fields]) +
33
+ ', '.join([f'{f.name}={{self.{f.name}!r}}' for f in fields]) +
34
34
  ')"',
35
35
  ]
36
36
  fn = create_fn(
@@ -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 = " {decorator}\n" if decorator else ""
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
- return '%s@%s(%s)' % (
70
- type(obj).__name__,
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 '%s@%s(%s)' % (
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 . import iterators as it
15
- from . import json
16
- from . import lang
17
- from . import os as oos
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') -> ta.List[str]:
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/%s/stat' % (pid,)) as f:
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] + r.strip().split(' ')
109
+ return [pid.strip(), comm, *r.strip().split(' ')]
110
110
 
111
111
 
112
- def get_process_chain(pid: PidLike = 'self') -> ta.List[ta.Tuple[int, str]]:
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/%s/oom_score_adj' % (pid,), 'w') as f:
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[ta.Dict[str, ta.Any]]:
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/%s/%s' % (pid, 'smaps' if sharing else 'maps'), 'r') as map_file:
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[ta.Dict[str, int]]:
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/%s/pagemap' % (pid,), 'rb') as pagemap_file:
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[ta.Dict[str, int]]:
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
- for p in get_process_range_pagemaps(m['address'], m['end_address'], pid):
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
- for pm in get_process_range_pagemaps(m['address'], m['end_address'], pid):
279
- yield pm
277
+ yield from get_process_range_pagemaps(m['address'], m['end_address'], pid)
280
278
 
281
- lpms, rpms = [g(pid) for pid in (lpid, rpid)]
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 .. import check
28
+ from ... import check
29
29
 
30
30
 
31
31
  log = logging.getLogger(__name__)
32
32
 
33
33
 
34
- class DisconnectException(Exception):
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('%s\n' % (str(banner),))
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 %s...\n' % self.__class__.__name__)
105
+ self.write(f'now exiting {self.__class__.__name__}...\n')
108
106
 
109
107
  elif exitmsg != '':
110
- self.write('%s\n' % exitmsg)
108
+ self.write(f'{exitmsg}\n')
111
109
 
112
- except DisconnectException:
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 DisconnectException
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
- type, value, tb = sys.exc_info()
232
- sys.last_type = type
233
- sys.last_value = 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 type is SyntaxError:
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) = value.args # type: ignore
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
- value = SyntaxError(msg, (filename, lineno, offset, line))
245
- sys.last_value = value
246
- lines = traceback.format_exception_only(type, value)
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))