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
omlish/lang/typing.py CHANGED
@@ -5,6 +5,7 @@ TODO:
5
5
  - probably need to gen types per inst
6
6
  - typed_factory
7
7
  """
8
+ import dataclasses as dc
8
9
  import functools
9
10
  import inspect
10
11
  import typing as ta
@@ -96,3 +97,39 @@ def typed_partial(obj, **kw): # noqa
96
97
  },
97
98
  )(inner)
98
99
  return _update_wrapper_no_anns(lam, obj)
100
+
101
+
102
+ ##
103
+ # A workaround for typing deficiencies (like `Argument 2 to NewType(...) must be subclassable`).
104
+
105
+
106
+ @dc.dataclass(frozen=True)
107
+ class Func0(ta.Generic[T]):
108
+ fn: ta.Callable[[], T]
109
+
110
+ def __call__(self) -> T:
111
+ return self.fn()
112
+
113
+
114
+ @dc.dataclass(frozen=True)
115
+ class Func1(ta.Generic[A0, T]):
116
+ fn: ta.Callable[[A0], T]
117
+
118
+ def __call__(self, a0: A0) -> T:
119
+ return self.fn(a0)
120
+
121
+
122
+ @dc.dataclass(frozen=True)
123
+ class Func2(ta.Generic[A0, A1, T]):
124
+ fn: ta.Callable[[A0, A1], T]
125
+
126
+ def __call__(self, a0: A0, a1: A1) -> T:
127
+ return self.fn(a0, a1)
128
+
129
+
130
+ @dc.dataclass(frozen=True)
131
+ class Func3(ta.Generic[A0, A1, A2, T]):
132
+ fn: ta.Callable[[A0, A1, A2], T]
133
+
134
+ def __call__(self, a0: A0, a1: A1, a2: A2) -> T:
135
+ return self.fn(a0, a1, a2)
@@ -0,0 +1 @@
1
+ # @omlish-lite
omlish/lite/cached.py ADDED
@@ -0,0 +1,18 @@
1
+ import functools
2
+
3
+
4
+ class cached_nullary: # noqa
5
+ def __init__(self, fn):
6
+ super().__init__()
7
+ self._fn = fn
8
+ self._value = self._missing = object()
9
+ functools.update_wrapper(self, fn)
10
+
11
+ def __call__(self, *args, **kwargs): # noqa
12
+ if self._value is self._missing:
13
+ self._value = self._fn()
14
+ return self._value
15
+
16
+ def __get__(self, instance, owner): # noqa
17
+ bound = instance.__dict__[self._fn.__name__] = self.__class__(self._fn.__get__(instance, owner))
18
+ return bound
omlish/lite/check.py ADDED
@@ -0,0 +1,29 @@
1
+ # ruff: noqa: UP006 UP007
2
+ import typing as ta
3
+
4
+
5
+ T = ta.TypeVar('T')
6
+
7
+
8
+ def check_isinstance(v: T, spec: ta.Union[ta.Type[T], tuple]) -> T:
9
+ if not isinstance(v, spec):
10
+ raise TypeError(v)
11
+ return v
12
+
13
+
14
+ def check_not_isinstance(v: T, spec: ta.Union[type, tuple]) -> T:
15
+ if isinstance(v, spec):
16
+ raise TypeError(v)
17
+ return v
18
+
19
+
20
+ def check_not_none(v: ta.Optional[T]) -> T:
21
+ if v is None:
22
+ raise ValueError
23
+ return v
24
+
25
+
26
+ def check_not(v: ta.Any) -> None:
27
+ if v:
28
+ raise ValueError(v)
29
+ return v
@@ -0,0 +1,18 @@
1
+ import contextlib
2
+
3
+
4
+ @contextlib.contextmanager
5
+ def attr_setting(obj, attr, val, *, default=None): # noqa
6
+ not_set = object()
7
+ orig = getattr(obj, attr, not_set)
8
+ try:
9
+ setattr(obj, attr, val)
10
+ if orig is not not_set:
11
+ yield orig
12
+ else:
13
+ yield default
14
+ finally:
15
+ if orig is not_set:
16
+ delattr(obj, attr)
17
+ else:
18
+ setattr(obj, attr, orig)
omlish/lite/json.py ADDED
@@ -0,0 +1,30 @@
1
+ import functools
2
+ import json
3
+ import typing as ta
4
+
5
+
6
+ ##
7
+
8
+
9
+ JSON_PRETTY_INDENT = 2
10
+
11
+ JSON_PRETTY_KWARGS: ta.Mapping[str, ta.Any] = dict(
12
+ indent=JSON_PRETTY_INDENT,
13
+ )
14
+
15
+ json_dump_pretty: ta.Callable[..., bytes] = functools.partial(json.dump, **JSON_PRETTY_KWARGS) # type: ignore
16
+ json_dumps_pretty: ta.Callable[..., str] = functools.partial(json.dumps, **JSON_PRETTY_KWARGS)
17
+
18
+
19
+ ##
20
+
21
+
22
+ JSON_COMPACT_SEPARATORS = (',', ':')
23
+
24
+ JSON_COMPACT_KWARGS: ta.Mapping[str, ta.Any] = dict(
25
+ indent=None,
26
+ separators=JSON_COMPACT_SEPARATORS,
27
+ )
28
+
29
+ json_dump_compact: ta.Callable[..., bytes] = functools.partial(json.dump, **JSON_COMPACT_KWARGS) # type: ignore
30
+ json_dumps_compact: ta.Callable[..., str] = functools.partial(json.dumps, **JSON_COMPACT_KWARGS)
omlish/lite/logs.py ADDED
@@ -0,0 +1,52 @@
1
+ """
2
+ TODO:
3
+ - debug
4
+ """
5
+ # ruff: noqa: UP007
6
+ import logging
7
+ import typing as ta
8
+
9
+ from .json import json_dumps_compact
10
+
11
+
12
+ log = logging.getLogger(__name__)
13
+
14
+
15
+ class JsonLogFormatter(logging.Formatter):
16
+
17
+ KEYS: ta.Mapping[str, bool] = {
18
+ 'name': False,
19
+ 'msg': False,
20
+ 'args': False,
21
+ 'levelname': False,
22
+ 'levelno': False,
23
+ 'pathname': False,
24
+ 'filename': False,
25
+ 'module': False,
26
+ 'exc_info': True,
27
+ 'exc_text': True,
28
+ 'stack_info': True,
29
+ 'lineno': False,
30
+ 'funcName': False,
31
+ 'created': False,
32
+ 'msecs': False,
33
+ 'relativeCreated': False,
34
+ 'thread': False,
35
+ 'threadName': False,
36
+ 'processName': False,
37
+ 'process': False,
38
+ }
39
+
40
+ def format(self, record: logging.LogRecord) -> str:
41
+ dct = {
42
+ k: v
43
+ for k, o in self.KEYS.items()
44
+ for v in [getattr(record, k)]
45
+ if not (o and v is None)
46
+ }
47
+ return json_dumps_compact(dct)
48
+
49
+
50
+ def configure_standard_logging(level: ta.Union[int, str] = logging.INFO) -> None:
51
+ logging.root.addHandler(logging.StreamHandler())
52
+ logging.root.setLevel(level)
omlish/lite/marshal.py ADDED
@@ -0,0 +1,316 @@
1
+ """
2
+ TODO:
3
+ - pickle stdlib objs? have to pin to 3.8 pickle protocol, will be cross-version
4
+ """
5
+ # ruff: noqa: UP006 UP007
6
+ import abc
7
+ import base64
8
+ import collections.abc
9
+ import dataclasses as dc # noqa
10
+ import datetime
11
+ import decimal
12
+ import enum
13
+ import fractions
14
+ import typing as ta
15
+ import uuid
16
+ import weakref # noqa
17
+
18
+ from .check import check_isinstance
19
+ from .check import check_not_none
20
+ from .reflect import deep_subclasses
21
+ from .reflect import get_optional_alias_arg
22
+ from .reflect import is_generic_alias
23
+ from .reflect import is_union_alias
24
+
25
+
26
+ T = ta.TypeVar('T')
27
+
28
+
29
+ ##
30
+
31
+
32
+ class ObjMarshaler(abc.ABC):
33
+ @abc.abstractmethod
34
+ def marshal(self, o: ta.Any) -> ta.Any:
35
+ raise NotImplementedError
36
+
37
+ @abc.abstractmethod
38
+ def unmarshal(self, o: ta.Any) -> ta.Any:
39
+ raise NotImplementedError
40
+
41
+
42
+ class NopObjMarshaler(ObjMarshaler):
43
+ def marshal(self, o: ta.Any) -> ta.Any:
44
+ return o
45
+
46
+ def unmarshal(self, o: ta.Any) -> ta.Any:
47
+ return o
48
+
49
+
50
+ @dc.dataclass()
51
+ class ProxyObjMarshaler(ObjMarshaler):
52
+ m: ta.Optional[ObjMarshaler] = None
53
+
54
+ def marshal(self, o: ta.Any) -> ta.Any:
55
+ return check_not_none(self.m).marshal(o)
56
+
57
+ def unmarshal(self, o: ta.Any) -> ta.Any:
58
+ return check_not_none(self.m).unmarshal(o)
59
+
60
+
61
+ @dc.dataclass(frozen=True)
62
+ class CastObjMarshaler(ObjMarshaler):
63
+ ty: type
64
+
65
+ def marshal(self, o: ta.Any) -> ta.Any:
66
+ return o
67
+
68
+ def unmarshal(self, o: ta.Any) -> ta.Any:
69
+ return self.ty(o)
70
+
71
+
72
+ class DynamicObjMarshaler(ObjMarshaler):
73
+ def marshal(self, o: ta.Any) -> ta.Any:
74
+ return marshal_obj(o)
75
+
76
+ def unmarshal(self, o: ta.Any) -> ta.Any:
77
+ return o
78
+
79
+
80
+ @dc.dataclass(frozen=True)
81
+ class Base64ObjMarshaler(ObjMarshaler):
82
+ ty: type
83
+
84
+ def marshal(self, o: ta.Any) -> ta.Any:
85
+ return base64.b64encode(o).decode('ascii')
86
+
87
+ def unmarshal(self, o: ta.Any) -> ta.Any:
88
+ return self.ty(base64.b64decode(o))
89
+
90
+
91
+ @dc.dataclass(frozen=True)
92
+ class EnumObjMarshaler(ObjMarshaler):
93
+ ty: type
94
+
95
+ def marshal(self, o: ta.Any) -> ta.Any:
96
+ return o.name
97
+
98
+ def unmarshal(self, o: ta.Any) -> ta.Any:
99
+ return self.ty.__members__[o] # type: ignore
100
+
101
+
102
+ @dc.dataclass(frozen=True)
103
+ class OptionalObjMarshaler(ObjMarshaler):
104
+ item: ObjMarshaler
105
+
106
+ def marshal(self, o: ta.Any) -> ta.Any:
107
+ if o is None:
108
+ return None
109
+ return self.item.marshal(o)
110
+
111
+ def unmarshal(self, o: ta.Any) -> ta.Any:
112
+ if o is None:
113
+ return None
114
+ return self.item.unmarshal(o)
115
+
116
+
117
+ @dc.dataclass(frozen=True)
118
+ class MappingObjMarshaler(ObjMarshaler):
119
+ ty: type
120
+ km: ObjMarshaler
121
+ vm: ObjMarshaler
122
+
123
+ def marshal(self, o: ta.Any) -> ta.Any:
124
+ return {self.km.marshal(k): self.vm.marshal(v) for k, v in o.items()}
125
+
126
+ def unmarshal(self, o: ta.Any) -> ta.Any:
127
+ return self.ty((self.km.unmarshal(k), self.vm.unmarshal(v)) for k, v in o.items())
128
+
129
+
130
+ @dc.dataclass(frozen=True)
131
+ class IterableObjMarshaler(ObjMarshaler):
132
+ ty: type
133
+ item: ObjMarshaler
134
+
135
+ def marshal(self, o: ta.Any) -> ta.Any:
136
+ return [self.item.marshal(e) for e in o]
137
+
138
+ def unmarshal(self, o: ta.Any) -> ta.Any:
139
+ return self.ty(self.item.unmarshal(e) for e in o)
140
+
141
+
142
+ @dc.dataclass(frozen=True)
143
+ class DataclassObjMarshaler(ObjMarshaler):
144
+ ty: type
145
+ fs: ta.Mapping[str, ObjMarshaler]
146
+
147
+ def marshal(self, o: ta.Any) -> ta.Any:
148
+ return {k: m.marshal(getattr(o, k)) for k, m in self.fs.items()}
149
+
150
+ def unmarshal(self, o: ta.Any) -> ta.Any:
151
+ return self.ty(**{k: self.fs[k].unmarshal(v) for k, v in o.items()})
152
+
153
+
154
+ @dc.dataclass(frozen=True)
155
+ class PolymorphicObjMarshaler(ObjMarshaler):
156
+ class Impl(ta.NamedTuple):
157
+ ty: type
158
+ tag: str
159
+ m: ObjMarshaler
160
+
161
+ impls_by_ty: ta.Mapping[type, Impl]
162
+ impls_by_tag: ta.Mapping[str, Impl]
163
+
164
+ def marshal(self, o: ta.Any) -> ta.Any:
165
+ impl = self.impls_by_ty[type(o)]
166
+ return {impl.tag: impl.m.marshal(o)}
167
+
168
+ def unmarshal(self, o: ta.Any) -> ta.Any:
169
+ [(t, v)] = o.items()
170
+ impl = self.impls_by_tag[t]
171
+ return impl.m.unmarshal(v)
172
+
173
+
174
+ @dc.dataclass(frozen=True)
175
+ class DatetimeObjMarshaler(ObjMarshaler):
176
+ ty: type
177
+
178
+ def marshal(self, o: ta.Any) -> ta.Any:
179
+ return o.isoformat()
180
+
181
+ def unmarshal(self, o: ta.Any) -> ta.Any:
182
+ return self.ty.fromisoformat(o) # type: ignore
183
+
184
+
185
+ class DecimalObjMarshaler(ObjMarshaler):
186
+ def marshal(self, o: ta.Any) -> ta.Any:
187
+ return str(check_isinstance(o, decimal.Decimal))
188
+
189
+ def unmarshal(self, v: ta.Any) -> ta.Any:
190
+ return decimal.Decimal(check_isinstance(v, str))
191
+
192
+
193
+ class FractionObjMarshaler(ObjMarshaler):
194
+ def marshal(self, o: ta.Any) -> ta.Any:
195
+ fr = check_isinstance(o, fractions.Fraction)
196
+ return [fr.numerator, fr.denominator]
197
+
198
+ def unmarshal(self, v: ta.Any) -> ta.Any:
199
+ num, denom = check_isinstance(v, list)
200
+ return fractions.Fraction(num, denom)
201
+
202
+
203
+ class UuidObjMarshaler(ObjMarshaler):
204
+ def marshal(self, o: ta.Any) -> ta.Any:
205
+ return str(o)
206
+
207
+ def unmarshal(self, o: ta.Any) -> ta.Any:
208
+ return uuid.UUID(o)
209
+
210
+
211
+ _OBJ_MARSHALERS: ta.Dict[ta.Any, ObjMarshaler] = {
212
+ **{t: NopObjMarshaler() for t in (type(None),)},
213
+ **{t: CastObjMarshaler(t) for t in (int, float, str, bool)},
214
+ **{t: Base64ObjMarshaler(t) for t in (bytes, bytearray)},
215
+ **{t: IterableObjMarshaler(t, DynamicObjMarshaler()) for t in (list, tuple, set, frozenset)},
216
+ **{t: MappingObjMarshaler(t, DynamicObjMarshaler(), DynamicObjMarshaler()) for t in (dict,)},
217
+
218
+ ta.Any: DynamicObjMarshaler(),
219
+
220
+ **{t: DatetimeObjMarshaler(t) for t in (datetime.date, datetime.time, datetime.datetime)},
221
+ decimal.Decimal: DecimalObjMarshaler(),
222
+ fractions.Fraction: FractionObjMarshaler(),
223
+ uuid.UUID: UuidObjMarshaler(),
224
+ }
225
+
226
+ _OBJ_MARSHALER_GENERIC_MAPPING_TYPES: ta.Dict[ta.Any, type] = {
227
+ **{t: t for t in (dict,)},
228
+ **{t: dict for t in (collections.abc.Mapping, collections.abc.MutableMapping)},
229
+ }
230
+
231
+ _OBJ_MARSHALER_GENERIC_ITERABLE_TYPES: ta.Dict[ta.Any, type] = {
232
+ **{t: t for t in (list, tuple, set, frozenset)},
233
+ **{t: frozenset for t in (collections.abc.Set, collections.abc.MutableSet)},
234
+ **{t: tuple for t in (collections.abc.Sequence, collections.abc.MutableSequence)},
235
+ }
236
+
237
+
238
+ def register_opj_marshaler(ty: ta.Any, m: ObjMarshaler) -> None:
239
+ if ty in _OBJ_MARSHALERS:
240
+ raise KeyError(ty)
241
+ _OBJ_MARSHALERS[ty] = m
242
+
243
+
244
+ def _make_obj_marshaler(ty: ta.Any) -> ObjMarshaler:
245
+ if isinstance(ty, type) and abc.ABC in ty.__bases__:
246
+ impls = [ # type: ignore
247
+ PolymorphicObjMarshaler.Impl(
248
+ ity,
249
+ ity.__qualname__,
250
+ get_obj_marshaler(ity),
251
+ )
252
+ for ity in deep_subclasses(ty)
253
+ if abc.ABC not in ity.__bases__
254
+ ]
255
+ return PolymorphicObjMarshaler(
256
+ {i.ty: i for i in impls},
257
+ {i.tag: i for i in impls},
258
+ )
259
+
260
+ if isinstance(ty, type) and issubclass(ty, enum.Enum):
261
+ return EnumObjMarshaler(ty)
262
+
263
+ if dc.is_dataclass(ty):
264
+ return DataclassObjMarshaler(
265
+ ty,
266
+ {f.name: get_obj_marshaler(f.type) for f in dc.fields(ty)},
267
+ )
268
+
269
+ if is_generic_alias(ty):
270
+ try:
271
+ mt = _OBJ_MARSHALER_GENERIC_MAPPING_TYPES[ta.get_origin(ty)]
272
+ except KeyError:
273
+ pass
274
+ else:
275
+ k, v = ta.get_args(ty)
276
+ return MappingObjMarshaler(mt, get_obj_marshaler(k), get_obj_marshaler(v))
277
+
278
+ try:
279
+ st = _OBJ_MARSHALER_GENERIC_ITERABLE_TYPES[ta.get_origin(ty)]
280
+ except KeyError:
281
+ pass
282
+ else:
283
+ [e] = ta.get_args(ty)
284
+ return IterableObjMarshaler(st, get_obj_marshaler(e))
285
+
286
+ if is_union_alias(ty):
287
+ return OptionalObjMarshaler(get_obj_marshaler(get_optional_alias_arg(ty)))
288
+
289
+ raise TypeError(ty)
290
+
291
+
292
+ def get_obj_marshaler(ty: ta.Any) -> ObjMarshaler:
293
+ try:
294
+ return _OBJ_MARSHALERS[ty]
295
+ except KeyError:
296
+ pass
297
+
298
+ p = ProxyObjMarshaler()
299
+ _OBJ_MARSHALERS[ty] = p
300
+ try:
301
+ m = _make_obj_marshaler(ty)
302
+ except Exception:
303
+ del _OBJ_MARSHALERS[ty]
304
+ raise
305
+ else:
306
+ p.m = m
307
+ _OBJ_MARSHALERS[ty] = m
308
+ return m
309
+
310
+
311
+ def marshal_obj(o: ta.Any, ty: ta.Any = None) -> ta.Any:
312
+ return get_obj_marshaler(ty if ty is not None else type(o)).marshal(o)
313
+
314
+
315
+ def unmarshal_obj(o: ta.Any, ty: ta.Union[ta.Type[T], ta.Any]) -> T:
316
+ return get_obj_marshaler(ty).unmarshal(o)
omlish/lite/reflect.py ADDED
@@ -0,0 +1,49 @@
1
+ # ruff: noqa: UP006
2
+ import functools
3
+ import typing as ta
4
+
5
+
6
+ T = ta.TypeVar('T')
7
+
8
+
9
+ _GENERIC_ALIAS_TYPES = (
10
+ ta._GenericAlias, # type: ignore # noqa
11
+ *([ta._SpecialGenericAlias] if hasattr(ta, '_SpecialGenericAlias') else []), # noqa
12
+ )
13
+
14
+
15
+ def is_generic_alias(obj, *, origin: ta.Any = None) -> bool:
16
+ return (
17
+ isinstance(obj, _GENERIC_ALIAS_TYPES) and
18
+ (origin is None or ta.get_origin(obj) is origin)
19
+ )
20
+
21
+
22
+ is_union_alias = functools.partial(is_generic_alias, origin=ta.Union)
23
+ is_callable_alias = functools.partial(is_generic_alias, origin=ta.Callable)
24
+
25
+
26
+ def is_optional_alias(spec: ta.Any) -> bool:
27
+ return (
28
+ isinstance(spec, _GENERIC_ALIAS_TYPES) and # noqa
29
+ ta.get_origin(spec) is ta.Union and
30
+ len(ta.get_args(spec)) == 2 and
31
+ any(a in (None, type(None)) for a in ta.get_args(spec))
32
+ )
33
+
34
+
35
+ def get_optional_alias_arg(spec: ta.Any) -> ta.Any:
36
+ [it] = [it for it in ta.get_args(spec) if it not in (None, type(None))]
37
+ return it
38
+
39
+
40
+ def deep_subclasses(cls: ta.Type[T]) -> ta.Iterator[ta.Type[T]]:
41
+ seen = set()
42
+ todo = list(reversed(cls.__subclasses__()))
43
+ while todo:
44
+ cur = todo.pop()
45
+ if cur in seen:
46
+ continue
47
+ seen.add(cur)
48
+ yield cur
49
+ todo.extend(reversed(cur.__subclasses__()))
omlish/lite/runtime.py ADDED
@@ -0,0 +1,18 @@
1
+ import inspect
2
+ import sys
3
+
4
+ from .cached import cached_nullary
5
+
6
+
7
+ @cached_nullary
8
+ def is_debugger_attached() -> bool:
9
+ return any(frame[1].endswith('pydevd.py') for frame in inspect.stack())
10
+
11
+
12
+ REQUIRED_PYTHON_VERSION = (3, 8)
13
+
14
+
15
+ def check_runtime_version() -> None:
16
+ if sys.version_info < REQUIRED_PYTHON_VERSION:
17
+ raise OSError(
18
+ f'Requires python {REQUIRED_PYTHON_VERSION}, got {sys.version_info} from {sys.executable}') # noqa
omlish/lite/secrets.py ADDED
@@ -0,0 +1,19 @@
1
+ import typing as ta
2
+
3
+
4
+ class Secret:
5
+ _VALUE_ATTR = '__secret_value__'
6
+
7
+ def __init__(self, *, key: str | None, value: str) -> None:
8
+ super().__init__()
9
+ self._key = key
10
+ setattr(self, self._VALUE_ATTR, lambda: value)
11
+
12
+ def __repr__(self) -> str:
13
+ return f'Secret<{self._key or ""}>'
14
+
15
+ def __str__(self) -> ta.NoReturn:
16
+ raise TypeError
17
+
18
+ def reveal(self) -> str:
19
+ return getattr(self, self._VALUE_ATTR)()
omlish/lite/strings.py ADDED
@@ -0,0 +1,25 @@
1
+ def camel_case(name: str) -> str:
2
+ return ''.join(map(str.capitalize, name.split('_'))) # noqa
3
+
4
+
5
+ def snake_case(name: str) -> str:
6
+ uppers: list[int | None] = [i for i, c in enumerate(name) if c.isupper()]
7
+ return '_'.join([name[l:r].lower() for l, r in zip([None, *uppers], [*uppers, None])]).strip('_')
8
+
9
+
10
+ def is_dunder(name: str) -> bool:
11
+ return (
12
+ name[:2] == name[-2:] == '__' and
13
+ name[2:3] != '_' and
14
+ name[-3:-2] != '_' and
15
+ len(name) > 4
16
+ )
17
+
18
+
19
+ def is_sunder(name: str) -> bool:
20
+ return (
21
+ name[0] == name[-1] == '_' and
22
+ name[1:2] != '_' and
23
+ name[-2:-1] != '_' and
24
+ len(name) > 2
25
+ )