omlish 0.0.0.dev6__py3-none-any.whl → 0.0.0.dev7__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 (106) hide show
  1. omlish/__about__.py +109 -5
  2. omlish/__init__.py +0 -8
  3. omlish/asyncs/__init__.py +0 -9
  4. omlish/asyncs/anyio.py +40 -0
  5. omlish/bootstrap.py +737 -0
  6. omlish/check.py +1 -1
  7. omlish/collections/__init__.py +4 -0
  8. omlish/collections/exceptions.py +2 -0
  9. omlish/collections/utils.py +38 -9
  10. omlish/configs/strings.py +2 -0
  11. omlish/dataclasses/__init__.py +7 -0
  12. omlish/dataclasses/impl/descriptors.py +95 -0
  13. omlish/dataclasses/impl/reflect.py +1 -1
  14. omlish/dataclasses/utils.py +23 -0
  15. omlish/{lang/datetimes.py → datetimes.py} +8 -4
  16. omlish/diag/procfs.py +1 -1
  17. omlish/diag/threads.py +131 -48
  18. omlish/docker.py +16 -1
  19. omlish/fnpairs.py +0 -4
  20. omlish/{serde → formats}/dotenv.py +3 -0
  21. omlish/{serde → formats}/yaml.py +2 -2
  22. omlish/graphs/trees.py +1 -1
  23. omlish/http/consts.py +6 -0
  24. omlish/http/sessions.py +2 -2
  25. omlish/inject/__init__.py +4 -0
  26. omlish/inject/binder.py +3 -3
  27. omlish/inject/elements.py +1 -1
  28. omlish/inject/impl/injector.py +57 -27
  29. omlish/inject/impl/origins.py +2 -0
  30. omlish/inject/origins.py +3 -0
  31. omlish/inject/utils.py +18 -0
  32. omlish/iterators.py +69 -2
  33. omlish/lang/__init__.py +16 -7
  34. omlish/lang/classes/restrict.py +10 -0
  35. omlish/lang/contextmanagers.py +1 -1
  36. omlish/lang/descriptors.py +3 -3
  37. omlish/lang/imports.py +67 -0
  38. omlish/lang/iterables.py +40 -0
  39. omlish/lang/maybes.py +3 -0
  40. omlish/lang/objects.py +38 -0
  41. omlish/lang/strings.py +25 -0
  42. omlish/lang/sys.py +9 -0
  43. omlish/lang/typing.py +37 -0
  44. omlish/lite/__init__.py +1 -0
  45. omlish/lite/cached.py +18 -0
  46. omlish/lite/check.py +29 -0
  47. omlish/lite/contextmanagers.py +18 -0
  48. omlish/lite/json.py +30 -0
  49. omlish/lite/logs.py +52 -0
  50. omlish/lite/marshal.py +316 -0
  51. omlish/lite/reflect.py +49 -0
  52. omlish/lite/runtime.py +18 -0
  53. omlish/lite/secrets.py +19 -0
  54. omlish/lite/strings.py +25 -0
  55. omlish/lite/subprocesses.py +112 -0
  56. omlish/logs/configs.py +15 -2
  57. omlish/logs/formatters.py +7 -2
  58. omlish/marshal/__init__.py +28 -0
  59. omlish/marshal/any.py +5 -5
  60. omlish/marshal/base.py +27 -11
  61. omlish/marshal/base64.py +24 -9
  62. omlish/marshal/dataclasses.py +34 -28
  63. omlish/marshal/datetimes.py +74 -18
  64. omlish/marshal/enums.py +14 -8
  65. omlish/marshal/exceptions.py +11 -1
  66. omlish/marshal/factories.py +59 -74
  67. omlish/marshal/forbidden.py +35 -0
  68. omlish/marshal/global_.py +11 -4
  69. omlish/marshal/iterables.py +21 -24
  70. omlish/marshal/mappings.py +23 -26
  71. omlish/marshal/numbers.py +51 -0
  72. omlish/marshal/optionals.py +11 -12
  73. omlish/marshal/polymorphism.py +86 -21
  74. omlish/marshal/primitives.py +4 -5
  75. omlish/marshal/standard.py +13 -8
  76. omlish/marshal/uuids.py +4 -5
  77. omlish/matchfns.py +218 -0
  78. omlish/os.py +64 -0
  79. omlish/reflect/__init__.py +39 -0
  80. omlish/reflect/isinstance.py +38 -0
  81. omlish/reflect/ops.py +84 -0
  82. omlish/reflect/subst.py +110 -0
  83. omlish/reflect/types.py +275 -0
  84. omlish/secrets/__init__.py +18 -2
  85. omlish/secrets/crypto.py +132 -0
  86. omlish/secrets/marshal.py +36 -7
  87. omlish/secrets/openssl.py +207 -0
  88. omlish/secrets/secrets.py +260 -8
  89. omlish/secrets/subprocesses.py +42 -0
  90. omlish/sql/dbs.py +6 -5
  91. omlish/sql/exprs.py +12 -0
  92. omlish/sql/secrets.py +10 -0
  93. omlish/term.py +1 -1
  94. omlish/testing/pytest/plugins/switches.py +54 -19
  95. omlish/text/glyphsplit.py +5 -0
  96. omlish-0.0.0.dev7.dist-info/METADATA +50 -0
  97. {omlish-0.0.0.dev6.dist-info → omlish-0.0.0.dev7.dist-info}/RECORD +104 -76
  98. {omlish-0.0.0.dev6.dist-info → omlish-0.0.0.dev7.dist-info}/WHEEL +1 -1
  99. omlish/reflect.py +0 -470
  100. omlish-0.0.0.dev6.dist-info/METADATA +0 -34
  101. /omlish/{asyncs/futures.py → concurrent.py} +0 -0
  102. /omlish/{serde → formats}/__init__.py +0 -0
  103. /omlish/{serde → formats}/json.py +0 -0
  104. /omlish/{serde → formats}/props.py +0 -0
  105. {omlish-0.0.0.dev6.dist-info → omlish-0.0.0.dev7.dist-info}/LICENSE +0 -0
  106. {omlish-0.0.0.dev6.dist-info → omlish-0.0.0.dev7.dist-info}/top_level.txt +0 -0
