omlish 0.0.0.dev216__py3-none-any.whl → 0.0.0.dev218__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.dev216'
2
- __revision__ = '4805bde6bf1ddb3b6c2099d185808394e0634392'
1
+ __version__ = '0.0.0.dev218'
2
+ __revision__ = '0d15b9d5ec9832621506a90856baf7d140dd63ad'
3
3
 
4
4
 
5
5
  #
@@ -1,6 +1,7 @@
1
1
  # ruff: noqa: I001
2
2
  from .asyncio import ( # noqa
3
- asyncio_once,
4
- drain_asyncio_tasks,
5
- draining_asyncio_tasks,
3
+ asyncio_once as once,
4
+ asyncio_wait_concurrent as wait_concurrent,
5
+ drain_asyncio_tasks as drain_tasks,
6
+ draining_asyncio_tasks as draining_tasks,
6
7
  )
@@ -93,8 +93,6 @@ from .impl.reflect import ( # noqa
93
93
  from .utils import ( # noqa
94
94
  is_immediate_dataclass,
95
95
 
96
- maybe_post_init,
97
-
98
96
  opt_repr,
99
97
  truthy_repr,
100
98
 
@@ -113,3 +111,9 @@ from .utils import ( # noqa
113
111
  iter_keys,
114
112
  iter_values,
115
113
  )
114
+
115
+ ##
116
+
117
+ from ..lite.dataclasses import ( # noqa
118
+ dataclass_maybe_post_init as maybe_post_init,
119
+ )
@@ -26,18 +26,6 @@ def is_immediate_dataclass(cls: type) -> bool:
26
26
  ##
27
27
 
28
28
 
29
- def maybe_post_init(sup: ta.Any) -> bool:
30
- try:
31
- fn = sup.__post_init__
32
- except AttributeError:
33
- return False
34
- fn()
35
- return True
36
-
37
-
38
- ##
39
-
40
-
41
29
  def opt_repr(o: ta.Any) -> str | None:
42
30
  return repr(o) if o is not None else None
43
31
 
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'})
@@ -0,0 +1,49 @@
1
+ # ruff: noqa: UP006 UP007
2
+ # @omlish-lite
3
+ import dataclasses as dc
4
+ import os
5
+ import typing as ta
6
+
7
+
8
+ @dc.dataclass(frozen=True)
9
+ class DockerPortRelay:
10
+ docker_port: int
11
+ host_port: int
12
+
13
+ name: ta.Optional[str] = None
14
+
15
+ DEFAULT_HOST_NAME: ta.ClassVar[str] = 'host.docker.internal'
16
+ host_name: str = DEFAULT_HOST_NAME
17
+
18
+ DEFAULT_INTERMEDIATE_PORT: ta.ClassVar[int] = 5000
19
+ intermediate_port: int = DEFAULT_INTERMEDIATE_PORT
20
+
21
+ DEFAULT_IMAGE: ta.ClassVar[str] = 'alpine/socat'
22
+ image: str = DEFAULT_IMAGE
23
+
24
+ def socat_args(self) -> ta.List[str]:
25
+ return [
26
+ '-d',
27
+ f'TCP-LISTEN:{self.intermediate_port},fork,reuseaddr',
28
+ f'TCP:{self.host_name}:{self.host_port}',
29
+ ]
30
+
31
+ def run_args(self) -> ta.List[str]:
32
+ if (name := self.name) is None:
33
+ name = f'docker_port_relay-{os.getpid()}'
34
+
35
+ return [
36
+ '--name', name,
37
+ '--rm',
38
+ '-p', f'{self.docker_port}:{self.intermediate_port}',
39
+ self.image,
40
+ *self.socat_args(),
41
+ ]
42
+
43
+ def run_cmd(self) -> ta.List[str]:
44
+ return [
45
+ 'docker',
46
+ 'run',
47
+ '-i',
48
+ *self.run_args(),
49
+ ]
@@ -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 # 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[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
@@ -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[bytes] = None
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/lang/imports.py CHANGED
@@ -281,6 +281,10 @@ class NamePackage(ta.NamedTuple):
281
281
 
282
282
 
283
283
  class _ProxyInit:
284
+ class _Import(ta.NamedTuple):
285
+ pkg: str
286
+ attr: str
287
+
284
288
  def __init__(
285
289
  self,
286
290
  name_package: NamePackage,
@@ -294,31 +298,35 @@ class _ProxyInit:
294
298
  self._globals = globals
295
299
  self._update_globals = update_globals
296
300
 
297
- self._pkgs_by_attr: dict[str, str] = {}
301
+ self._imps_by_attr: dict[str, _ProxyInit._Import] = {}
298
302
  self._mods_by_pkgs: dict[str, ta.Any] = {}
299
303
 
300
304
  @property
301
305
  def name_package(self) -> NamePackage:
302
306
  return self._name_package
303
307
 
304
- def add(self, package: str, attrs: ta.Iterable[str]) -> None:
308
+ def add(self, package: str, attrs: ta.Iterable[str | tuple[str, str]]) -> None:
305
309
  if isinstance(attrs, str):
306
310
  raise TypeError(attrs)
307
311
  for attr in attrs:
308
- self._pkgs_by_attr[attr] = package
312
+ if isinstance(attr, tuple):
313
+ imp_attr, attr = attr
314
+ else:
315
+ imp_attr = attr
316
+ self._imps_by_attr[attr] = self._Import(package, imp_attr)
309
317
 
310
318
  def get(self, attr: str) -> ta.Any:
311
319
  try:
312
- pkg = self._pkgs_by_attr[attr]
320
+ imp = self._imps_by_attr[attr]
313
321
  except KeyError:
314
322
  raise AttributeError(attr) # noqa
315
323
 
316
324
  try:
317
- mod = self._mods_by_pkgs[pkg]
325
+ mod = self._mods_by_pkgs[imp.pkg]
318
326
  except KeyError:
319
- mod = importlib.import_module(pkg, package=self._name_package.package)
327
+ mod = importlib.import_module(imp.pkg, package=self._name_package.package)
320
328
 
321
- val = getattr(mod, attr)
329
+ val = getattr(mod, imp.attr)
322
330
 
323
331
  if self._update_globals and self._globals is not None:
324
332
  self._globals[attr] = val
@@ -329,7 +337,7 @@ class _ProxyInit:
329
337
  def proxy_init(
330
338
  globals: ta.MutableMapping[str, ta.Any], # noqa
331
339
  package: str,
332
- attrs: ta.Iterable[str],
340
+ attrs: ta.Iterable[str | tuple[str, str]],
333
341
  ) -> None:
334
342
  if isinstance(attrs, str):
335
343
  raise TypeError(attrs)
@@ -34,8 +34,10 @@ def dataclass_cache_hash(
34
34
 
35
35
 
36
36
  def dataclass_maybe_post_init(sup: ta.Any) -> bool:
37
+ if not isinstance(sup, super):
38
+ raise TypeError(sup)
37
39
  try:
38
- fn = sup.__post_init__
40
+ fn = sup.__post_init__ # type: ignore
39
41
  except AttributeError:
40
42
  return False
41
43
  fn()
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
+ )