omlish 0.0.0.dev6__py3-none-any.whl → 0.0.0.dev8__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.
Files changed (108) 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 +121 -0
  50. omlish/lite/marshal.py +318 -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/__init__.py +13 -9
  57. omlish/logs/configs.py +17 -22
  58. omlish/logs/formatters.py +3 -48
  59. omlish/marshal/__init__.py +28 -0
  60. omlish/marshal/any.py +5 -5
  61. omlish/marshal/base.py +27 -11
  62. omlish/marshal/base64.py +24 -9
  63. omlish/marshal/dataclasses.py +34 -28
  64. omlish/marshal/datetimes.py +74 -18
  65. omlish/marshal/enums.py +14 -8
  66. omlish/marshal/exceptions.py +11 -1
  67. omlish/marshal/factories.py +59 -74
  68. omlish/marshal/forbidden.py +35 -0
  69. omlish/marshal/global_.py +11 -4
  70. omlish/marshal/iterables.py +21 -24
  71. omlish/marshal/mappings.py +23 -26
  72. omlish/marshal/numbers.py +51 -0
  73. omlish/marshal/optionals.py +11 -12
  74. omlish/marshal/polymorphism.py +86 -21
  75. omlish/marshal/primitives.py +4 -5
  76. omlish/marshal/standard.py +13 -8
  77. omlish/marshal/uuids.py +4 -5
  78. omlish/matchfns.py +218 -0
  79. omlish/os.py +64 -0
  80. omlish/reflect/__init__.py +39 -0
  81. omlish/reflect/isinstance.py +38 -0
  82. omlish/reflect/ops.py +84 -0
  83. omlish/reflect/subst.py +110 -0
  84. omlish/reflect/types.py +275 -0
  85. omlish/secrets/__init__.py +18 -2
  86. omlish/secrets/crypto.py +132 -0
  87. omlish/secrets/marshal.py +36 -7
  88. omlish/secrets/openssl.py +207 -0
  89. omlish/secrets/secrets.py +260 -8
  90. omlish/secrets/subprocesses.py +42 -0
  91. omlish/sql/dbs.py +6 -5
  92. omlish/sql/exprs.py +12 -0
  93. omlish/sql/secrets.py +10 -0
  94. omlish/term.py +1 -1
  95. omlish/testing/pytest/plugins/switches.py +54 -19
  96. omlish/text/glyphsplit.py +5 -0
  97. omlish-0.0.0.dev8.dist-info/METADATA +50 -0
  98. {omlish-0.0.0.dev6.dist-info → omlish-0.0.0.dev8.dist-info}/RECORD +105 -78
  99. {omlish-0.0.0.dev6.dist-info → omlish-0.0.0.dev8.dist-info}/WHEEL +1 -1
  100. omlish/logs/filters.py +0 -11
  101. omlish/reflect.py +0 -470
  102. omlish-0.0.0.dev6.dist-info/METADATA +0 -34
  103. /omlish/{asyncs/futures.py → concurrent.py} +0 -0
  104. /omlish/{serde → formats}/__init__.py +0 -0
  105. /omlish/{serde → formats}/json.py +0 -0
  106. /omlish/{serde → formats}/props.py +0 -0
  107. {omlish-0.0.0.dev6.dist-info → omlish-0.0.0.dev8.dist-info}/LICENSE +0 -0
  108. {omlish-0.0.0.dev6.dist-info → omlish-0.0.0.dev8.dist-info}/top_level.txt +0 -0
@@ -19,12 +19,29 @@ from .base import Unmarshaler
19
19
  from .base import UnmarshalerFactory
20
20
  from .naming import Naming
21
21
  from .naming import translate_name
22
+ from .registries import RegistryItem
22
23
  from .values import Value
23
24
 
24
25
 
25
26
  ##
26
27
 
27
28
 
29
+ class TypeTagging(RegistryItem, lang.Abstract, lang.Sealed):
30
+ pass
31
+
32
+
33
+ class WrapperTypeTagging(TypeTagging, lang.Final):
34
+ pass
35
+
36
+
37
+ @dc.dataclass(frozen=True)
38
+ class FieldTypeTagging(TypeTagging, lang.Final):
39
+ field: str
40
+
41
+
42
+ ##
43
+
44
+
28
45
  @dc.dataclass(frozen=True)
29
46
  class Impl:
30
47
  ty: type
@@ -33,7 +50,11 @@ class Impl:
33
50
 
34
51
 
35
52
  class Polymorphism:
36
- def __init__(self, ty: type, impls: ta.Iterable[Impl]) -> None:
53
+ def __init__(
54
+ self,
55
+ ty: type,
56
+ impls: ta.Iterable[Impl],
57
+ ) -> None:
37
58
  super().__init__()
38
59
  self._ty = ty
39
60
  self._impls = list(impls)
@@ -72,7 +93,11 @@ class Polymorphism:
72
93
  return self._by_tag
73
94
 
74
95
 
75
- def polymorphism_from_subclasses(ty: type, *, naming: Naming | None = None) -> Polymorphism:
96
+ def polymorphism_from_subclasses(
97
+ ty: type,
98
+ *,
99
+ naming: Naming | None = None,
100
+ ) -> Polymorphism:
76
101
  dct: dict[str, Impl] = {}
77
102
  seen: set[type] = set()
78
103
  todo: list[type] = [ty]
