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.
@@ -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)
@@ -9,6 +9,7 @@ from .. import collections as col
9
9
  from .. import dataclasses as dc
10
10
  from .. import lang
11
11
  from .. import reflect as rfl
12
+ from ..lite import marshal as lm
12
13
  from .base import MarshalContext
13
14
  from .base import Marshaler
14
15
  from .base import MarshalerFactory
@@ -96,6 +97,31 @@ def get_field_infos(
96
97
  unmarshal_names=col.unique([fmd.name, *(fmd.alts or ())]),
97
98
  )
98
99
 
100
+ else:
101
+ try:
102
+ lfk = field.metadata[lm.OBJ_MARSHALER_FIELD_KEY]
103
+ except KeyError:
104
+ pass
105
+ else:
106
+ if lfk is not None:
107
+ check.non_empty_str(lfk)
108
+ has_set_name = True
109
+ fi_kw.update(
110
+ marshal_name=lfk,
111
+ unmarshal_names=[lfk],
112
+ )
113
+ else:
114
+ fo_kw.update(
115
+ no_marshal=True,
116
+ no_unmarshal=True,
117
+ )
118
+
119
+ if (lon := field.metadata.get(lm.OBJ_MARSHALER_OMIT_IF_NONE)) is not None:
120
+ if check.isinstance(lon, bool):
121
+ fo_kw.update(
122
+ omit_if=lang.is_none,
123
+ )
124
+
99
125
  if fo_kw.get('embed') and not has_set_name:
