omlish 0.0.0.dev217__py3-none-any.whl → 0.0.0.dev219__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 +3 -3
- 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 +18 -1
- omlish/http/sessions.py +1 -1
- omlish/http/simple.py +101 -0
- 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/secrets/__init__.py +0 -24
- omlish/secrets/all.py +16 -0
- omlish/secrets/secrets.py +6 -0
- omlish/sockets/addresses.py +33 -12
- omlish/sockets/bind.py +333 -0
- omlish/sockets/handlers.py +4 -15
- omlish/sockets/io.py +69 -0
- omlish/sockets/server/__init__.py +0 -0
- omlish/sockets/server/handlers.py +134 -0
- omlish/sockets/server/server.py +152 -0
- omlish/sockets/server/threading.py +123 -0
- omlish/sql/alchemy/secrets.py +1 -1
- omlish/sql/dbs.py +1 -1
- {omlish-0.0.0.dev217.dist-info → omlish-0.0.0.dev219.dist-info}/METADATA +3 -3
- {omlish-0.0.0.dev217.dist-info → omlish-0.0.0.dev219.dist-info}/RECORD +32 -21
- omlish/docker/oci.py +0 -81
- omlish/sockets/server.py +0 -66
- {omlish-0.0.0.dev217.dist-info → omlish-0.0.0.dev219.dist-info}/LICENSE +0 -0
- {omlish-0.0.0.dev217.dist-info → omlish-0.0.0.dev219.dist-info}/WHEEL +0 -0
- {omlish-0.0.0.dev217.dist-info → omlish-0.0.0.dev219.dist-info}/entry_points.txt +0 -0
- {omlish-0.0.0.dev217.dist-info → omlish-0.0.0.dev219.dist-info}/top_level.txt +0 -0
omlish/__about__.py
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
-
__version__ = '0.0.0.
|
2
|
-
__revision__ = '
|
1
|
+
__version__ = '0.0.0.dev219'
|
2
|
+
__revision__ = '5e741e0670fb75899653bd454587a1cf2add710f'
|
3
3
|
|
4
4
|
|
5
5
|
#
|
@@ -42,7 +42,7 @@ class Project(ProjectBase):
|
|
42
42
|
],
|
43
43
|
|
44
44
|
'compress': [
|
45
|
-
'lz4 ~= 4.
|
45
|
+
'lz4 ~= 4.4',
|
46
46
|
# 'lz4 @ git+https://github.com/wrmsr/python-lz4@wrmsr_20240830_GIL_NOT_USED'
|
47
47
|
|
48
48
|
'python-snappy ~= 0.7',
|
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
|
68
|
+
from ...sockets.handlers import SocketHandler_
|
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
@@ -1,5 +1,6 @@
|
|
1
1
|
# ruff: noqa: UP006 UP007
|
2
2
|
# @omlish-lite
|
3
|
+
import abc
|
3
4
|
import dataclasses as dc
|
4
5
|
import http.server
|
5
6
|
import typing as ta
|
@@ -9,6 +10,10 @@ from .parsing import HttpHeaders
|
|
9
10
|
|
10
11
|
|
11
12
|
HttpHandler = ta.Callable[['HttpHandlerRequest'], 'HttpHandlerResponse'] # ta.TypeAlias
|
13
|
+
HttpHandlerResponseData = ta.Union[bytes, 'HttpHandlerResponseStreamedData'] # ta.TypeAlias # noqa
|
14
|
+
|
15
|
+
|
16
|
+
##
|
12
17
|
|
13
18
|
|
14
19
|
@dc.dataclass(frozen=True)
|
@@ -25,13 +30,25 @@ class HttpHandlerResponse:
|
|
25
30
|
status: ta.Union[http.HTTPStatus, int]
|
26
31
|
|
27
32
|
headers: ta.Optional[ta.Mapping[str, str]] = None
|
28
|
-
data: ta.Optional[
|
33
|
+
data: ta.Optional[HttpHandlerResponseData] = None
|
29
34
|
close_connection: ta.Optional[bool] = None
|
30
35
|
|
31
36
|
|
37
|
+
@dc.dataclass(frozen=True)
|
38
|
+
class HttpHandlerResponseStreamedData:
|
39
|
+
iter: ta.Iterable[bytes]
|
40
|
+
length: ta.Optional[int] = None
|
41
|
+
|
42
|
+
|
32
43
|
class HttpHandlerError(Exception):
|
33
44
|
pass
|
34
45
|
|
35
46
|
|
36
47
|
class UnsupportedMethodHttpHandlerError(Exception):
|
37
48
|
pass
|
49
|
+
|
50
|
+
|
51
|
+
class HttpHandler_(abc.ABC): # noqa
|
52
|
+
@abc.abstractmethod
|
53
|
+
def __call__(self, req: HttpHandlerRequest) -> HttpHandlerResponse:
|
54
|
+
raise NotImplementedError
|
omlish/http/sessions.py
CHANGED
@@ -9,8 +9,8 @@ import typing as ta
|
|
9
9
|
import zlib
|
10
10
|
|
11
11
|
from .. import lang
|
12
|
-
from .. import secrets as sec
|
13
12
|
from ..funcs import pairs as fpa
|
13
|
+
from ..secrets import all as sec
|
14
14
|
from .cookies import dump_cookie
|
15
15
|
from .cookies import parse_cookie
|
16
16
|
from .json import JSON_TAGGER
|
omlish/http/simple.py
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
# @omlish-lite
|
3
|
+
import concurrent.futures as cf
|
4
|
+
import contextlib
|
5
|
+
import functools
|
6
|
+
import typing as ta
|
7
|
+
|
8
|
+
from ..lite.check import check
|
9
|
+
from ..sockets.addresses import SocketAndAddress
|
10
|
+
from ..sockets.bind import CanSocketBinder
|
11
|
+
from ..sockets.bind import SocketBinder
|
12
|
+
from ..sockets.server.handlers import ExecutorSocketServerHandler
|
13
|
+
from ..sockets.server.handlers import SocketHandlerSocketServerHandler
|
14
|
+
from ..sockets.server.handlers import SocketServerHandler
|
15
|
+
from ..sockets.server.handlers import SocketWrappingSocketServerHandler
|
16
|
+
from ..sockets.server.handlers import StandardSocketServerHandler
|
17
|
+
from ..sockets.server.server import SocketServer
|
18
|
+
from ..sockets.server.threading import ThreadingSocketServerHandler
|
19
|
+
from .coro.server import CoroHttpServer
|
20
|
+
from .coro.server import CoroHttpServerSocketHandler
|
21
|
+
from .handlers import HttpHandler
|
22
|
+
from .parsing import HttpRequestParser
|
23
|
+
from .versions import HttpProtocolVersion
|
24
|
+
from .versions import HttpProtocolVersions
|
25
|
+
|
26
|
+
|
27
|
+
if ta.TYPE_CHECKING:
|
28
|
+
import ssl
|
29
|
+
|
30
|
+
|
31
|
+
@contextlib.contextmanager
|
32
|
+
def make_simple_http_server(
|
33
|
+
bind: CanSocketBinder,
|
34
|
+
handler: HttpHandler,
|
35
|
+
*,
|
36
|
+
server_version: HttpProtocolVersion = HttpProtocolVersions.HTTP_1_1,
|
37
|
+
ssl_context: ta.Optional['ssl.SSLContext'] = None,
|
38
|
+
executor: ta.Optional[cf.Executor] = None,
|
39
|
+
use_threads: bool = False,
|
40
|
+
) -> ta.Iterator[SocketServer]:
|
41
|
+
check.arg(not (executor is not None and use_threads))
|
42
|
+
|
43
|
+
#
|
44
|
+
|
45
|
+
with contextlib.ExitStack() as es:
|
46
|
+
server_factory = functools.partial(
|
47
|
+
CoroHttpServer,
|
48
|
+
handler=handler,
|
49
|
+
parser=HttpRequestParser(
|
50
|
+
server_version=server_version,
|
51
|
+
),
|
52
|
+
)
|
53
|
+
|
54
|
+
socket_handler = CoroHttpServerSocketHandler(
|
55
|
+
server_factory,
|
56
|
+
)
|
57
|
+
|
58
|
+
#
|
59
|
+
|
60
|
+
server_handler: SocketServerHandler = SocketHandlerSocketServerHandler(
|
61
|
+
socket_handler,
|
62
|
+
)
|
63
|
+
|
64
|
+
#
|
65
|
+
|
66
|
+
if ssl_context is not None:
|
67
|
+
server_handler = SocketWrappingSocketServerHandler(
|
68
|
+
server_handler,
|
69
|
+
SocketAndAddress.socket_wrapper(functools.partial(
|
70
|
+
ssl_context.wrap_socket,
|
71
|
+
server_side=True,
|
72
|
+
)),
|
73
|
+
)
|
74
|
+
|
75
|
+
#
|
76
|
+
|
77
|
+
server_handler = StandardSocketServerHandler(
|
78
|
+
server_handler,
|
79
|
+
)
|
80
|
+
|
81
|
+
#
|
82
|
+
|
83
|
+
if executor is not None:
|
84
|
+
server_handler = ExecutorSocketServerHandler(
|
85
|
+
server_handler,
|
86
|
+
executor,
|
87
|
+
)
|
88
|
+
|
89
|
+
elif use_threads:
|
90
|
+
server_handler = es.enter_context(ThreadingSocketServerHandler(
|
91
|
+
server_handler,
|
92
|
+
))
|
93
|
+
|
94
|
+
#
|
95
|
+
|
96
|
+
server = es.enter_context(SocketServer(
|
97
|
+
SocketBinder.of(bind),
|
98
|
+
server_handler,
|
99
|
+
))
|
100
|
+
|
101
|
+
yield server
|
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
|
+
)
|