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.
@@ -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)
@@ -1,24 +0,0 @@
1
- from .secrets import ( # noqa
2
- CachingSecrets,
3
- CompositeSecrets,
4
- EMPTY_SECRETS,
5
- EmptySecrets,
6
- EnvVarSecrets,
7
- FnSecrets,
8
- LoggingSecrets,
9
- MappingSecrets,
10
- Secret,
11
- SecretRef,
12
- SecretRefOrStr,
13
- Secrets,
14
- secret_field,
15
- secret_repr,
16
- )
17
-
18
-
19
- ##
20
-
21
-
22
- from ..lang.imports import _register_conditional_import # noqa
23
-
24
- _register_conditional_import('..marshal', '.marshal', __package__)
omlish/secrets/all.py ADDED
@@ -0,0 +1,16 @@
1
+ from .secrets import ( # noqa
2
+ CachingSecrets,
3
+ CompositeSecrets,
4
+ EMPTY_SECRETS,
5
+ EmptySecrets,
6
+ EnvVarSecrets,
7
+ FnSecrets,
8
+ LoggingSecrets,
9
+ MappingSecrets,
10
+ Secret,
11
+ SecretRef,
12
+ SecretRefOrStr,
13
+ Secrets,
14
+ secret_field,
15
+ secret_repr,
16
+ )
omlish/secrets/secrets.py CHANGED
@@ -335,3 +335,9 @@ class EnvVarSecrets(Secrets):
335
335
  return dct.pop(ekey)
336
336
  else:
337
337
  return dct[ekey]
338
+
339
+
340
+ ##
341
+
342
+
343
+ lang.imports._register_conditional_import('..marshal', '.marshal', __package__) # noqa
@@ -2,11 +2,10 @@
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
- import socket
8
+ import socket as socket_
10
9
  import typing as ta
11
10
 
12
11
 
@@ -20,34 +19,56 @@ SocketAddress = ta.Any
20
19
  class SocketAddressInfoArgs:
21
20
  host: ta.Optional[str]
22
21
  port: ta.Union[str, int, None]
23
- family: socket.AddressFamily = socket.AddressFamily.AF_UNSPEC
22
+ family: socket_.AddressFamily = socket_.AddressFamily.AF_UNSPEC
24
23
  type: int = 0
25
24
  proto: int = 0
26
- flags: socket.AddressInfo = socket.AddressInfo(0)
25
+ flags: socket_.AddressInfo = socket_.AddressInfo(0)
27
26
 
28
27
 
29
28
  @dc.dataclass(frozen=True)
30
29
  class SocketAddressInfo:
31
- family: socket.AddressFamily
30
+ family: socket_.AddressFamily
32
31
  type: int
33
32
  proto: int
34
33
  canonname: ta.Optional[str]
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
- family: ta.Union[int, socket.AddressFamily] = socket.AddressFamily.AF_UNSPEC,
42
- ) -> ta.Tuple[socket.AddressFamily, SocketAddress]:
45
+ family: ta.Union[int, socket_.AddressFamily] = socket_.AddressFamily.AF_UNSPEC,
46
+ ) -> SocketFamilyAndAddress:
43
47
  """https://github.com/python/cpython/commit/f289084c83190cc72db4a70c58f007ec62e75247"""
44
48
 
