omlish 0.0.0.dev4__py3-none-any.whl → 0.0.0.dev6__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 (143) hide show
  1. omlish/__about__.py +1 -1
  2. omlish/__init__.py +1 -1
  3. omlish/asyncs/__init__.py +10 -4
  4. omlish/asyncs/anyio.py +142 -12
  5. omlish/asyncs/asyncio.py +23 -0
  6. omlish/asyncs/asyncs.py +9 -6
  7. omlish/asyncs/bridge.py +316 -0
  8. omlish/asyncs/flavors.py +27 -1
  9. omlish/asyncs/trio_asyncio.py +28 -18
  10. omlish/c3.py +1 -1
  11. omlish/cached.py +1 -2
  12. omlish/collections/__init__.py +5 -1
  13. omlish/collections/cache/impl.py +1 -1
  14. omlish/collections/identity.py +7 -0
  15. omlish/collections/indexed.py +1 -1
  16. omlish/collections/utils.py +38 -6
  17. omlish/configs/__init__.py +5 -0
  18. omlish/configs/classes.py +53 -0
  19. omlish/configs/strings.py +94 -0
  20. omlish/dataclasses/__init__.py +9 -0
  21. omlish/dataclasses/impl/api.py +1 -1
  22. omlish/dataclasses/impl/as_.py +1 -1
  23. omlish/dataclasses/impl/copy.py +30 -0
  24. omlish/dataclasses/impl/exceptions.py +6 -0
  25. omlish/dataclasses/impl/fields.py +25 -25
  26. omlish/dataclasses/impl/init.py +5 -3
  27. omlish/dataclasses/impl/main.py +3 -0
  28. omlish/dataclasses/impl/metaclass.py +6 -1
  29. omlish/dataclasses/impl/order.py +1 -1
  30. omlish/dataclasses/impl/reflect.py +15 -2
  31. omlish/dataclasses/utils.py +44 -0
  32. omlish/defs.py +1 -1
  33. omlish/diag/__init__.py +4 -0
  34. omlish/diag/procfs.py +31 -3
  35. omlish/diag/procstats.py +32 -0
  36. omlish/{testing → diag}/pydevd.py +35 -0
  37. omlish/diag/replserver/console.py +3 -3
  38. omlish/diag/replserver/server.py +6 -5
  39. omlish/diag/threads.py +86 -0
  40. omlish/dispatch/_dispatch2.py +65 -0
  41. omlish/dispatch/_dispatch3.py +104 -0
  42. omlish/docker.py +20 -1
  43. omlish/fnpairs.py +37 -18
  44. omlish/graphs/dags.py +113 -0
  45. omlish/graphs/domination.py +268 -0
  46. omlish/graphs/trees.py +2 -2
  47. omlish/http/__init__.py +25 -0
  48. omlish/http/asgi.py +132 -0
  49. omlish/http/collections.py +15 -0
  50. omlish/http/consts.py +47 -5
  51. omlish/http/cookies.py +194 -0
  52. omlish/http/dates.py +70 -0
  53. omlish/http/encodings.py +6 -0
  54. omlish/http/json.py +273 -0
  55. omlish/http/sessions.py +204 -0
  56. omlish/inject/__init__.py +51 -17
  57. omlish/inject/binder.py +185 -5
  58. omlish/inject/bindings.py +3 -36
  59. omlish/inject/eagers.py +2 -8
  60. omlish/inject/elements.py +30 -9
  61. omlish/inject/exceptions.py +3 -3
  62. omlish/inject/impl/elements.py +65 -31
  63. omlish/inject/impl/injector.py +20 -2
  64. omlish/inject/impl/inspect.py +33 -5
  65. omlish/inject/impl/multis.py +74 -0
  66. omlish/inject/impl/origins.py +75 -0
  67. omlish/inject/impl/{private.py → privates.py} +2 -2
  68. omlish/inject/impl/providers.py +19 -39
  69. omlish/inject/{proxy.py → impl/proxy.py} +2 -2
  70. omlish/inject/impl/scopes.py +7 -2
  71. omlish/inject/injector.py +9 -4
  72. omlish/inject/inspect.py +18 -0
  73. omlish/inject/keys.py +11 -23
  74. omlish/inject/listeners.py +26 -0
  75. omlish/inject/managed.py +76 -10
  76. omlish/inject/multis.py +120 -0
  77. omlish/inject/origins.py +27 -0
  78. omlish/inject/overrides.py +5 -4
  79. omlish/inject/{private.py → privates.py} +6 -10
  80. omlish/inject/providers.py +12 -85
  81. omlish/inject/scopes.py +20 -9
  82. omlish/inject/types.py +2 -8
  83. omlish/iterators.py +13 -0
  84. omlish/lang/__init__.py +12 -2
  85. omlish/lang/cached.py +2 -2
  86. omlish/lang/classes/restrict.py +3 -2
  87. omlish/lang/classes/simple.py +18 -8
  88. omlish/lang/classes/virtual.py +2 -2
  89. omlish/lang/contextmanagers.py +75 -2
  90. omlish/lang/datetimes.py +6 -5
  91. omlish/lang/descriptors.py +131 -0
  92. omlish/lang/functions.py +18 -28
  93. omlish/lang/imports.py +11 -2
  94. omlish/lang/iterables.py +20 -1
  95. omlish/lang/typing.py +6 -0
  96. omlish/lifecycles/__init__.py +34 -0
  97. omlish/lifecycles/abstract.py +43 -0
  98. omlish/lifecycles/base.py +51 -0
  99. omlish/lifecycles/contextmanagers.py +74 -0
  100. omlish/lifecycles/controller.py +116 -0
  101. omlish/lifecycles/manager.py +161 -0
  102. omlish/lifecycles/states.py +43 -0
  103. omlish/lifecycles/transitions.py +64 -0
  104. omlish/logs/formatters.py +1 -1
  105. omlish/logs/utils.py +1 -1
  106. omlish/marshal/__init__.py +4 -0
  107. omlish/marshal/datetimes.py +1 -1
  108. omlish/marshal/naming.py +4 -0
  109. omlish/marshal/objects.py +1 -0
  110. omlish/marshal/polymorphism.py +4 -4
  111. omlish/reflect.py +139 -18
  112. omlish/secrets/__init__.py +7 -0
  113. omlish/secrets/marshal.py +41 -0
  114. omlish/secrets/passwords.py +120 -0
  115. omlish/secrets/secrets.py +47 -0
  116. omlish/serde/__init__.py +0 -0
  117. omlish/serde/dotenv.py +574 -0
  118. omlish/{json.py → serde/json.py} +4 -2
  119. omlish/serde/props.py +604 -0
  120. omlish/serde/yaml.py +223 -0
  121. omlish/sql/dbs.py +1 -1
  122. omlish/sql/duckdb.py +136 -0
  123. omlish/sql/sqlean.py +17 -0
  124. omlish/sync.py +70 -0
  125. omlish/term.py +7 -2
  126. omlish/testing/pytest/__init__.py +8 -2
  127. omlish/testing/pytest/helpers.py +0 -24
  128. omlish/testing/pytest/inject/harness.py +4 -4
  129. omlish/testing/pytest/marks.py +45 -0
  130. omlish/testing/pytest/plugins/__init__.py +3 -0
  131. omlish/testing/pytest/plugins/asyncs.py +136 -0
  132. omlish/testing/pytest/plugins/managermarks.py +60 -0
  133. omlish/testing/pytest/plugins/pydevd.py +1 -1
  134. omlish/testing/testing.py +10 -0
  135. omlish/text/delimit.py +4 -0
  136. omlish/text/glyphsplit.py +92 -0
  137. {omlish-0.0.0.dev4.dist-info → omlish-0.0.0.dev6.dist-info}/METADATA +1 -1
  138. omlish-0.0.0.dev6.dist-info/RECORD +240 -0
  139. {omlish-0.0.0.dev4.dist-info → omlish-0.0.0.dev6.dist-info}/WHEEL +1 -1
  140. omlish/configs/props.py +0 -64
  141. omlish-0.0.0.dev4.dist-info/RECORD +0 -195
  142. {omlish-0.0.0.dev4.dist-info → omlish-0.0.0.dev6.dist-info}/LICENSE +0 -0
  143. {omlish-0.0.0.dev4.dist-info → omlish-0.0.0.dev6.dist-info}/top_level.txt +0 -0
