omlish 0.0.0.dev217__py3-none-any.whl → 0.0.0.dev219__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.
@@ -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