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.

Files changed (147) hide show
  1. omlish/__about__.py +2 -3
  2. omlish/argparse.py +8 -8
  3. omlish/asyncs/__init__.py +2 -2
  4. omlish/asyncs/anyio.py +64 -1
  5. omlish/asyncs/asyncs.py +1 -3
  6. omlish/asyncs/futures.py +16 -15
  7. omlish/c3.py +5 -5
  8. omlish/check.py +8 -8
  9. omlish/collections/__init__.py +98 -63
  10. omlish/collections/_abc.py +2 -0
  11. omlish/collections/_io_abc.py +4 -2
  12. omlish/collections/cache/__init__.py +1 -1
  13. omlish/collections/cache/descriptor.py +12 -12
  14. omlish/collections/cache/impl.py +27 -20
  15. omlish/collections/cache/types.py +1 -1
  16. omlish/collections/coerce.py +44 -44
  17. omlish/collections/frozen.py +9 -9
  18. omlish/collections/identity.py +4 -5
  19. omlish/collections/mappings.py +5 -5
  20. omlish/collections/ordered.py +8 -8
  21. omlish/collections/skiplist.py +7 -7
  22. omlish/collections/sorted.py +4 -4
  23. omlish/collections/treap.py +42 -17
  24. omlish/collections/treapmap.py +59 -7
  25. omlish/collections/unmodifiable.py +25 -24
  26. omlish/collections/utils.py +1 -1
  27. omlish/configs/flattening.py +8 -7
  28. omlish/configs/props.py +3 -3
  29. omlish/dataclasses/__init__.py +1 -1
  30. omlish/dataclasses/impl/__init__.py +18 -0
  31. omlish/dataclasses/impl/api.py +15 -24
  32. omlish/dataclasses/impl/as_.py +4 -4
  33. omlish/dataclasses/impl/exceptions.py +1 -1
  34. omlish/dataclasses/impl/fields.py +8 -8
  35. omlish/dataclasses/impl/frozen.py +2 -2
  36. omlish/dataclasses/impl/init.py +6 -6
  37. omlish/dataclasses/impl/internals.py +16 -1
  38. omlish/dataclasses/impl/main.py +4 -4
  39. omlish/dataclasses/impl/metaclass.py +2 -2
  40. omlish/dataclasses/impl/metadata.py +1 -1
  41. omlish/dataclasses/impl/order.py +2 -2
  42. omlish/dataclasses/impl/params.py +4 -38
  43. omlish/dataclasses/impl/reflect.py +1 -7
  44. omlish/dataclasses/impl/replace.py +1 -1
  45. omlish/dataclasses/impl/repr.py +24 -6
  46. omlish/dataclasses/impl/simple.py +2 -2
  47. omlish/dataclasses/impl/slots.py +2 -2
  48. omlish/dataclasses/impl/utils.py +7 -7
  49. omlish/defs.py +13 -17
  50. omlish/diag/procfs.py +334 -0
  51. omlish/diag/ps.py +47 -0
  52. omlish/{replserver → diag/replserver}/console.py +26 -28
  53. omlish/{replserver → diag/replserver}/server.py +12 -12
  54. omlish/dispatch/dispatch.py +14 -16
  55. omlish/dispatch/functions.py +1 -1
  56. omlish/dispatch/methods.py +6 -7
  57. omlish/docker.py +8 -6
  58. omlish/dynamic.py +13 -13
  59. omlish/fnpairs.py +311 -0
  60. omlish/graphs/dot/items.py +1 -1
  61. omlish/graphs/trees.py +25 -31
  62. omlish/inject/__init__.py +7 -7
  63. omlish/inject/elements.py +2 -2
  64. omlish/inject/exceptions.py +8 -8
  65. omlish/inject/impl/elements.py +4 -4
  66. omlish/inject/impl/injector.py +6 -6
  67. omlish/inject/impl/inspect.py +3 -3
  68. omlish/inject/impl/scopes.py +9 -9
  69. omlish/inject/injector.py +1 -1
  70. omlish/inject/providers.py +2 -2
  71. omlish/inject/proxy.py +5 -5
  72. omlish/iterators.py +62 -26
  73. omlish/json.py +7 -6
  74. omlish/lang/__init__.py +172 -112
  75. omlish/lang/cached.py +15 -10
  76. omlish/lang/classes/__init__.py +35 -24
  77. omlish/lang/classes/abstract.py +3 -3
  78. omlish/lang/classes/restrict.py +14 -14
  79. omlish/lang/classes/simple.py +2 -2
  80. omlish/lang/classes/virtual.py +5 -5
  81. omlish/lang/clsdct.py +2 -2
  82. omlish/lang/cmp.py +2 -2
  83. omlish/lang/contextmanagers.py +31 -25
  84. omlish/lang/datetimes.py +1 -1
  85. omlish/lang/descriptors.py +51 -6
  86. omlish/lang/exceptions.py +2 -0
  87. omlish/lang/functions.py +101 -35
  88. omlish/lang/imports.py +25 -30
  89. omlish/lang/iterables.py +2 -2
  90. omlish/lang/maybes.py +2 -1
  91. omlish/lang/objects.py +17 -11
  92. omlish/lang/resolving.py +1 -1
  93. omlish/lang/strings.py +1 -1
  94. omlish/lang/timeouts.py +53 -0
  95. omlish/lang/typing.py +5 -5
  96. omlish/libc.py +15 -11
  97. omlish/logs/_abc.py +5 -1
  98. omlish/logs/filters.py +2 -0
  99. omlish/logs/formatters.py +6 -2
  100. omlish/logs/utils.py +1 -1
  101. omlish/marshal/base.py +9 -9
  102. omlish/marshal/dataclasses.py +2 -2
  103. omlish/marshal/enums.py +2 -2
  104. omlish/marshal/exceptions.py +1 -1
  105. omlish/marshal/factories.py +10 -10
  106. omlish/marshal/global_.py +10 -4
  107. omlish/marshal/iterables.py +2 -2
  108. omlish/marshal/mappings.py +2 -2
  109. omlish/marshal/objects.py +1 -2
  110. omlish/marshal/optionals.py +4 -4
  111. omlish/marshal/polymorphism.py +4 -4
  112. omlish/marshal/registries.py +3 -3
  113. omlish/marshal/standard.py +6 -6
  114. omlish/marshal/utils.py +3 -3
  115. omlish/marshal/values.py +1 -1
  116. omlish/math.py +9 -9
  117. omlish/os.py +13 -4
  118. omlish/reflect.py +5 -15
  119. omlish/sql/__init__.py +0 -0
  120. omlish/sql/_abc.py +65 -0
  121. omlish/sql/dbs.py +90 -0
  122. omlish/stats.py +7 -8
  123. omlish/term.py +1 -1
  124. omlish/testing/pydevd.py +30 -12
  125. omlish/testing/pytest/inject/__init__.py +7 -0
  126. omlish/testing/pytest/inject/harness.py +24 -2
  127. omlish/testing/pytest/plugins/__init__.py +1 -1
  128. omlish/testing/pytest/plugins/pydevd.py +12 -0
  129. omlish/testing/pytest/plugins/switches.py +3 -3
  130. omlish/testing/testing.py +5 -5
  131. omlish/text/delimit.py +3 -6
  132. omlish/text/parts.py +3 -3
  133. omlish-0.0.0.dev3.dist-info/METADATA +31 -0
  134. omlish-0.0.0.dev3.dist-info/RECORD +191 -0
  135. {omlish-0.0.0.dev1.dist-info → omlish-0.0.0.dev3.dist-info}/WHEEL +1 -1
  136. omlish/lang/classes/test/test_abstract.py +0 -89
  137. omlish/lang/classes/test/test_restrict.py +0 -71
  138. omlish/lang/classes/test/test_simple.py +0 -58
  139. omlish/lang/classes/test/test_virtual.py +0 -72
  140. omlish/testing/pytest/plugins/pycharm.py +0 -54
  141. omlish-0.0.0.dev1.dist-info/METADATA +0 -17
  142. omlish-0.0.0.dev1.dist-info/RECORD +0 -187
  143. /omlish/{lang/classes/test → diag}/__init__.py +0 -0
  144. /omlish/{replserver → diag/replserver}/__init__.py +0 -0
  145. /omlish/{replserver → diag/replserver}/__main__.py +0 -0
  146. {omlish-0.0.0.dev1.dist-info → omlish-0.0.0.dev3.dist-info}/LICENSE +0 -0
  147. {omlish-0.0.0.dev1.dist-info → omlish-0.0.0.dev3.dist-info}/top_level.txt +0 -0