100
126
  fi_kw.update(
101
127
  marshal_name=fi_kw['marshal_name'] + '_',
@@ -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
omlish/sockets/bind.py ADDED
@@ -0,0 +1,332 @@
1
+ # ruff: noqa: UP006 UP007
2
+ # @omlish-lite
3
+ """
4
+ TODO:
5
+ - DupSocketBinder
6
+ """
7
+ import abc
8
+ import dataclasses as dc
9
+ import errno
10
+ import os
11
+ import socket as socket_
12
+ import stat
13
+ import typing as ta
14
+
15
+ from omlish.lite.check import check
16
+ from omlish.lite.dataclasses import dataclass_maybe_post_init
17
+ from omlish.sockets.addresses import SocketAddress
18
+ from omlish.sockets.addresses import SocketAndAddress
19
+
20
+
21
+ SocketBinderT = ta.TypeVar('SocketBinderT', bound='SocketBinder')
22
+ SocketBinderConfigT = ta.TypeVar('SocketBinderConfigT', bound='SocketBinder.Config')
23
+
24
+
25
+ ##
26
+
27
+
28
+ class SocketBinder(abc.ABC, ta.Generic[SocketBinderConfigT]):
29
+ @dc.dataclass(frozen=True)
30
+ class Config:
31
+ listen_backlog: int = 5
32
+
33
+ allow_reuse_address: bool = True
34
+ allow_reuse_port: bool = True
35
+
36
+ set_inheritable: bool = False
37
+
38
+ #
39
+
40
+ @classmethod
41
+ def new(
42
+ cls,
43
+ target: ta.Union[
44
+ int,
45
+ ta.Tuple[str, int],
46
+ str,
47
+ ],
48
+ ) -> 'SocketBinder.Config':
49
+ if isinstance(target, int):
50
+ return TcpSocketBinder.Config(
51
+ port=target,
52
+ )
53
+
54
+ elif isinstance(target, tuple):
55
+ host, port = target
56
+ return TcpSocketBinder.Config(
57
+ host=host,
58
+ port=port,
59
+ )
60
+
61
+ elif isinstance(target, str):
62
+ return UnixSocketBinder.Config(
63
+ file=target,
64
+ )
65
+
66
+ else:
67
+ raise TypeError(target)
68
+
69
+ #
70
+
71
+ def __init__(self, config: SocketBinderConfigT) -> None:
72
+ super().__init__()
73
+
74
+ self._config = config
75
+
76
+ #
77
+
78
+ @classmethod
79
+ def new(cls, target: ta.Any) -> 'SocketBinder':
80
+ config: SocketBinder.Config
81
+ if isinstance(target, SocketBinder.Config):
82
+ config = target
83
+
84
+ else:
85
+ config = SocketBinder.Config.new(target)
86
+
87
+ if isinstance(config, TcpSocketBinder.Config):
88
+ return TcpSocketBinder(config)
89
+
90
+ elif isinstance(config, UnixSocketBinder.Config):
91
+ return UnixSocketBinder(config)
92
+
93
+ else:
94
+ raise TypeError(config)
95
+
96
+ #
97
+
98
+ class Error(RuntimeError):
99
+ pass
100
+
101
+ class NotBoundError(Error):
102
+ pass
103
+
104
+ class AlreadyBoundError(Error):
105
+ pass
106
+
107
+ #
108
+
109
+ @property
110
+ @abc.abstractmethod
111
+ def address_family(self) -> int:
112
+ raise NotImplementedError
113
+
114
+ @property
115
+ @abc.abstractmethod
116
+ def address(self) -> SocketAddress:
117
+ raise NotImplementedError
118
+
119
+ #
120
+
121
+ _socket: socket_.socket
122
+
123
+ @property
124
+ def is_bound(self) -> bool:
125
+ return hasattr(self, '_socket')
126
+
127
+ @property
128
+ def socket(self) -> socket_.socket:
129
+ try:
130
+ return self._socket
131
+ except AttributeError:
132
+ raise self.NotBoundError from None
133
+
134
+ _name: str
135
+
136
+ @property
137
+ def name(self) -> str:
138
+ try:
139
+ return self._name
140
+ except AttributeError:
141
+ raise self.NotBoundError from None
142
+
143
+ _port: ta.Optional[int]
144
+
145
+ @property
146
+ def port(self) -> ta.Optional[int]:
147
+ try:
148
+ return self._port
149
+ except AttributeError:
150
+ raise self.NotBoundError from None
151
+
152
+ #
153
+
154
+ def fileno(self) -> int:
155
+ return self.socket.fileno()
156
+
157
+ #
158
+
159
+ def __enter__(self: SocketBinderT) -> SocketBinderT:
160
+ self.bind()
161
+
162
+ return self
163
+
164
+ def __exit__(self, exc_type, exc_val, exc_tb) -> None:
165
+ self.close()
166
+
167
+ #
168
+
169
+ def _init_socket(self) -> None:
170
+ if hasattr(self, '_socket'):
171
+ raise self.AlreadyBoundError
172
+
173
+ socket = socket_.socket(self.address_family, socket_.SOCK_STREAM)
174
+ self._socket = socket
175
+
176
+ if self._config.allow_reuse_address and hasattr(socket_, 'SO_REUSEADDR'):
177
+ socket.setsockopt(socket_.SOL_SOCKET, socket_.SO_REUSEADDR, 1)
178
+
179
+ # Since Linux 6.12.9, SO_REUSEPORT is not allowed on other address families than AF_INET/AF_INET6.
180
+ if (
181
+ self._config.allow_reuse_port and hasattr(socket_, 'SO_REUSEPORT') and
182
+ self.address_family in (socket_.AF_INET, socket_.AF_INET6)
183
+ ):
184
+ try:
185
+ socket.setsockopt(socket_.SOL_SOCKET, socket_.SO_REUSEPORT, 1)
186
+ except OSError as err:
187
+ if err.errno not in (errno.ENOPROTOOPT, errno.EINVAL):
188
+ raise
189
+
190
+ if self._config.set_inheritable and hasattr(socket, 'set_inheritable'):
191
+ socket.set_inheritable(True)
192
+
193
+ def _pre_bind(self) -> None:
194
+ pass
195
+
196
+ def _post_bind(self) -> None:
197
+ pass
198
+
199
+ def bind(self) -> None:
200
+ self._init_socket()
201
+
202
+ self._pre_bind()
203
+
204
+ self.socket.bind(self.address)
205
+
206
+ self._post_bind()
207
+
208
+ check.state(all(hasattr(self, a) for a in ('_socket', '_name', '_port')))
209
+
210
+ #
211
+
212
+ def close(self) -> None:
213
+ if hasattr(self, '_socket'):
214
+ self._socket.close()
215
+
216
+ #
217
+
218
+ def listen(self) -> None:
219
+ self.socket.listen(self._config.listen_backlog)
220
+
221
+ @abc.abstractmethod
222
+ def accept(self, socket: ta.Optional[socket_.socket] = None) -> SocketAndAddress:
223
+ raise NotImplementedError
224
+
225
+
226
+ ##
227
+
228
+
229
+ class TcpSocketBinder(SocketBinder):
230
+ @dc.dataclass(frozen=True)
231
+ class Config(SocketBinder.Config):
232
+ DEFAULT_HOST: ta.ClassVar[str] = 'localhost'
233
+ host: str = DEFAULT_HOST
234
+
235
+ port: int = 0
236
+
237
+ def __post_init__(self) -> None:
238
+ dataclass_maybe_post_init(super())
239
+ check.non_empty_str(self.host)
240
+ check.isinstance(self.port, int)
241
+ check.arg(self.port > 0)
242
+
243
+ def __init__(self, config: Config) -> None:
244
+ super().__init__(check.isinstance(config, self.Config))
245
+
246
+ self._address = (config.host, config.port)
247
+
248
+ #
249
+
250
+ address_family = socket_.AF_INET
251
+
252
+ @property
253
+ def address(self) -> SocketAddress:
254
+ return self._address
255
+
256
+ #
257
+
258
+ def _post_bind(self) -> None:
259
+ super()._post_bind()
260
+
261
+ host, port, *_ = self.socket.getsockname()
262
+
263
+ self._name = socket_.getfqdn(host)
264
+ self._port = port
265
+
266
+ #
267
+
268
+ def accept(self, socket: ta.Optional[socket_.socket] = None) -> SocketAndAddress:
269
+ if socket is None:
270
+ socket = self.socket
271
+
272
+ conn, client_address = socket.accept()
273
+ return SocketAndAddress(conn, client_address)
274
+
275
+
276
+ ##
277
+
278
+
279
+ class UnixSocketBinder(SocketBinder):
280
+ @dc.dataclass(frozen=True)
281
+ class Config(SocketBinder.Config):
282
+ file: str = ''
283
+
284
+ unlink: bool = False
285
+
286
+ def __post_init__(self) -> None:
287
+ dataclass_maybe_post_init(super())
288
+ check.non_empty_str(self.file)
289
+
290
+ def __init__(self, config: Config) -> None:
291
+ super().__init__(check.isinstance(config, self.Config))
292
+
293
+ self._address = config.file
294
+
295
+ #
296
+
297
+ address_family = socket_.AF_UNIX
298
+
299
+ @property
300
+ def address(self) -> SocketAddress:
301
+ return self._address
302
+
303
+ #
304
+
305
+ def _pre_bind(self) -> None:
306
+ super()._pre_bind()
307
+
308
+ if self._config.unlink:
309
+ try:
310
+ os.unlink(self._config.file)
311
+ except FileNotFoundError:
312
+ pass
313
+
314
+ def _post_bind(self) -> None:
315
+ super()._post_bind()
316
+
317
+ name = self.socket.getsockname()
318
+
319
+ os.chmod(name, stat.S_IRWXU | stat.S_IRWXG) # noqa
320
+
321
+ self._name = name
322
+ self._port = None
323
+
324
+ #
325
+
326
+ def accept(self, sock: ta.Optional[socket_.socket] = None) -> SocketAndAddress:
327
+ if sock is None:
328
+ sock = self.socket
329
+
330
+ conn, _ = sock.accept()
331
+ client_address = ('', 0)
332
+ return SocketAndAddress(conn, client_address)
@@ -1,30 +1,12 @@
1
1
  # ruff: noqa: UP006 UP007
2
2
  # @omlish-lite
3
- import abc
4
3
  import typing as ta
5
4
 
6
5
  from .addresses import SocketAddress
6
+ from .io import SocketIoPair # noqa
7
7
 
8
8
 
9
- SocketHandlerFactory = ta.Callable[[SocketAddress, ta.BinaryIO, ta.BinaryIO], 'SocketHandler']
9
+ SocketHandler = ta.Callable[[SocketAddress, 'SocketIoPair'], None] # ta.TypeAlias
10
10
 
11
11
 
12
12
  ##
13
-
14
-
15
- class SocketHandler(abc.ABC):
16
- def __init__(
17
- self,
18
- client_address: SocketAddress,
19
- rfile: ta.BinaryIO,
20
- wfile: ta.BinaryIO,
21
- ) -> None:
22
- super().__init__()
23
-
24
- self._client_address = client_address
25
- self._rfile = rfile
26
- self._wfile = wfile
27
-
28
- @abc.abstractmethod
29
- def handle(self) -> None:
30
- raise NotImplementedError
omlish/sockets/io.py ADDED
@@ -0,0 +1,69 @@
1
+ # ruff: noqa: UP006 UP007
2
+ # @omlish-lite
3
+ import io
4
+ import socket
5
+ import typing as ta
6
+
7
+
8
+ ##
9
+
10
+
11
+ class SocketWriter(io.BufferedIOBase):
12
+ """
13
+ Simple writable BufferedIOBase implementation for a socket
14
+
15
+ Does not hold data in a buffer, avoiding any need to call flush().
16
+ """
17
+
18
+ def __init__(self, sock):
19
+ super().__init__()
20
+
21
+ self._sock = sock
22
+
23
+ def writable(self):
24
+ return True
25
+
26
+ def write(self, b):
27
+ self._sock.sendall(b)
28
+ with memoryview(b) as view:
29
+ return view.nbytes
30
+
31
+ def fileno(self):
32
+ return self._sock.fileno()
33
+
34
+
35
+ class SocketIoPair(ta.NamedTuple):
36
+ r: ta.BinaryIO
37
+ w: ta.BinaryIO
38
+
39
+ @classmethod
40
+ def from_socket(
41
+ cls,
42
+ sock: socket.socket,
43
+ *,
44
+ r_buf_size: int = -1,
45
+ w_buf_size: int = 0,
46
+ ) -> 'SocketIoPair':
47
+ rf: ta.Any = sock.makefile('rb', r_buf_size)
48
+
49
+ if w_buf_size:
50
+ wf: ta.Any = SocketWriter(sock)
51
+ else:
52
+ wf = sock.makefile('wb', w_buf_size)
53
+
54
+ return cls(rf, wf)
55
+
56
+
57
+ ##
58
+
59
+
60
+ def close_socket_immediately(sock: socket.socket) -> None:
61
+ try:
62
+ # Explicitly shutdown. socket.close() merely releases the socket and waits for GC to perform the actual close.
63
+ sock.shutdown(socket.SHUT_WR)
64
+
65
+ except OSError:
66
+ # Some platforms may raise ENOTCONN here
67
+ pass
68
+
69
+ sock.close()
File without changes