@@ -97,7 +122,7 @@ def polymorphism_from_subclasses(ty: type, *, naming: Naming | None = None) -> P
97
122
 
98
123
 
99
124
  @dc.dataclass(frozen=True)
100
- class DictKeyPolymorphismMarshaler(Marshaler):
125
+ class WrapperPolymorphismMarshaler(Marshaler):
101
126
  m: ta.Mapping[type, tuple[str, Marshaler]]
102
127
 
103
128
  def marshal(self, ctx: MarshalContext, o: ta.Any | None) -> Value:
@@ -105,24 +130,43 @@ class DictKeyPolymorphismMarshaler(Marshaler):
105
130
  return {tag: m.marshal(ctx, o)}
106
131
 
107
132
 
133
+ @dc.dataclass(frozen=True)
134
+ class FieldPolymorphismMarshaler(Marshaler):
135
+ m: ta.Mapping[type, tuple[str, Marshaler]]
136
+ tf: str
137
+
138
+ def marshal(self, ctx: MarshalContext, o: ta.Any | None) -> Value:
139
+ tag, m = self.m[type(o)]
140
+ return {self.tf: tag, **m.marshal(ctx, o)} # type: ignore
141
+
142
+
108
143
  @dc.dataclass(frozen=True)
109
144
  class PolymorphismMarshalerFactory(MarshalerFactory):
110
145
  p: Polymorphism
146
+ tt: TypeTagging = WrapperTypeTagging()
111
147
 
112
- def __call__(self, ctx: MarshalContext, rty: rfl.Type) -> Marshaler | None:
113
- if rty is self.p.ty:
114
- return DictKeyPolymorphismMarshaler({
115
- i.ty: (i.tag, ctx.make(i.ty))
116
- for i in self.p.impls
117
- })
118
- return None
148
+ def guard(self, ctx: MarshalContext, rty: rfl.Type) -> bool:
149
+ return rty is self.p.ty
150
+
151
+ def fn(self, ctx: MarshalContext, rty: rfl.Type) -> Marshaler:
152
+ check.is_(rty, self.p.ty)
153
+ m = {
154
+ i.ty: (i.tag, ctx.make(i.ty))
155
+ for i in self.p.impls
156
+ }
157
+ if isinstance(self.tt, WrapperTypeTagging):
158
+ return WrapperPolymorphismMarshaler(m)
159
+ elif isinstance(self.tt, FieldTypeTagging):
160
+ return FieldPolymorphismMarshaler(m, self.tt.field)
161
+ else:
162
+ raise TypeError(self.tt)
119
163
 
120
164
 
121
165
  ##
122
166
 
123
167
 
124
168
  @dc.dataclass(frozen=True)
125
- class DictKeyPolymorphismUnmarshaler(Unmarshaler):
169
+ class WrapperPolymorphismUnmarshaler(Unmarshaler):
126
170
  m: ta.Mapping[str, Unmarshaler]
127
171
 
128
172
  def unmarshal(self, ctx: UnmarshalContext, v: Value) -> ta.Any | None:
@@ -132,16 +176,37 @@ class DictKeyPolymorphismUnmarshaler(Unmarshaler):
132
176
  return u.unmarshal(ctx, iv) # type: ignore
133
177
 
134
178
 
179
+ @dc.dataclass(frozen=True)
180
+ class FieldPolymorphismUnmarshaler(Unmarshaler):
181
+ m: ta.Mapping[str, Unmarshaler]
182
+ tf: str
183
+
184
+ def unmarshal(self, ctx: UnmarshalContext, v: Value) -> ta.Any | None:
185
+ ma = dict(check.isinstance(v, collections.abc.Mapping))
186
+ tag = ma.pop(self.tf) # type: ignore
187
+ u = self.m[tag] # type: ignore
188
+ return u.unmarshal(ctx, ma)
189
+
190
+
135
191
  @dc.dataclass(frozen=True)
136
192
  class PolymorphismUnmarshalerFactory(UnmarshalerFactory):
137
193
  p: Polymorphism
138
-
139
- def __call__(self, ctx: UnmarshalContext, rty: rfl.Type) -> Unmarshaler | None:
140
- if rty is self.p.ty:
141
- return DictKeyPolymorphismUnmarshaler({
142
- t: u
143
- for i in self.p.impls
144
- for u in [ctx.make(i.ty)]
145
- for t in [i.tag, *i.alts]
146
- })
147
- return None
194
+ tt: TypeTagging = WrapperTypeTagging()
195
+
196
+ def guard(self, ctx: UnmarshalContext, rty: rfl.Type) -> bool:
197
+ return rty is self.p.ty
198
+
199
+ def fn(self, ctx: UnmarshalContext, rty: rfl.Type) -> Unmarshaler:
200
+ check.is_(rty, self.p.ty)
201
+ m = {
202
+ t: u
203
+ for i in self.p.impls
204
+ for u in [ctx.make(i.ty)]
205
+ for t in [i.tag, *i.alts]
206
+ }
207
+ if isinstance(self.tt, WrapperTypeTagging):
208
+ return WrapperPolymorphismUnmarshaler(m)
209
+ elif isinstance(self.tt, FieldTypeTagging):
210
+ return FieldPolymorphismUnmarshaler(m, self.tt.field)
211
+ else:
212
+ raise TypeError(self.tt)
@@ -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)