omlish/inject/elements.py CHANGED
@@ -25,9 +25,9 @@ class Elements(lang.Final):
25
25
  yield from c
26
26
 
27
27
 
28
- def as_elements(*args: ta.Union[Element, Elements]) -> Elements:
28
+ def as_elements(*args: Element | Elements) -> Elements:
29
29
  es: set[Element] = set()
30
- cs: set['Elements'] = set()
30
+ cs: set[Elements] = set()
31
31
  for a in args:
32
32
  if isinstance(a, Element):
33
33
  es.add(a)
@@ -9,25 +9,25 @@ from .types import Scope
9
9
 
10
10
 
11
11
  @dc.dataclass()
12
- class KeyException(Exception):
12
+ class BaseKeyError(Exception):
13
13
  key: Key
14
14
 
15
15
  source: ta.Any = None
16
- name: ta.Optional[str] = None
16
+ name: str | None = None
17
17
 
18
18
 
19
19
  @dc.dataclass()
20
- class UnboundKeyException(KeyException):
20
+ class UnboundKeyError(KeyError):
21
21
  pass
22
22
 
23
23
 
24
24
  @dc.dataclass()
25
- class DuplicateKeyException(KeyException):
25
+ class DuplicateKeyError(KeyError):
26
26
  pass
27
27
 
