omlish 0.0.0.dev217__py3-none-any.whl → 0.0.0.dev218__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 +2 -2
- omlish/docker/oci/__init__.py +0 -0
- omlish/docker/oci/data.py +71 -0
- omlish/docker/oci/media.py +124 -0
- omlish/formats/json5/Json5.g4 +0 -3
- omlish/http/coro/server.py +45 -25
- omlish/http/handlers.py +11 -1
- omlish/iterators/tools.py +1 -0
- omlish/logs/all.py +13 -0
- omlish/logs/callers.py +45 -0
- omlish/logs/protocol.py +176 -0
- omlish/sockets/addresses.py +13 -4
- omlish/sockets/bind.py +332 -0
- omlish/sockets/handlers.py +2 -20
- omlish/sockets/io.py +69 -0
- omlish/sockets/server/__init__.py +0 -0
- omlish/sockets/server/handlers.py +99 -0
- omlish/sockets/server/server.py +144 -0
- omlish/sockets/server/threading.py +123 -0
- {omlish-0.0.0.dev217.dist-info → omlish-0.0.0.dev218.dist-info}/METADATA +1 -1
- {omlish-0.0.0.dev217.dist-info → omlish-0.0.0.dev218.dist-info}/RECORD +25 -16
- omlish/docker/oci.py +0 -81
- omlish/sockets/server.py +0 -66
- {omlish-0.0.0.dev217.dist-info → omlish-0.0.0.dev218.dist-info}/LICENSE +0 -0
- {omlish-0.0.0.dev217.dist-info → omlish-0.0.0.dev218.dist-info}/WHEEL +0 -0
- {omlish-0.0.0.dev217.dist-info → omlish-0.0.0.dev218.dist-info}/entry_points.txt +0 -0
- {omlish-0.0.0.dev217.dist-info → omlish-0.0.0.dev218.dist-info}/top_level.txt +0 -0
omlish/__about__.py
CHANGED
File without changes
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
# @omlish-lite
|
3
|
+
import abc
|
4
|
+
import dataclasses as dc
|
5
|
+
import typing as ta
|
6
|
+
|
7
|
+
from ...lite.marshal import OBJ_MARSHALER_FIELD_KEY
|
8
|
+
from ...lite.marshal import OBJ_MARSHALER_OMIT_IF_NONE
|
9
|
+
|
10
|
+
|
11
|
+
##
|
12
|
+
|
13
|
+
|
14
|
+
@dc.dataclass(frozen=True)
|
15
|
+
class OciDataclass(abc.ABC): # noqa
|
16
|
+
pass
|
17
|
+
|
18
|
+
|
19
|
+
##
|
20
|
+
|
21
|
+
|
22
|
+
@dc.dataclass(frozen=True)
|
23
|
+
class OciImageConfig(OciDataclass):
|
24
|
+
"""https://github.com/opencontainers/image-spec/blob/92353b0bee778725c617e7d57317b568a7796bd0/config.md"""
|
25
|
+
|
26
|
+
architecture: str
|
27
|
+
os: str
|
28
|
+
|
29
|
+
@dc.dataclass(frozen=True)
|
30
|
+
class RootFs:
|
31
|
+
type: str
|
32
|
+
diff_ids: ta.Sequence[str]
|
33
|
+
|
34
|
+
rootfs: RootFs
|
35
|
+
|
36
|
+
#
|
37
|
+
|
38
|
+
created: ta.Optional[str] = dc.field(default=None, metadata={OBJ_MARSHALER_OMIT_IF_NONE: True})
|
39
|
+
author: ta.Optional[str] = dc.field(default=None, metadata={OBJ_MARSHALER_OMIT_IF_NONE: True})
|
40
|
+
os_version: ta.Optional[str] = dc.field(default=None, metadata={OBJ_MARSHALER_FIELD_KEY: 'os.version', OBJ_MARSHALER_OMIT_IF_NONE: True}) # noqa
|
41
|
+
os_features: ta.Optional[ta.Sequence[str]] = dc.field(default=None, metadata={OBJ_MARSHALER_FIELD_KEY: 'os.features', OBJ_MARSHALER_OMIT_IF_NONE: True}) # noqa
|
42
|
+
variant: ta.Optional[str] = dc.field(default=None, metadata={OBJ_MARSHALER_OMIT_IF_NONE: True})
|
43
|
+
|
44
|
+
"""
|
45
|
+
config object, OPTIONAL
|
46
|
+
User string, OPTIONAL
|
47
|
+
ExposedPorts object, OPTIONAL
|
48
|
+
Env array of strings, OPTIONAL
|
49
|
+
Entrypoint array of strings, OPTIONAL
|
50
|
+
Cmd array of strings, OPTIONAL
|
51
|
+
Volumes object, OPTIONAL
|
52
|
+
WorkingDir string, OPTIONAL
|
53
|
+
Labels object, OPTIONAL
|
54
|
+
StopSignal string, OPTIONAL
|
55
|
+
ArgsEscaped boolean, OPTIONAL
|
56
|
+
Memory integer, OPTIONAL
|
57
|
+
MemorySwap integer, OPTIONAL
|
58
|
+
CpuShares integer, OPTIONAL
|
59
|
+
Healthcheck object, OPTIONAL
|
60
|
+
"""
|
61
|
+
config: ta.Optional[ta.Mapping[str, ta.Any]] = dc.field(default=None, metadata={OBJ_MARSHALER_OMIT_IF_NONE: True})
|
62
|
+
|
63
|
+
@dc.dataclass(frozen=True)
|
64
|
+
class History:
|
65
|
+
created: ta.Optional[str] = dc.field(default=None, metadata={OBJ_MARSHALER_OMIT_IF_NONE: True})
|
66
|
+
author: ta.Optional[str] = dc.field(default=None, metadata={OBJ_MARSHALER_OMIT_IF_NONE: True})
|
67
|
+
created_by: ta.Optional[str] = dc.field(default=None, metadata={OBJ_MARSHALER_OMIT_IF_NONE: True})
|
68
|
+
comment: ta.Optional[str] = dc.field(default=None, metadata={OBJ_MARSHALER_OMIT_IF_NONE: True})
|
69
|
+
empty_layer: ta.Optional[bool] = dc.field(default=None, metadata={OBJ_MARSHALER_OMIT_IF_NONE: True})
|
70
|
+
|
71
|
+
history: ta.Optional[ta.Sequence[History]] = dc.field(default=None, metadata={OBJ_MARSHALER_OMIT_IF_NONE: True})
|
@@ -0,0 +1,124 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
# @omlish-lite
|
3
|
+
import abc
|
4
|
+
import dataclasses as dc
|
5
|
+
import typing as ta
|
6
|
+
|
7
|
+
from ...lite.check import check
|
8
|
+
from ...lite.marshal import OBJ_MARSHALER_FIELD_KEY
|
9
|
+
from ...lite.marshal import OBJ_MARSHALER_OMIT_IF_NONE
|
10
|
+
from ...lite.marshal import unmarshal_obj
|
11
|
+
from .data import OciDataclass
|
12
|
+
from .data import OciImageConfig
|
13
|
+
|
14
|
+
|
15
|
+
##
|
16
|
+
|
17
|
+
|
18
|
+
@dc.dataclass(frozen=True)
|
19
|
+
class OciMediaDataclass(OciDataclass, abc.ABC): # noqa
|
20
|
+
SCHEMA_VERSION: ta.ClassVar[int]
|
21
|
+
MEDIA_TYPE: ta.ClassVar[str]
|
22
|
+
|
23
|
+
|
24
|
+
_REGISTERED_OCI_MEDIA_DATACLASSES: ta.Dict[str, ta.Type[OciMediaDataclass]] = {}
|
25
|
+
|
26
|
+
|
27
|
+
def _register_oci_media_dataclass(cls):
|
28
|
+
check.issubclass(cls, OciMediaDataclass)
|
29
|
+
check.arg(dc.is_dataclass(cls))
|
30
|
+
mt = check.non_empty_str(cls.__dict__['MEDIA_TYPE'])
|
31
|
+
check.not_in(mt, _REGISTERED_OCI_MEDIA_DATACLASSES)
|
32
|
+
_REGISTERED_OCI_MEDIA_DATACLASSES[mt] = cls
|
33
|
+
return cls
|
34
|
+
|
35
|
+
|
36
|
+
def get_registered_oci_media_dataclass(media_type: str) -> ta.Optional[ta.Type[OciMediaDataclass]]:
|
37
|
+
return _REGISTERED_OCI_MEDIA_DATACLASSES.get(media_type)
|
38
|
+
|
39
|
+
|
40
|
+
def unmarshal_oci_media_dataclass(
|
41
|
+
dct: ta.Mapping[str, ta.Any],
|
42
|
+
*,
|
43
|
+
media_type: ta.Optional[str] = None,
|
44
|
+
) -> ta.Any:
|
45
|
+
if media_type is None:
|
46
|
+
media_type = check.non_empty_str(dct['mediaType'])
|
47
|
+
cls = _REGISTERED_OCI_MEDIA_DATACLASSES[media_type]
|
48
|
+
return unmarshal_obj(dct, cls)
|
49
|
+
|
50
|
+
|
51
|
+
#
|
52
|
+
|
53
|
+
|
54
|
+
@dc.dataclass(frozen=True)
|
55
|
+
class OciMediaDescriptor(OciDataclass):
|
56
|
+
"""https://github.com/opencontainers/image-spec/blob/92353b0bee778725c617e7d57317b568a7796bd0/descriptor.md#properties""" # noqa
|
57
|
+
|
58
|
+
media_type: str = dc.field(metadata={OBJ_MARSHALER_FIELD_KEY: 'mediaType'})
|
59
|
+
digest: str
|
60
|
+
size: int
|
61
|
+
|
62
|
+
#
|
63
|
+
|
64
|
+
urls: ta.Optional[ta.Sequence[str]] = dc.field(default=None, metadata={OBJ_MARSHALER_OMIT_IF_NONE: True})
|
65
|
+
annotations: ta.Optional[ta.Mapping[str, str]] = dc.field(default=None, metadata={OBJ_MARSHALER_OMIT_IF_NONE: True}) # noqa
|
66
|
+
data: ta.Optional[str] = dc.field(default=None, metadata={OBJ_MARSHALER_OMIT_IF_NONE: True})
|
67
|
+
artifact_type: ta.Optional[str] = dc.field(default=None, metadata={OBJ_MARSHALER_FIELD_KEY: 'artifactType', OBJ_MARSHALER_OMIT_IF_NONE: True}) # noqa
|
68
|
+
|
69
|
+
#
|
70
|
+
|
71
|
+
platform: ta.Optional[ta.Mapping[str, ta.Any]] = dc.field(default=None, metadata={OBJ_MARSHALER_OMIT_IF_NONE: True}) # noqa
|
72
|
+
|
73
|
+
|
74
|
+
@_register_oci_media_dataclass
|
75
|
+
@dc.dataclass(frozen=True)
|
76
|
+
class OciMediaImageIndex(OciMediaDataclass):
|
77
|
+
"""https://github.com/opencontainers/image-spec/blob/92353b0bee778725c617e7d57317b568a7796bd0/image-index.md"""
|
78
|
+
|
79
|
+
manifests: ta.Sequence[OciMediaDescriptor] # -> OciMediaImageIndex | OciMediaImageManifest
|
80
|
+
|
81
|
+
#
|
82
|
+
|
83
|
+
annotations: ta.Optional[ta.Mapping[str, str]] = dc.field(default=None, metadata={OBJ_MARSHALER_OMIT_IF_NONE: True}) # noqa
|
84
|
+
|
85
|
+
#
|
86
|
+
|
87
|
+
SCHEMA_VERSION: ta.ClassVar[int] = 2
|
88
|
+
schema_version: int = dc.field(default=SCHEMA_VERSION, metadata={OBJ_MARSHALER_FIELD_KEY: 'schemaVersion'})
|
89
|
+
|
90
|
+
MEDIA_TYPE: ta.ClassVar[str] = 'application/vnd.oci.image.index.v1+json'
|
91
|
+
media_type: str = dc.field(default=MEDIA_TYPE, metadata={OBJ_MARSHALER_FIELD_KEY: 'mediaType'})
|
92
|
+
|
93
|
+
|
94
|
+
@_register_oci_media_dataclass
|
95
|
+
@dc.dataclass(frozen=True)
|
96
|
+
class OciMediaImageManifest(OciMediaDataclass):
|
97
|
+
"""https://github.com/opencontainers/image-spec/blob/92353b0bee778725c617e7d57317b568a7796bd0/manifest.md"""
|
98
|
+
|
99
|
+
config: OciMediaDescriptor # -> OciMediaImageConfig
|
100
|
+
|
101
|
+
# MEDIA_TYPES: ta.ClassVar[ta.Mapping[str, str]] = {
|
102
|
+
# 'TAR': 'application/vnd.oci.image.layer.v1.tar',
|
103
|
+
# 'TAR_GZIP': 'application/vnd.oci.image.layer.v1.tar+gzip',
|
104
|
+
# 'TAR_ZSTD': 'application/vnd.oci.image.layer.v1.tar+zstd',
|
105
|
+
# }
|
106
|
+
layers: ta.Sequence[OciMediaDescriptor]
|
107
|
+
|
108
|
+
#
|
109
|
+
|
110
|
+
SCHEMA_VERSION: ta.ClassVar[int] = 2
|
111
|
+
schema_version: int = dc.field(default=SCHEMA_VERSION, metadata={OBJ_MARSHALER_FIELD_KEY: 'schemaVersion'})
|
112
|
+
|
113
|
+
MEDIA_TYPE: ta.ClassVar[str] = 'application/vnd.oci.image.manifest.v1+json'
|
114
|
+
media_type: str = dc.field(default=MEDIA_TYPE, metadata={OBJ_MARSHALER_FIELD_KEY: 'mediaType'})
|
115
|
+
|
116
|
+
|
117
|
+
@_register_oci_media_dataclass
|
118
|
+
@dc.dataclass(frozen=True)
|
119
|
+
class OciMediaImageConfig(OciImageConfig, OciMediaDataclass):
|
120
|
+
SCHEMA_VERSION: ta.ClassVar[int] = 2
|
121
|
+
schema_version: int = dc.field(default=SCHEMA_VERSION, metadata={OBJ_MARSHALER_FIELD_KEY: 'schemaVersion'})
|
122
|
+
|
123
|
+
MEDIA_TYPE: ta.ClassVar[str] = 'application/vnd.oci.image.config.v1+json'
|
124
|
+
media_type: str = dc.field(default=MEDIA_TYPE, metadata={OBJ_MARSHALER_FIELD_KEY: 'mediaType'})
|
omlish/formats/json5/Json5.g4
CHANGED
@@ -6,9 +6,6 @@
|
|
6
6
|
// See https://json5.org/
|
7
7
|
// Derived from ../json/JSON.g4 which original derived from http://json.org
|
8
8
|
|
9
|
-
// $antlr-format alignTrailingComments true, columnLimit 150, minEmptyLines 1, maxEmptyLinesToKeep 1, reflowComments false, useTab false
|
10
|
-
// $antlr-format allowShortRulesOnASingleLine false, allowShortBlocksOnASingleLine true, alignSemicolons hanging, alignColons hanging
|
11
|
-
|
12
9
|
grammar Json5;
|
13
10
|
|
14
11
|
json5
|
omlish/http/coro/server.py
CHANGED
@@ -65,9 +65,12 @@ import typing as ta
|
|
65
65
|
|
66
66
|
from ...lite.check import check
|
67
67
|
from ...sockets.addresses import SocketAddress
|
68
|
-
from ...sockets.handlers import SocketHandler
|
68
|
+
from ...sockets.handlers import SocketHandler # noqa
|
69
|
+
from ...sockets.io import SocketIoPair
|
69
70
|
from ..handlers import HttpHandler
|
70
71
|
from ..handlers import HttpHandlerRequest
|
72
|
+
from ..handlers import HttpHandlerResponseData
|
73
|
+
from ..handlers import HttpHandlerResponseStreamedData
|
71
74
|
from ..handlers import UnsupportedMethodHttpHandlerError
|
72
75
|
from ..parsing import EmptyParsedHttpResult
|
73
76
|
from ..parsing import HttpRequestParser
|
@@ -193,7 +196,7 @@ class CoroHttpServer:
|
|
193
196
|
|
194
197
|
message: ta.Optional[str] = None
|
195
198
|
headers: ta.Optional[ta.Sequence['CoroHttpServer._Header']] = None
|
196
|
-
data: ta.Optional[
|
199
|
+
data: ta.Optional[HttpHandlerResponseData] = None
|
197
200
|
close_connection: ta.Optional[bool] = False
|
198
201
|
|
199
202
|
def get_header(self, key: str) -> ta.Optional['CoroHttpServer._Header']:
|
@@ -204,7 +207,7 @@ class CoroHttpServer:
|
|
204
207
|
|
205
208
|
#
|
206
209
|
|
207
|
-
def
|
210
|
+
def _build_response_head_bytes(self, a: _Response) -> bytes:
|
208
211
|
out = io.BytesIO()
|
209
212
|
|
210
213
|
if a.version >= HttpProtocolVersions.HTTP_1_0:
|
@@ -219,11 +222,22 @@ class CoroHttpServer:
|
|
219
222
|
|
220
223
|
out.write(b'\r\n')
|
221
224
|
|
222
|
-
if a.data is not None:
|
223
|
-
out.write(a.data)
|
224
|
-
|
225
225
|
return out.getvalue()
|
226
226
|
|
227
|
+
def _yield_response_data(self, a: _Response) -> ta.Iterator[bytes]:
|
228
|
+
if a.data is None:
|
229
|
+
return
|
230
|
+
|
231
|
+
elif isinstance(a.data, bytes):
|
232
|
+
yield a.data
|
233
|
+
return
|
234
|
+
|
235
|
+
elif isinstance(a.data, HttpHandlerResponseStreamedData):
|
236
|
+
yield from a.data.iter
|
237
|
+
|
238
|
+
else:
|
239
|
+
raise TypeError(a.data)
|
240
|
+
|
227
241
|
#
|
228
242
|
|
229
243
|
DEFAULT_CONTENT_TYPE = 'text/plain'
|
@@ -234,8 +248,17 @@ class CoroHttpServer:
|
|
234
248
|
|
235
249
|
if resp.get_header('Content-Type') is None:
|
236
250
|
nh.append(self._Header('Content-Type', self._default_content_type))
|
251
|
+
|
237
252
|
if resp.data is not None and resp.get_header('Content-Length') is None:
|
238
|
-
|
253
|
+
cl: ta.Optional[int]
|
254
|
+
if isinstance(resp.data, bytes):
|
255
|
+
cl = len(resp.data)
|
256
|
+
elif isinstance(resp.data, HttpHandlerResponseStreamedData):
|
257
|
+
cl = resp.data.length
|
258
|
+
else:
|
259
|
+
raise TypeError(resp.data)
|
260
|
+
if cl is not None:
|
261
|
+
nh.append(self._Header('Content-Length', str(cl)))
|
239
262
|
|
240
263
|
if nh:
|
241
264
|
kw.update(headers=[*(resp.headers or []), *nh])
|
@@ -409,9 +432,13 @@ class CoroHttpServer:
|
|
409
432
|
|
410
433
|
elif isinstance(o, self._Response):
|
411
434
|
i = None
|
435
|
+
|
412
436
|
r = self._preprocess_response(o)
|
413
|
-
|
414
|
-
check.none((yield self.WriteIo(
|
437
|
+
hb = self._build_response_head_bytes(r)
|
438
|
+
check.none((yield self.WriteIo(hb)))
|
439
|
+
|
440
|
+
for b in self._yield_response_data(r):
|
441
|
+
yield self.WriteIo(b)
|
415
442
|
|
416
443
|
else:
|
417
444
|
raise TypeError(o)
|
@@ -530,27 +557,20 @@ class CoroHttpServer:
|
|
530
557
|
##
|
531
558
|
|
532
559
|
|
533
|
-
class CoroHttpServerSocketHandler
|
560
|
+
class CoroHttpServerSocketHandler: # SocketHandler
|
534
561
|
def __init__(
|
535
562
|
self,
|
536
|
-
client_address: SocketAddress,
|
537
|
-
rfile: ta.BinaryIO,
|
538
|
-
wfile: ta.BinaryIO,
|
539
|
-
*,
|
540
563
|
server_factory: CoroHttpServerFactory,
|
564
|
+
*,
|
541
565
|
log_handler: ta.Optional[ta.Callable[[CoroHttpServer, CoroHttpServer.AnyLogIo], None]] = None,
|
542
566
|
) -> None:
|
543
|
-
super().__init__(
|
544
|
-
client_address,
|
545
|
-
rfile,
|
546
|
-
wfile,
|
547
|
-
)
|
567
|
+
super().__init__()
|
548
568
|
|
549
569
|
self._server_factory = server_factory
|
550
570
|
self._log_handler = log_handler
|
551
571
|
|
552
|
-
def
|
553
|
-
server = self._server_factory(
|
572
|
+
def __call__(self, client_address: SocketAddress, fp: SocketIoPair) -> None:
|
573
|
+
server = self._server_factory(client_address)
|
554
574
|
|
555
575
|
gen = server.coro_handle()
|
556
576
|
|
@@ -562,15 +582,15 @@ class CoroHttpServerSocketHandler(SocketHandler):
|
|
562
582
|
self._log_handler(server, o)
|
563
583
|
|
564
584
|
elif isinstance(o, CoroHttpServer.ReadIo):
|
565
|
-
i =
|
585
|
+
i = fp.r.read(o.sz)
|
566
586
|
|
567
587
|
elif isinstance(o, CoroHttpServer.ReadLineIo):
|
568
|
-
i =
|
588
|
+
i = fp.r.readline(o.sz)
|
569
589
|
|
570
590
|
elif isinstance(o, CoroHttpServer.WriteIo):
|
571
591
|
i = None
|
572
|
-
|
573
|
-
|
592
|
+
fp.w.write(o.data)
|
593
|
+
fp.w.flush()
|
574
594
|
|
575
595
|
else:
|
576
596
|
raise TypeError(o)
|
omlish/http/handlers.py
CHANGED
@@ -9,6 +9,10 @@ from .parsing import HttpHeaders
|
|
9
9
|
|
10
10
|
|
11
11
|
HttpHandler = ta.Callable[['HttpHandlerRequest'], 'HttpHandlerResponse'] # ta.TypeAlias
|
12
|
+
HttpHandlerResponseData = ta.Union[bytes, 'HttpHandlerResponseStreamedData'] # ta.TypeAlias # noqa
|
13
|
+
|
14
|
+
|
15
|
+
##
|
12
16
|
|
13
17
|
|
14
18
|
@dc.dataclass(frozen=True)
|
@@ -25,10 +29,16 @@ class HttpHandlerResponse:
|
|
25
29
|
status: ta.Union[http.HTTPStatus, int]
|
26
30
|
|
27
31
|
headers: ta.Optional[ta.Mapping[str, str]] = None
|
28
|
-
data: ta.Optional[
|
32
|
+
data: ta.Optional[HttpHandlerResponseData] = None
|
29
33
|
close_connection: ta.Optional[bool] = None
|
30
34
|
|
31
35
|
|
36
|
+
@dc.dataclass(frozen=True)
|
37
|
+
class HttpHandlerResponseStreamedData:
|
38
|
+
iter: ta.Iterable[bytes]
|
39
|
+
length: ta.Optional[int] = None
|
40
|
+
|
41
|
+
|
32
42
|
class HttpHandlerError(Exception):
|
33
43
|
pass
|
34
44
|
|
omlish/iterators/tools.py
CHANGED
@@ -45,6 +45,7 @@ def take(n: int, iterable: ta.Iterable[T]) -> list[T]:
|
|
45
45
|
|
46
46
|
|
47
47
|
def chunk(n: int, iterable: ta.Iterable[T], strict: bool = False) -> ta.Iterator[list[T]]:
|
48
|
+
# TODO: remove with 3.13 - 3.12 doesn't support strict
|
48
49
|
iterator = iter(functools.partial(take, n, iter(iterable)), [])
|
49
50
|
if strict:
|
50
51
|
def ret():
|
omlish/logs/all.py
CHANGED
@@ -1,3 +1,7 @@
|
|
1
|
+
from .callers import ( # noqa
|
2
|
+
LoggingCaller,
|
3
|
+
)
|
4
|
+
|
1
5
|
from .color import ( # noqa
|
2
6
|
ColorLogFormatter,
|
3
7
|
)
|
@@ -18,6 +22,15 @@ from .noisy import ( # noqa
|
|
18
22
|
silence_noisy_loggers,
|
19
23
|
)
|
20
24
|
|
25
|
+
from .protocol import ( # noqa
|
26
|
+
LogLevel,
|
27
|
+
|
28
|
+
Logging,
|
29
|
+
NopLogging,
|
30
|
+
AbstractLogging,
|
31
|
+
StdlibLogging,
|
32
|
+
)
|
33
|
+
|
21
34
|
from .proxy import ( # noqa
|
22
35
|
ProxyLogFilterer,
|
23
36
|
ProxyLogHandler,
|
omlish/logs/callers.py
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
# @omlish-lite
|
3
|
+
import io
|
4
|
+
import sys
|
5
|
+
import traceback
|
6
|
+
import types
|
7
|
+
import typing as ta
|
8
|
+
|
9
|
+
|
10
|
+
class LoggingCaller(ta.NamedTuple):
|
11
|
+
filename: str
|
12
|
+
lineno: int
|
13
|
+
func: str
|
14
|
+
sinfo: ta.Optional[str]
|
15
|
+
|
16
|
+
@classmethod
|
17
|
+
def find_frame(cls, ofs: int = 0) -> types.FrameType:
|
18
|
+
f: ta.Any = sys._getframe(2 + ofs) # noqa
|
19
|
+
while hasattr(f, 'f_code'):
|
20
|
+
if f.f_code.co_filename != __file__:
|
21
|
+
return f
|
22
|
+
f = f.f_back
|
23
|
+
raise RuntimeError
|
24
|
+
|
25
|
+
@classmethod
|
26
|
+
def find(cls, stack_info: bool = False) -> 'LoggingCaller':
|
27
|
+
f = cls.find_frame(1)
|
28
|
+
# TODO: ('(unknown file)', 0, '(unknown function)', None) ?
|
29
|
+
|
30
|
+
sinfo = None
|
31
|
+
if stack_info:
|
32
|
+
sio = io.StringIO()
|
33
|
+
sio.write('Stack (most recent call last):\n')
|
34
|
+
traceback.print_stack(f, file=sio)
|
35
|
+
sinfo = sio.getvalue()
|
36
|
+
sio.close()
|
37
|
+
if sinfo[-1] == '\n':
|
38
|
+
sinfo = sinfo[:-1]
|
39
|
+
|
40
|
+
return cls(
|
41
|
+
f.f_code.co_filename,
|
42
|
+
f.f_lineno,
|
43
|
+
f.f_code.co_name,
|
44
|
+
sinfo,
|
45
|
+
)
|
omlish/logs/protocol.py
ADDED
@@ -0,0 +1,176 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
# @omlish-lite
|
3
|
+
import abc
|
4
|
+
import logging
|
5
|
+
import sys
|
6
|
+
import typing as ta
|
7
|
+
|
8
|
+
from .callers import LoggingCaller
|
9
|
+
|
10
|
+
|
11
|
+
LogLevel = int # ta.TypeAlias
|
12
|
+
|
13
|
+
|
14
|
+
##
|
15
|
+
|
16
|
+
|
17
|
+
class Logging(ta.Protocol):
|
18
|
+
def isEnabledFor(self, level: LogLevel) -> bool: # noqa
|
19
|
+
...
|
20
|
+
|
21
|
+
def getEffectiveLevel(self) -> LogLevel: # noqa
|
22
|
+
...
|
23
|
+
|
24
|
+
#
|
25
|
+
|
26
|
+
def debug(self, msg: str, *args, **kwargs) -> None:
|
27
|
+
...
|
28
|
+
|
29
|
+
def info(self, msg: str, *args, **kwargs) -> None:
|
30
|
+
...
|
31
|
+
|
32
|
+
def warning(self, msg: str, *args, **kwargs) -> None:
|
33
|
+
...
|
34
|
+
|
35
|
+
def error(self, msg: str, *args, **kwargs) -> None:
|
36
|
+
...
|
37
|
+
|
38
|
+
def exception(self, msg: str, *args, exc_info=True, **kwargs) -> None:
|
39
|
+
...
|
40
|
+
|
41
|
+
def critical(self, msg: str, *args, **kwargs) -> None:
|
42
|
+
...
|
43
|
+
|
44
|
+
def log(self, level: LogLevel, msg: str, *args, **kwargs) -> None:
|
45
|
+
...
|
46
|
+
|
47
|
+
|
48
|
+
##
|
49
|
+
|
50
|
+
|
51
|
+
class AbstractLogging(abc.ABC):
|
52
|
+
@ta.final
|
53
|
+
def isEnabledFor(self, level: LogLevel) -> bool: # noqa
|
54
|
+
return self.is_enabled_for(level)
|
55
|
+
|
56
|
+
def is_enabled_for(self, level: LogLevel) -> bool: # noqa
|
57
|
+
return level >= self.getEffectiveLevel()
|
58
|
+
|
59
|
+
@ta.final
|
60
|
+
def getEffectiveLevel(self) -> LogLevel: # noqa
|
61
|
+
return self.get_effective_level()
|
62
|
+
|
63
|
+
@abc.abstractmethod
|
64
|
+
def get_effective_level(self) -> LogLevel: # noqa
|
65
|
+
raise NotImplementedError
|
66
|
+
|
67
|
+
#
|
68
|
+
|
69
|
+
def debug(self, msg: str, *args, **kwargs) -> None:
|
70
|
+
if self.is_enabled_for(logging.DEBUG):
|
71
|
+
self.log(logging.DEBUG, msg, args, **kwargs)
|
72
|
+
|
73
|
+
def info(self, msg: str, *args, **kwargs) -> None:
|
74
|
+
if self.is_enabled_for(logging.INFO):
|
75
|
+
self.log(logging.INFO, msg, args, **kwargs)
|
76
|
+
|
77
|
+
def warning(self, msg: str, *args, **kwargs) -> None:
|
78
|
+
if self.is_enabled_for(logging.WARNING):
|
79
|
+
self.log(logging.WARNING, msg, args, **kwargs)
|
80
|
+
|
81
|
+
def error(self, msg: str, *args, **kwargs) -> None:
|
82
|
+
if self.is_enabled_for(logging.ERROR):
|
83
|
+
self.log(logging.ERROR, msg, args, **kwargs)
|
84
|
+
|
85
|
+
def exception(self, msg: str, *args, exc_info=True, **kwargs) -> None:
|
86
|
+
self.error(msg, *args, exc_info=exc_info, **kwargs)
|
87
|
+
|
88
|
+
def critical(self, msg: str, *args, **kwargs) -> None:
|
89
|
+
if self.is_enabled_for(logging.CRITICAL):
|
90
|
+
self.log(logging.CRITICAL, msg, args, **kwargs)
|
91
|
+
|
92
|
+
def log(self, level: LogLevel, msg: str, *args, **kwargs) -> None:
|
93
|
+
if not isinstance(level, int):
|
94
|
+
raise TypeError('Level must be an integer.')
|
95
|
+
if self.is_enabled_for(level):
|
96
|
+
self._log(level, msg, args, **kwargs)
|
97
|
+
|
98
|
+
@abc.abstractmethod
|
99
|
+
def _log(
|
100
|
+
self,
|
101
|
+
level: int,
|
102
|
+
msg: str,
|
103
|
+
args: ta.Any,
|
104
|
+
*,
|
105
|
+
exc_info: ta.Any = None,
|
106
|
+
extra: ta.Any = None,
|
107
|
+
stack_info: bool = False,
|
108
|
+
) -> None:
|
109
|
+
raise NotImplementedError
|
110
|
+
|
111
|
+
|
112
|
+
##
|
113
|
+
|
114
|
+
|
115
|
+
class NopLogging(AbstractLogging):
|
116
|
+
def get_effective_level(self) -> LogLevel:
|
117
|
+
return logging.CRITICAL + 1
|
118
|
+
|
119
|
+
def _log(self, *args: ta.Any, **kwargs: ta.Any) -> None:
|
120
|
+
pass
|
121
|
+
|
122
|
+
|
123
|
+
##
|
124
|
+
|
125
|
+
|
126
|
+
class StdlibLogging(AbstractLogging):
|
127
|
+
def __init__(self, underlying: logging.Logger) -> None:
|
128
|
+
super().__init__()
|
129
|
+
|
130
|
+
if not isinstance(underlying, logging.Logger):
|
131
|
+
raise TypeError(underlying)
|
132
|
+
|
133
|
+
self._underlying = underlying
|
134
|
+
|
135
|
+
#
|
136
|
+
|
137
|
+
def is_enabled_for(self, level: int) -> bool: # noqa
|
138
|
+
return self._underlying.isEnabledFor(level)
|
139
|
+
|
140
|
+
def get_effective_level(self) -> int: # noqa
|
141
|
+
return self._underlying.getEffectiveLevel()
|
142
|
+
|
143
|
+
#
|
144
|
+
|
145
|
+
def _log(
|
146
|
+
self,
|
147
|
+
level: int,
|
148
|
+
msg: str,
|
149
|
+
args: ta.Any,
|
150
|
+
*,
|
151
|
+
exc_info: ta.Any = None,
|
152
|
+
extra: ta.Any = None,
|
153
|
+
stack_info: bool = False,
|
154
|
+
) -> None:
|
155
|
+
caller = LoggingCaller.find(stack_info)
|
156
|
+
|
157
|
+
if exc_info:
|
158
|
+
if isinstance(exc_info, BaseException):
|
159
|
+
exc_info = (type(exc_info), exc_info, exc_info.__traceback__)
|
160
|
+
elif not isinstance(exc_info, tuple):
|
161
|
+
exc_info = sys.exc_info()
|
162
|
+
|
163
|
+
record = self._underlying.makeRecord(
|
164
|
+
name=self._underlying.name,
|
165
|
+
level=level,
|
166
|
+
fn=caller.filename,
|
167
|
+
lno=caller.lineno,
|
168
|
+
msg=msg,
|
169
|
+
args=args,
|
170
|
+
exc_info=exc_info,
|
171
|
+
func=caller.func,
|
172
|
+
extra=extra,
|
173
|
+
sinfo=caller.sinfo,
|
174
|
+
)
|
175
|
+
|
176
|
+
self._underlying.handle(record)
|
omlish/sockets/addresses.py
CHANGED
@@ -2,8 +2,7 @@
|
|
2
2
|
# @omlish-lite
|
3
3
|
"""
|
4
4
|
TODO:
|
5
|
-
-
|
6
|
-
+ codification of https://docs.python.org/3/library/socket.html#socket-families
|
5
|
+
- codification of https://docs.python.org/3/library/socket.html#socket-families
|
7
6
|
"""
|
8
7
|
import dataclasses as dc
|
9
8
|
import socket
|
@@ -35,11 +34,16 @@ class SocketAddressInfo:
|
|
35
34
|
sockaddr: SocketAddress
|
36
35
|
|
37
36
|
|
37
|
+
class SocketFamilyAndAddress(ta.NamedTuple):
|
38
|
+
family: socket.AddressFamily
|
39
|
+
address: SocketAddress
|
40
|
+
|
41
|
+
|
38
42
|
def get_best_socket_family(
|
39
43
|
host: ta.Optional[str],
|
40
44
|
port: ta.Union[str, int, None],
|
41
45
|
family: ta.Union[int, socket.AddressFamily] = socket.AddressFamily.AF_UNSPEC,
|
42
|
-
) ->
|
46
|
+
) -> SocketFamilyAndAddress:
|
43
47
|
"""https://github.com/python/cpython/commit/f289084c83190cc72db4a70c58f007ec62e75247"""
|
44
48
|
|
45
49
|
infos = socket.getaddrinfo(
|
@@ -50,4 +54,9 @@ def get_best_socket_family(
|
|
50
54
|
flags=socket.AI_PASSIVE,
|
51
55
|
)
|
52
56
|
ai = SocketAddressInfo(*next(iter(infos)))
|
53
|
-
return ai.family, ai.sockaddr
|
57
|
+
return SocketFamilyAndAddress(ai.family, ai.sockaddr)
|
58
|
+
|
59
|
+
|
60
|
+
class SocketAndAddress(ta.NamedTuple):
|
61
|
+
socket: socket.socket
|
62
|
+
address: SocketAddress
|