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
@@ -19,16 +19,16 @@ class ContextManaged:
19
19
 
20
20
  def __exit__(
21
21
  self,
22
- exc_type: ta.Optional[ta.Type[Exception]],
23
- exc_val: ta.Optional[Exception],
24
- exc_tb: ta.Optional[types.TracebackType]
25
- ) -> ta.Optional[bool]:
22
+ exc_type: type[BaseException] | None,
23
+ exc_val: BaseException | None,
24
+ exc_tb: types.TracebackType | None,
25
+ ) -> bool | None:
26
26
  return None
27
27
 
28
28
 
29
29
  class NopContextManaged(ContextManaged):
30
30
 
31
- def __init_subclass__(cls, **kwargs):
31
+ def __init_subclass__(cls, **kwargs: ta.Any) -> None:
32
32
  raise TypeError
33
33
 
34
34
 
@@ -37,7 +37,7 @@ NOP_CONTEXT_MANAGED = NopContextManaged()
37
37
 
38
38
  class NopContextManager:
39
39
 
40
- def __init_subclass__(cls, **kwargs):
40
+ def __init_subclass__(cls, **kwargs: ta.Any) -> None:
41
41
  raise TypeError
42
42
 
43
43
  def __call__(self, *args, **kwargs):
@@ -51,7 +51,7 @@ NOP_CONTEXT_MANAGER = NopContextManager()
51
51
 
52
52
 
53
53
  @contextlib.contextmanager
54
- def defer(fn: ta.Callable) -> ta.Iterator[ta.Callable]:
54
+ def defer(fn: ta.Callable) -> ta.Generator[ta.Callable, None, None]:
55
55
  try:
56
56
  yield fn
57
57
  finally:
@@ -59,7 +59,7 @@ def defer(fn: ta.Callable) -> ta.Iterator[ta.Callable]:
59
59
 
60
60
 
61
61
  @contextlib.asynccontextmanager
62
- async def a_defer(fn: ta.Awaitable) -> ta.AsyncIterator[ta.Awaitable]:
62
+ async def a_defer(fn: ta.Awaitable) -> ta.AsyncGenerator[ta.Awaitable, None]:
63
63
  try:
64
64
  yield fn
65
65
  finally:
@@ -84,11 +84,11 @@ def disposing(obj: T, attr: str = 'dispose') -> ta.Iterator[T]:
84
84
 
85
85
 
86
86
  @contextlib.contextmanager
87
- def breakpoint_on_exception():
87
+ def breakpoint_on_exception(): # noqa
88
88
  try:
89
89
  yield
90
90
  except Exception as e: # noqa
91
- breakpoint()
91
+ breakpoint() # noqa
92
92
  raise
93
93
 
94
94
 
@@ -102,7 +102,7 @@ def context_var_setting(var: contextvars.ContextVar[T], val: T) -> ta.Iterator[T
102
102
 
103
103
 
104
104
  @contextlib.contextmanager
105
- def attr_setting(obj, attr, val, *, default=None):
105
+ def attr_setting(obj, attr, val, *, default=None): # noqa
106
106
  not_set = object()
107
107
  orig = getattr(obj, attr, not_set)
108
108
  try:
@@ -146,10 +146,10 @@ class ExitStacked:
146
146
 
147
147
  def __exit__(
148
148
  self,
149
- exc_type: ta.Optional[ta.Type[Exception]],
150
- exc_val: ta.Optional[Exception],
151
- exc_tb: ta.Optional[types.TracebackType]
152
- ) -> ta.Optional[bool]:
149
+ exc_type: type[BaseException] | None,
150
+ exc_val: BaseException | None,
151
+ exc_tb: types.TracebackType | None,
152
+ ) -> bool | None:
153
153
  self._exit_stack.__exit__(exc_type, exc_val, exc_tb)
154
154
  try:
155
155
  superfn = super().__exit__ # type: ignore
@@ -162,20 +162,28 @@ class ExitStacked:
162
162
  ##
163
163
 
164
164
 
165
- ContextWrappable: ta.TypeAlias = ta.Union[ta.ContextManager, str, ta.Callable[..., ta.ContextManager]]
165
+ ContextWrappable: ta.TypeAlias = ta.ContextManager | str | ta.Callable[..., ta.ContextManager]
166
166
 
167
167
 