@@ -78,7 +78,8 @@ class PackageSealed:
78
78
  for base in cls.__bases__:
79
79
  if base is not Abstract:
80
80
  if PackageSealed in base.__bases__:
81
- if cls.__module__.split('.')[:-1] != base.__module__.split('.')[:-1]:
81
+ pfx = base.__module__.split('.')[:-1]
82
+ if cls.__module__.split('.')[:len(pfx)] != pfx:
82
83
  raise SealedError(base)
83
84
  super().__init_subclass__(**kwargs)
84
85
 
@@ -89,7 +90,7 @@ class PackageSealed:
89
90
  class NotInstantiable(Abstract):
90
91
  __slots__ = ()
91
92
 
92
- def __new__(cls, *args, **kwargs):
93
+ def __new__(cls, *args, **kwargs): # noqa
93
94
  raise TypeError
94
95
 
95
96
 
@@ -14,13 +14,25 @@ V = ta.TypeVar('V')
14
14
  ##
15
15
 
16
16
 
17
- class _Namespace(Final):
17
+ class _NamespaceMeta(abc.ABCMeta):
18
+ def __new__(mcls, name, bases, namespace):
19
+ if bases:
20
+ for nc in (NotInstantiable,):
21
+ if nc not in bases:
22
+ bases += (nc,)
23
+ return super().__new__(mcls, name, bases, namespace)
18
24
 
