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
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,121 @@
1
+ """
2
+ TODO:
3
+ - translate json keys
4
+ - debug
5
+ """
6
+ # ruff: noqa: UP006 UP007 N802
7
+ import datetime
8
+ import logging
9
+ import threading
10
+ import typing as ta
11
+
12
+ from .json import json_dumps_compact
13
+
14
+
15
+ log = logging.getLogger(__name__)
16
+
17
+
18
+ ##
19
+
20
+
21
+ class TidLogFilter(logging.Filter):
22
+
23
+ def filter(self, record):
24
+ record.tid = threading.get_native_id()
25
+ return True
26
+
27
+
28
+ ##
29
+
30
+
31
+ class JsonLogFormatter(logging.Formatter):
32
+
33
+ KEYS: ta.Mapping[str, bool] = {
34
+ 'name': False,
35
+ 'msg': False,
36
+ 'args': False,
37
+ 'levelname': False,
38
+ 'levelno': False,
39
+ 'pathname': False,
40
+ 'filename': False,
41
+ 'module': False,
42
+ 'exc_info': True,
43
+ 'exc_text': True,
44
+ 'stack_info': True,
45
+ 'lineno': False,
46
+ 'funcName': False,
47
+ 'created': False,
48
+ 'msecs': False,
49
+ 'relativeCreated': False,
50
+ 'thread': False,
51
+ 'threadName': False,
52
+ 'processName': False,
53
+ 'process': False,
54
+ }
55
+
56
+ def format(self, record: logging.LogRecord) -> str:
57
+ dct = {
58
+ k: v
59
+ for k, o in self.KEYS.items()
60
+ for v in [getattr(record, k)]
61
+ if not (o and v is None)
62
+ }
63
+ return json_dumps_compact(dct)
64
+
65
+
66
+ ##
67
+
68
+
69
+ STANDARD_LOG_FORMAT_PARTS = [
70
+ ('asctime', '%(asctime)-15s'),
71
+ ('process', 'pid=%(process)-6s'),
72
+ ('thread', 'tid=%(thread)-16s'),
73
+ ('levelname', '%(levelname)-8s'),
74
+ ('name', '%(name)s'),
75
+ ('separator', '::'),
76
+ ('message', '%(message)s'),
77
+ ]
78
+
79
+
80
+ class StandardLogFormatter(logging.Formatter):
81
+
82
+ @staticmethod
83
+ def build_log_format(parts: ta.Iterable[ta.Tuple[str, str]]) -> str:
84
+ return ' '.join(v for k, v in parts)
85
+
86
+ converter = datetime.datetime.fromtimestamp # type: ignore
87
+
88
+ def formatTime(self, record, datefmt=None):
89
+ ct = self.converter(record.created) # type: ignore
90
+ if datefmt:
91
+ return ct.strftime(datefmt) # noqa
92
+ else:
93
+ t = ct.strftime("%Y-%m-%d %H:%M:%S") # noqa
94
+ return '%s.%03d' % (t, record.msecs)
95
+
96
+
97
+ ##
98
+
99
+
100
+ def configure_standard_logging(
101
+ level: ta.Union[int, str] = logging.INFO,
102
+ *,
103
+ json: bool = False,
104
+ ) -> logging.Handler:
105
+ handler = logging.StreamHandler()
106
+
107
+ formatter: logging.Formatter
108
+ if json:
109
+ formatter = JsonLogFormatter()
110
+ else:
111
+ formatter = StandardLogFormatter(StandardLogFormatter.build_log_format(STANDARD_LOG_FORMAT_PARTS))
112
+ handler.setFormatter(formatter)
113
+
114
+ handler.addFilter(TidLogFilter())
115
+
116
+ logging.root.addHandler(handler)
117
+
118
+ if level is not None:
119
+ logging.root.setLevel(level)
120
+
121
+ return handler
omlish/lite/marshal.py ADDED
@@ -0,0 +1,318 @@
1
+ """
2
+ TODO:
3
+ - pickle stdlib objs? have to pin to 3.8 pickle protocol, will be cross-version
4
+ - nonstrict toggle
5
+ """
6
+ # ruff: noqa: UP006 UP007
7
+ import abc
8
+ import base64
9
+ import collections.abc
10
+ import dataclasses as dc # noqa
11
+ import datetime
12
+ import decimal
13
+ import enum
14
+ import fractions
15
+ import typing as ta
16
+ import uuid
17
+ import weakref # noqa
18
+
19
+ from .check import check_isinstance
20
+ from .check import check_not_none
21
+ from .reflect import deep_subclasses
22
+ from .reflect import get_optional_alias_arg
23
+ from .reflect import is_generic_alias
24
+ from .reflect import is_union_alias
25
+
26
+
27
+ T = ta.TypeVar('T')
28
+
29
+
30
+ ##
31
+
32
+
33
+ class ObjMarshaler(abc.ABC):
34
+ @abc.abstractmethod
35
+ def marshal(self, o: ta.Any) -> ta.Any:
36
+ raise NotImplementedError
37
+
38
+ @abc.abstractmethod
39
+ def unmarshal(self, o: ta.Any) -> ta.Any:
40
+ raise NotImplementedError
41
+
42
+
43
+ class NopObjMarshaler(ObjMarshaler):
44
+ def marshal(self, o: ta.Any) -> ta.Any:
45
+ return o
46
+
47
+ def unmarshal(self, o: ta.Any) -> ta.Any:
48
+ return o
49
+
50
+
51
+ @dc.dataclass()
52
+ class ProxyObjMarshaler(ObjMarshaler):
53
+ m: ta.Optional[ObjMarshaler] = None
54
+
55
+ def marshal(self, o: ta.Any) -> ta.Any:
56
+ return check_not_none(self.m).marshal(o)
57
+
58
+ def unmarshal(self, o: ta.Any) -> ta.Any:
59
+ return check_not_none(self.m).unmarshal(o)
60
+
61
+
62
+ @dc.dataclass(frozen=True)
63
+ class CastObjMarshaler(ObjMarshaler):
64
+ ty: type
65
+
66
+ def marshal(self, o: ta.Any) -> ta.Any:
67
+ return o
68
+
69
+ def unmarshal(self, o: ta.Any) -> ta.Any:
70
+ return self.ty(o)
71
+
72
+
73
+ class DynamicObjMarshaler(ObjMarshaler):
74
+ def marshal(self, o: ta.Any) -> ta.Any:
75
+ return marshal_obj(o)
76
+
77
+ def unmarshal(self, o: ta.Any) -> ta.Any:
78
+ return o
79
+
80
+
81
+ @dc.dataclass(frozen=True)
82
+ class Base64ObjMarshaler(ObjMarshaler):
83
+ ty: type
84
+
85
+ def marshal(self, o: ta.Any) -> ta.Any:
86
+ return base64.b64encode(o).decode('ascii')
87
+
88
+ def unmarshal(self, o: ta.Any) -> ta.Any:
89
+ return self.ty(base64.b64decode(o))
90
+
91
+
92
+ @dc.dataclass(frozen=True)
93
+ class EnumObjMarshaler(ObjMarshaler):
94
+ ty: type
95
+
96
+ def marshal(self, o: ta.Any) -> ta.Any:
97
+ return o.name
98
+
99
+ def unmarshal(self, o: ta.Any) -> ta.Any:
100
+ return self.ty.__members__[o] # type: ignore
101
+
102
+
103
+ @dc.dataclass(frozen=True)
104
+ class OptionalObjMarshaler(ObjMarshaler):
105
+ item: ObjMarshaler
106
+
107
+ def marshal(self, o: ta.Any) -> ta.Any:
108
+ if o is None:
109
+ return None
110
+ return self.item.marshal(o)
111
+
112
+ def unmarshal(self, o: ta.Any) -> ta.Any:
113
+ if o is None:
114
+ return None
115
+ return self.item.unmarshal(o)
116
+
117
+
118
+ @dc.dataclass(frozen=True)
119
+ class MappingObjMarshaler(ObjMarshaler):
120
+ ty: type
121
+ km: ObjMarshaler
122
+ vm: ObjMarshaler
123
+
124
+ def marshal(self, o: ta.Any) -> ta.Any:
125
+ return {self.km.marshal(k): self.vm.marshal(v) for k, v in o.items()}
126
+
127
+ def unmarshal(self, o: ta.Any) -> ta.Any:
128
+ return self.ty((self.km.unmarshal(k), self.vm.unmarshal(v)) for k, v in o.items())
129
+
130
+
131
+ @dc.dataclass(frozen=True)
132
+ class IterableObjMarshaler(ObjMarshaler):
133
+ ty: type
134
+ item: ObjMarshaler
135
+
136
+ def marshal(self, o: ta.Any) -> ta.Any:
137
+ return [self.item.marshal(e) for e in o]
138
+
139
+ def unmarshal(self, o: ta.Any) -> ta.Any:
140
+ return self.ty(self.item.unmarshal(e) for e in o)
141
+
142
+
143
+ @dc.dataclass(frozen=True)
144
+ class DataclassObjMarshaler(ObjMarshaler):
145
+ ty: type
146
+ fs: ta.Mapping[str, ObjMarshaler]
147
+ nonstrict: bool = False
148
+
149
+ def marshal(self, o: ta.Any) -> ta.Any:
150
+ return {k: m.marshal(getattr(o, k)) for k, m in self.fs.items()}
151
+
152
+ def unmarshal(self, o: ta.Any) -> ta.Any:
153
+ return self.ty(**{k: self.fs[k].unmarshal(v) for k, v in o.items() if self.nonstrict or k in self.fs})
154
+
155
+
156
+ @dc.dataclass(frozen=True)
157
+ class PolymorphicObjMarshaler(ObjMarshaler):
158
+ class Impl(ta.NamedTuple):
159
+ ty: type
160
+ tag: str
161
+ m: ObjMarshaler
162
+
163
+ impls_by_ty: ta.Mapping[type, Impl]
164
+ impls_by_tag: ta.Mapping[str, Impl]
165
+
166
+ def marshal(self, o: ta.Any) -> ta.Any:
167
+ impl = self.impls_by_ty[type(o)]
168
+ return {impl.tag: impl.m.marshal(o)}
169
+
170
+ def unmarshal(self, o: ta.Any) -> ta.Any:
171
+ [(t, v)] = o.items()
172
+ impl = self.impls_by_tag[t]
173
+ return impl.m.unmarshal(v)
174
+
175
+
176
+ @dc.dataclass(frozen=True)
177
+ class DatetimeObjMarshaler(ObjMarshaler):
178
+ ty: type
179
+
180
+ def marshal(self, o: ta.Any) -> ta.Any:
181
+ return o.isoformat()
182
+
183
+ def unmarshal(self, o: ta.Any) -> ta.Any:
184
+ return self.ty.fromisoformat(o) # type: ignore
185
+
186
+
187
+ class DecimalObjMarshaler(ObjMarshaler):
188
+ def marshal(self, o: ta.Any) -> ta.Any:
189
+ return str(check_isinstance(o, decimal.Decimal))
190
+
191
+ def unmarshal(self, v: ta.Any) -> ta.Any:
192
+ return decimal.Decimal(check_isinstance(v, str))
193
+
194
+
195
+ class FractionObjMarshaler(ObjMarshaler):
196
+ def marshal(self, o: ta.Any) -> ta.Any:
197
+ fr = check_isinstance(o, fractions.Fraction)
198
+ return [fr.numerator, fr.denominator]
199
+
200
+ def unmarshal(self, v: ta.Any) -> ta.Any:
201
+ num, denom = check_isinstance(v, list)
202
+ return fractions.Fraction(num, denom)
203
+
204
+
205
+ class UuidObjMarshaler(ObjMarshaler):
206
+ def marshal(self, o: ta.Any) -> ta.Any:
207
+ return str(o)
208
+
209
+ def unmarshal(self, o: ta.Any) -> ta.Any:
210
+ return uuid.UUID(o)
211
+
212
+
213
+ _OBJ_MARSHALERS: ta.Dict[ta.Any, ObjMarshaler] = {
214
+ **{t: NopObjMarshaler() for t in (type(None),)},
215
+ **{t: CastObjMarshaler(t) for t in (int, float, str, bool)},
216
+ **{t: Base64ObjMarshaler(t) for t in (bytes, bytearray)},
217
+ **{t: IterableObjMarshaler(t, DynamicObjMarshaler()) for t in (list, tuple, set, frozenset)},
218
+ **{t: MappingObjMarshaler(t, DynamicObjMarshaler(), DynamicObjMarshaler()) for t in (dict,)},
219
+
220
+ ta.Any: DynamicObjMarshaler(),
221
+
222
+ **{t: DatetimeObjMarshaler(t) for t in (datetime.date, datetime.time, datetime.datetime)},
223
+ decimal.Decimal: DecimalObjMarshaler(),
224
+ fractions.Fraction: FractionObjMarshaler(),
225
+ uuid.UUID: UuidObjMarshaler(),
226
+ }
227
+
228
+ _OBJ_MARSHALER_GENERIC_MAPPING_TYPES: ta.Dict[ta.Any, type] = {
229
+ **{t: t for t in (dict,)},
230
+ **{t: dict for t in (collections.abc.Mapping, collections.abc.MutableMapping)},
231
+ }
232
+
233
+ _OBJ_MARSHALER_GENERIC_ITERABLE_TYPES: ta.Dict[ta.Any, type] = {
234
+ **{t: t for t in (list, tuple, set, frozenset)},
235
+ **{t: frozenset for t in (collections.abc.Set, collections.abc.MutableSet)},
236
+ **{t: tuple for t in (collections.abc.Sequence, collections.abc.MutableSequence)},
237
+ }
238
+
239
+
240
+ def register_opj_marshaler(ty: ta.Any, m: ObjMarshaler) -> None:
241
+ if ty in _OBJ_MARSHALERS:
242
+ raise KeyError(ty)
243
+ _OBJ_MARSHALERS[ty] = m
244
+
245
+
246
+ def _make_obj_marshaler(ty: ta.Any) -> ObjMarshaler:
247
+ if isinstance(ty, type) and abc.ABC in ty.__bases__:
248
+ impls = [ # type: ignore
249
+ PolymorphicObjMarshaler.Impl(
250
+ ity,
251
+ ity.__qualname__,
252
+ get_obj_marshaler(ity),
253
+ )
254
+ for ity in deep_subclasses(ty)
255
+ if abc.ABC not in ity.__bases__
256
+ ]
257
+ return PolymorphicObjMarshaler(
258
+ {i.ty: i for i in impls},
259
+ {i.tag: i for i in impls},
260
+ )
261
+
262
+ if isinstance(ty, type) and issubclass(ty, enum.Enum):
263
+ return EnumObjMarshaler(ty)
264
+
265
+ if dc.is_dataclass(ty):
266
+ return DataclassObjMarshaler(
267
+ ty,
268
+ {f.name: get_obj_marshaler(f.type) for f in dc.fields(ty)},
269
+ )
270
+
271
+ if is_generic_alias(ty):
272
+ try:
273
+ mt = _OBJ_MARSHALER_GENERIC_MAPPING_TYPES[ta.get_origin(ty)]
274
+ except KeyError:
275
+ pass
276
+ else:
277
+ k, v = ta.get_args(ty)
278
+ return MappingObjMarshaler(mt, get_obj_marshaler(k), get_obj_marshaler(v))
279
+
280
+ try:
281
+ st = _OBJ_MARSHALER_GENERIC_ITERABLE_TYPES[ta.get_origin(ty)]
282
+ except KeyError:
283
+ pass
284
+ else:
285
+ [e] = ta.get_args(ty)
286
+ return IterableObjMarshaler(st, get_obj_marshaler(e))
287
+
288
+ if is_union_alias(ty):
289
+ return OptionalObjMarshaler(get_obj_marshaler(get_optional_alias_arg(ty)))
290
+
291
+ raise TypeError(ty)
292
+
293
+
294
+ def get_obj_marshaler(ty: ta.Any) -> ObjMarshaler:
295
+ try:
296
+ return _OBJ_MARSHALERS[ty]
297
+ except KeyError:
298
+ pass
299
+
300
+ p = ProxyObjMarshaler()
301
+ _OBJ_MARSHALERS[ty] = p
302
+ try:
303
+ m = _make_obj_marshaler(ty)
304
+ except Exception:
305
+ del _OBJ_MARSHALERS[ty]
306
+ raise
307
+ else:
308
+ p.m = m
309
+ _OBJ_MARSHALERS[ty] = m
310
+ return m
311
+
312
+
313
+ def marshal_obj(o: ta.Any, ty: ta.Any = None) -> ta.Any:
314
+ return get_obj_marshaler(ty if ty is not None else type(o)).marshal(o)
315
+
316
+
317
+ def unmarshal_obj(o: ta.Any, ty: ta.Union[ta.Type[T], ta.Any]) -> T:
318
+ 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
+ )