168
168
  class ContextWrapped:
169
169
 
170
- def __init__(self, fn: ta.Callable, cm: ta.Union[str, ContextWrappable]) -> None:
170
+ def __init__(self, fn: ta.Callable, cm: str | ContextWrappable) -> None:
171
171
  super().__init__()
172
172
 
173
- self._fn = fn
173
+ self._fn = (fn,)
174
174
  self._cm = cm
175
175
  self._name: str | None = None
176
176
 
177
177
  functools.update_wrapper(self, fn)
178
178
 
179
+ @property
180
+ def _fn(self):
181
+ return self.__fn
182
+
183
+ @_fn.setter
184
+ def _fn(self, x):
185
+ self.__fn = x
186
+
179
187
  def __set_name__(self, owner, name):
180
188
  if name is not None:
181
189
  if self._name is not None:
@@ -187,7 +195,7 @@ class ContextWrapped:
187
195
  def __get__(self, instance, owner=None):
188
196
  if instance is None and owner is None:
189
197
  return self
190
- fn = self._fn.__get__(instance, owner) # noqa
198
+ fn = self._fn[0].__get__(instance, owner) # noqa
191
199
  cm: ta.Any = self._cm
192
200
  if isinstance(self._cm, str):
193
201
  if instance is not None:
@@ -204,10 +212,8 @@ class ContextWrapped:
204
212
  raise TypeError(cm)
205
213
  ret = type(self)(fn, cm)
206
214
  if self._name is not None:
207
- try:
215
+ with contextlib.suppress(TypeError):
208
216
  instance.__dict__[self._name] = ret
209
- except TypeError:
210
- pass
211
217
  return ret
212
218
 
213
219
  def __call__(self, *args, **kwargs):
@@ -217,10 +223,10 @@ class ContextWrapped:
217
223
  if not hasattr(cm, '__enter__') and callable(cm):
218
224
  cm = cm(*args, **kwargs)
219
225
  with cm: # type: ignore
220
- return self._fn(*args, **kwargs)
226
+ return self._fn[0](*args, **kwargs)
221
227
 
222
228
 
223
- def context_wrapped(cm): # ContextWrappable -> ta.Callable[[CallableT], CallableT]:
229
+ def context_wrapped(cm): # ContextWrappable -> ta.Callable[[CallableT], CallableT]: # noqa
224
230
  def inner(fn):
225
231
  return ContextWrapped(fn, cm)
226
232
  return inner
@@ -230,7 +236,7 @@ def context_wrapped(cm): # ContextWrappable -> ta.Callable[[CallableT], Callabl
230
236
 
231
237
 
232
238
  Lockable = ta.Callable[[], ta.ContextManager]
233
- DefaultLockable = ta.Union[None, bool, Lockable, ta.ContextManager]
239
+ DefaultLockable = bool | Lockable | ta.ContextManager | None
234
240
 
235
241
 
236
242
  def default_lock(value: DefaultLockable, default: DefaultLockable) -> Lockable:
omlish/lang/datetimes.py CHANGED
@@ -62,6 +62,6 @@ def parse_timedelta(s: str) -> datetime.timedelta:
62
62
  for k, v in match.groupdict().items()
63
63
  if k != 'negative' and v is not None}
64
64
  if not timedelta_kwargs:
65
- raise ValueError()
65
+ raise ValueError
66
66
  sign = -1 if match.groupdict().get('negative') else 1
67
67
  return sign * datetime.timedelta(**timedelta_kwargs)
@@ -1,6 +1,22 @@
1
+ import functools
2
+ import operator
1
3
  import typing as ta
2
4
 
3
5
 
6
+ T = ta.TypeVar('T')
7
+
8
+
9
+ ##
10
+
11
+
12
+ def attr_property(n: str):
13
+ return property(operator.attrgetter(n))
14
+
15
+
16
+ def item_property(n: str):
17
+ return property(operator.itemgetter(n))
18
+
19
+
4
20
  ##
5
21
 
6
22
 
@@ -24,29 +40,58 @@ def unwrap_method_descriptors(fn: ta.Callable) -> ta.Callable:
24
40
  ##
25
41
 
26
42
 
27
- class AccessForbiddenException(Exception):
43
+ class AccessForbiddenError(Exception):
28
44
 