19
- def __mro_entries__(self, bases, **kwargs):
20
- return (Final, NotInstantiable)
25
+ def __iter__(cls) -> ta.Iterator[tuple[str, ta.Any]]:
26
+ for a in dir(cls):
27
+ if not a.startswith('_'):
28
+ yield (a, getattr(cls, a))
21
29
 
30
+ def __getitem__(cls, n: str) -> ta.Any:
31
+ return getattr(cls, n)
22
32
 
23
- Namespace: type = _Namespace() # type: ignore
33
+
34
+ class Namespace(metaclass=_NamespaceMeta):
35
+ pass
24
36
 
25
37
 
26
38
  ##
@@ -54,8 +66,6 @@ class _MarkerMeta(abc.ABCMeta):
54
66
  class Marker(NotInstantiable, metaclass=_MarkerMeta):
55
67
  """A marker."""
56
68
 
57
- __slots__ = ()
58
-
59
69
 
60
70
  ##
61
71
 
@@ -77,7 +87,7 @@ _SINGLETON_LOCK = threading.RLock()
77
87
 
78
88
 
79
89
  def _set_singleton_instance(inst):
80
- cls = type(inst)
90
+ cls = inst.__class__
81
91
  if _SINGLETON_INSTANCE_ATTR in cls.__dict__:
82
92
  raise TypeError(cls)
83
93
 
@@ -86,7 +96,7 @@ def _set_singleton_instance(inst):
86
96
 
87
97
  @functools.wraps(old_init)
88
98
  def new_init(self):
89
- if type(self) is not cls:
99
+ if self.__class__ is not cls:
90
100
  old_init(self) # noqa
91
101
 
92
102
  setattr(cls, '__init__', new_init)
@@ -3,8 +3,8 @@ import types
3
3
  import typing as ta
4
4
 
5
5
  from .abstract import make_abstract
6
- from .restrict import NotInstantiable
7
6
  from .restrict import Final
7
+ from .restrict import NotInstantiable
8
8
 
9
9
 
10
10
  T = ta.TypeVar('T')
@@ -122,7 +122,7 @@ class Callable(NotInstantiable, Final, ta.Generic[T]):
122
122
 
123
123
  @classmethod
124
124
  def __subclasscheck__(cls, subclass: type) -> bool:
125
- if not hasattr(subclass, '__call__'):
125
+ if not hasattr(subclass, '__call__'): # noqa
126
126
  return False
127
127
  call = subclass.__call__
128
128
  if isinstance(call, types.MethodWrapperType) and call.__self__ is subclass:
@@ -1,3 +1,8 @@
1
+ """
2
+ TODO:
3
+ - AsyncExitStacked
4
+ """
5
+ import abc
1
6
  import contextlib
2
7
  import contextvars
3
8
  import functools
@@ -14,7 +19,7 @@ T = ta.TypeVar('T')
14
19
 
15
20
  class ContextManaged:
16
21
 
17
- def __enter__(self: ta.Self) -> ta.Self:
22
+ def __enter__(self) -> ta.Self:
18
23
  return self
19
24
 
