omlish 0.0.0.dev5__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 (100) hide show
  1. omlish/__about__.py +1 -1
  2. omlish/asyncs/__init__.py +9 -0
  3. omlish/asyncs/anyio.py +83 -19
  4. omlish/asyncs/asyncio.py +23 -0
  5. omlish/asyncs/asyncs.py +9 -6
  6. omlish/asyncs/bridge.py +316 -0
  7. omlish/asyncs/trio_asyncio.py +7 -3
  8. omlish/collections/__init__.py +1 -0
  9. omlish/collections/identity.py +7 -0
  10. omlish/configs/strings.py +94 -0
  11. omlish/dataclasses/__init__.py +9 -0
  12. omlish/dataclasses/impl/copy.py +30 -0
  13. omlish/dataclasses/impl/exceptions.py +6 -0
  14. omlish/dataclasses/impl/fields.py +24 -25
  15. omlish/dataclasses/impl/init.py +4 -2
  16. omlish/dataclasses/impl/main.py +2 -0
  17. omlish/dataclasses/utils.py +44 -0
  18. omlish/diag/__init__.py +4 -0
  19. omlish/diag/procfs.py +2 -2
  20. omlish/{testing → diag}/pydevd.py +35 -0
  21. omlish/dispatch/_dispatch2.py +65 -0
  22. omlish/dispatch/_dispatch3.py +104 -0
  23. omlish/docker.py +1 -1
  24. omlish/fnpairs.py +11 -0
  25. omlish/http/asgi.py +2 -1
  26. omlish/http/collections.py +15 -0
  27. omlish/http/consts.py +16 -1
  28. omlish/http/sessions.py +10 -3
  29. omlish/inject/__init__.py +45 -17
  30. omlish/inject/binder.py +185 -5
  31. omlish/inject/bindings.py +3 -36
  32. omlish/inject/eagers.py +2 -8
  33. omlish/inject/elements.py +30 -9
  34. omlish/inject/exceptions.py +1 -1
  35. omlish/inject/impl/elements.py +37 -12
  36. omlish/inject/impl/injector.py +19 -2
  37. omlish/inject/impl/inspect.py +33 -5
  38. omlish/inject/impl/origins.py +75 -0
  39. omlish/inject/impl/{private.py → privates.py} +2 -2
  40. omlish/inject/impl/scopes.py +6 -2
  41. omlish/inject/injector.py +8 -4
  42. omlish/inject/inspect.py +18 -0
  43. omlish/inject/keys.py +8 -14
  44. omlish/inject/listeners.py +26 -0
  45. omlish/inject/managed.py +76 -10
  46. omlish/inject/multis.py +68 -18
  47. omlish/inject/origins.py +27 -0
  48. omlish/inject/overrides.py +5 -4
  49. omlish/inject/{private.py → privates.py} +6 -10
  50. omlish/inject/providers.py +12 -85
  51. omlish/inject/scopes.py +13 -6
  52. omlish/inject/types.py +3 -1
  53. omlish/lang/__init__.py +8 -2
  54. omlish/lang/cached.py +2 -2
  55. omlish/lang/classes/restrict.py +2 -1
  56. omlish/lang/classes/simple.py +18 -8
  57. omlish/lang/contextmanagers.py +12 -3
  58. omlish/lang/descriptors.py +131 -0
  59. omlish/lang/functions.py +8 -28
  60. omlish/lang/iterables.py +20 -1
  61. omlish/lang/typing.py +5 -0
  62. omlish/lifecycles/__init__.py +34 -0
  63. omlish/lifecycles/abstract.py +43 -0
  64. omlish/lifecycles/base.py +51 -0
  65. omlish/lifecycles/contextmanagers.py +74 -0
  66. omlish/lifecycles/controller.py +116 -0
  67. omlish/lifecycles/manager.py +161 -0
  68. omlish/lifecycles/states.py +43 -0
  69. omlish/lifecycles/transitions.py +64 -0
  70. omlish/logs/formatters.py +1 -1
  71. omlish/marshal/__init__.py +4 -0
  72. omlish/marshal/naming.py +4 -0
  73. omlish/marshal/objects.py +1 -0
  74. omlish/marshal/polymorphism.py +4 -4
  75. omlish/reflect.py +134 -19
  76. omlish/secrets/__init__.py +7 -0
  77. omlish/secrets/marshal.py +41 -0
  78. omlish/secrets/passwords.py +120 -0
  79. omlish/secrets/secrets.py +47 -0
  80. omlish/serde/__init__.py +0 -0
  81. omlish/{configs → serde}/dotenv.py +12 -24
  82. omlish/{json.py → serde/json.py} +2 -1
  83. omlish/serde/yaml.py +223 -0
  84. omlish/sql/dbs.py +1 -1
  85. omlish/sql/duckdb.py +136 -0
  86. omlish/sql/sqlean.py +17 -0
  87. omlish/term.py +1 -1
  88. omlish/testing/pytest/__init__.py +3 -2
  89. omlish/testing/pytest/inject/harness.py +3 -3
  90. omlish/testing/pytest/marks.py +4 -7
  91. omlish/testing/pytest/plugins/__init__.py +1 -0
  92. omlish/testing/pytest/plugins/asyncs.py +136 -0
  93. omlish/testing/pytest/plugins/pydevd.py +1 -1
  94. omlish/text/glyphsplit.py +92 -0
  95. {omlish-0.0.0.dev5.dist-info → omlish-0.0.0.dev6.dist-info}/METADATA +1 -1
  96. {omlish-0.0.0.dev5.dist-info → omlish-0.0.0.dev6.dist-info}/RECORD +100 -72
  97. /omlish/{configs → serde}/props.py +0 -0
  98. {omlish-0.0.0.dev5.dist-info → omlish-0.0.0.dev6.dist-info}/LICENSE +0 -0
  99. {omlish-0.0.0.dev5.dist-info → omlish-0.0.0.dev6.dist-info}/WHEEL +0 -0
  100. {omlish-0.0.0.dev5.dist-info → omlish-0.0.0.dev6.dist-info}/top_level.txt +0 -0