45
- infos = socket.getaddrinfo(
49
+ infos = socket_.getaddrinfo(
46
50
  host,
47
51
  port,
48
52
  family,
49
- type=socket.SOCK_STREAM,
50
- flags=socket.AI_PASSIVE,
53
+ type=socket_.SOCK_STREAM,
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
63
+
64
+ def wrap_socket(self, fn: ta.Callable[[socket_.socket], socket_.socket]):
65
+ return self._replace(socket=fn(self.socket))
66
+
67
+ @classmethod
68
+ def socket_wrapper(
69
+ cls,
70
+ fn: ta.Callable[[socket_.socket], socket_.socket],
71
+ ) -> ta.Callable[['SocketAndAddress'], 'SocketAndAddress']:
72
+ def inner(conn):
73
+ return conn.wrap_socket(fn)
74
+ return inner
omlish/sockets/bind.py ADDED
@@ -0,0 +1,333 @@
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
+ CanSocketBinderConfig = ta.Union['SocketBinder.Config', int, ta.Tuple[str, int], str] # ta.TypeAlias
24
+ CanSocketBinder = ta.Union['SocketBinder', CanSocketBinderConfig] # ta.TypeAlias
25
+
26
+
27
+ ##
28
+
29
+
30
+ class SocketBinder(abc.ABC, ta.Generic[SocketBinderConfigT]):
31
+ @dc.dataclass(frozen=True)
32
+ class Config:
33
+ listen_backlog: int = 5
34
+
35
+ allow_reuse_address: bool = True
36
+ allow_reuse_port: bool = True
37
+
38
+ set_inheritable: bool = False
39
+
40
+ #
41
+
42
+ @classmethod
43
+ def of(cls, obj: CanSocketBinderConfig) -> 'SocketBinder.Config':
44
+ if isinstance(obj, SocketBinder.Config):
45
+ return obj
46
+
47
+ elif isinstance(obj, int):
48
+ return TcpSocketBinder.Config(
49
+ port=obj,
50
+ )
51
+
52
+ elif isinstance(obj, tuple):
53
+ host, port = obj
54
+ return TcpSocketBinder.Config(
55
+ host=host,
56
+ port=port,
57
+ )
58
+
59
+ elif isinstance(obj, str):
60
+ return UnixSocketBinder.Config(
61
+ file=obj,
62
+ )
63
+
64
+ else:
65
+ raise TypeError(obj)
66
+
67
+ #
68
+
69
+ def __init__(self, config: SocketBinderConfigT) -> None:
70
+ super().__init__()
71
+
72
+ self._config = config
73
+
74
+ #
75
+
76
+ @classmethod
77
+ def of(cls, obj: CanSocketBinder) -> 'SocketBinder':
78
+ if isinstance(obj, SocketBinder):
79
+ return obj
80
+
81
+ config: SocketBinder.Config
82
+ if isinstance(obj, SocketBinder.Config):
83
+ config = obj
84
+
85
+ else:
86
+ config = SocketBinder.Config.of(obj)
87
+
88
+ if isinstance(config, TcpSocketBinder.Config):
89
+ return TcpSocketBinder(config)
90
+
91
+ elif isinstance(config, UnixSocketBinder.Config):
92
+ return UnixSocketBinder(config)
93
+
94
+ else:
95
+ raise TypeError(config)
96
+
97
+ #
98
+
99
+ class Error(RuntimeError):
100
+ pass
101
+
102
+ class NotBoundError(Error):
103
+ pass
104
+
105
+ class AlreadyBoundError(Error):
106
+ pass
107
+
108
+ #
109
+
110
+ @property
111
+ @abc.abstractmethod
112
+ def address_family(self) -> int:
113
+ raise NotImplementedError
114
+
115
+ @property
116
+ @abc.abstractmethod
117
+ def address(self) -> SocketAddress:
118
+ raise NotImplementedError
119
+
120
+ #
121
+
122
+ _socket: socket_.socket
123
+
124
+ @property
125
+ def is_bound(self) -> bool:
126
+ return hasattr(self, '_socket')
127
+
128
+ @property
129
+ def socket(self) -> socket_.socket:
130
+ try:
131
+ return self._socket
132
+ except AttributeError:
133
+ raise self.NotBoundError from None
134
+
135
+ _name: str
136
+
137
+ @property
138
+ def name(self) -> str:
139
+ try:
140
+ return self._name
141
+ except AttributeError:
142
+ raise self.NotBoundError from None
143
+
144
+ _port: ta.Optional[int]
145
+
146
+ @property
147
+ def port(self) -> ta.Optional[int]:
148
+ try:
149
+ return self._port
150
+ except AttributeError:
151
+ raise self.NotBoundError from None
152
+
153
+ #
154
+
155
+ def fileno(self) -> int:
156
+ return self.socket.fileno()
157
+
158
+ #
159
+
160
+ def __enter__(self: SocketBinderT) -> SocketBinderT:
161
+ self.bind()
162
+
163
+ return self
164
+
165
+ def __exit__(self, exc_type, exc_val, exc_tb) -> None:
166
+ self.close()
167
+
168
+ #
169
+
170
+ def _init_socket(self) -> None:
171
+ if hasattr(self, '_socket'):
172
+ raise self.AlreadyBoundError
173
+
174
+ socket = socket_.socket(self.address_family, socket_.SOCK_STREAM)
175
+ self._socket = socket
176
+
177
+ if self._config.allow_reuse_address and hasattr(socket_, 'SO_REUSEADDR'):
178
+ socket.setsockopt(socket_.SOL_SOCKET, socket_.SO_REUSEADDR, 1)
179
+
180
+ # Since Linux 6.12.9, SO_REUSEPORT is not allowed on other address families than AF_INET/AF_INET6.
181
+ if (
182
+ self._config.allow_reuse_port and hasattr(socket_, 'SO_REUSEPORT') and
183
+ self.address_family in (socket_.AF_INET, socket_.AF_INET6)
184
+ ):
185
+ try:
186
+ socket.setsockopt(socket_.SOL_SOCKET, socket_.SO_REUSEPORT, 1)
187
+ except OSError as err:
188
+ if err.errno not in (errno.ENOPROTOOPT, errno.EINVAL):
189
+ raise
190
+
191
+ if self._config.set_inheritable and hasattr(socket, 'set_inheritable'):
192
+ socket.set_inheritable(True)
193
+
194
+ def _pre_bind(self) -> None:
195
+ pass
196
+
197
+ def _post_bind(self) -> None:
198
+ pass
199
+
200
+ def bind(self) -> None:
201
+ self._init_socket()
202
+
203
+ self._pre_bind()
204
+
205
+ self.socket.bind(self.address)
206
+
207
+ self._post_bind()
208
+
209
+ check.state(all(hasattr(self, a) for a in ('_socket', '_name', '_port')))
210
+
211
+ #
212
+
213
+ def close(self) -> None:
214
+ if hasattr(self, '_socket'):
215
+ self._socket.close()
216
+
217
+ #
218
+
219
+ def listen(self) -> None:
220
+ self.socket.listen(self._config.listen_backlog)
221
+
222
+ @abc.abstractmethod
223
+ def accept(self, socket: ta.Optional[socket_.socket] = None) -> SocketAndAddress:
224
+ raise NotImplementedError
225
+
226
+
227
+ ##
228
+
229
+
230
+ class TcpSocketBinder(SocketBinder):
231
+ @dc.dataclass(frozen=True)
232
+ class Config(SocketBinder.Config):
233
+ DEFAULT_HOST: ta.ClassVar[str] = 'localhost'
234
+ host: str = DEFAULT_HOST
235
+
236
+ port: int = 0
237
+
238
+ def __post_init__(self) -> None:
239
+ dataclass_maybe_post_init(super())
240
+ check.non_empty_str(self.host)
241
+ check.isinstance(self.port, int)
242
+ check.arg(self.port > 0)
243
+
244
+ def __init__(self, config: Config) -> None:
245
+ super().__init__(check.isinstance(config, self.Config))
246
+
247
+ self._address = (config.host, config.port)
248
+
249
+ #
250
+
251
+ address_family = socket_.AF_INET
252
+
253
+ @property
254
+ def address(self) -> SocketAddress:
255
+ return self._address
256
+
257
+ #
258
+
259
+ def _post_bind(self) -> None:
260
+ super()._post_bind()
261
+
262
+ host, port, *_ = self.socket.getsockname()
263
+
264
+ self._name = socket_.getfqdn(host)
265
+ self._port = port
266
+
267
+ #
268
+
269
+ def accept(self, socket: ta.Optional[socket_.socket] = None) -> SocketAndAddress:
270
+ if socket is None:
271
+ socket = self.socket
272
+
273
+ conn, client_address = socket.accept()
274
+ return SocketAndAddress(conn, client_address)
275
+
276
+
277
+ ##
278
+
279
+
280
+ class UnixSocketBinder(SocketBinder):
281
+ @dc.dataclass(frozen=True)
282
+ class Config(SocketBinder.Config):
283
+ file: str = ''
284
+
285
+ unlink: bool = False
286
+
287
+ def __post_init__(self) -> None:
288
+ dataclass_maybe_post_init(super())
289
+ check.non_empty_str(self.file)
290
+
291
+ def __init__(self, config: Config) -> None:
292
+ super().__init__(check.isinstance(config, self.Config))
293
+
294
+ self._address = config.file
295
+
296
+ #
297
+
298
+ address_family = socket_.AF_UNIX
299
+
300
+ @property
301
+ def address(self) -> SocketAddress:
302
+ return self._address
303
+
304
+ #
305
+
306
+ def _pre_bind(self) -> None:
307
+ super()._pre_bind()
308
+
309
+ if self._config.unlink:
310
+ try:
311
+ os.unlink(self._config.file)
312
+ except FileNotFoundError:
313
+ pass
314
+
315
+ def _post_bind(self) -> None:
316
+ super()._post_bind()
317
+
318
+ name = self.socket.getsockname()
319
+
320
+ os.chmod(name, stat.S_IRWXU | stat.S_IRWXG) # noqa
321
+
322
+ self._name = name
323
+ self._port = None
324
+
325
+ #
326
+
327
+ def accept(self, sock: ta.Optional[socket_.socket] = None) -> SocketAndAddress:
328
+ if sock is None:
329
+ sock = self.socket
330
+
331
+ conn, _ = sock.accept()
332
+ client_address = ('', 0)
333
+ return SocketAndAddress(conn, client_address)
@@ -4,27 +4,16 @@ import abc
4
4
  import typing as ta
5
5
 
6
6
  from .addresses import SocketAddress
7
+ from .io import SocketIoPair # noqa
7
8
 
8
9
 
9
- SocketHandlerFactory = ta.Callable[[SocketAddress, ta.BinaryIO, ta.BinaryIO], 'SocketHandler']
10
+ SocketHandler = ta.Callable[[SocketAddress, 'SocketIoPair'], None] # ta.TypeAlias
10
11
 
11
12
 
12
13
  ##
13
14
 
14
15
 
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
-
16
+ class SocketHandler_(abc.ABC): # noqa
28
17
  @abc.abstractmethod
29
- def handle(self) -> None:
18
+ def __call__(self, addr: SocketAddress, f: SocketIoPair) -> None:
30
19
  raise NotImplementedError