29
- def __init__(self, name: ta.Optional[str] = None, *args: ta.Any, **kwargs: ta.Any) -> None:
45
+ def __init__(self, name: str | None = None, *args: ta.Any, **kwargs: ta.Any) -> None:
30
46
  super().__init__(*((name,) if name is not None else ()), *args, **kwargs) # noqa
31
47
  self.name = name
32
48
 
33
49
 
34
50
  class AccessForbiddenDescriptor:
35
51
 
36
- def __init__(self, name: ta.Optional[str] = None) -> None:
52
+ def __init__(self, name: str | None = None) -> None:
37
53
  super().__init__()
38
54
 
39
55
  self._name = name
40
56
 
41
- def __set_name__(self, owner: ta.Type, name: str) -> None:
57
+ def __set_name__(self, owner: type, name: str) -> None:
42
58
  if self._name is None:
43
59
  self._name = name
44
60
  elif name != self._name:
45
61
  raise NameError(name)
46
62
 
47
63
  def __get__(self, instance, owner=None):
48
- raise AccessForbiddenException(self._name)
64
+ raise AccessForbiddenError(self._name)
49
65
 
50
66
 
51
- def access_forbidden():
67
+ def access_forbidden(): # noqa
52
68
  return AccessForbiddenDescriptor()
69
+
70
+
71
+ ##
72
+
73
+
74
+ class _ClassOnly:
75
+ def __init__(self, mth):
76
+ if not isinstance(mth, classmethod):
77
+ raise TypeError(f'must be classmethod: {mth}')
78
+ super().__init__()
79
+ self._mth = (mth,)
80
+ functools.update_wrapper(self, mth) # type: ignore
81
+
82
+ @property
83
+ def _mth(self):
84
+ return self.__mth
85
+
86
+ @_mth.setter
87
+ def _mth(self, x):
88
+ self.__mth = x
89
+
90
+ def __get__(self, instance, owner):
91
+ if instance is not None:
92
+ raise TypeError(f'method must not be used on instance: {self._mth}')
93
+ return self._mth[0].__get__(instance, owner)
94
+
95
+
96
+ def classonly(obj: T) -> T: # noqa
97
+ return _ClassOnly(obj) # type: ignore
@@ -0,0 +1,2 @@
1
+ class Unreachable(Exception): # noqa
2
+ pass
omlish/lang/functions.py CHANGED
@@ -1,3 +1,4 @@
1
+ import dataclasses as dc
1
2
  import functools
2
3
  import time
3
4
  import typing as ta
@@ -6,6 +7,11 @@ from .descriptors import is_method_descriptor
6
7
 
7
8
 
8
9
  T = ta.TypeVar('T')
10
+ P = ta.ParamSpec('P')
11
+ CallableT = ta.TypeVar('CallableT', bound=ta.Callable)
12
+
13
+
14
+ ##
9
15
 
10
16
 
11
17
  def is_lambda(f: ta.Any) -> bool:
@@ -13,6 +19,9 @@ def is_lambda(f: ta.Any) -> bool:
13
19
  return isinstance(f, type(l)) and f.__name__ == l.__name__
14
20
 
15
21
 
22
+ ##
23
+
24
+
16
25
  def maybe_call(obj: ta.Any, att: str, *args, default: ta.Any = None, **kwargs) -> ta.Any:
17
26
  try:
18
27
  fn = getattr(obj, att)
@@ -22,6 +31,16 @@ def maybe_call(obj: ta.Any, att: str, *args, default: ta.Any = None, **kwargs) -
22
31
  return fn(*args, **kwargs)
23
32
 
24
33
 
34
+ def recurse(fn: ta.Callable[..., T], *args, **kwargs) -> T:
35
+ def rec(*args, **kwargs) -> T:
36
+ return fn(rec, *args, **kwargs)
37
+
38
+ return rec(*args, **kwargs)
39
+
40
+
41
+ ##
42
+
43
+
25
44
  def unwrap_func(fn: ta.Callable) -> ta.Callable:
26
45
  fn, _ = unwrap_func_with_partials(fn)
27
46
  return fn