28
28
 
29
29
  @dc.dataclass()
30
- class CyclicDependencyException(KeyException):
30
+ class CyclicDependencyError(KeyError):
31
31
  pass
32
32
 
33
33
 
@@ -35,15 +35,15 @@ class CyclicDependencyException(KeyException):
35
35
 
36
36
 
37
37
  @dc.dataclass()
38
- class ScopeException(Exception):
38
+ class ScopeError(Exception):
39
39
  scope: Scope
40
40
 
41
41
 
42
42
  @dc.dataclass()
43
- class ScopeAlreadyOpenException(ScopeException):
43
+ class ScopeAlreadyOpenError(ScopeError):
44
44
  pass
45
45
 
46
46
 
47
47
  @dc.dataclass()
48
- class ScopeNotOpenException(ScopeException):
48
+ class ScopeNotOpenError(ScopeError):
49
49
  pass
@@ -25,7 +25,7 @@ from ..bindings import Binding
25
25
  from ..eagers import Eager
26
26
  from ..elements import Element
27
27
  from ..elements import Elements
28
- from ..exceptions import DuplicateKeyException
28
+ from ..exceptions import DuplicateKeyError
29
29
  from ..keys import Key
30
30
  from ..overrides import Overrides
31
31
  from ..private import Expose
@@ -51,7 +51,7 @@ class ElementCollection(lang.Final):
51
51
 
52
52
  self._es = check.isinstance(es, Elements)
53
53
 
54
- self._private_infos: ta.MutableMapping[Private, 'private_.PrivateInfo'] | None = None
54
+ self._private_infos: ta.MutableMapping[Private, private_.PrivateInfo] | None = None
55
55
 
56
56
  ##
57
57
 
@@ -98,7 +98,7 @@ class ElementCollection(lang.Final):
98
98
  try:
99
99
  bs = ovr[k]
100
100
  except KeyError:
101
- bs = src[k]
101
+ bs = b
102
102
  add(k, *bs)
103
103
 
104
104
  else:
@@ -139,7 +139,7 @@ class ElementCollection(lang.Final):
139
139
  mm.setdefault(k, []).extend(bis)
140
140
  else:
141
141
  if len(bis) > 1:
142
- raise DuplicateKeyException(k)
142
+ raise DuplicateKeyError(k)
143
143
  [pm[k]] = bis
144
144
 
145
145
  if mm:
@@ -16,8 +16,8 @@ from ... import check
16
16
  from ... import lang
17
17
  from ..eagers import Eager
18
18
  from ..elements import Elements
19
- from ..exceptions import CyclicDependencyException
20
- from ..exceptions import UnboundKeyException
19
+ from ..exceptions import CyclicDependencyError
20
+ from ..exceptions import UnboundKeyError
21
21
  from ..injector import Injector
22
22
  from ..inspect import KwargsTarget
23
23
  from ..keys import Key
@@ -45,7 +45,7 @@ class InjectorImpl(Injector, lang.Final):
45
45
  super().__init__()