@@ -1,111 +1,38 @@
1
- import abc
2
1
  import typing as ta
3
2
 
4
3
  from .. import check
5
4
  from .. import dataclasses as dc
6
5
  from .. import lang
7
- from .. import reflect as rfl
8
- from .elements import Element
9
- from .elements import Elements
10
- from .impl.inspect import signature
11
6
  from .keys import Key
12
- from .keys import as_key
13
7
 
14
8
 
15
9
  class _Missing(lang.NotInstantiable):
16
10
  pass
17
11
 
18
12
 
19
- ##
20
-
21
-
22
13
  class Provider(lang.Abstract):
23
- @abc.abstractmethod
24
- def provided_ty(self) -> rfl.Type | None:
25
- raise NotImplementedError
26
-
27
-
28
- ##
29
-
30
-
31
- def as_provider(o: ta.Any) -> Provider:
32
- check.not_isinstance(o, (Element, Elements))
33
- if isinstance(o, Provider):
34
- return o
35
- if isinstance(o, Key):
36
- return link(o)
37
- if isinstance(o, type):
38
- return ctor(o)
39
- if callable(o):
40
- return fn(o)
41
- return const(o)
42
-
43
-
44
- ##
14
+ pass
45
15
 
46
16
 
47
- @dc.dataclass(frozen=True, eq=False)
17
+ @dc.dataclass(frozen=True)
18
+ @dc.extra_params(cache_hash=True)
48
19
  class FnProvider(Provider):
49
- fn: ta.Any
50
- ty: rfl.Type | None = None
51
-
52
- def provided_ty(self) -> rfl.Type | None:
53
- return self.ty
54
-
55
-
56
- def fn(fn: ta.Any, ty: rfl.Type | None = _Missing) -> Provider:
57
- check.not_isinstance(fn, type)
58
- check.callable(fn)
59
- if ty is _Missing:
60
- sig = signature(fn)
61
- ty = check.isinstance(sig.return_annotation, type)
62
- return FnProvider(fn, ty)
63
-
64
-
65
- ##
20
+ fn: ta.Any = dc.xfield(check=callable)
66
21
 
67
22
 
68
- @dc.dataclass(frozen=True, eq=False)
23
+ @dc.dataclass(frozen=True)
24
+ @dc.extra_params(cache_hash=True)
69
25
  class CtorProvider(Provider):
70
- ty: type
26
+ ty: type = dc.xfield(coerce=check.of_isinstance(type))
71
27
 