@@ -39,38 +58,51 @@ def unwrap_func_with_partials(fn: ta.Callable) -> tuple[ta.Callable, list[functo
39
58
  nxt = getattr(fn, '__wrapped__', None)
40
59
  if not callable(nxt):
41
60
  break
42
- elif nxt is fn:
61
+ if nxt is fn:
43
62
  raise TypeError(fn)
44
63
  fn = nxt
45
64
  return fn, ps
46
65
 
47
66
 
67
+ ##
68
+
69
+
48
70
  def raise_(o: BaseException) -> ta.NoReturn:
49
71
  raise o
50
72
 
51
73
 
74
+ def raising(o: BaseException) -> ta.Callable[..., ta.NoReturn]:
75
+ def inner(*args, **kwargs):
76
+ raise o
77
+ return inner
78
+
79
+
52
80
  def try_(
53
- exc: ta.Union[type[Exception], ta.Iterable[type[Exception]]] = Exception,
54
- default: ta.Optional[T] = None,
55
- ) -> ta.Callable[..., T]:
56
- def outer(fn):
57
- def inner(*args, **kwargs):
58
- try:
59
- return fn(*args, **kwargs)
60
- except exct:
61
- return default
62
-
63
- return inner
81
+ fn: ta.Callable[P, T],
82
+ exc: type[Exception] | ta.Iterable[type[Exception]] = Exception,
83
+ default: T | None = None,
84
+ ) -> ta.Callable[P, T]:
85
+ def inner(*args, **kwargs):
86
+ try:
87
+ return fn(*args, **kwargs)
88
+ except exct:
89
+ return default
64
90
 
65
91
  exct = (exc,) if isinstance(exc, type) else tuple(exc)
66
- return outer
92
+ return inner
67
93
 
68
94
 
69
- def recurse(fn: ta.Callable[..., T], *args, **kwargs) -> T:
70
- def rec(*args, **kwargs) -> T:
71
- return fn(rec, *args, **kwargs)
95
+ def finally_(fn: ta.Callable[P, T], fin: ta.Callable) -> ta.Callable[P, T]:
96
+ def inner(*args, **kwargs):
97
+ try:
98
+ return fn(*args, **kwargs)
99
+ finally:
100
+ fin()
72
101
 
73
- return rec(*args, **kwargs)
102
+ return inner
103
+
104
+
105
+ ##
74
106
 
75
107
 
76
108
  def identity(obj: T) -> T:
@@ -96,31 +128,65 @@ def is_not_none(o: ta.Any) -> bool:
96
128
  return o is not None
97
129
 
98
130
 
99
- class VoidException(Exception):
131
+ class VoidError(Exception):
100
132
  pass
101
133
 
102
134
 
103
135
  class Void:
104
136
 
105
- def __new__(cls, *args, **kwargs):
106
- raise VoidException
137
+ def __new__(cls, *args: ta.Any, **kwargs: ta.Any) -> None: # type: ignore # noqa
138
+ raise VoidError
139
+
140
+ def __init_subclass__(cls, **kwargs: ta.Any) -> None:
141
+ raise VoidError
142
+
143
+
144
+ def void(*args: ta.Any, **kwargs: ta.Any) -> ta.NoReturn:
145
+ raise VoidError
146
+
107
147
 
108
- def __init_subclass__(cls, **kwargs):
109
- raise VoidException
148
+ ##
110
149
 
111
150
 
112
- def void(*args, **kwargs) -> ta.NoReturn:
113
- raise VoidException
151
+ _MISSING = object()
114
152
 
115
153
 
116
- def ticking_timeout(
117
- s: int | float | None,
118
- ex: type[BaseException] | BaseException = TimeoutError,
119
- ) -> ta.Callable[[], None]:
120
- if s is None:
121
- return lambda: None
122
- def tick(): # noqa
123
- if time.time() >= deadline:
124
- raise ex
125
- deadline = time.time() + s
126
- return tick
154
+ def periodically(
155
+ fn: CallableT,
156
+ interval_s: float,
157
+ initial: ta.Any = _MISSING,
158
+ *,
159
+ include_runtime: bool = False,
160
+ ) -> CallableT:
161
+ nxt = time.time() + interval_s
162
+ ret = initial
163
+
164
+ @functools.wraps(fn)
165
+ def inner(*args, **kwargs):
166
+ nonlocal nxt, ret
167
+ if time.time() >= nxt or ret is _MISSING:
168
+ if include_runtime:
169
+ nxt = time.time() + interval_s
170
+ ret = fn(*args, **kwargs)
171
+ if not include_runtime:
172
+ nxt = time.time() + interval_s
173
+ return ret
174
+
175
+ return inner # type: ignore
176
+
177
+
178
+ ##
179
+
180
+
181
+ @dc.dataclass(init=False)
182
+ class Args:
183
+ args: ta.Sequence[ta.Any]
184
+ kwargs: ta.Mapping[str, ta.Any]
185
+
186
+ def __init__(self, *args: ta.Any, **kwargs: ta.Any) -> None:
187
+ super().__init__()
188
+ self.args = args
189
+ self.kwargs = kwargs
190
+
191
+ def __call__(self, fn: ta.Callable[..., T]) -> T:
192
+ return fn(*self.args, **self.kwargs)
omlish/lang/imports.py CHANGED
@@ -1,5 +1,6 @@
1
+ import contextlib
1
2
  import functools
2
- import importlib
3
+ import importlib.resources
3
4
  import sys
4
5
  import types
5
6
  import typing as ta
@@ -10,14 +11,14 @@ from .cached import cached_function
10
11
  ##
11
12
 
12
13
 
13
- def lazy_import(name: str, package: ta.Optional[str] = None) -> ta.Callable[[], ta.Any]:
14
+ def lazy_import(name: str, package: str | None = None) -> ta.Callable[[], ta.Any]:
14
15
  return cached_function(functools.partial(importlib.import_module, name, package=package))
15
16
 
16
17
 
17
- def proxy_import(name: str, package: ta.Optional[str] = None) -> types.ModuleType:
18
+ def proxy_import(name: str, package: str | None = None) -> types.ModuleType:
18
19
  omod = None
19
20
 
20
- def __getattr__(att):
21
+ def __getattr__(att): # noqa
21
22
  nonlocal omod
22
23
  if omod is None:
23
24
  omod = importlib.import_module(name, package=package)
@@ -31,9 +32,6 @@ def proxy_import(name: str, package: ta.Optional[str] = None) -> types.ModuleTyp
31
32
  ##
32
33
 
33
34
 
34
- _pkg_resources = lazy_import('pkg_resources')
35
-
36
-
37
35
  def import_module(dotted_path: str) -> types.ModuleType:
38
36
  if not dotted_path:
39
37
  raise ImportError(dotted_path)
@@ -42,7 +40,7 @@ def import_module(dotted_path: str) -> types.ModuleType:
42
40
  try:
43
41
  mod = getattr(mod, name)
44
42
  except AttributeError:
45
- raise AttributeError('Module %r has no attribute %r' % (mod, name))
43
+ raise AttributeError(f'Module {mod!r} has no attribute {name!r}') from None
46
44
  return mod
47
45
 
48
46
 
@@ -52,7 +50,7 @@ def import_module_attr(dotted_path: str) -> ta.Any:
52
50
  try:
53
51
  return getattr(mod, class_name)
54
52
  except AttributeError:
55
- raise AttributeError('Module %r has no attr %r' % (module_name, class_name))
53
+ raise AttributeError(f'Module {module_name!r} has no attr {class_name!r}') from None
56
54
 
57
55
 
58
56
  SPECIAL_IMPORTABLE: ta.AbstractSet[str] = frozenset([
@@ -65,46 +63,43 @@ def yield_importable(
65
63
  package_root: str,
66
64
  *,
67
65
  recursive: bool = False,
68
- filter: ta.Optional[ta.Callable[[str], bool]] = None,
66
+ filter: ta.Callable[[str], bool] | None = None, # noqa
69
67
  include_special: bool = False,
70
68
  ) -> ta.Iterator[str]:
71
- def rec(dir):
72
- if dir.split('.')[-1] == '__pycache__':
69
+ def rec(cur):
70
+ if cur.split('.')[-1] == '__pycache__':
73
71
  return
74
72
 
75
73
  try:
76
- module = sys.modules[dir]
74
+ module = sys.modules[cur]
77
75
  except KeyError:
78
76
  try:
79
- __import__(dir)
77
+ __import__(cur)
80
78
  except ImportError:
81
79
  return
82
- module = sys.modules[dir]
80
+ module = sys.modules[cur]
83
81
 
84
82
  # FIXME: pyox
85
83
  if getattr(module, '__file__', None) is None:
86
84
  return
87
85
 
88
- for file in _pkg_resources().resource_listdir(dir, '.'):
89
- if file.endswith('.py'):
90
- if not (include_special or file not in SPECIAL_IMPORTABLE):
86
+ for file in importlib.resources.files(cur).iterdir():
87
+ if file.is_file() and file.name.endswith('.py'):
88
+ if not (include_special or file.name not in SPECIAL_IMPORTABLE):
91
89
  continue
92
90
 
93
- name = dir + '.' + file[:-3]
91
+ name = cur + '.' + file.name[:-3]
94
92
  if filter is not None and not filter(name):
95
93
  continue
96
94
 
97
95
  yield name
98
96
 
99
- elif recursive and '.' not in file:
100
- name = dir + '.' + file
97
+ elif recursive and file.is_dir():
98
+ name = cur + '.' + file.name
101
99
  if filter is not None and not filter(name):
102
100
  continue
103
-
104
- try:
101
+ with contextlib.suppress(ImportError, NotImplementedError):
105
102
  yield from rec(name)
106
- except (ImportError, NotImplementedError):
107
- pass
108
103
 
109
104
  yield from rec(package_root)
110
105
 
@@ -112,10 +107,10 @@ def yield_importable(
112
107
  def yield_import_all(
113
108
  package_root: str,
114
109
  *,
115
- globals: ta.Optional[dict[str, ta.Any]] = None,
116
- locals: ta.Optional[dict[str, ta.Any]] = None,
110
+ globals: dict[str, ta.Any] | None = None, # noqa
111
+ locals: dict[str, ta.Any] | None = None, # noqa
117
112
  recursive: bool = False,
118
- filter: ta.Optional[ta.Callable[[str], bool]] = None,
113
+ filter: ta.Callable[[str], bool] | None = None, # noqa
119
114
  include_special: bool = False,
120
115
  ) -> ta.Iterator[str]:
121
116
  for import_path in yield_importable(
@@ -132,7 +127,7 @@ def import_all(
132
127
  package_root: str,
133
128
  *,
134
129
  recursive: bool = False,
135
- filter: ta.Optional[ta.Callable[[str], bool]] = None,
130
+ filter: ta.Callable[[str], bool] | None = None, # noqa
136
131
  include_special: bool = False,
137
132
  ) -> None:
138
133
  for _ in yield_import_all(
@@ -144,7 +139,7 @@ def import_all(
144
139
  pass
145
140
 
146
141
 
147
- def try_import(spec: str) -> ta.Optional[types.ModuleType]:
142
+ def try_import(spec: str) -> types.ModuleType | None:
148
143
  s = spec.lstrip('.')
149
144
  l = len(spec) - len(s)
150
145
  try:
omlish/lang/iterables.py CHANGED
@@ -28,13 +28,13 @@ def exhaust(it: ta.Iterable[ta.Any]) -> None:
28
28
  pass
29
29
 
30
30
 
31
- def peek(vs: ta.Iterable[T]) -> ta.Tuple[T, ta.Iterator[T]]:
31
+ def peek(vs: ta.Iterable[T]) -> tuple[T, ta.Iterator[T]]:
32
32
  it = iter(vs)
33
33
  v = next(it)
34
34
  return v, itertools.chain(iter((v,)), it)
35
35
 
36
36
 
37
- Rangeable: ta.TypeAlias = ta.Union[int, ta.Tuple[int], ta.Tuple[int, int], ta.Iterable[int]]
37
+ Rangeable: ta.TypeAlias = int | tuple[int] | tuple[int, int] | ta.Iterable[int]
38
38
 
39
39
 
40
40
  def asrange(i: Rangeable) -> ta.Iterable[int]:
omlish/lang/maybes.py CHANGED
@@ -63,6 +63,7 @@ class Maybe(abc.ABC, ta.Generic[T]):
63
63
 
64
64
 
65
65
  class _Maybe(Maybe[T], tuple):
66
+ __slots__ = ()
66
67
 
67
68
  @property
68
69
  def present(self) -> bool:
@@ -130,7 +131,7 @@ def empty() -> Maybe[T]:
130
131
  return _empty # noqa
131
132
 
132
133
 
133
- def maybe(o: ta.Optional[T]) -> Maybe[T]:
134
+ def maybe(o: T | None) -> Maybe[T]:
134
135
  if o is None:
135
136
  return _empty # noqa
136
137
  return just(o)