46
46
 
47
47
  self._ec = check.isinstance(ec, ElementCollection)
48
- self._p: ta.Optional[InjectorImpl] = check.isinstance(p, (InjectorImpl, None))
48
+ self._p: InjectorImpl | None = check.isinstance(p, (InjectorImpl, None))
49
49
 
50
50
  self._internal_consts: dict[Key, ta.Any] = {
51
51
  Key(Injector): self,
@@ -54,7 +54,7 @@ class InjectorImpl(Injector, lang.Final):
54
54
  self._bim = ec.binding_impl_map()
55
55
 
56
56
  self._cs: weakref.WeakSet[InjectorImpl] | None = None
57
- self._root: InjectorImpl = p._root if p is not None else self
57
+ self._root: InjectorImpl = p._root if p is not None else self # noqa
58
58
 
59
59
  self.__cur_req: InjectorImpl._Request | None = None
60
60
 
@@ -97,7 +97,7 @@ class InjectorImpl(Injector, lang.Final):
97
97
  except KeyError:
98
98
  pass
99
99
  if key in self._seen_keys:
100
- raise CyclicDependencyException(key)
100
+ raise CyclicDependencyError(key)
101
101
  self._seen_keys.add(key)
102
102
  return lang.empty()
103
103
 
@@ -154,7 +154,7 @@ class InjectorImpl(Injector, lang.Final):
154
154
  v = self.try_provide(key)
155
155
  if v.present:
156
156
  return v.must()
157
- raise UnboundKeyException(key)
157
+ raise UnboundKeyError(key)
158
158
 
159
159
  def provide_kwargs(self, kt: KwargsTarget) -> ta.Mapping[str, ta.Any]:
160
160
  ret: dict[str, ta.Any] = {}
@@ -11,7 +11,7 @@ import typing as ta
11
11
  import weakref
12
12
 
13
13
  from ... import reflect as rfl
14
- from ..exceptions import DuplicateKeyException
14
+ from ..exceptions import DuplicateKeyError
15
15
  from ..inspect import Kwarg
16
16
  from ..inspect import KwargsTarget
17
17
  from ..keys import Key
@@ -52,7 +52,7 @@ def build_kwargs_target(
52
52
  obj: ta.Any,
53
53
  *,
54
54
  skip_args: int = 0,
55
- skip_kwargs: ta.Optional[ta.Iterable[ta.Any]] = None,
55
+ skip_kwargs: ta.Iterable[ta.Any] | None = None,
56
56
  raw_optional: bool = False,
57
57
  ) -> KwargsTarget:
58
58
  sig = signature(obj)
@@ -83,7 +83,7 @@ def build_kwargs_target(
83
83
  k = tag(k, pt)
84
84
 
85
85
  if k in seen:
86
- raise DuplicateKeyException(k)
86
+ raise DuplicateKeyError(k)
87
87
  seen.add(k)
88
88
 
89
89
  kws.append(Kwarg(
@@ -9,8 +9,8 @@ from ... import lang
9
9
  from ..bindings import Binding
10
10
  from ..elements import Elements
11
11
  from ..elements import as_elements
12
- from ..exceptions import ScopeAlreadyOpenException
13
- from ..exceptions import ScopeNotOpenException
12
+ from ..exceptions import ScopeAlreadyOpenError
13
+ from ..exceptions import ScopeNotOpenError
14
14
  from ..injector import Injector
15
15
  from ..keys import Key
16
16
  from ..providers import Provider
@@ -134,7 +134,7 @@ class SeededScopeImpl(ScopeImpl):
134
134
 
135
135
  def must_state(self) -> 'SeededScopeImpl.State':
136
136
  if (st := self._st) is None:
137
- raise ScopeNotOpenException(self._ss)
137
+ raise ScopeNotOpenError(self._ss)
138
138
  return st
139
139
 
140
140
  class Manager(SeededScope.Manager, lang.Final):
@@ -147,14 +147,14 @@ class SeededScopeImpl(ScopeImpl):
147
147
  @contextlib.contextmanager
148
148
  def __call__(self, seeds: ta.Mapping[Key, ta.Any]) -> ta.Generator[None, None, None]:
149
149
  try:
150
- if self._ssi._st is not None:
151
- raise ScopeAlreadyOpenException(self._ss)
152
- self._ssi._st = SeededScopeImpl.State(dict(seeds))
150
+ if self._ssi._st is not None: # noqa
151
+ raise ScopeAlreadyOpenError(self._ss)
152
+ self._ssi._st = SeededScopeImpl.State(dict(seeds)) # noqa
153
153
  yield
154
154
  finally:
155
- if self._ssi._st is None:
156
- raise ScopeNotOpenException(self._ss)
157
- self._ssi._st = None
155
+ if self._ssi._st is None: # noqa
156
+ raise ScopeNotOpenError(self._ss)
157
+ self._ssi._st = None # noqa
158
158
 
159
159
  def auto_elements(self) -> Elements:
160
160
  return as_elements(
omlish/inject/injector.py CHANGED
@@ -31,7 +31,7 @@ class Injector(lang.Abstract):
31
31
 
32
32
  def __getitem__(
33
33
  self,
34
- target: ta.Union[Key[T], type[T]],
34
+ target: Key[T] | type[T],
35
35
  ) -> T:
36
36
  return self.provide(target)
37
37
 
@@ -53,7 +53,7 @@ class FnProvider(Provider):
53
53
  return self.cls
54
54
 
55
55
 
56
- def fn(fn: ta.Any, cls: ta.Optional[Cls] = _Missing) -> Provider:
56
+ def fn(fn: ta.Any, cls: Cls | None = _Missing) -> Provider:
57
57
  check.not_isinstance(fn, type)
58
58
  check.callable(fn)
59
59
  if cls is _Missing:
@@ -90,7 +90,7 @@ class ConstProvider(Provider):
90
90
  return self.cls
91
91
 
92
92
 
93
- def const(v: ta.Any, cls: ta.Optional[Cls] = _Missing) -> Provider:
93
+ def const(v: ta.Any, cls: Cls | None = _Missing) -> Provider:
94
94
  if cls is _Missing:
95
95
  cls = type(v)
96
96
  return ConstProvider(v, cls)
omlish/inject/proxy.py CHANGED
@@ -22,7 +22,7 @@ def _cyclic_dependency_proxy() -> tuple[type, ta.Callable[[ta.Any, ta.Any], None
22
22
  def __init__(self, cls):
23
23
  super().__init__(_CyclicDependencyPlaceholder(cls))
24
24
  if isinstance(cls, type):
25
- self._self__class__ = cls
25
+ self._self__class__ = cls # noqa
26
26
 
27
27
  def __repr__(self) -> str:
28
28
  return f'_CyclicDependencyProxy({self.__wrapped__!r})'
@@ -35,14 +35,14 @@ def _cyclic_dependency_proxy() -> tuple[type, ta.Callable[[ta.Any, ta.Any], None
35
35
  pass
36
36
  return object.__getattribute__(self, att) # noqa
37
37
 
38
- def set(prox, obj):
38
+ def set_obj(prox, obj):
39
39
  check.state(type(prox) is _CyclicDependencyProxy)
40
40
  check.not_isinstance(obj, _CyclicDependencyPlaceholder)
41
41
  check.isinstance(prox.__wrapped__, _CyclicDependencyPlaceholder)
42
42
  if hasattr(prox, '_self__class__'):
43
- check.issubclass(type(obj), prox._self__class__)
43
+ check.issubclass(type(obj), prox._self__class__) # noqa
44
44
  prox.__wrapped__ = obj
45
45
  if hasattr(prox, '_self__class__'):
46
- del prox._self__class__
46
+ del prox._self__class__ # noqa
47
47
 
48
- return (_CyclicDependencyProxy, set) # noqa
48
+ return (_CyclicDependencyProxy, set_obj) # noqa
omlish/iterators.py CHANGED
@@ -1,10 +1,12 @@
1
1
  import collections
2
2
  import functools
3
+ import heapq
3
4
  import itertools
4
5
  import typing as ta
5
6
 
6
7
 
7
8
  T = ta.TypeVar('T')
9
+ U = ta.TypeVar('U')
8
10
 
9
11
  _MISSING = object()
10
12
 
@@ -20,66 +22,61 @@ class PeekIterator(ta.Iterator[T]):
20
22
 
21
23
  _item: T
22
24
 
23
- def __iter__(self) -> ta.Iterator[T]:
25
+ def __iter__(self) -> ta.Self:
24
26
  return self
25
27
 
26
28
  @property
27
29
  def done(self) -> bool:
28
30
  try:
29
31
  self.peek()
30
- return False
31
32
  except StopIteration:
32
33
  return True
34
+ else:
35
+ return False
33
36
 
34
37
  def __next__(self) -> T:
35
38
  if self._next_item is not _MISSING:
36
39
  self._item = ta.cast(T, self._next_item)
37
40
  self._next_item = _MISSING
38
41
  else:
39
- try:
40
- self._item = next(self._it)
41
- except StopIteration:
42
- raise
42
+ self._item = next(self._it)
43
43
  self._pos += 1
44
44
  return self._item
45
45
 
46
46
  def peek(self) -> T:
47
47
  if self._next_item is not _MISSING:
48
48
  return ta.cast(T, self._next_item)
49
- try:
50
- self._next_item = next(self._it)
51
- except StopIteration:
52
- raise
49
+ self._next_item = next(self._it)
53
50
  return self._next_item
54
51
 
55
52
  def next_peek(self) -> T:
56
53
  next(self)
57
54
  return self.peek()
58
55
 
59
- def takewhile(self, fn):
56
+ def takewhile(self, fn: ta.Callable[[T], bool]) -> ta.Iterator[T]:
60
57
  while fn(self.peek()):
61
58
  yield next(self)
62
59
 
63
- def skipwhile(self, fn):
60
+ def skipwhile(self, fn: ta.Callable[[T], bool]) -> None:
64
61
  while fn(self.peek()):
65
62
  next(self)
66
63
 
67
- def takeuntil(self, fn):
64
+ def takeuntil(self, fn: ta.Callable[[T], bool]) -> ta.Iterator[T]:
68
65
  return self.takewhile(lambda e: not fn(e))
69
66
 
70
- def skipuntil(self, fn):
67
+ def skipuntil(self, fn: ta.Callable[[T], bool]) -> None:
71
68
  self.skipwhile(lambda e: not fn(e))
72
69
 
73
- def takethrough(self, pos):
70
+ def takethrough(self, pos: int) -> ta.Iterator[T]:
74
71
  return self.takewhile(lambda _: self._pos < pos)
75
72
 
76
- def skipthrough(self, pos):
73
+ def skipthrough(self, pos: int) -> None:
77
74
  self.skipwhile(lambda _: self._pos < pos)
78
75
 
79
- def taketo(self, pos):
76
+ def taketo(self, pos: int) -> ta.Iterator[T]:
80
77
  return self.takethrough(pos - 1)
81
78
 
82
- def skipto(self, pos):
79
+ def skipto(self, pos: int) -> None:
83
80
  self.skipthrough(pos - 1)
84
81
 
85
82
 
@@ -88,7 +85,7 @@ class ProxyIterator(ta.Iterator[T]):
88
85
  def __init__(self, fn: ta.Callable[[], T]) -> None:
89
86
  self._fn = fn
90
87
 
91
- def __iter__(self) -> ta.Iterator[T]:
88
+ def __iter__(self) -> ta.Self:
92
89
  return self
93
90
 
94
91
  def __next__(self) -> T:
@@ -97,13 +94,13 @@ class ProxyIterator(ta.Iterator[T]):
97
94
 
98
95
  class PrefetchIterator(ta.Iterator[T]):
99
96
 
100
- def __init__(self, fn: ta.Optional[ta.Callable[[], T]] = None) -> None:
97
+ def __init__(self, fn: ta.Callable[[], T] | None = None) -> None:
101
98
  super().__init__()
102
99
 
103
100
  self._fn = fn
104
- self._deque: ta.Deque[T] = collections.deque()
101
+ self._deque: collections.deque[T] = collections.deque()
105
102
 
106
- def __iter__(self) -> ta.Iterator[T]:
103
+ def __iter__(self) -> ta.Self:
107
104
  return self
108
105
 
109
106
  def push(self, item) -> None:
@@ -114,7 +111,7 @@ class PrefetchIterator(ta.Iterator[T]):
114
111
  return self._deque.popleft()
115
112
  except IndexError:
116
113
  if self._fn is None:
117
- raise StopIteration
114
+ raise StopIteration from None
118
115
  return self._fn()
119
116
 
120
117
 
@@ -124,9 +121,9 @@ class RetainIterator(ta.Iterator[T]):
124
121
  super().__init__()
125
122
 
126
123
  self._fn = fn
127
- self._deque: ta.Deque[T] = collections.deque()
124
+ self._deque: collections.deque[T] = collections.deque()
128
125
 
129
- def __iter__(self) -> ta.Iterator[T]:
126
+ def __iter__(self) -> ta.Self:
130
127
  return self
131
128
 
132
129
  def pop(self) -> None:
@@ -138,7 +135,7 @@ class RetainIterator(ta.Iterator[T]):
138
135
  return item
139
136
 
140
137
 
141
- def unzip(it: ta.Iterable[T], width: ta.Optional[int] = None) -> list:
138
+ def unzip(it: ta.Iterable[T], width: int | None = None) -> list:
142
139
  if width is None:
143
140
  if not isinstance(it, PeekIterator):
144
141
  it = PeekIterator(iter(it))
@@ -182,3 +179,42 @@ def chunk(n: int, iterable: ta.Iterable[T], strict: bool = False) -> ta.Iterator
182
179
  return iter(ret())
183
180
  else:
184
181
  return iterator
182
+
183
+
184
+ def merge_on(
185
+ function: ta.Callable[[T], U],
186
+ *its: ta.Iterable[T],
187
+ ) -> ta.Iterator[tuple[U, list[tuple[int, T]]]]:
188
+ indexed_its = [
189
+ (
190
+ (function(item), it_idx, item)
191
+ for it_idx, item in zip(itertools.repeat(it_idx), it)
192
+ )
193
+ for it_idx, it in enumerate(its)
194
+ ]
195
+
196
+ grouped_indexed_its = itertools.groupby(
197
+ heapq.merge(*indexed_its),
198
+ key=lambda item_tuple: item_tuple[0],
199
+ )
200
+
201
+ return (
202
+ (fn_item, [(it_idx, item) for _, it_idx, item in grp])
203
+ for fn_item, grp in grouped_indexed_its
204
+ )
205
+
206
+
207
+ def expand_indexed_pairs(
208
+ seq: ta.Iterable[tuple[int, T]],
209
+ default: T,
210
+ *,
211
+ width: int | None = None,
212
+ ) -> list[T]:
213
+ width_ = width
214
+ if width_ is None:
215
+ width_ = (max(idx for idx, _ in seq) + 1) if seq else 0
216
+ result = [default] * width_
217
+ for idx, value in seq:
218
+ if idx < width_:
219
+ result[idx] = value
220
+ return result
omlish/json.py CHANGED
@@ -1,5 +1,6 @@
1
1
  import functools
2
2
  import json as _json
3
+ import typing as ta
3
4
 
4
5
 
5
6
  dump = _json.dump
@@ -15,25 +16,25 @@ loads = _json.loads
15
16
 
16
17
  PRETTY_INDENT = 2
17
18
 
18
- PRETTY_KWARGS = dict(
19
+ PRETTY_KWARGS: ta.Mapping[str, ta.Any] = dict(
19
20
  indent=PRETTY_INDENT,
20
21
  )
21
22
 
22
- dump_pretty = functools.partial(dump, **PRETTY_KWARGS)
23
- dumps_pretty = functools.partial(dumps, **PRETTY_KWARGS)
23
+ dump_pretty: ta.Callable[..., bytes] = functools.partial(dump, **PRETTY_KWARGS) # type: ignore
24
+ dumps_pretty: ta.Callable[..., str] = functools.partial(dumps, **PRETTY_KWARGS)
24
25
 
25
26
  ##
26
27
 
27
28
 
28
29
  COMPACT_SEPARATORS = (',', ':')
29
30
 
30
- COMPACT_KWARGS = dict(
31
+ COMPACT_KWARGS: ta.Mapping[str, ta.Any] = dict(
31
32
  indent=0,
32
33
  separators=COMPACT_SEPARATORS,
33
34
  )
34
35
 
35
- dump_compact = functools.partial(dump, **COMPACT_KWARGS)
36
- dumps_compact = functools.partial(dumps, **COMPACT_KWARGS)
36
+ dump_compact: ta.Callable[..., bytes] = functools.partial(dump, **COMPACT_KWARGS) # type: ignore
37
+ dumps_compact: ta.Callable[..., str] = functools.partial(dumps, **COMPACT_KWARGS)
37
38
 
38
39
 
39
40
  ##