omlish 0.0.0.dev217__py3-none-any.whl → 0.0.0.dev219__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
omlish/__about__.py CHANGED
@@ -1,5 +1,5 @@
1
- __version__ = '0.0.0.dev217'
2
- __revision__ = 'd43ca6092cff650a9fc533e97d87ce9112f6f71a'
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.3',
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'})
@@ -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
@@ -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_
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[bytes] = None
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 _build_response_bytes(self, a: _Response) -> bytes:
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
- nh.append(self._Header('Content-Length', str(len(resp.data))))
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
- b = self._build_response_bytes(r)
414
- check.none((yield self.WriteIo(b)))
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(SocketHandler):
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 handle(self) -> None:
553
- server = self._server_factory(self._client_address)
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 = self._rfile.read(o.sz)
585
+ i = fp.r.read(o.sz)
566
586
 
567
587
  elif isinstance(o, CoroHttpServer.ReadLineIo):
568
- i = self._rfile.readline(o.sz)
588
+ i = fp.r.readline(o.sz)
569
589
 
570
590
  elif isinstance(o, CoroHttpServer.WriteIo):
571
591
  i = None
572
- self._wfile.write(o.data)
573
- self._wfile.flush()
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[bytes] = None
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
+ )