@@ -2,11 +2,10 @@ import typing as ta
2
2
 
3
3
  from .base import MarshalContext
4
4
  from .base import Marshaler
5
- from .base import MarshalerFactory
5
+ from .base import TypeMapMarshalerFactory
6
+ from .base import TypeMapUnmarshalerFactory
6
7
  from .base import UnmarshalContext
7
8
  from .base import Unmarshaler
8
- from .base import UnmarshalerFactory
9
- from .factories import TypeMapFactory
10
9
  from .values import Value
11
10
 
12
11
 
@@ -34,10 +33,10 @@ class PrimitiveMarshalerUnmarshaler(Marshaler, Unmarshaler):
34
33
 
35
34
  PRIMITIVE_MARSHALER_UNMARSHALER = PrimitiveMarshalerUnmarshaler()
36
35
 
37
- PRIMITIVE_MARSHALER_FACTORY: MarshalerFactory = TypeMapFactory({ # noqa
36
+ PRIMITIVE_MARSHALER_FACTORY = TypeMapMarshalerFactory({ # noqa
38
37
  t: PRIMITIVE_MARSHALER_UNMARSHALER for t in PRIMITIVE_TYPES
39
38
  })
40
39
 
41
- PRIMITIVE_UNMARSHALER_FACTORY: UnmarshalerFactory = TypeMapFactory({ # noqa
40
+ PRIMITIVE_UNMARSHALER_FACTORY = TypeMapUnmarshalerFactory({ # noqa
42
41
  t: PRIMITIVE_MARSHALER_UNMARSHALER for t in PRIMITIVE_TYPES
43
42
  })
@@ -1,8 +1,11 @@
1
+ from .. import matchfns as mfs
1
2
  from .any import ANY_MARSHALER_FACTORY
2
3
  from .any import ANY_UNMARSHALER_FACTORY
3
4
  from .base import MarshalerFactory
4
5
  from .base import RecursiveMarshalerFactory
5
6
  from .base import RecursiveUnmarshalerFactory
7
+ from .base import TypeCacheMarshalerFactory
8
+ from .base import TypeCacheUnmarshalerFactory
6
9
  from .base import UnmarshalerFactory
7
10
  from .base64 import BASE64_MARSHALER_FACTORY
8
11
  from .base64 import BASE64_UNMARSHALER_FACTORY
@@ -12,12 +15,12 @@ from .datetimes import DATETIME_MARSHALER_FACTORY
12
15
  from .datetimes import DATETIME_UNMARSHALER_FACTORY
13
16
  from .enums import EnumMarshalerFactory
14
17
  from .enums import EnumUnmarshalerFactory
15
- from .factories import CompositeFactory
16
- from .factories import TypeCacheFactory
17
18
  from .iterables import IterableMarshalerFactory
18
19
  from .iterables import IterableUnmarshalerFactory
19
20
  from .mappings import MappingMarshalerFactory
20
21
  from .mappings import MappingUnmarshalerFactory
22
+ from .numbers import NUMBERS_MARSHALER_FACTORY
23
+ from .numbers import NUMBERS_UNMARSHALER_FACTORY
21
24
  from .optionals import OptionalMarshalerFactory
22
25
  from .optionals import OptionalUnmarshalerFactory
23
26
  from .primitives import PRIMITIVE_MARSHALER_FACTORY
@@ -34,6 +37,7 @@ STANDARD_MARSHALER_FACTORIES: list[MarshalerFactory] = [
34
37
  OptionalMarshalerFactory(),
35
38
  DataclassMarshalerFactory(),
36
39
  EnumMarshalerFactory(),
40
+ NUMBERS_MARSHALER_FACTORY,
37
41
  UUID_MARSHALER_FACTORY,
38
42
  BASE64_MARSHALER_FACTORY,
39
43
  DATETIME_MARSHALER_FACTORY,
@@ -44,10 +48,10 @@ STANDARD_MARSHALER_FACTORIES: list[MarshalerFactory] = [
44
48
 
45
49
 
46
50
  def new_standard_marshaler_factory() -> MarshalerFactory:
47
- return TypeCacheFactory( # noqa
51
+ return TypeCacheMarshalerFactory(
48
52
  RecursiveMarshalerFactory(
49
- CompositeFactory(
50
- *STANDARD_MARSHALER_FACTORIES,
53
+ mfs.MultiMatchFn(
54
+ list(STANDARD_MARSHALER_FACTORIES),
51
55
  ),
52
56
  ),
53
57
  )
@@ -61,6 +65,7 @@ STANDARD_UNMARSHALER_FACTORIES: list[UnmarshalerFactory] = [
61
65
  OptionalUnmarshalerFactory(),
62
66
  DataclassUnmarshalerFactory(),
63
67
  EnumUnmarshalerFactory(),
68
+ NUMBERS_UNMARSHALER_FACTORY,
64
69
  UUID_UNMARSHALER_FACTORY,
65
70
  BASE64_UNMARSHALER_FACTORY,
66
71
  DATETIME_UNMARSHALER_FACTORY,
@@ -71,10 +76,10 @@ STANDARD_UNMARSHALER_FACTORIES: list[UnmarshalerFactory] = [
71
76
 
72
77
 
73
78
  def new_standard_unmarshaler_factory() -> UnmarshalerFactory:
74
- return TypeCacheFactory( # noqa
79
+ return TypeCacheUnmarshalerFactory(
75
80
  RecursiveUnmarshalerFactory(
76
- CompositeFactory(
77
- *STANDARD_UNMARSHALER_FACTORIES,
81
+ mfs.MultiMatchFn(
82
+ list(STANDARD_UNMARSHALER_FACTORIES),
78
83
  ),
79
84
  ),
80
85
  )
omlish/marshal/uuids.py CHANGED
@@ -4,11 +4,10 @@ import uuid
4
4
  from .. import check
5
5
  from .base import MarshalContext
6
6
  from .base import Marshaler
7
- from .base import MarshalerFactory
7
+ from .base import TypeMapMarshalerFactory
8
+ from .base import TypeMapUnmarshalerFactory
8
9
  from .base import UnmarshalContext
9
10
  from .base import Unmarshaler
10
- from .base import UnmarshalerFactory
11
- from .factories import TypeMapFactory
12
11
  from .values import Value
13
12
 
14
13
 
@@ -25,5 +24,5 @@ class UuidMarshalerUnmarshaler(Marshaler, Unmarshaler):
25
24
 
26
25
  UUID_MARSHALER_UNMARSHALER = UuidMarshalerUnmarshaler()
27
26
 
28
- UUID_MARSHALER_FACTORY: MarshalerFactory = TypeMapFactory({uuid.UUID: UUID_MARSHALER_UNMARSHALER})
29
- UUID_UNMARSHALER_FACTORY: UnmarshalerFactory = TypeMapFactory({uuid.UUID: UUID_MARSHALER_UNMARSHALER})
27
+ UUID_MARSHALER_FACTORY = TypeMapMarshalerFactory({uuid.UUID: UUID_MARSHALER_UNMARSHALER})
28
+ UUID_UNMARSHALER_FACTORY = TypeMapUnmarshalerFactory({uuid.UUID: UUID_MARSHALER_UNMARSHALER})
omlish/matchfns.py ADDED
@@ -0,0 +1,218 @@
1
+ import abc
2
+ import dataclasses as dc
3
+ import typing as ta
4
+
5
+ from . import lang
6
+
7
+
8
+ T = ta.TypeVar('T')
9
+ P = ta.ParamSpec('P')
10
+
11
+
12
+ ##
13
+
14
+
15
+ class MatchGuardError(Exception):
16
+ pass
17
+
18
+
19
+ class MatchFn(abc.ABC, ta.Generic[P, T]):
20
+ @abc.abstractmethod
21
+ def guard(self, *args: P.args, **kwargs: P.kwargs) -> bool:
22
+ raise NotImplementedError
23
+
24
+ @abc.abstractmethod
25
+ def fn(self, *args: P.args, **kwargs: P.kwargs) -> T:
26
+ raise NotImplementedError
27
+
28
+ def __get__(self, instance, owner=None):
29
+ return self
30
+
31
+ @ta.final
32
+ def __call__(self, *args: P.args, **kwargs: P.kwargs) -> T:
33
+ if not self.guard(*args, **kwargs):
34
+ raise MatchGuardError(*args, **kwargs)
35
+ return self.fn(*args, **kwargs)
36
+
37
+
38
+ ##
39
+
40
+
41
+ @dc.dataclass(frozen=True)
42
+ class SimpleMatchFn(MatchFn[P, T]):
43
+ _guard: ta.Callable[P, bool]
44
+ _fn: ta.Callable[P, T]
45
+
46
+ def guard(self, *args: P.args, **kwargs: P.kwargs) -> bool:
47
+ return self._guard(*args, **kwargs)
48
+
49
+ def fn(self, *args: P.args, **kwargs: P.kwargs) -> T:
50
+ return self._fn(*args, **kwargs)
51
+
52
+ def __get__(self, instance, owner=None):
53
+ return self.__class__(
54
+ self._guard.__get__(instance, owner), # noqa
55
+ self._fn.__get__(instance, owner), # noqa
56
+ )
57
+
58
+
59
+ @ta.overload
60
+ def simple(guard: ta.Callable[..., bool], fn: ta.Callable[P, T]) -> SimpleMatchFn[P, T]:
61
+ ...
62
+
63
+
64
+ @ta.overload
65
+ def simple(guard: ta.Callable[..., bool]) -> ta.Callable[[ta.Callable[P, T]], SimpleMatchFn[P, T]]:
66
+ ...
67
+
68
+
69
+ def simple(guard, fn=None):
70
+ def inner(fn): # noqa
71
+ return SimpleMatchFn(guard, fn)
72
+ if fn is not None:
73
+ return inner(fn)
74
+ else:
75
+ return inner
76
+
77
+
78
+ ##
79
+
80
+
81
+ class AmbiguousMatchesError(Exception):
82
+ pass
83
+
84
+
85
+ @dc.dataclass(frozen=True)
86
+ class MultiMatchFn(MatchFn[P, T]):
87
+ children: ta.Sequence[MatchFn[P, T]]
88
+ strict: bool = False
89
+
90
+ def _match(self, *args: P.args, **kwargs: P.kwargs) -> MatchFn[P, T] | None:
91
+ matches = []
92
+ for cur in self.children:
93
+ if cur.guard(*args, **kwargs):
94
+ if self.strict:
95
+ matches.append(cur)
96
+ else:
97
+ return cur
98
+ if not matches:
99
+ return None
100
+ elif len(matches) > 1:
101
+ raise AmbiguousMatchesError
102
+ else:
103
+ return matches[0]
104
+
105
+ def guard(self, *args: P.args, **kwargs: P.kwargs) -> bool:
106
+ return self._match(*args, **kwargs) is not None
107
+
108
+ def fn(self, *args: P.args, **kwargs: P.kwargs) -> T:
109
+ if (m := self._match(*args, **kwargs)) is None:
110
+ raise MatchGuardError(*args, **kwargs)
111
+ return m.fn(*args, **kwargs)
112
+
113
+ def __get__(self, instance, owner=None):
114
+ return self.__class__(
115
+ [c.__get__(instance, owner) for c in self.children],
116
+ strict=self.strict,
117
+ )
118
+
119
+
120
+ def multi(*children: MatchFn[P, T], strict: bool = False):
121
+ return MultiMatchFn(children, strict=strict) # noqa
122
+
123
+
124
+ ##
125
+
126
+
127
+ class CachedMultiFn(MatchFn[P, T]):
128
+ @staticmethod
129
+ def _default_key(*args, **kwargs):
130
+ return (args, tuple(sorted(kwargs.items(), key=lambda t: t[0])))
131
+
132
+ def __init__(
133
+ self,
134
+ f: MatchFn[P, T],
135
+ *,
136
+ key: ta.Callable[P, ta.Any] = _default_key,
137
+ lock: lang.DefaultLockable = None,
138
+ ) -> None:
139
+ super().__init__()
140
+ self._f = f
141
+ self._key = key
142
+ self._lock = lock
143
+ self._lock_impl = lang.default_lock(lock)()
144
+ self._dct: dict[ta.Any, lang.Maybe[ta.Any]] = {}
145
+
146
+ def guard(self, *args: P.args, **kwargs: P.kwargs) -> bool:
147
+ with self._lock_impl:
148
+ k = self._key(*args, **kwargs)
149
+ try:
150
+ e = self._dct[k]
151
+ except KeyError:
152
+ if self._f.guard(*args, **kwargs):
153
+ return True
154
+ else:
155
+ self._dct[k] = lang.empty()
156
+ return False
157
+ else:
158
+ return e.present
159
+
160
+ def fn(self, *args: P.args, **kwargs: P.kwargs) -> T:
161
+ with self._lock_impl:
162
+ k = self._key(*args, **kwargs)
163
+ try:
164
+ e = self._dct[k]
165
+ except KeyError:
166
+ try:
167
+ ret = self._f(*args, **kwargs)
168
+ except MatchGuardError:
169
+ self._dct[k] = lang.empty()
170
+ raise
171
+ else:
172
+ self._dct[k] = lang.just(ret)
173
+ return ret
174
+ else:
175
+ if e.present:
176
+ return e.must()
177
+ else:
178
+ raise MatchGuardError(*args, **kwargs)
179
+
180
+ def __get__(self, instance, owner=None):
181
+ return self.__class__(self._f.__get__(instance, owner), key=self._key) # noqa
182
+
183
+
184
+ cached = CachedMultiFn
185
+
186
+
187
+ ##
188
+
189
+
190
+ class MatchFnClass(MatchFn[P, T]):
191
+ _cls_match_fn: ta.ClassVar[MultiMatchFn]
192
+
193
+ def __init__(self) -> None:
194
+ super().__init__()
195
+ self.__match_fn: MatchFn[P, T] | None = None
196
+
197
+ @property
198
+ def _match_fn(self) -> MatchFn[P, T]:
199
+ if (mf := self.__match_fn) is None:
200
+ mf = self.__match_fn = self._cls_match_fn.__get__(self)
201
+ return mf
202
+
203
+ def __init_subclass__(cls, strict: bool = False, **kwargs):
204
+ super().__init_subclass__()
205
+ if '_cls_match_fn' in cls.__dict__:
206
+ raise AttributeError('_cls_match_fn')
207
+ d = {}
208
+ for c in cls.__mro__:
209
+ for a, o in c.__dict__.items():
210
+ if isinstance(o, MatchFn) and a not in d:
211
+ d[a] = o
212
+ cls._cls_match_fn = MultiMatchFn(list(d.values()), strict=strict)
213
+
214
+ def guard(self, *args: P.args, **kwargs: P.kwargs) -> bool:
215
+ return self._match_fn.guard(*args, **kwargs)
216
+
217
+ def fn(self, *args: P.args, **kwargs: P.kwargs) -> T:
218
+ return self._match_fn.fn(*args, **kwargs)
omlish/os.py CHANGED
@@ -1,6 +1,9 @@
1
1
  import contextlib
2
+ import fcntl
3
+ import os
2
4
  import resource
3
5
  import shutil
6
+ import signal
4
7
  import tempfile
5
8
  import typing as ta
6
9
 
@@ -39,3 +42,64 @@ def tmp_file(
39
42
  finally:
40
43
  if cleanup:
41
44
  shutil.rmtree(f.name, ignore_errors=True)
45
+
46
+
47
+ class Pidfile:
48
+ def __init__(self, path: str) -> None:
49
+ super().__init__()
50
+ self._path = path
51
+
52
+ _f: ta.TextIO
53
+
54
+ def __repr__(self) -> str:
55
+ return f'{self.__class__.__name__}({self._path!r})'
56
+
57
+ def __enter__(self) -> ta.Self:
58
+ fd = os.open(self._path, os.O_RDWR | os.O_CREAT, 0o600)
59
+ try:
60
+ os.set_inheritable(fd, True)
61
+ f = os.fdopen(fd, 'r+')
62
+ except Exception:
63
+ try:
64
+ os.close(fd)
65
+ except Exception: # noqa
66
+ pass
67
+ raise
68
+ self._f = f
69
+ return self
70
+
71
+ def __exit__(self, exc_type, exc_val, exc_tb):
72
+ if self._f is not None:
73
+ self._f.close()
74
+ del self._f
75
+
76
+ def try_lock(self) -> bool:
77
+ try:
78
+ fcntl.flock(self._f, fcntl.LOCK_EX | fcntl.LOCK_NB)
79
+ return True
80
+ except OSError:
81
+ return False
82
+
83
+ def write(self, pid: int | None = None) -> None:
84
+ if not self.try_lock():
85
+ raise RuntimeError('Could not get lock')
86
+ if pid is None:
87
+ pid = os.getpid()
88
+ self._f.write(f'{pid}\n')
89
+ self._f.flush()
90
+
91
+ def clear(self) -> None:
92
+ if not self.try_lock():
93
+ raise RuntimeError('Could not get lock')
94
+ self._f.seek(0)
95
+ self._f.truncate()
96
+
97
+ def read(self) -> int:
98
+ if self.try_lock():
99
+ raise RuntimeError('Got lock')
100
+ self._f.seek(0)
101
+ return int(self._f.read())
102
+
103
+ def kill(self, sig: int = signal.SIGTERM) -> None:
104
+ pid = self.read()
105
+ os.kill(pid, sig) # Still racy
@@ -0,0 +1,39 @@
1
+ from .types import ( # noqa
2
+ ANY,
3
+ Annotated,
4
+ Any,
5
+ Generic,
6
+ NewType,
7
+ TYPES,
8
+ Type,
9
+ Union,
10
+ get_orig_class,
11
+ get_params,
12
+ is_type,
13
+ is_union_type,
14
+ type_,
15
+ )
16
+
17
+ from .ops import ( # noqa
18
+ get_concrete_type,
19
+ get_underlying,
20
+ strip_annotations,
21
+ strip_objs,
22
+ to_annotation,
23
+ types_equivalent,
24
+ )
25
+
26
+ from .isinstance import ( # noqa
27
+ KNOWN_ISINSTANCE_GENERICS,
28
+ isinstance_of,
29
+ )
30
+
31
+ from .subst import ( # noqa
32
+ ALIAS_UPDATING_GENERIC_SUBSTITUTION,
33
+ DEFAULT_GENERIC_SUBSTITUTION,
34
+ GenericSubstitution,
35
+ generic_mro,
36
+ get_generic_bases,
37
+ get_type_var_replacements,
38
+ replace_type_vars,
39
+ )
@@ -0,0 +1,38 @@
1
+ import collections.abc
2
+ import typing as ta
3
+
4
+ from .ops import get_underlying
5
+ from .types import Generic
6
+ from .types import NewType
7
+ from .types import Type
8
+ from .types import Union
9
+
10
+
11
+ KNOWN_ISINSTANCE_GENERICS: ta.AbstractSet[type] = frozenset([
12
+ collections.abc.Mapping,
13
+ collections.abc.Sequence,
14
+ collections.abc.Set,
15
+ ])
16
+
17
+
18
+ def isinstance_of(rfl: Type) -> ta.Callable[[ta.Any], bool]:
19
+ if isinstance(rfl, type):
20
+ return lambda o: isinstance(o, rfl)
21
+
22
+ if isinstance(rfl, NewType):
23
+ return isinstance_of(get_underlying(rfl))
24
+
25
+ if isinstance(rfl, Union):
26
+ fns = [isinstance_of(a) for a in rfl.args]
27
+ return lambda o: any(fn(o) for fn in fns)
28
+
29
+ if isinstance(rfl, Generic):
30
+ if rfl.cls in (collections.abc.Sequence, collections.abc.Set):
31
+ [efn] = map(isinstance_of, rfl.args)
32
+ return lambda o: isinstance(o, rfl.cls) and all(efn(e) for e in o) # type: ignore
33
+
34
+ if rfl.cls == collections.abc.Mapping:
35
+ kfn, vfn = map(isinstance_of, rfl.args)
36
+ return lambda o: isinstance(o, rfl.cls) and all(kfn(k) and vfn(v) for k, v in o.items()) # type: ignore
37
+
38
+ raise TypeError(rfl)
omlish/reflect/ops.py ADDED
@@ -0,0 +1,84 @@
1
+ import typing as ta
2
+
3
+ from .types import Annotated
4
+ from .types import Any
5
+ from .types import Generic
6
+ from .types import NewType
7
+ from .types import Type
8
+ from .types import Union
9
+ from .types import type_
10
+
11
+
12
+ def strip_objs(ty: Type) -> Type:
13
+ if isinstance(ty, (type, ta.TypeVar, NewType, Any)):
14
+ return ty
15
+
16
+ if isinstance(ty, Union):
17
+ return Union(frozenset(map(strip_objs, ty.args)))
18
+
19
+ if isinstance(ty, Generic):
20
+ return Generic(ty.cls, tuple(map(strip_objs, ty.args)), ty.params, None)
21
+
22
+ if isinstance(ty, Annotated):
23
+ return Annotated(strip_objs(ty.ty), ty.md, None)
24
+
25
+ raise TypeError(ty)
26
+
27
+
28
+ def strip_annotations(ty: Type) -> Type:
29
+ if isinstance(ty, (type, ta.TypeVar, NewType, Any)):
30
+ return ty
31
+
32
+ if isinstance(ty, Union):
33
+ return Union(frozenset(map(strip_annotations, ty.args)))
34
+
35
+ if isinstance(ty, Generic):
36
+ return Generic(ty.cls, tuple(map(strip_annotations, ty.args)), ty.params, ty.obj)
37
+
38
+ if isinstance(ty, Annotated):
39
+ return strip_annotations(ty.ty)
40
+
41
+ raise TypeError(ty)
42
+
43
+
44
+ def types_equivalent(l: Type, r: Type) -> bool:
45
+ if isinstance(l, Generic) and isinstance(r, Generic):
46
+ return l.cls == r.cls and l.args == r.args
47
+
48
+ return l == r
49
+
50
+
51
+ def get_underlying(nt: NewType) -> Type:
52
+ return type_(nt.obj.__supertype__) # noqa
53
+
54
+
55
+ def get_concrete_type(ty: Type) -> type | None:
56
+ if isinstance(ty, type):
57
+ return ty
58
+
59
+ if isinstance(ty, Generic):
60
+ return ty.cls
61
+
62
+ if isinstance(ty, NewType):
63
+ return get_concrete_type(get_underlying(ty))
64
+
65
+ if isinstance(ty, (Union, ta.TypeVar, Any)):
66
+ return None
67
+
68
+ raise TypeError(ty)
69
+
70
+
71
+ def to_annotation(ty: Type) -> ta.Any:
72
+ if isinstance(ty, Generic):
73
+ return ty.obj if ty.obj is not None else ty.cls
74
+
75
+ if isinstance(ty, Union):
76
+ return ta.Union[*tuple(to_annotation(e) for e in ty.args)]
77
+
78
+ if isinstance(ty, (type, ta.TypeVar, NewType)):
79
+ return ty
80
+
81
+ if isinstance(ty, Any):
82
+ return ta.Any
83
+
84
+ raise TypeError(ty)
@@ -0,0 +1,110 @@
1
+ import dataclasses as dc
2
+ import types
3
+ import typing as ta
4
+
5
+ from .. import c3
6
+ from .. import lang
7
+ from .ops import get_concrete_type
8
+ from .ops import to_annotation
9
+ from .types import Any
10
+ from .types import Generic
11
+ from .types import NewType
12
+ from .types import Type
13
+ from .types import Union
14
+ from .types import get_params
15
+ from .types import type_
16
+
17
+
18
+ if ta.TYPE_CHECKING:
19
+ from ..collections import cache
20
+ else:
21
+ cache = lang.proxy_import('.collections.cache', __package__)
22
+
23
+
24
+ def get_type_var_replacements(ty: Type) -> ta.Mapping[ta.TypeVar, Type]:
25
+ if isinstance(ty, Generic):
26
+ return dict(zip(ty.params, ty.args))
27
+
28
+ return {}
29
+
30
+
31
+ def replace_type_vars(
32
+ ty: Type,
33
+ rpl: ta.Mapping[ta.TypeVar, Type],
34
+ *,
35
+ update_aliases: bool = False,
36
+ ) -> Type:
37
+ def rec(cur):
38
+ if isinstance(cur, (type, NewType, Any)):
39
+ return cur
40
+
41
+ if isinstance(cur, Generic):
42
+ args = tuple(rec(a) for a in cur.args)
43
+ if update_aliases:
44
+ obj = cur.obj
45
+ if (ops := get_params(obj)):
46
+ nargs = [to_annotation(rpl[p]) for p in ops]
47
+ if ta.get_origin(obj) is ta.Generic:
48
+ # FIXME: None? filter_typing_generic in get_generic_bases?
49
+ pass
50
+ else:
51
+ obj = cur.obj[*nargs]
52
+ else:
53
+ obj = None
54
+ return dc.replace(cur, args=args, obj=obj)
55
+
56
+ if isinstance(cur, Union):
57
+ return Union(frozenset(rec(e) for e in cur.args))
58
+
59
+ if isinstance(cur, ta.TypeVar):
60
+ return rpl[cur]
61
+
62
+ raise TypeError(cur)
63
+
64
+ return rec(ty)
65
+
66
+
67
+ class GenericSubstitution:
68
+ def __init__(
69
+ self,
70
+ *,
71
+ update_aliases: bool = False,
72
+ cache_size: int = 0, # FIXME: ta.Generic isn't weakrefable..
73
+ ) -> None:
74
+ super().__init__()
75
+
76
+ self._update_aliases = update_aliases
77
+
78
+ if cache_size > 0:
79
+ self.get_generic_bases = cache.cache(weak_keys=True, max_size=cache_size)(self.get_generic_bases) # type: ignore # noqa
80
+ self.generic_mro = cache.cache(weak_keys=True, max_size=cache_size)(self.generic_mro) # type: ignore
81
+
82
+ def get_generic_bases(self, ty: Type) -> tuple[Type, ...]:
83
+ if (cty := get_concrete_type(ty)) is not None:
84
+ rpl = get_type_var_replacements(ty)
85
+ ret: list[Type] = []
86
+ for b in types.get_original_bases(cty):
87
+ bty = type_(b)
88
+ if isinstance(bty, Generic) and isinstance(b, type):
89
+ # FIXME: throws away relative types, but can't use original vars as they're class-contextual
90
+ bty = type_(b[*((ta.Any,) * len(bty.params))]) # type: ignore
91
+ rty = replace_type_vars(bty, rpl, update_aliases=self._update_aliases)
92
+ ret.append(rty)
93
+ return tuple(ret)
94
+ return ()
95
+
96
+ def generic_mro(self, obj: ta.Any) -> list[Type]:
97
+ mro = c3.mro(
98
+ type_(obj),
99
+ get_bases=lambda t: self.get_generic_bases(t),
100
+ is_subclass=lambda l, r: issubclass(get_concrete_type(l), get_concrete_type(r)), # type: ignore
101
+ )
102
+ return [ty for ty in mro if get_concrete_type(ty) is not ta.Generic]
103
+
104
+
105
+ DEFAULT_GENERIC_SUBSTITUTION = GenericSubstitution()
106
+
107
+ get_generic_bases = DEFAULT_GENERIC_SUBSTITUTION.get_generic_bases
108
+ generic_mro = DEFAULT_GENERIC_SUBSTITUTION.generic_mro
109
+
110
+ ALIAS_UPDATING_GENERIC_SUBSTITUTION = GenericSubstitution(update_aliases=True)