20
25
  def __exit__(
@@ -50,6 +55,69 @@ NOP_CONTEXT_MANAGER = NopContextManager()
50
55
  ##
51
56
 
52
57
 
58
+ class ContextManager(abc.ABC, ta.Generic[T]):
59
+
60
+ def __init_subclass__(cls, **kwargs: ta.Any) -> None:
61
+ super().__init_subclass__(**kwargs)
62
+
63
+ if not hasattr(cls.__contextmanager__, '_is_contextmanager'):
64
+ cls.__contextmanager__ = contextlib.contextmanager(cls.__contextmanager__) # type: ignore # noqa
65
+ cls.__contextmanager__._is_contextmanager = True # type: ignore # noqa
66
+
67
+ @abc.abstractmethod
68
+ def __contextmanager__(self) -> ta.Iterable[T]:
69
+ raise NotImplementedError
70
+
71
+ __contextmanager__._is_contextmanager = True # type: ignore # noqa
72
+
73
+ _contextmanager: ta.Any
74
+
75
+ def __enter__(self) -> T:
76
+ self._contextmanager = self.__contextmanager__()
77
+ return self._contextmanager.__enter__() # type: ignore
78
+
79
+ def __exit__(
80
+ self,
81
+ exc_type: type[BaseException] | None,
82
+ exc_val: BaseException | None,
83
+ exc_tb: types.TracebackType | None,
84
+ ) -> bool | None:
85
+ return self._contextmanager.__exit__(exc_type, exc_val, exc_tb)
86
+
87
+
88
+ class AsyncContextManager(abc.ABC, ta.Generic[T]):
89
+
90
+ def __init_subclass__(cls, **kwargs: ta.Any) -> None:
91
+ super().__init_subclass__(**kwargs)
92
+
93
+ if not hasattr(cls.__asynccontextmanager__, '_is_asynccontextmanager'):
94
+ cls.__asynccontextmanager__ = contextlib.asynccontextmanager(cls.__asynccontextmanager__) # type: ignore # noqa
95
+ cls.__asynccontextmanager__._is_asynccontextmanager = True # type: ignore # noqa
96
+
97
+ @abc.abstractmethod
98
+ def __asynccontextmanager__(self) -> ta.AsyncIterator[T]:
99
+ raise NotImplementedError
100
+
101
+ __asynccontextmanager__._is_asynccontextmanager = True # type: ignore # noqa
102
+
103
+ _asynccontextmanager: ta.Any
104
+
105
+ async def __aenter__(self) -> T:
106
+ self._asynccontextmanager = self.__asynccontextmanager__()
107
+ return await self._asynccontextmanager.__aenter__() # type: ignore
108
+
109
+ async def __aexit__(
110
+ self,
111
+ exc_type: type[BaseException] | None,
112
+ exc_val: BaseException | None,
113
+ exc_tb: types.TracebackType | None,
114
+ ) -> None:
115
+ return await self._asynccontextmanager.__aexit__(exc_type, exc_val, exc_tb)
116
+
117
+
118
+ ##
119
+
120
+
53
121
  @contextlib.contextmanager
54
122
  def defer(fn: ta.Callable) -> ta.Generator[ta.Callable, None, None]:
55
123
  try:
@@ -134,7 +202,7 @@ class ExitStacked:
134
202
  def _enter_context(self, context_manager: ta.ContextManager[T]) -> T:
135
203
  return self._exit_stack.enter_context(ta.cast(ta.ContextManager, context_manager))
136
204
 
137
- def __enter__(self: ta.Self) -> ta.Self:
205
+ def __enter__(self) -> ta.Self:
138
206
  try:
139
207
  superfn = super().__enter__ # type: ignore
140
208
  except AttributeError:
@@ -242,14 +310,19 @@ DefaultLockable = bool | Lockable | ta.ContextManager | None
242
310
  def default_lock(value: DefaultLockable, default: DefaultLockable) -> Lockable:
243
311
  if value is None:
244
312
  value = default
313
+
245
314
  if value is True:
246
315
  lock = threading.RLock()
247
316
  return lambda: lock
317
+
248
318
  elif value is False or value is None:
249
319
  return NOP_CONTEXT_MANAGER
320
+
250
321
  elif callable(value):
251
322
  return value
323
+
252
324
  elif isinstance(value, ta.ContextManager):
253
325
  return lambda: value
326
+
254
327
  else:
255
328
  raise TypeError(value)
omlish/lang/datetimes.py CHANGED
@@ -18,17 +18,18 @@ def months_ago(date: datetime.date, num: int) -> datetime.date:
18
18
  return datetime.date(ago_year, ago_month, 1)
19
19
 
20
20
 
21
- def parse_date(s: str) -> datetime.date:
21
+ def parse_date(s: str, tz: datetime.timezone | None = None) -> datetime.date:
22
+
22
23
  if s.lower() in ['today', 'now']:
23
- return datetime.date.today()
24
+ return datetime.datetime.now(tz=tz)
24
25
  elif s.lower() == 'yesterday':
25
- return datetime.date.today() - datetime.timedelta(days=1)
26
+ return datetime.datetime.now(tz=tz) - datetime.timedelta(days=1)
26
27
  elif s.lower().endswith(' days ago'):
27
28
  num = int(s.split(' ', 1)[0])
28
- return datetime.date.today() - datetime.timedelta(days=num)
29
+ return datetime.datetime.now(tz=tz) - datetime.timedelta(days=num)
29
30
  elif s.lower().endswith(' months ago'):
30
31
  months = int(s.split(' ', 1)[0])
31
- return months_ago(datetime.date.today(), months)
32
+ return months_ago(datetime.datetime.now(tz=tz), months)
32
33
  else:
33
34
  return datetime.date(*map(int, s.split('-', 3)))
34
35
 
@@ -1,9 +1,11 @@
1
1
  import functools
2
2
  import operator
3
+ import types
3
4
  import typing as ta
4
5
 
5
6
 
6
7
  T = ta.TypeVar('T')
8
+ P = ta.ParamSpec('P')
7
9
 
8
10
 
9
11
  ##
@@ -31,6 +33,19 @@ def is_method_descriptor(obj: ta.Any) -> bool:
31
33
  return isinstance(obj, (*BUILTIN_METHOD_DESCRIPTORS, _MethodDescriptor))
32
34
 
33
35
 
36
+ def _has_method_descriptor(obj: ta.Any) -> bool:
37
+ while True:
38
+ if is_method_descriptor(obj):
39
+ return True
40
+ elif isinstance(obj, functools.partial):
41
+ obj = obj.func
42
+ else:
43
+ try:
44
+ obj = getattr(obj, '__wrapped__')
45
+ except AttributeError:
46
+ return False
47
+
48
+
34
49
  def unwrap_method_descriptors(fn: ta.Callable) -> ta.Callable:
35
50
  while is_method_descriptor(fn):
36
51
  fn = fn.__func__ # type: ignore # noqa
@@ -40,6 +55,122 @@ def unwrap_method_descriptors(fn: ta.Callable) -> ta.Callable:
40
55
  ##
41
56
 
42
57
 
58
+ def unwrap_func(fn: ta.Callable) -> ta.Callable:
59
+ fn, _ = unwrap_func_with_partials(fn)
60
+ return fn
61
+
62
+
63
+ def unwrap_func_with_partials(fn: ta.Callable) -> tuple[ta.Callable, list[functools.partial]]:
64
+ ps = []
65
+ while True:
66
+ if is_method_descriptor(fn) or isinstance(fn, types.MethodType):
67
+ fn = fn.__func__ # type: ignore
68
+ elif hasattr(fn, '__wrapped__'):
69
+ nxt = fn.__wrapped__
70
+ if not callable(nxt):
71
+ raise TypeError(nxt)
72
+ if nxt is fn:
73
+ raise TypeError(fn)
74
+ fn = nxt
75
+ # NOTE: __wrapped__ takes precedence - a partial might point to a bound Method when the important information is
76
+ # still the unbound func. see _decorator_descriptor for an example of this.
77
+ elif isinstance(fn, functools.partial):
78
+ ps.append(fn)
79
+ fn = fn.func
80
+ else:
81
+ break
82
+ return fn, ps
83
+
84
+
85
+ ##
86
+
87
+
88
+ WRAPPER_UPDATES_EXCEPT_DICT = tuple(a for a in functools.WRAPPER_UPDATES if a != '__dict__')
89
+
90
+
91
+ def update_wrapper_except_dict(
92
+ wrapper,
93
+ wrapped,
94
+ assigned=functools.WRAPPER_ASSIGNMENTS,
95
+ updated=WRAPPER_UPDATES_EXCEPT_DICT,
96
+ ):
97
+ return functools.update_wrapper(
98
+ wrapper,
99
+ wrapped,
100
+ assigned=assigned,
101
+ updated=updated,
102
+ )
103
+
104
+
105
+ ##
106
+
107
+
108
+ _DECORATOR_HANDLES_UNBOUND_METHODS = True
109
+
110
+
111
+ class _decorator_descriptor: # noqa
112
+ if not _DECORATOR_HANDLES_UNBOUND_METHODS:
113
+ def __init__(self, wrapper, fn):
114
+ self._wrapper, self._fn = wrapper, fn
115
+ update_wrapper_except_dict(self, fn)
116
+
117
+ def __get__(self, instance, owner):
118
+ return functools.update_wrapper(functools.partial(self._wrapper, fn := self._fn.__get__(instance, owner)), fn) # noqa
119
+
120
+ else:
121
+ def __init__(self, wrapper, fn):
122
+ self._wrapper, self._fn = wrapper, fn
123
+ self._md = _has_method_descriptor(fn)
124
+ update_wrapper_except_dict(self, fn)
125
+
126
+ def __get__(self, instance, owner):
127
+ fn = self._fn.__get__(instance, owner)
128
+ if self._md or instance is not None:
129
+ @functools.wraps(fn)
130
+ def inner(*args, **kwargs):
131
+ return self._wrapper(fn, *args, **kwargs)
132
+ return inner
133
+ else:
134
+ @functools.wraps(fn)
135
+ def outer(this, *args, **kwargs):
136
+ @functools.wraps(self._fn)
137
+ def inner(*args2, **kwargs2):
138
+ return fn(this, *args2, **kwargs2)
139
+ return self._wrapper(inner, *args, **kwargs)
140
+ return outer
141
+
142
+ def __repr__(self):
143
+ return f'{self.__class__.__name__}<{self._wrapper}, {self._fn}>'
144
+
145
+ def __call__(self, *args, **kwargs):
146
+ return self._wrapper(self._fn, *args, **kwargs)
147
+
148
+
149
+ class _decorator: # noqa
150
+ def __init__(self, wrapper):
151
+ self._wrapper = wrapper
152
+ update_wrapper_except_dict(self, wrapper)
153
+
154
+ def __repr__(self):
155
+ return f'{self.__class__.__name__}<{self._wrapper}>'
156
+
157
+ def __call__(self, fn):
158
+ return _decorator_descriptor(self._wrapper, fn) # noqa
159
+
160
+
161
+ # FIXME:
162
+ # def decorator(
163
+ # wrapper: ta.Callable[ta.Concatenate[ta.Any, P], T], # FIXME: https://youtrack.jetbrains.com/issue/PY-72164 # noqa
164
+ # ) -> ta.Callable[[ta.Callable[P, T]], ta.Callable[P, T]]:
165
+ # return _decorator(wrapper)
166
+
167
+
168
+ decorator = _decorator
169
+
170
+
171
+ ##
172
+
173
+
43
174
  class AccessForbiddenError(Exception):
44
175
 
45
176
  def __init__(self, name: str | None = None, *args: ta.Any, **kwargs: ta.Any) -> None:
omlish/lang/functions.py CHANGED
@@ -3,8 +3,6 @@ import functools
3
3
  import time
4
4
  import typing as ta
5
5
 
6
- from .descriptors import is_method_descriptor
7
-
8
6
 
9
7
  T = ta.TypeVar('T')
10
8
  P = ta.ParamSpec('P')
@@ -41,32 +39,6 @@ def recurse(fn: ta.Callable[..., T], *args, **kwargs) -> T:
41
39
  ##
42
40
 
43
41
 
44
- def unwrap_func(fn: ta.Callable) -> ta.Callable:
45
- fn, _ = unwrap_func_with_partials(fn)
46
- return fn
47
-
48
-
49
- def unwrap_func_with_partials(fn: ta.Callable) -> tuple[ta.Callable, list[functools.partial]]:
50
- ps = []
51
- while True:
52
- if is_method_descriptor(fn):
53
- fn = fn.__func__ # type: ignore
54
- elif isinstance(fn, functools.partial):
55
- ps.append(fn)
56
- fn = fn.func
57
- else:
58
- nxt = getattr(fn, '__wrapped__', None)
59
- if not callable(nxt):
60
- break
61
- if nxt is fn:
62
- raise TypeError(fn)
63
- fn = nxt
64
- return fn, ps
65
-
66
-
67
- ##
68
-
69
-
70
42
  def raise_(o: BaseException) -> ta.NoReturn:
71
43
  raise o
72
44
 
@@ -128,6 +100,14 @@ def is_not_none(o: ta.Any) -> bool:
128
100
  return o is not None
129
101
 
130
102
 
103
+ def isinstance_of(class_or_tuple: ta.Any) -> ta.Callable[[ta.Any], bool]:
104
+ return lambda o: isinstance(o, class_or_tuple)
105
+
106
+
107
+ def issubclass_of(class_or_tuple: ta.Any) -> ta.Callable[[ta.Any], bool]:
108
+ return lambda o: issubclass(o, class_or_tuple)
109
+
110
+
131
111
  class VoidError(Exception):
132
112
  pass
133
113
 
@@ -148,6 +128,16 @@ def void(*args: ta.Any, **kwargs: ta.Any) -> ta.NoReturn:
148
128
  ##
149
129
 
150
130
 
131
+ def as_async(fn: ta.Callable[P, T]) -> ta.Callable[P, ta.Awaitable[T]]:
132
+ @functools.wraps(fn)
133
+ async def inner(*args, **kwargs):
134
+ return fn(*args, **kwargs)
135
+ return inner
136
+
137
+
138
+ ##
139
+
140
+
151
141
  _MISSING = object()
152
142
 
153
143
 
omlish/lang/imports.py CHANGED
@@ -1,6 +1,6 @@
1
1
  import contextlib
2
2
  import functools
3
- import importlib.resources
3
+ import importlib.util
4
4
  import sys
5
5
  import types
6
6
  import typing as ta
@@ -11,6 +11,13 @@ from .cached import cached_function
11
11
  ##
12
12
 
13
13
 
14
+ def can_import(name: str, package: str | None = None) -> bool:
15
+ return importlib.util.find_spec(name, package) is not None
16
+
17
+
18
+ ##
19
+
20
+
14
21
  def lazy_import(name: str, package: str | None = None) -> ta.Callable[[], ta.Any]:
15
22
  return cached_function(functools.partial(importlib.import_module, name, package=package))
16
23
 
@@ -66,6 +73,8 @@ def yield_importable(
66
73
  filter: ta.Callable[[str], bool] | None = None, # noqa
67
74
  include_special: bool = False,
68
75
  ) -> ta.Iterator[str]:
76
+ from importlib import resources
77
+
69
78
  def rec(cur):
70
79
  if cur.split('.')[-1] == '__pycache__':
71
80
  return
@@ -83,7 +92,7 @@ def yield_importable(
83
92
  if getattr(module, '__file__', None) is None:
84
93
  return
85
94
 
86
- for file in importlib.resources.files(cur).iterdir():
95
+ for file in resources.files(cur).iterdir():
87
96
  if file.is_file() and file.name.endswith('.py'):
88
97
  if not (include_special or file.name not in SPECIAL_IMPORTABLE):
89
98
  continue
omlish/lang/iterables.py CHANGED
@@ -1,3 +1,4 @@
1
+ import dataclasses as dc
1
2
  import itertools
2
3
  import typing as ta
3
4
 
@@ -34,7 +35,13 @@ def peek(vs: ta.Iterable[T]) -> tuple[T, ta.Iterator[T]]:
34
35
  return v, itertools.chain(iter((v,)), it)
35
36
 
36
37
 
37
- Rangeable: ta.TypeAlias = int | tuple[int] | tuple[int, int] | ta.Iterable[int]
38
+ Rangeable: ta.TypeAlias = ta.Union[ # noqa
39
+ int,
40
+ tuple[int],
41
+ tuple[int, int],
42
+ tuple[int, int, int],
43
+ ta.Iterable[int],
44
+ ]
38
45
 
39
46
 
40
47
  def asrange(i: Rangeable) -> ta.Iterable[int]:
@@ -52,3 +59,15 @@ def prodrange(*dims: Rangeable) -> ta.Iterable[ta.Sequence[int]]:
52
59
  if not dims:
53
60
  return []
54
61
  return itertools.product(*map(asrange, dims))
62
+
63
+
64
+ @dc.dataclass(frozen=True)
65
+ class itergen(ta.Generic[T]): # noqa
66
+ fn: ta.Callable[[], ta.Iterable[T]]
67
+
68
+ def __iter__(self):
69
+ return iter(self.fn())
70
+
71
+
72
+ def renumerate(it: ta.Iterable[T]) -> ta.Iterable[tuple[T, int]]:
73
+ return ((e, i) for i, e in enumerate(it))
omlish/lang/typing.py CHANGED
@@ -3,6 +3,7 @@ TODO:
3
3
  - typed_lambda is really kinda just 'annotate'
4
4
  - TypedLambda / TypedPartial classes, picklable, reprs
5
5
  - probably need to gen types per inst
6
+ - typed_factory
6
7
  """
7
8
  import functools
8
9
  import inspect
@@ -11,6 +12,11 @@ import typing as ta
11
12
 
12
13
  Ty = ta.TypeVar('Ty', bound=type)
13
14
 
15
+ T = ta.TypeVar('T')
16
+ A0 = ta.TypeVar('A0')
17
+ A1 = ta.TypeVar('A1')
18
+ A2 = ta.TypeVar('A2')
19
+
14
20
 
15
21
  BytesLike: ta.TypeAlias = bytes | bytearray
16
22
 
@@ -0,0 +1,34 @@
1
+ from .abstract import ( # noqa
2
+ AbstractLifecycle,
3
+ )
4
+
5
+ from .base import ( # noqa
6
+ CallbackLifecycle,
7
+ Lifecycle,
8
+ LifecycleCallback,
9
+ )
10
+
11
+ from .contextmanagers import ( # noqa
12
+ ContextManagerLifecycle,
13
+ LifecycleContextManager,
14
+ )
15
+
16
+ from .controller import ( # noqa
17
+ LifecycleController,
18
+ LifecycleListener,
19
+ )
20
+
21
+ from .manager import ( # noqa
22
+ LifecycleManager,
23
+ )
24
+
25
+ from .states import ( # noqa
26
+ LifecycleState,
27
+ LifecycleStateError,
28
+ LifecycleStates,
29
+ )
30
+
31
+ from .transitions import ( # noqa
32
+ LifecycleTransition,
33
+ LifecycleTransitions,
34
+ )
@@ -0,0 +1,43 @@
1
+ import typing as ta
2
+
3
+ from .. import cached
4
+ from .. import dataclasses as dc
5
+ from .. import lang
6
+ from .base import Lifecycle
7
+
8
+
9
+ AbstractLifecycleT = ta.TypeVar('AbstractLifecycleT', bound='AbstractLifecycle')
10
+
11
+
12
+ class AbstractLifecycle(lang.Abstract):
13
+ @dc.dataclass(frozen=True)
14
+ class _Lifecycle(Lifecycle, lang.Final, ta.Generic[AbstractLifecycleT]):
15
+ obj: AbstractLifecycleT
16
+
17
+ def lifecycle_construct(self) -> None:
18
+ self.obj._lifecycle_construct() # noqa
19
+
20
+ def lifecycle_start(self) -> None:
21
+ self.obj._lifecycle_start() # noqa
22
+
23
+ def lifecycle_stop(self) -> None:
24
+ self.obj._lifecycle_stop() # noqa
25
+
26
+ def lifecycle_destroy(self) -> None:
27
+ self.obj._lifecycle_destroy() # noqa
28
+
29
+ @cached.property
30
+ def _lifecycle(self) -> _Lifecycle[ta.Self]:
31
+ return AbstractLifecycle._Lifecycle(self)
32
+
33
+ def _lifecycle_construct(self) -> None:
34
+ pass
35
+
36
+ def _lifecycle_start(self) -> None:
37
+ pass
38
+
39
+ def _lifecycle_stop(self) -> None:
40
+ pass
41
+
42
+ def _lifecycle_destroy(self) -> None:
43
+ pass
@@ -0,0 +1,51 @@
1
+ import typing as ta
2
+
3
+ from .. import dataclasses as dc
4
+ from .. import lang
5
+
6
+
7
+ LifecycleT = ta.TypeVar('LifecycleT', bound='Lifecycle')
8
+ LifecycleCallback: ta.TypeAlias = ta.Callable[[LifecycleT], None]
9
+
10
+
11
+ class Lifecycle:
12
+
13
+ def lifecycle_construct(self) -> None:
14
+ pass
15
+
16
+ def lifecycle_start(self) -> None:
17
+ pass
18
+
19
+ def lifecycle_stop(self) -> None:
20
+ pass
21
+
22
+ def lifecycle_destroy(self) -> None:
23
+ pass
24
+
25
+
26
+ @dc.dataclass(frozen=True, kw_only=True)
27
+ class CallbackLifecycle(Lifecycle, lang.Final, ta.Generic[LifecycleT]):
28
+ on_construct: LifecycleCallback['CallbackLifecycle[LifecycleT]'] | None = None
29
+ on_start: LifecycleCallback['CallbackLifecycle[LifecycleT]'] | None = None
30
+ on_stop: LifecycleCallback['CallbackLifecycle[LifecycleT]'] | None = None
31
+ on_destroy: LifecycleCallback['CallbackLifecycle[LifecycleT]'] | None = None
32
+
33
+ @ta.override
34
+ def lifecycle_construct(self) -> None:
35
+ if self.on_construct is not None:
36
+ self.on_construct(self)
37
+
38
+ @ta.override
39
+ def lifecycle_start(self) -> None:
40
+ if self.on_start is not None:
41
+ self.on_start(self)
42
+
43
+ @ta.override
44
+ def lifecycle_stop(self) -> None:
45
+ if self.on_stop is not None:
46
+ self.on_stop(self)
47
+
48
+ @ta.override
49
+ def lifecycle_destroy(self) -> None:
50
+ if self.on_destroy is not None:
51
+ self.on_destroy(self)