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.
- omlish/__about__.py +109 -5
- omlish/__init__.py +0 -8
- omlish/asyncs/__init__.py +0 -9
- omlish/asyncs/anyio.py +40 -0
- omlish/bootstrap.py +737 -0
- omlish/check.py +1 -1
- omlish/collections/__init__.py +4 -0
- omlish/collections/exceptions.py +2 -0
- omlish/collections/utils.py +38 -9
- omlish/configs/strings.py +2 -0
- omlish/dataclasses/__init__.py +7 -0
- omlish/dataclasses/impl/descriptors.py +95 -0
- omlish/dataclasses/impl/reflect.py +1 -1
- omlish/dataclasses/utils.py +23 -0
- omlish/{lang/datetimes.py → datetimes.py} +8 -4
- omlish/diag/procfs.py +1 -1
- omlish/diag/threads.py +131 -48
- omlish/docker.py +16 -1
- omlish/fnpairs.py +0 -4
- omlish/{serde → formats}/dotenv.py +3 -0
- omlish/{serde → formats}/yaml.py +2 -2
- omlish/graphs/trees.py +1 -1
- omlish/http/consts.py +6 -0
- omlish/http/sessions.py +2 -2
- omlish/inject/__init__.py +4 -0
- omlish/inject/binder.py +3 -3
- omlish/inject/elements.py +1 -1
- omlish/inject/impl/injector.py +57 -27
- omlish/inject/impl/origins.py +2 -0
- omlish/inject/origins.py +3 -0
- omlish/inject/utils.py +18 -0
- omlish/iterators.py +69 -2
- omlish/lang/__init__.py +16 -7
- omlish/lang/classes/restrict.py +10 -0
- omlish/lang/contextmanagers.py +1 -1
- omlish/lang/descriptors.py +3 -3
- omlish/lang/imports.py +67 -0
- omlish/lang/iterables.py +40 -0
- omlish/lang/maybes.py +3 -0
- omlish/lang/objects.py +38 -0
- omlish/lang/strings.py +25 -0
- omlish/lang/sys.py +9 -0
- omlish/lang/typing.py +37 -0
- omlish/lite/__init__.py +1 -0
- omlish/lite/cached.py +18 -0
- omlish/lite/check.py +29 -0
- omlish/lite/contextmanagers.py +18 -0
- omlish/lite/json.py +30 -0
- omlish/lite/logs.py +121 -0
- omlish/lite/marshal.py +318 -0
- omlish/lite/reflect.py +49 -0
- omlish/lite/runtime.py +18 -0
- omlish/lite/secrets.py +19 -0
- omlish/lite/strings.py +25 -0
- omlish/lite/subprocesses.py +112 -0
- omlish/logs/__init__.py +13 -9
- omlish/logs/configs.py +17 -22
- omlish/logs/formatters.py +3 -48
- omlish/marshal/__init__.py +28 -0
- omlish/marshal/any.py +5 -5
- omlish/marshal/base.py +27 -11
- omlish/marshal/base64.py +24 -9
- omlish/marshal/dataclasses.py +34 -28
- omlish/marshal/datetimes.py +74 -18
- omlish/marshal/enums.py +14 -8
- omlish/marshal/exceptions.py +11 -1
- omlish/marshal/factories.py +59 -74
- omlish/marshal/forbidden.py +35 -0
- omlish/marshal/global_.py +11 -4
- omlish/marshal/iterables.py +21 -24
- omlish/marshal/mappings.py +23 -26
- omlish/marshal/numbers.py +51 -0
- omlish/marshal/optionals.py +11 -12
- omlish/marshal/polymorphism.py +86 -21
- omlish/marshal/primitives.py +4 -5
- omlish/marshal/standard.py +13 -8
- omlish/marshal/uuids.py +4 -5
- omlish/matchfns.py +218 -0
- omlish/os.py +64 -0
- omlish/reflect/__init__.py +39 -0
- omlish/reflect/isinstance.py +38 -0
- omlish/reflect/ops.py +84 -0
- omlish/reflect/subst.py +110 -0
- omlish/reflect/types.py +275 -0
- omlish/secrets/__init__.py +18 -2
- omlish/secrets/crypto.py +132 -0
- omlish/secrets/marshal.py +36 -7
- omlish/secrets/openssl.py +207 -0
- omlish/secrets/secrets.py +260 -8
- omlish/secrets/subprocesses.py +42 -0
- omlish/sql/dbs.py +6 -5
- omlish/sql/exprs.py +12 -0
- omlish/sql/secrets.py +10 -0
- omlish/term.py +1 -1
- omlish/testing/pytest/plugins/switches.py +54 -19
- omlish/text/glyphsplit.py +5 -0
- omlish-0.0.0.dev8.dist-info/METADATA +50 -0
- {omlish-0.0.0.dev6.dist-info → omlish-0.0.0.dev8.dist-info}/RECORD +105 -78
- {omlish-0.0.0.dev6.dist-info → omlish-0.0.0.dev8.dist-info}/WHEEL +1 -1
- omlish/logs/filters.py +0 -11
- omlish/reflect.py +0 -470
- omlish-0.0.0.dev6.dist-info/METADATA +0 -34
- /omlish/{asyncs/futures.py → concurrent.py} +0 -0
- /omlish/{serde → formats}/__init__.py +0 -0
- /omlish/{serde → formats}/json.py +0 -0
- /omlish/{serde → formats}/props.py +0 -0
- {omlish-0.0.0.dev6.dist-info → omlish-0.0.0.dev8.dist-info}/LICENSE +0 -0
- {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)
|
omlish/lite/__init__.py
ADDED
|
@@ -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
|
+
)
|