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 CHANGED
@@ -1,5 +1,5 @@
1
- __version__ = '0.0.0.dev217'
2
- __revision__ = 'd43ca6092cff650a9fc533e97d87ce9112f6f71a'
1
+ __version__ = '0.0.0.dev218'
2
+ __revision__ = '0d15b9d5ec9832621506a90856baf7d140dd63ad'
3
3
 
4
4
 
5
5
  #
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 # 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/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
+ )
@@ -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)
@@ -2,8 +2,7 @@
2
2
  # @omlish-lite
3
3
  """
4
4
  TODO:
5
- - SocketClientAddress family / tuple pairs
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
- ) -> ta.Tuple[socket.AddressFamily, SocketAddress]:
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