72
- def provided_ty(self) -> type:
73
- return self.ty
74
28
 
75
-
76
- def ctor(ty: type) -> Provider:
77
- check.isinstance(ty, type)
78
- return CtorProvider(ty)
79
-
80
-
81
- ##
82
-
83
-
84
- @dc.dataclass(frozen=True, eq=False)
29
+ @dc.dataclass(frozen=True)
30
+ @dc.extra_params(cache_hash=True)
85
31
  class ConstProvider(Provider):
86
32
  v: ta.Any
87
- ty: rfl.Type | None = None
88
-
89
- def provided_ty(self) -> rfl.Type | None:
90
- return self.ty
91
-
92
33
 
93
- def const(v: ta.Any, ty: rfl.Type | None = _Missing) -> Provider:
94
- if ty is _Missing:
95
- ty = type(v)
96
- return ConstProvider(v, ty)
97
34
 
98
-
99
- ##
100
-
101
-
102
- @dc.dataclass(frozen=True, eq=False)
35
+ @dc.dataclass(frozen=True)
36
+ @dc.extra_params(cache_hash=True)
103
37
  class LinkProvider(Provider):
104
- k: Key
105
-
106
- def provided_ty(self) -> rfl.Type | None:
107
- return None
108
-
109
-
110
- def link(k: ta.Any) -> Provider:
111
- return LinkProvider(as_key(k))
38
+ k: Key = dc.xfield(coerce=check.of_isinstance(Key))
omlish/inject/scopes.py CHANGED
@@ -7,7 +7,6 @@ from .. import dataclasses as dc
7
7
  from .. import lang
8
8
  from .. import reflect as rfl
9
9
  from .bindings import Binding
10
- from .bindings import as_binding
11
10
  from .elements import Element
12
11
  from .keys import Key
13
12
  from .keys import as_key
@@ -24,6 +23,9 @@ else:
24
23
  ##
25
24
 
26
25
 
26
+ SCOPE_ALIASES: dict[str, Scope] = {}
27
+
28
+
27
29
  @dc.dataclass(frozen=True)
28
30
  @dc.extra_params(cache_hash=True)
29
31
  class ScopeBinding(Element, lang.Final):
@@ -34,22 +36,26 @@ def bind_scope(sc: Scope) -> Element:
34
36
  return ScopeBinding(sc)
35
37
 
36
38
 
37
- def in_(b: ta.Any, sc: Scope) -> 'Binding':
38
- return dc.replace(as_binding(b), scope=check.isinstance(sc, Scope))
39
+ ##
39
40
 
40
41
 
41
42
  class Singleton(Scope, lang.Singleton, lang.Final):
42
43
  pass
43
44
 
44
45
 
45
- def singleton(b: ta.Any) -> 'Binding':
46
- return in_(b, Singleton())
46
+ SCOPE_ALIASES['singleton'] = Singleton()
47
+
48
+
49
+ ##
47
50
 
48
51
 
49
52
  class Thread(Scope, lang.Singleton, lang.Final):
50
53
  pass
51
54
 
52
55
 
56
+ SCOPE_ALIASES['thread'] = Thread()
57
+
58
+
53
59
  ##
54
60
 
55
61
 
@@ -64,7 +70,8 @@ class SeededScope(Scope, lang.Final):
64
70
  raise NotImplementedError
65
71
 
66
72
 
67
- @dc.dataclass(frozen=True, eq=False)
73
+ @dc.dataclass(frozen=True)
74
+ @dc.extra_params(cache_hash=True)
68
75
  class ScopeSeededProvider(Provider):
69
76
  ss: SeededScope = dc.xfield(coerce=check.of_isinstance(SeededScope))
70
77
  key: Key = dc.xfield(coerce=check.of_isinstance(Key))
omlish/inject/types.py CHANGED
@@ -1,7 +1,9 @@
1
+ import collections
2
+
1
3
  from .. import lang
2
4
 
3
5
 
4
- ##
6
+ Tag = collections.namedtuple('Tag', 'tag') # noqa
5
7
 
6
8
 
7
9
  class Scope(lang.Abstract):
