omlish 0.0.0.dev24__py3-none-any.whl → 0.0.0.dev26__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 +20 -4
- omlish/_manifests.json +1 -0
- omlish/bootstrap/diag.py +25 -0
- omlish/bootstrap/harness.py +3 -2
- omlish/check.py +1 -1
- omlish/collections/__init__.py +1 -0
- omlish/collections/utils.py +7 -2
- omlish/dataclasses/__init__.py +4 -4
- omlish/dataclasses/impl/LICENSE +279 -0
- omlish/dataclasses/impl/api.py +5 -3
- omlish/dataclasses/impl/exceptions.py +2 -2
- omlish/dataclasses/impl/fields.py +7 -4
- omlish/dataclasses/impl/init.py +8 -8
- omlish/dataclasses/impl/metadata.py +3 -3
- omlish/dataclasses/impl/params.py +4 -3
- omlish/diag/replserver/server.py +17 -2
- omlish/docker.py +74 -6
- omlish/formats/yaml.py +10 -0
- omlish/graphs/dot/__init__.py +31 -19
- omlish/graphs/dot/make.py +16 -0
- omlish/graphs/dot/rendering.py +9 -8
- omlish/http/cookies.py +2 -1
- omlish/inject/impl/scopes.py +2 -0
- omlish/inject/keys.py +1 -1
- omlish/inject/multis.py +4 -4
- omlish/inject/providers.py +1 -1
- omlish/lang/__init__.py +9 -0
- omlish/lang/classes/__init__.py +3 -0
- omlish/lang/classes/restrict.py +25 -4
- omlish/lang/classes/simple.py +0 -4
- omlish/lang/classes/virtual.py +6 -4
- omlish/lang/datetimes.py +9 -0
- omlish/lang/typing.py +32 -0
- omlish/lite/logs.py +30 -25
- omlish/lite/secrets.py +3 -1
- omlish/logs/configs.py +2 -2
- omlish/marshal/dataclasses.py +1 -1
- omlish/secrets/secrets.py +1 -1
- omlish/specs/jsonschema/schemas/draft202012/metaschema.json +58 -0
- omlish/specs/jsonschema/schemas/draft202012/vocabularies/applicator.json +48 -0
- omlish/specs/jsonschema/schemas/draft202012/vocabularies/content.json +17 -0
- omlish/specs/jsonschema/schemas/draft202012/vocabularies/core.json +51 -0
- omlish/specs/jsonschema/schemas/draft202012/vocabularies/format-annotation.json +14 -0
- omlish/specs/jsonschema/schemas/draft202012/vocabularies/format-assertion.json +14 -0
- omlish/specs/jsonschema/schemas/draft202012/vocabularies/format.json +14 -0
- omlish/specs/jsonschema/schemas/draft202012/vocabularies/meta-data.json +37 -0
- omlish/specs/jsonschema/schemas/draft202012/vocabularies/unevaluated.json +15 -0
- omlish/specs/jsonschema/schemas/draft202012/vocabularies/validation.json +98 -0
- omlish/stats.py +1 -1
- {omlish-0.0.0.dev24.dist-info → omlish-0.0.0.dev26.dist-info}/METADATA +1 -1
- {omlish-0.0.0.dev24.dist-info → omlish-0.0.0.dev26.dist-info}/RECORD +54 -40
- {omlish-0.0.0.dev24.dist-info → omlish-0.0.0.dev26.dist-info}/WHEEL +1 -1
- {omlish-0.0.0.dev24.dist-info → omlish-0.0.0.dev26.dist-info}/LICENSE +0 -0
- {omlish-0.0.0.dev24.dist-info → omlish-0.0.0.dev26.dist-info}/top_level.txt +0 -0
omlish/docker.py
CHANGED
@@ -7,12 +7,6 @@ TODO:
|
|
7
7
|
- https://stackoverflow.com/questions/55386202/how-can-i-use-the-docker-registry-api-to-pull-information-about-a-container-get
|
8
8
|
- https://ops.tips/blog/inspecting-docker-image-without-pull/
|
9
9
|
|
10
|
-
repo=library/nginx
|
11
|
-
token=$(curl -s "https://auth.docker.io/token?service=registry.docker.io&scope=repository:${repo}:pull" | jq -r '.token')
|
12
|
-
curl -H "Authorization: Bearer $token" -s "https://registry-1.docker.io/v2/${repo}/tags/list" | jq
|
13
|
-
api="application/vnd.docker.distribution.manifest.v2+json"
|
14
|
-
apil="application/vnd.docker.distribution.manifest.list.v2+json"
|
15
|
-
curl -H "Accept: ${api}" -H "Accept: ${apil}" -H "Authorization: Bearer $token" -s "https://registry-1.docker.io/v2/${repo}/manifests/latest" | jq .
|
16
10
|
""" # noqa
|
17
11
|
import datetime
|
18
12
|
import os
|
@@ -21,6 +15,7 @@ import shlex
|
|
21
15
|
import subprocess
|
22
16
|
import sys
|
23
17
|
import typing as ta
|
18
|
+
import urllib.request
|
24
19
|
|
25
20
|
from . import check
|
26
21
|
from . import dataclasses as dc
|
@@ -191,3 +186,76 @@ DOCKER_HOST_PLATFORM_KEY = 'DOCKER_HOST_PLATFORM'
|
|
191
186
|
|
192
187
|
def get_docker_host_platform() -> str | None:
|
193
188
|
return os.environ.get(DOCKER_HOST_PLATFORM_KEY)
|
189
|
+
|
190
|
+
|
191
|
+
##
|
192
|
+
|
193
|
+
|
194
|
+
@dc.dataclass(frozen=True)
|
195
|
+
class HubRepoInfo:
|
196
|
+
repo: str
|
197
|
+
tags: ta.Mapping[str, ta.Any]
|
198
|
+
latest_manifests: ta.Mapping[str, ta.Any]
|
199
|
+
|
200
|
+
|
201
|
+
def get_hub_repo_info(
|
202
|
+
repo: str,
|
203
|
+
*,
|
204
|
+
auth_url: str = 'https://auth.docker.io/',
|
205
|
+
api_url: str = 'https://registry-1.docker.io/v2/',
|
206
|
+
) -> HubRepoInfo:
|
207
|
+
"""
|
208
|
+
https://stackoverflow.com/a/39376254
|
209
|
+
|
210
|
+
==
|
211
|
+
|
212
|
+
repo=library/nginx
|
213
|
+
token=$(
|
214
|
+
curl -s "https://auth.docker.io/token?service=registry.docker.io&scope=repository:${repo}:pull" \
|
215
|
+
| jq -r '.token' \
|
216
|
+
)
|
217
|
+
curl -H "Authorization: Bearer $token" -s "https://registry-1.docker.io/v2/${repo}/tags/list" | jq
|
218
|
+
curl \
|
219
|
+
-H "Accept: application/vnd.docker.distribution.manifest.v2+json" \
|
220
|
+
-H "Accept: application/vnd.docker.distribution.manifest.list.v2+json" \
|
221
|
+
-H "Authorization: Bearer $token" \
|
222
|
+
-s "https://registry-1.docker.io/v2/${repo}/manifests/latest" \
|
223
|
+
| jq .
|
224
|
+
"""
|
225
|
+
|
226
|
+
auth_url = auth_url.rstrip('/')
|
227
|
+
api_url = api_url.rstrip('/')
|
228
|
+
|
229
|
+
#
|
230
|
+
|
231
|
+
def req_json(url: str, **kwargs: ta.Any) -> ta.Any:
|
232
|
+
with urllib.request.urlopen(urllib.request.Request(url, **kwargs)) as resp: # noqa
|
233
|
+
return json.loads(resp.read().decode('utf-8'))
|
234
|
+
|
235
|
+
#
|
236
|
+
|
237
|
+
token_dct = req_json(f'{auth_url}/token?service=registry.docker.io&scope=repository:{repo}:pull')
|
238
|
+
token = token_dct['token']
|
239
|
+
|
240
|
+
req_hdrs = {'Authorization': f'Bearer {token}'}
|
241
|
+
|
242
|
+
#
|
243
|
+
|
244
|
+
tags_dct = req_json(
|
245
|
+
f'{api_url}/{repo}/tags/list',
|
246
|
+
headers=req_hdrs,
|
247
|
+
)
|
248
|
+
|
249
|
+
latest_mani_dct = req_json(
|
250
|
+
f'{api_url}/{repo}/manifests/latest',
|
251
|
+
headers={
|
252
|
+
**req_hdrs,
|
253
|
+
'Accept': 'application/vnd.docker.distribution.manifest.v2+json',
|
254
|
+
},
|
255
|
+
)
|
256
|
+
|
257
|
+
return HubRepoInfo(
|
258
|
+
repo,
|
259
|
+
tags_dct,
|
260
|
+
latest_mani_dct,
|
261
|
+
)
|
omlish/formats/yaml.py
CHANGED
@@ -5,6 +5,7 @@ TODO:
|
|
5
5
|
- goal: perfect rewrites (comments, whitespace)
|
6
6
|
- or at least comments
|
7
7
|
- rename 'objects'? codecs/serde interplay still unresolved
|
8
|
+
- look ma, a monad
|
8
9
|
"""
|
9
10
|
import datetime
|
10
11
|
import types
|
@@ -26,6 +27,9 @@ else:
|
|
26
27
|
T = ta.TypeVar('T')
|
27
28
|
|
28
29
|
|
30
|
+
##
|
31
|
+
|
32
|
+
|
29
33
|
@dc.dataclass(frozen=True)
|
30
34
|
class NodeWrapped(lang.Final, ta.Generic[T]):
|
31
35
|
value: T
|
@@ -124,6 +128,9 @@ class NodeWrappingConstructorMixin:
|
|
124
128
|
return self.__construct_yaml_pairs(node, super().construct_yaml_pairs) # type: ignore # noqa
|
125
129
|
|
126
130
|
|
131
|
+
##
|
132
|
+
|
133
|
+
|
127
134
|
class _cached_class_property: # noqa
|
128
135
|
def __init__(self, fn):
|
129
136
|
super().__init__()
|
@@ -204,6 +211,9 @@ class WrappedLoaders(lang.Namespace):
|
|
204
211
|
return cls.CUnsafe(*args, **kwargs)
|
205
212
|
|
206
213
|
|
214
|
+
##
|
215
|
+
|
216
|
+
|
207
217
|
def load(stream, Loader): # noqa
|
208
218
|
with lang.disposing(Loader(stream)) as loader:
|
209
219
|
return loader.get_single_data()
|
omlish/graphs/dot/__init__.py
CHANGED
@@ -1,19 +1,31 @@
|
|
1
|
-
from .items import
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
from .
|
18
|
-
|
19
|
-
|
1
|
+
from .items import ( # noqa
|
2
|
+
Attrs,
|
3
|
+
Cell,
|
4
|
+
Edge,
|
5
|
+
Graph,
|
6
|
+
Id,
|
7
|
+
Item,
|
8
|
+
Node,
|
9
|
+
Raw,
|
10
|
+
RawStmt,
|
11
|
+
Row,
|
12
|
+
Stmt,
|
13
|
+
Table,
|
14
|
+
Text,
|
15
|
+
)
|
16
|
+
|
17
|
+
from .make import ( # noqa
|
18
|
+
make_simple,
|
19
|
+
)
|
20
|
+
|
21
|
+
from .rendering import ( # noqa
|
22
|
+
Renderer,
|
23
|
+
open_dot,
|
24
|
+
render,
|
25
|
+
)
|
26
|
+
|
27
|
+
from .utils import ( # noqa
|
28
|
+
Color,
|
29
|
+
escape,
|
30
|
+
gen_rainbow,
|
31
|
+
)
|
@@ -0,0 +1,16 @@
|
|
1
|
+
import typing as ta
|
2
|
+
|
3
|
+
from ... import lang
|
4
|
+
from .items import Edge
|
5
|
+
from .items import Graph
|
6
|
+
from .items import Node
|
7
|
+
|
8
|
+
|
9
|
+
T = ta.TypeVar('T')
|
10
|
+
|
11
|
+
|
12
|
+
def make_simple(graph: ta.Mapping[T, ta.Iterable[T]]) -> Graph:
|
13
|
+
return Graph([
|
14
|
+
*[Node(n) for n in {*graph, *lang.flatten(graph.values())}],
|
15
|
+
*[Edge(k, v) for k, vs in graph.items() for v in vs],
|
16
|
+
])
|
omlish/graphs/dot/rendering.py
CHANGED
@@ -120,6 +120,7 @@ def open_dot(
|
|
120
120
|
*,
|
121
121
|
timeout_s: float = 1.,
|
122
122
|
sleep_s: float = 0.,
|
123
|
+
delete: bool = False,
|
123
124
|
) -> None:
|
124
125
|
stdout, _ = subprocess.Popen(
|
125
126
|
['dot', '-Tpdf'],
|
@@ -132,16 +133,16 @@ def open_dot(
|
|
132
133
|
|
133
134
|
with tempfile.NamedTemporaryFile(
|
134
135
|
suffix='.pdf',
|
135
|
-
delete=
|
136
|
+
delete=delete,
|
136
137
|
) as pdf:
|
137
138
|
pdf.file.write(stdout)
|
138
139
|
pdf.file.flush()
|
139
140
|
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
141
|
+
_, _ = subprocess.Popen(
|
142
|
+
['open', pdf.name],
|
143
|
+
).communicate(
|
144
|
+
timeout=timeout_s,
|
145
|
+
)
|
145
146
|
|
146
|
-
|
147
|
-
|
147
|
+
if sleep_s > 0.:
|
148
|
+
time.sleep(sleep_s)
|
omlish/http/cookies.py
CHANGED
@@ -29,6 +29,7 @@ import typing as ta
|
|
29
29
|
import urllib.parse
|
30
30
|
|
31
31
|
from .. import collections as col
|
32
|
+
from .. import lang
|
32
33
|
from .dates import http_date
|
33
34
|
|
34
35
|
|
@@ -140,7 +141,7 @@ def dump_cookie(
|
|
140
141
|
if not isinstance(expires, str):
|
141
142
|
expires = http_date(expires)
|
142
143
|
elif max_age is not None and sync_expires:
|
143
|
-
expires = http_date(
|
144
|
+
expires = http_date(lang.utcnow().timestamp() + max_age)
|
144
145
|
|
145
146
|
if samesite is not None:
|
146
147
|
samesite = samesite.title()
|
omlish/inject/impl/scopes.py
CHANGED
omlish/inject/keys.py
CHANGED
omlish/inject/multis.py
CHANGED
@@ -32,14 +32,14 @@ def _check_set_multi_key(mk: Key) -> bool:
|
|
32
32
|
@dc.dataclass(frozen=True)
|
33
33
|
@dc.extra_params(cache_hash=True)
|
34
34
|
class SetBinding(Element, lang.Final):
|
35
|
-
multi_key: Key = dc.xfield(
|
35
|
+
multi_key: Key = dc.xfield(validate=_check_set_multi_key)
|
36
36
|
dst: Key = dc.xfield(coerce=check.of_isinstance(Key))
|
37
37
|
|
38
38
|
|
39
39
|
@dc.dataclass(frozen=True)
|
40
40
|
@dc.extra_params(cache_hash=True)
|
41
41
|
class SetProvider(Provider):
|
42
|
-
multi_key: Key = dc.xfield(
|
42
|
+
multi_key: Key = dc.xfield(validate=_check_set_multi_key)
|
43
43
|
|
44
44
|
|
45
45
|
##
|
@@ -52,7 +52,7 @@ def _check_map_multi_key(mk: Key) -> bool:
|
|
52
52
|
@dc.dataclass(frozen=True)
|
53
53
|
@dc.extra_params(cache_hash=True)
|
54
54
|
class MapBinding(Element, lang.Final):
|
55
|
-
multi_key: Key = dc.xfield(
|
55
|
+
multi_key: Key = dc.xfield(validate=_check_map_multi_key)
|
56
56
|
map_key: ta.Any = dc.xfield()
|
57
57
|
dst: Key = dc.xfield(coerce=check.of_isinstance(Key))
|
58
58
|
|
@@ -60,7 +60,7 @@ class MapBinding(Element, lang.Final):
|
|
60
60
|
@dc.dataclass(frozen=True)
|
61
61
|
@dc.extra_params(cache_hash=True)
|
62
62
|
class MapProvider(Provider):
|
63
|
-
multi_key: Key = dc.xfield(
|
63
|
+
multi_key: Key = dc.xfield(validate=_check_map_multi_key)
|
64
64
|
|
65
65
|
|
66
66
|
##
|
omlish/inject/providers.py
CHANGED
omlish/lang/__init__.py
CHANGED
@@ -5,6 +5,7 @@ from .cached import ( # noqa
|
|
5
5
|
|
6
6
|
from .classes import ( # noqa
|
7
7
|
Abstract,
|
8
|
+
AnySensitive,
|
8
9
|
Callable,
|
9
10
|
Descriptor,
|
10
11
|
Final,
|
@@ -17,8 +18,10 @@ from .classes import ( # noqa
|
|
17
18
|
NotPicklable,
|
18
19
|
PackageSealed,
|
19
20
|
Picklable,
|
21
|
+
SENSITIVE_ATTR,
|
20
22
|
Sealed,
|
21
23
|
SealedError,
|
24
|
+
Sensitive,
|
22
25
|
SimpleMetaDict,
|
23
26
|
Singleton,
|
24
27
|
Virtual,
|
@@ -68,6 +71,11 @@ from .contextmanagers import ( # noqa
|
|
68
71
|
maybe_managing,
|
69
72
|
)
|
70
73
|
|
74
|
+
from .datetimes import ( # noqa
|
75
|
+
utcnow,
|
76
|
+
utcfromtimestamp,
|
77
|
+
)
|
78
|
+
|
71
79
|
from .descriptors import ( # noqa
|
72
80
|
AccessForbiddenError,
|
73
81
|
access_forbidden,
|
@@ -202,6 +210,7 @@ from .typing import ( # noqa
|
|
202
210
|
Func1,
|
203
211
|
Func2,
|
204
212
|
Func3,
|
213
|
+
SequenceNotStr,
|
205
214
|
protocol_check,
|
206
215
|
typed_lambda,
|
207
216
|
typed_partial,
|
omlish/lang/classes/__init__.py
CHANGED
@@ -7,14 +7,17 @@ from .abstract import ( # noqa
|
|
7
7
|
)
|
8
8
|
|
9
9
|
from .restrict import ( # noqa
|
10
|
+
AnySensitive,
|
10
11
|
Final,
|
11
12
|
FinalError,
|
12
13
|
NoBool,
|
13
14
|
NotInstantiable,
|
14
15
|
NotPicklable,
|
15
16
|
PackageSealed,
|
17
|
+
SENSITIVE_ATTR,
|
16
18
|
Sealed,
|
17
19
|
SealedError,
|
20
|
+
Sensitive,
|
18
21
|
no_bool,
|
19
22
|
)
|
20
23
|
|
omlish/lang/classes/restrict.py
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
import abc
|
1
2
|
import functools
|
2
3
|
import typing as ta
|
3
4
|
|
@@ -9,7 +10,6 @@ from .abstract import is_abstract
|
|
9
10
|
|
10
11
|
|
11
12
|
class FinalError(TypeError):
|
12
|
-
|
13
13
|
def __init__(self, _type: type) -> None:
|
14
14
|
super().__init__()
|
15
15
|
|
@@ -49,7 +49,6 @@ class Final(Abstract):
|
|
49
49
|
|
50
50
|
|
51
51
|
class SealedError(TypeError):
|
52
|
-
|
53
52
|
def __init__(self, _type) -> None:
|
54
53
|
super().__init__()
|
55
54
|
|
@@ -118,13 +117,11 @@ class NotPicklable:
|
|
118
117
|
|
119
118
|
|
120
119
|
class NoBool:
|
121
|
-
|
122
120
|
def __bool__(self) -> bool:
|
123
121
|
raise TypeError
|
124
122
|
|
125
123
|
|
126
124
|
class _NoBoolDescriptor:
|
127
|
-
|
128
125
|
def __init__(self, fn, instance=None, owner=None) -> None:
|
129
126
|
super().__init__()
|
130
127
|
self._fn = fn
|
@@ -146,3 +143,27 @@ class _NoBoolDescriptor:
|
|
146
143
|
|
147
144
|
def no_bool(fn): # noqa
|
148
145
|
return _NoBoolDescriptor(fn)
|
146
|
+
|
147
|
+
|
148
|
+
##
|
149
|
+
|
150
|
+
|
151
|
+
SENSITIVE_ATTR = '__sensitive__'
|
152
|
+
|
153
|
+
|
154
|
+
class Sensitive:
|
155
|
+
__sensitive__ = True
|
156
|
+
|
157
|
+
|
158
|
+
class _AnySensitiveMeta(abc.ABCMeta):
|
159
|
+
@classmethod
|
160
|
+
def __instancecheck__(cls, instance: object) -> bool:
|
161
|
+
return hasattr(instance, SENSITIVE_ATTR) or super().__instancecheck__(AnySensitive, instance)
|
162
|
+
|
163
|
+
@classmethod
|
164
|
+
def __subclasscheck__(cls, subclass: type) -> bool:
|
165
|
+
return hasattr(subclass, SENSITIVE_ATTR) or super().__subclasscheck__(AnySensitive, subclass)
|
166
|
+
|
167
|
+
|
168
|
+
class AnySensitive(NotInstantiable, Final, metaclass=_AnySensitiveMeta):
|
169
|
+
pass
|
omlish/lang/classes/simple.py
CHANGED
@@ -42,7 +42,6 @@ _MARKER_NAMESPACE_KEYS: set[str] | None = None
|
|
42
42
|
|
43
43
|
|
44
44
|
class _MarkerMeta(abc.ABCMeta):
|
45
|
-
|
46
45
|
def __new__(mcls, name, bases, namespace):
|
47
46
|
global _MARKER_NAMESPACE_KEYS
|
48
47
|
|
@@ -74,7 +73,6 @@ class Marker(NotInstantiable, metaclass=_MarkerMeta):
|
|
74
73
|
|
75
74
|
|
76
75
|
class SimpleMetaDict(dict):
|
77
|
-
|
78
76
|
def update(self, m: ta.Mapping[K, V], **kwargs: V) -> None: # type: ignore
|
79
77
|
for k, v in m.items():
|
80
78
|
self[k] = v
|
@@ -109,7 +107,6 @@ def _set_singleton_instance(inst):
|
|
109
107
|
|
110
108
|
|
111
109
|
class Singleton:
|
112
|
-
|
113
110
|
def __new__(cls):
|
114
111
|
return cls.__dict__[_SINGLETON_INSTANCE_ATTR]
|
115
112
|
|
@@ -119,7 +116,6 @@ class Singleton:
|
|
119
116
|
|
120
117
|
|
121
118
|
class LazySingleton:
|
122
|
-
|
123
119
|
def __new__(cls):
|
124
120
|
try:
|
125
121
|
return cls.__dict__[_SINGLETON_INSTANCE_ATTR]
|
omlish/lang/classes/virtual.py
CHANGED
@@ -40,7 +40,12 @@ class _VirtualMeta(abc.ABCMeta):
|
|
40
40
|
if absv is not v:
|
41
41
|
namespace[k] = absv
|
42
42
|
|
43
|
-
reqs = {
|
43
|
+
reqs = {
|
44
|
+
k: v
|
45
|
+
for k, v in namespace.items()
|
46
|
+
if getattr(v, '__isabstractmethod__', False)
|
47
|
+
}
|
48
|
+
|
44
49
|
user_subclasshook = namespace.pop('__subclasshook__', None)
|
45
50
|
|
46
51
|
def get_missing_reqs(cls):
|
@@ -94,13 +99,11 @@ def virtual_check(virtual: type) -> ta.Callable[[Ty], Ty]:
|
|
94
99
|
|
95
100
|
|
96
101
|
class Descriptor(Virtual):
|
97
|
-
|
98
102
|
def __get__(self, instance, owner=None):
|
99
103
|
raise NotImplementedError
|
100
104
|
|
101
105
|
|
102
106
|
class Picklable(Virtual):
|
103
|
-
|
104
107
|
def __getstate__(self):
|
105
108
|
raise NotImplementedError
|
106
109
|
|
@@ -112,7 +115,6 @@ class Picklable(Virtual):
|
|
112
115
|
|
113
116
|
|
114
117
|
class Callable(NotInstantiable, Final, ta.Generic[T]):
|
115
|
-
|
116
118
|
def __call__(self, *args: ta.Any, **kwargs: ta.Any) -> T:
|
117
119
|
raise TypeError
|
118
120
|
|
omlish/lang/datetimes.py
ADDED
omlish/lang/typing.py
CHANGED
@@ -14,6 +14,9 @@ import typing as ta
|
|
14
14
|
Ty = ta.TypeVar('Ty', bound=type)
|
15
15
|
|
16
16
|
T = ta.TypeVar('T')
|
17
|
+
T_co = ta.TypeVar('T_co', covariant=True)
|
18
|
+
T_contra = ta.TypeVar('T_contra', contravariant=True)
|
19
|
+
|
17
20
|
A0 = ta.TypeVar('A0')
|
18
21
|
A1 = ta.TypeVar('A1')
|
19
22
|
A2 = ta.TypeVar('A2')
|
@@ -133,3 +136,32 @@ class Func3(ta.Generic[A0, A1, A2, T]):
|
|
133
136
|
|
134
137
|
def __call__(self, a0: A0, a1: A1, a2: A2) -> T:
|
135
138
|
return self.fn(a0, a1, a2)
|
139
|
+
|
140
|
+
|
141
|
+
##
|
142
|
+
|
143
|
+
|
144
|
+
class SequenceNotStr(ta.Protocol[T_co]):
|
145
|
+
"""
|
146
|
+
https://github.com/python/mypy/issues/11001
|
147
|
+
https://github.com/python/typing/issues/256#issuecomment-1442633430
|
148
|
+
https://github.com/hauntsaninja/useful_types/blob/735ef9dd0b55b35b118ef630d5a0f3618ecedbff/useful_types/__init__.py#L285
|
149
|
+
"""
|
150
|
+
|
151
|
+
@ta.overload
|
152
|
+
def __getitem__(self, index: ta.SupportsIndex, /) -> T_co: ...
|
153
|
+
|
154
|
+
@ta.overload
|
155
|
+
def __getitem__(self, index: slice, /) -> ta.Sequence[T_co]: ... # noqa
|
156
|
+
|
157
|
+
def __contains__(self, value: object, /) -> bool: ...
|
158
|
+
|
159
|
+
def __len__(self) -> int: ...
|
160
|
+
|
161
|
+
def __iter__(self) -> ta.Iterator[T_co]: ...
|
162
|
+
|
163
|
+
def index(self, value: ta.Any, start: int = 0, stop: int = ..., /) -> int: ...
|
164
|
+
|
165
|
+
def count(self, value: ta.Any, /) -> int: ...
|
166
|
+
|
167
|
+
def __reversed__(self) -> ta.Iterator[T_co]: ...
|
omlish/lite/logs.py
CHANGED
@@ -208,43 +208,48 @@ def configure_standard_logging(
|
|
208
208
|
*,
|
209
209
|
json: bool = False,
|
210
210
|
target: ta.Optional[logging.Logger] = None,
|
211
|
-
|
211
|
+
force: bool = False,
|
212
212
|
) -> ta.Optional[StandardLogHandler]:
|
213
|
-
|
214
|
-
|
213
|
+
logging._acquireLock() # type: ignore # noqa
|
214
|
+
try:
|
215
|
+
if target is None:
|
216
|
+
target = logging.root
|
215
217
|
|
216
|
-
|
218
|
+
#
|
217
219
|
|
218
|
-
|
219
|
-
|
220
|
-
|
220
|
+
if not force:
|
221
|
+
if any(isinstance(h, StandardLogHandler) for h in list(target.handlers)):
|
222
|
+
return None
|
221
223
|
|
222
|
-
|
224
|
+
#
|
223
225
|
|
224
|
-
|
226
|
+
handler = logging.StreamHandler()
|
225
227
|
|
226
|
-
|
228
|
+
#
|
227
229
|
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
230
|
+
formatter: logging.Formatter
|
231
|
+
if json:
|
232
|
+
formatter = JsonLogFormatter()
|
233
|
+
else:
|
234
|
+
formatter = StandardLogFormatter(StandardLogFormatter.build_log_format(STANDARD_LOG_FORMAT_PARTS))
|
235
|
+
handler.setFormatter(formatter)
|
236
|
+
|
237
|
+
#
|
234
238
|
|
235
|
-
|
239
|
+
handler.addFilter(TidLogFilter())
|
236
240
|
|
237
|
-
|
241
|
+
#
|
238
242
|
|
239
|
-
|
243
|
+
target.addHandler(handler)
|
240
244
|
|
241
|
-
|
245
|
+
#
|
242
246
|
|
243
|
-
|
247
|
+
if level is not None:
|
248
|
+
target.setLevel(level)
|
244
249
|
|
245
|
-
|
246
|
-
target.setLevel(level)
|
250
|
+
#
|
247
251
|
|
248
|
-
|
252
|
+
return StandardLogHandler(handler)
|
249
253
|
|
250
|
-
|
254
|
+
finally:
|
255
|
+
logging._releaseLock() # type: ignore # noqa
|
omlish/lite/secrets.py
CHANGED
@@ -3,9 +3,11 @@ import typing as ta
|
|
3
3
|
|
4
4
|
|
5
5
|
class Secret:
|
6
|
+
__sensitive__ = True
|
7
|
+
|
6
8
|
_VALUE_ATTR = '__secret_value__'
|
7
9
|
|
8
|
-
def __init__(self, *, key: ta.Optional[str], value: str) -> None:
|
10
|
+
def __init__(self, *, key: ta.Optional[str] = None, value: str) -> None:
|
9
11
|
super().__init__()
|
10
12
|
self._key = key
|
11
13
|
setattr(self, self._VALUE_ATTR, lambda: value)
|
omlish/logs/configs.py
CHANGED
@@ -36,13 +36,13 @@ def configure_standard_logging(
|
|
36
36
|
*,
|
37
37
|
json: bool = False,
|
38
38
|
target: logging.Logger | None = None,
|
39
|
-
|
39
|
+
force: bool = False,
|
40
40
|
) -> StandardLogHandler | None:
|
41
41
|
handler = configure_lite_standard_logging(
|
42
42
|
level,
|
43
43
|
json=json,
|
44
44
|
target=target,
|
45
|
-
|
45
|
+
force=force,
|
46
46
|
)
|
47
47
|
|
48
48
|
if handler is None:
|
omlish/marshal/dataclasses.py
CHANGED
@@ -24,7 +24,7 @@ from .objects import ObjectUnmarshaler
|
|
24
24
|
|
25
25
|
|
26
26
|
def get_dataclass_metadata(ty: type) -> ObjectMetadata:
|
27
|
-
return check.
|
27
|
+
return check.opt_single(
|
28
28
|
e
|
29
29
|
for e in dc.get_merged_metadata(ty).get(dc.UserMetadata, [])
|
30
30
|
if isinstance(e, ObjectMetadata)
|
omlish/secrets/secrets.py
CHANGED