omlish/lang/__init__.py CHANGED
@@ -80,9 +80,13 @@ from .descriptors import ( # noqa
80
80
  access_forbidden,
81
81
  attr_property,
82
82
  classonly,
83
+ decorator,
83
84
  is_method_descriptor,
84
85
  item_property,
86
+ unwrap_func,
87
+ unwrap_func_with_partials,
85
88
  unwrap_method_descriptors,
89
+ update_wrapper_except_dict,
86
90
  )
87
91
 
88
92
  from .exceptions import ( # noqa
@@ -99,14 +103,14 @@ from .functions import ( # noqa
99
103
  is_lambda,
100
104
  is_none,
101
105
  is_not_none,
106
+ isinstance_of,
107
+ issubclass_of,
102
108
  maybe_call,
103
109
  periodically,
104
110
  raise_,
105
111
  raising,
106
112
  recurse,
107
113
  try_,
108
- unwrap_func,
109
- unwrap_func_with_partials,
110
114
  void,
111
115
  )
112
116
 
@@ -127,8 +131,10 @@ from .iterables import ( # noqa
127
131
  asrange,
128
132
  exhaust,
129
133
  ilen,
134
+ itergen,
130
135
  peek,
131
136
  prodrange,
137
+ renumerate,
132
138
  take,
133
139
  )
134
140
 
omlish/lang/cached.py CHANGED
@@ -11,8 +11,8 @@ import typing as ta
11
11
 
12
12
  from .contextmanagers import DefaultLockable
13
13
  from .contextmanagers import default_lock
14
- from .functions import unwrap_func
15
- from .functions import unwrap_func_with_partials
14
+ from .descriptors import unwrap_func
15
+ from .descriptors import unwrap_func_with_partials
16
16
 
17
17
 
18
18
  P = ta.ParamSpec('P')
@@ -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
 
@@ -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)
@@ -1,3 +1,7 @@
1
+ """
2
+ TODO:
3
+ - AsyncExitStacked
4
+ """
1
5
  import abc
2
6
  import contextlib
3
7
  import contextvars
@@ -15,7 +19,7 @@ T = ta.TypeVar('T')
15
19
 
16
20
  class ContextManaged:
17
21
 
18
- def __enter__(self: ta.Self) -> ta.Self:
22
+ def __enter__(self) -> ta.Self:
19
23
  return self
20
24
 
21
25
  def __exit__(
@@ -77,7 +81,7 @@ class ContextManager(abc.ABC, ta.Generic[T]):
77
81
  exc_type: type[BaseException] | None,
78
82
  exc_val: BaseException | None,
79
83
  exc_tb: types.TracebackType | None,
80
- ) -> None:
84
+ ) -> bool | None:
81
85
  return self._contextmanager.__exit__(exc_type, exc_val, exc_tb)
82
86
 
83
87
 
@@ -198,7 +202,7 @@ class ExitStacked:
198
202
  def _enter_context(self, context_manager: ta.ContextManager[T]) -> T:
199
203
  return self._exit_stack.enter_context(ta.cast(ta.ContextManager, context_manager))
200
204
 
201
- def __enter__(self: ta.Self) -> ta.Self:
205
+ def __enter__(self) -> ta.Self:
202
206
  try:
203
207
  superfn = super().__enter__ # type: ignore
204
208
  except AttributeError:
@@ -306,14 +310,19 @@ DefaultLockable = bool | Lockable | ta.ContextManager | None
306
310
  def default_lock(value: DefaultLockable, default: DefaultLockable) -> Lockable:
307
311
  if value is None:
308
312
  value = default
313
+
309
314
  if value is True:
310
315
  lock = threading.RLock()
311
316
  return lambda: lock
317
+
312
318
  elif value is False or value is None:
313
319
  return NOP_CONTEXT_MANAGER
320
+
314
321
  elif callable(value):
315
322
  return value
323
+
316
324
  elif isinstance(value, ta.ContextManager):
317
325
  return lambda: value
326
+
318
327
  else:
319
328
  raise TypeError(value)
@@ -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
 
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
@@ -12,6 +12,11 @@ import typing as ta
12
12
 
13
13
  Ty = ta.TypeVar('Ty', bound=type)
14
14
 
15
+ T = ta.TypeVar('T')
16
+ A0 = ta.TypeVar('A0')
17
+ A1 = ta.TypeVar('A1')
18
+ A2 = ta.TypeVar('A2')
19
+
15
20
 
16
21
  BytesLike: ta.TypeAlias = bytes | bytearray
17
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