omlish 0.0.0.dev219__py3-none-any.whl → 0.0.0.dev221__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. omlish/__about__.py +2 -2
  2. omlish/algorithm/__init__.py +0 -0
  3. omlish/algorithm/all.py +13 -0
  4. omlish/algorithm/distribute.py +46 -0
  5. omlish/algorithm/toposort.py +26 -0
  6. omlish/algorithm/unify.py +31 -0
  7. omlish/antlr/dot.py +13 -6
  8. omlish/collections/__init__.py +0 -2
  9. omlish/collections/utils.py +0 -46
  10. omlish/dataclasses/__init__.py +1 -0
  11. omlish/dataclasses/impl/api.py +10 -0
  12. omlish/dataclasses/impl/fields.py +8 -1
  13. omlish/dataclasses/impl/init.py +1 -1
  14. omlish/dataclasses/impl/main.py +1 -1
  15. omlish/dataclasses/impl/metaclass.py +112 -29
  16. omlish/dataclasses/impl/overrides.py +53 -0
  17. omlish/dataclasses/impl/params.py +3 -0
  18. omlish/dataclasses/impl/reflect.py +17 -5
  19. omlish/dataclasses/impl/simple.py +0 -42
  20. omlish/docker/oci/building.py +122 -0
  21. omlish/docker/oci/data.py +62 -8
  22. omlish/docker/oci/datarefs.py +98 -0
  23. omlish/docker/oci/loading.py +120 -0
  24. omlish/docker/oci/media.py +44 -14
  25. omlish/docker/oci/repositories.py +72 -0
  26. omlish/graphs/trees.py +2 -1
  27. omlish/http/coro/server.py +53 -24
  28. omlish/http/{simple.py → coro/simple.py} +17 -17
  29. omlish/http/handlers.py +8 -0
  30. omlish/io/fileno.py +11 -0
  31. omlish/lang/__init__.py +4 -1
  32. omlish/lang/cached.py +0 -1
  33. omlish/lang/classes/__init__.py +3 -1
  34. omlish/lang/classes/abstract.py +14 -1
  35. omlish/lang/classes/restrict.py +5 -5
  36. omlish/lang/classes/virtual.py +0 -1
  37. omlish/lang/clsdct.py +0 -1
  38. omlish/lang/contextmanagers.py +0 -8
  39. omlish/lang/descriptors.py +0 -1
  40. omlish/lang/maybes.py +0 -1
  41. omlish/lang/objects.py +0 -2
  42. omlish/secrets/ssl.py +9 -0
  43. omlish/secrets/tempssl.py +50 -0
  44. omlish/sockets/bind.py +6 -1
  45. omlish/sockets/server/server.py +18 -5
  46. omlish/specs/irc/__init__.py +0 -0
  47. omlish/specs/irc/format/LICENSE +11 -0
  48. omlish/specs/irc/format/__init__.py +61 -0
  49. omlish/specs/irc/format/consts.py +6 -0
  50. omlish/specs/irc/format/errors.py +30 -0
  51. omlish/specs/irc/format/message.py +18 -0
  52. omlish/specs/irc/format/nuh.py +52 -0
  53. omlish/specs/irc/format/parsing.py +155 -0
  54. omlish/specs/irc/format/rendering.py +150 -0
  55. omlish/specs/irc/format/tags.py +99 -0
  56. omlish/specs/irc/format/utils.py +27 -0
  57. omlish/specs/irc/numerics/__init__.py +0 -0
  58. omlish/specs/irc/numerics/formats.py +94 -0
  59. omlish/specs/irc/numerics/numerics.py +808 -0
  60. omlish/specs/irc/numerics/types.py +59 -0
  61. {omlish-0.0.0.dev219.dist-info → omlish-0.0.0.dev221.dist-info}/METADATA +1 -1
  62. {omlish-0.0.0.dev219.dist-info → omlish-0.0.0.dev221.dist-info}/RECORD +66 -38
  63. {omlish-0.0.0.dev219.dist-info → omlish-0.0.0.dev221.dist-info}/LICENSE +0 -0
  64. {omlish-0.0.0.dev219.dist-info → omlish-0.0.0.dev221.dist-info}/WHEEL +0 -0
  65. {omlish-0.0.0.dev219.dist-info → omlish-0.0.0.dev221.dist-info}/entry_points.txt +0 -0
  66. {omlish-0.0.0.dev219.dist-info → omlish-0.0.0.dev221.dist-info}/top_level.txt +0 -0
omlish/graphs/trees.py CHANGED
@@ -10,6 +10,7 @@ from .. import cached
10
10
  from .. import check
11
11
  from .. import collections as col
12
12
  from .. import lang
13
+ from ..algorithm import all as alg
13
14
 
14
15
 
15
16
  T = ta.TypeVar('T')
@@ -194,7 +195,7 @@ class BasicTreeAnalysis(ta.Generic[NodeT]):
194
195
  else:
195
196
  e, d = lang.identity, lang.identity
196
197
  tsd = {e(n): {e(p)} for n, p in parents_by_node.items()}
197
- ts = list(col.mut_toposort(tsd))
198
+ ts = list(alg.mut_toposort(tsd))
198
199
  root = d(check.single(ts[0]))
199
200
 
200
201
  return cls(
@@ -205,6 +205,10 @@ class CoroHttpServer:
205
205
  return h
206
206
  return None
207
207
 
208
+ def close(self) -> None:
209
+ if isinstance(d := self.data, HttpHandlerResponseStreamedData):
210
+ d.close()
211
+
208
212
  #
209
213
 
210
214
  def _build_response_head_bytes(self, a: _Response) -> bytes:
@@ -417,12 +421,20 @@ class CoroHttpServer:
417
421
  #
418
422
 
419
423
  def coro_handle(self) -> ta.Generator[Io, ta.Optional[bytes], None]:
420
- while True:
421
- gen = self.coro_handle_one()
424
+ return self._coro_run_handler(self._coro_handle_one())
422
425
 
423
- o = next(gen)
424
- i: ta.Optional[bytes]
425
- while True:
426
+ def _coro_run_handler(
427
+ self,
428
+ gen: ta.Generator[
429
+ ta.Union[AnyLogIo, AnyReadIo, _Response],
430
+ ta.Optional[bytes],
431
+ None,
432
+ ],
433
+ ) -> ta.Generator[Io, ta.Optional[bytes], None]:
434
+ i: ta.Optional[bytes]
435
+ o: ta.Any = next(gen)
436
+ while True:
437
+ try:
426
438
  if isinstance(o, self.AnyLogIo):
427
439
  i = None
428
440
  yield o
@@ -440,8 +452,13 @@ class CoroHttpServer:
440
452
  for b in self._yield_response_data(r):
441
453
  yield self.WriteIo(b)
442
454
 
455
+ o.close()
456
+ if o.close_connection:
457
+ break
458
+ o = None
459
+
443
460
  else:
444
- raise TypeError(o)
461
+ raise TypeError(o) # noqa
445
462
 
446
463
  try:
447
464
  o = gen.send(i)
@@ -450,7 +467,13 @@ class CoroHttpServer:
450
467
  except StopIteration:
451
468
  break
452
469
 
453
- def coro_handle_one(self) -> ta.Generator[
470
+ except Exception: # noqa
471
+ if hasattr(o, 'close'):
472
+ o.close()
473
+
474
+ raise
475
+
476
+ def _coro_handle_one(self) -> ta.Generator[
454
477
  ta.Union[AnyLogIo, AnyReadIo, _Response],
455
478
  ta.Optional[bytes],
456
479
  None,
@@ -530,28 +553,34 @@ class CoroHttpServer:
530
553
  yield self._build_error_response(err)
531
554
  return
532
555
 
533
- # Build internal response
556
+ try:
557
+ # Build internal response
534
558
 
535
- response_headers = handler_response.headers or {}
536
- response_data = handler_response.data
559
+ response_headers = handler_response.headers or {}
560
+ response_data = handler_response.data
537
561
 
538
- headers: ta.List[CoroHttpServer._Header] = [
539
- *self._make_default_headers(),
540
- ]
562
+ headers: ta.List[CoroHttpServer._Header] = [
563
+ *self._make_default_headers(),
564
+ ]
541
565
 
542
- for k, v in response_headers.items():
543
- headers.append(self._Header(k, v))
566
+ for k, v in response_headers.items():
567
+ headers.append(self._Header(k, v))
544
568
 
545
- if handler_response.close_connection and 'Connection' not in headers:
546
- headers.append(self._Header('Connection', 'close'))
569
+ if handler_response.close_connection and 'Connection' not in headers:
570
+ headers.append(self._Header('Connection', 'close'))
547
571
 
548
- yield self._Response(
549
- version=parsed.version,
550
- code=http.HTTPStatus(handler_response.status),
551
- headers=headers,
552
- data=response_data,
553
- close_connection=handler_response.close_connection,
554
- )
572
+ yield self._Response(
573
+ version=parsed.version,
574
+ code=http.HTTPStatus(handler_response.status),
575
+ headers=headers,
576
+ data=response_data,
577
+ close_connection=handler_response.close_connection,
578
+ )
579
+
580
+ except Exception: # noqa
581
+ handler_response.close()
582
+
583
+ raise
555
584
 
556
585
 
557
586
  ##
@@ -5,23 +5,23 @@ import contextlib
5
5
  import functools
6
6
  import typing as ta
7
7
 
8
- from ..lite.check import check
9
- from ..sockets.addresses import SocketAndAddress
10
- from ..sockets.bind import CanSocketBinder
11
- from ..sockets.bind import SocketBinder
12
- from ..sockets.server.handlers import ExecutorSocketServerHandler
13
- from ..sockets.server.handlers import SocketHandlerSocketServerHandler
14
- from ..sockets.server.handlers import SocketServerHandler
15
- from ..sockets.server.handlers import SocketWrappingSocketServerHandler
16
- from ..sockets.server.handlers import StandardSocketServerHandler
17
- from ..sockets.server.server import SocketServer
18
- from ..sockets.server.threading import ThreadingSocketServerHandler
19
- from .coro.server import CoroHttpServer
20
- from .coro.server import CoroHttpServerSocketHandler
21
- from .handlers import HttpHandler
22
- from .parsing import HttpRequestParser
23
- from .versions import HttpProtocolVersion
24
- from .versions import HttpProtocolVersions
8
+ from ...lite.check import check
9
+ from ...sockets.addresses import SocketAndAddress
10
+ from ...sockets.bind import CanSocketBinder
11
+ from ...sockets.bind import SocketBinder
12
+ from ...sockets.server.handlers import ExecutorSocketServerHandler
13
+ from ...sockets.server.handlers import SocketHandlerSocketServerHandler
14
+ from ...sockets.server.handlers import SocketServerHandler
15
+ from ...sockets.server.handlers import SocketWrappingSocketServerHandler
16
+ from ...sockets.server.handlers import StandardSocketServerHandler
17
+ from ...sockets.server.server import SocketServer
18
+ from ...sockets.server.threading import ThreadingSocketServerHandler
19
+ from ..handlers import HttpHandler
20
+ from ..parsing import HttpRequestParser
21
+ from ..versions import HttpProtocolVersion
22
+ from ..versions import HttpProtocolVersions
23
+ from .server import CoroHttpServer
24
+ from .server import CoroHttpServerSocketHandler
25
25
 
26
26
 
27
27
  if ta.TYPE_CHECKING:
omlish/http/handlers.py CHANGED
@@ -33,12 +33,20 @@ class HttpHandlerResponse:
33
33
  data: ta.Optional[HttpHandlerResponseData] = None
34
34
  close_connection: ta.Optional[bool] = None
35
35
 
36
+ def close(self) -> None:
37
+ if isinstance(d := self.data, HttpHandlerResponseStreamedData):
38
+ d.close()
39
+
36
40
 
37
41
  @dc.dataclass(frozen=True)
38
42
  class HttpHandlerResponseStreamedData:
39
43
  iter: ta.Iterable[bytes]
40
44
  length: ta.Optional[int] = None
41
45
 
46
+ def close(self) -> None:
47
+ if hasattr(d := self.iter, 'close'):
48
+ d.close() # noqa
49
+
42
50
 
43
51
  class HttpHandlerError(Exception):
44
52
  pass
omlish/io/fileno.py ADDED
@@ -0,0 +1,11 @@
1
+ # ruff: noqa: UP006 UP007
2
+ # @omlish-lite
3
+ import dataclasses as dc
4
+
5
+
6
+ @dc.dataclass(frozen=True)
7
+ class Fileno:
8
+ fd: int
9
+
10
+ def fileno(self) -> int:
11
+ return self.fd
omlish/lang/__init__.py CHANGED
@@ -6,11 +6,12 @@ from .cached import ( # noqa
6
6
 
7
7
  from .classes import ( # noqa
8
8
  Abstract,
9
+ AbstractTypeError,
9
10
  AnySensitive,
10
11
  Callable,
11
12
  Descriptor,
12
13
  Final,
13
- FinalError,
14
+ FinalTypeError,
14
15
  LazySingleton,
15
16
  Marker,
16
17
  Namespace,
@@ -26,6 +27,7 @@ from .classes import ( # noqa
26
27
  SimpleMetaDict,
27
28
  Singleton,
28
29
  Virtual,
30
+ get_abstract_methods,
29
31
  is_abstract,
30
32
  is_abstract_class,
31
33
  is_abstract_method,
@@ -52,6 +54,7 @@ from .cmp import ( # noqa
52
54
 
53
55
  from .contextmanagers import ( # noqa
54
56
  AsyncContextManager,
57
+ AsyncExitStacked,
55
58
  ContextManaged,
56
59
  ContextManager,
57
60
  ContextWrapped,
omlish/lang/cached.py CHANGED
@@ -158,7 +158,6 @@ class _CachedFunction(ta.Generic[T]):
158
158
 
159
159
 
160
160
  class _CachedFunctionDescriptor(_CachedFunction[T]):
161
-
162
161
  def __init__(
163
162
  self,
164
163
  fn: ta.Callable[P, T],
@@ -1,5 +1,7 @@
1
1
  from .abstract import ( # noqa
2
2
  Abstract,
3
+ AbstractTypeError,
4
+ get_abstract_methods,
3
5
  is_abstract,
4
6
  is_abstract_class,
5
7
  is_abstract_method,
@@ -10,7 +12,7 @@ from .abstract import ( # noqa
10
12
  from .restrict import ( # noqa
11
13
  AnySensitive,
12
14
  Final,
13
- FinalError,
15
+ FinalTypeError,
14
16
  NoBool,
15
17
  NotInstantiable,
16
18
  NotPicklable,
@@ -11,6 +11,8 @@ _ABSTRACT_METHODS_ATTR = '__abstractmethods__'
11
11
  _IS_ABSTRACT_METHOD_ATTR = '__isabstractmethod__'
12
12
  _FORCE_ABSTRACT_ATTR = '__forceabstract__'
13
13
 
14
+ _INTERNAL_ABSTRACT_ATTRS = frozenset([_FORCE_ABSTRACT_ATTR])
15
+
14
16
 
15
17
  def make_abstract(obj: T) -> T:
16
18
  if callable(obj):
@@ -27,6 +29,10 @@ def make_abstract(obj: T) -> T:
27
29
  return obj
28
30
 
29
31
 
32
+ class AbstractTypeError(TypeError):
33
+ pass
34
+
35
+
30
36
  class Abstract(abc.ABC): # noqa
31
37
  __slots__ = ()
32
38
 
@@ -50,7 +56,7 @@ class Abstract(abc.ABC): # noqa
50
56
  ams.update(set(getattr(b, _ABSTRACT_METHODS_ATTR, [])) - seen)
51
57
  seen.update(dir(b))
52
58
  if ams:
53
- raise TypeError(
59
+ raise AbstractTypeError(
54
60
  f'Cannot subclass abstract class {cls.__name__} with abstract methods: '
55
61
  f'{", ".join(map(str, sorted(ams)))}',
56
62
  )
@@ -78,6 +84,13 @@ def is_abstract(obj: ta.Any) -> bool:
78
84
  return is_abstract_method(obj) or is_abstract_class(obj)
79
85
 
80
86
 
87
+ def get_abstract_methods(cls: type, *, include_internal: bool = False) -> frozenset[str]:
88
+ ms = frozenset(getattr(cls, _ABSTRACT_METHODS_ATTR))
89
+ if not include_internal:
90
+ ms -= _INTERNAL_ABSTRACT_ATTRS
91
+ return ms
92
+
93
+
81
94
  def unabstract_class(
82
95
  members: ta.Iterable[str | tuple[str, ta.Any]],
83
96
  ): # -> ta.Callable[[type[T]], type[T]]:
@@ -9,7 +9,7 @@ from .abstract import is_abstract
9
9
  ##
10
10
 
11
11
 
12
- class FinalError(TypeError):
12
+ class FinalTypeError(TypeError):
13
13
  def __init__(self, _type: type) -> None:
14
14
  super().__init__()
15
15
 
@@ -28,11 +28,11 @@ class Final(Abstract):
28
28
  abstracts: set[ta.Any] = set()
29
29
  for base in cls.__bases__:
30
30
  if base is Abstract:
31
- raise FinalError(base)
31
+ raise FinalTypeError(base)
32
32
  elif base is Final:
33
33
  continue
34
34
  elif Final in base.__mro__:
35
- raise FinalError(base)
35
+ raise FinalTypeError(base)
36
36
  else:
37
37
  abstracts.update(getattr(base, '__abstractmethods__', []))
38
38
 
@@ -40,9 +40,9 @@ class Final(Abstract):
40
40
  try:
41
41
  v = cls.__dict__[a]
42
42
  except KeyError:
43
- raise FinalError(a) from None
43
+ raise FinalTypeError(a) from None
44
44
  if is_abstract(v):
45
- raise FinalError(a)
45
+ raise FinalTypeError(a)
46
46
 
47
47
 
48
48
  ##
@@ -28,7 +28,6 @@ def _make_not_instantiable():
28
28
 
29
29
 
30
30
  class _VirtualMeta(abc.ABCMeta):
31
-
32
31
  def __new__(mcls, name, bases, namespace):
33
32
  if 'Virtual' not in globals():
34
33
  return super().__new__(mcls, name, bases, namespace)
omlish/lang/clsdct.py CHANGED
@@ -36,7 +36,6 @@ def get_caller_cls_dct(offset: int = 0) -> ta.MutableMapping[str, ta.Any]:
36
36
 
37
37
 
38
38
  class ClassDctFn:
39
-
40
39
  def __init__(self, fn: ta.Callable, offset: int | None = None, *, wrap=True) -> None:
41
40
  super().__init__()
42
41
 
@@ -19,7 +19,6 @@ T = ta.TypeVar('T')
19
19
 
20
20
 
21
21
  class ContextManaged:
22
-
23
22
  def __enter__(self) -> ta.Self:
24
23
  return self
25
24
 
@@ -33,7 +32,6 @@ class ContextManaged:
33
32
 
34
33
 
35
34
  class NopContextManaged(ContextManaged):
36
-
37
35
  def __init_subclass__(cls, **kwargs: ta.Any) -> None:
38
36
  raise TypeError
39
37
 
@@ -42,7 +40,6 @@ NOP_CONTEXT_MANAGED = NopContextManaged()
42
40
 
43
41
 
44
42
  class NopContextManager:
45
-
46
43
  def __init_subclass__(cls, **kwargs: ta.Any) -> None:
47
44
  raise TypeError
48
45
 
@@ -57,7 +54,6 @@ NOP_CONTEXT_MANAGER = NopContextManager()
57
54
 
58
55
 
59
56
  class ContextManager(abc.ABC, ta.Generic[T]):
60
-
61
57
  def __init_subclass__(cls, **kwargs: ta.Any) -> None:
62
58
  super().__init_subclass__(**kwargs)
63
59
 
@@ -87,7 +83,6 @@ class ContextManager(abc.ABC, ta.Generic[T]):
87
83
 
88
84
 
89
85
  class AsyncContextManager(abc.ABC, ta.Generic[T]):
90
-
91
86
  def __init_subclass__(cls, **kwargs: ta.Any) -> None:
92
87
  super().__init_subclass__(**kwargs)
93
88
 
@@ -191,7 +186,6 @@ def attr_setting(obj, attr, val, *, default=None): # noqa
191
186
 
192
187
 
193
188
  class ExitStacked:
194
-
195
189
  @property
196
190
  def _exit_stack(self) -> contextlib.ExitStack:
197
191
  try:
@@ -229,7 +223,6 @@ class ExitStacked:
229
223
 
230
224
 
231
225
  class AsyncExitStacked:
232
-
233
226
  @property
234
227
  def _exit_stack(self) -> contextlib.AsyncExitStack:
235
228
  try:
@@ -276,7 +269,6 @@ ContextWrappable: ta.TypeAlias = ta.ContextManager | str | ta.Callable[..., ta.C
276
269
 
277
270
 
278
271
  class ContextWrapped:
279
-
280
272
  def __init__(self, fn: ta.Callable, cm: str | ContextWrappable) -> None:
281
273
  super().__init__()
282
274
 
@@ -197,7 +197,6 @@ decorator = _decorator
197
197
 
198
198
 
199
199
  class AccessForbiddenError(Exception):
200
-
201
200
  def __init__(self, name: str | None = None, *args: ta.Any, **kwargs: ta.Any) -> None:
202
201
  super().__init__(*((name,) if name is not None else ()), *args, **kwargs) # noqa
203
202
  self.name = name
omlish/lang/maybes.py CHANGED
@@ -11,7 +11,6 @@ class ValueNotPresentException(BaseException):
11
11
 
12
12
 
13
13
  class Maybe(abc.ABC, ta.Generic[T]):
14
-
15
14
  @property
16
15
  @abc.abstractmethod
17
16
  def present(self) -> bool:
omlish/lang/objects.py CHANGED
@@ -140,9 +140,7 @@ def dir_dict(o: ta.Any) -> dict[str, ta.Any]:
140
140
 
141
141
 
142
142
  class SimpleProxy(ta.Generic[T]):
143
-
144
143
  class Descriptor:
145
-
146
144
  def __init__(self, attr: str) -> None:
147
145
  super().__init__()
148
146
  self._attr = attr
omlish/secrets/ssl.py ADDED
@@ -0,0 +1,9 @@
1
+ # @omlish-lite
2
+ # ruff: noqa: UP006 UP007
3
+ import dataclasses as dc
4
+
5
+
6
+ @dc.dataclass(frozen=True)
7
+ class SslCert:
8
+ key_file: str
9
+ cert_file: str
@@ -0,0 +1,50 @@
1
+ # @omlish-lite
2
+ # ruff: noqa: UP006 UP007
3
+ import os.path
4
+ import subprocess
5
+ import tempfile
6
+ import typing as ta
7
+
8
+ from .ssl import SslCert
9
+
10
+
11
+ class TempSslCert(ta.NamedTuple):
12
+ cert: SslCert
13
+ temp_dir: str
14
+
15
+
16
+ def generate_temp_localhost_ssl_cert() -> TempSslCert:
17
+ temp_dir = tempfile.mkdtemp()
18
+
19
+ proc = subprocess.run(
20
+ [
21
+ 'openssl',
22
+ 'req',
23
+ '-x509',
24
+ '-newkey', 'rsa:2048',
25
+
26
+ '-keyout', 'key.pem',
27
+ '-out', 'cert.pem',
28
+
29
+ '-days', '365',
30
+
31
+ '-nodes',
32
+
33
+ '-subj', '/CN=localhost',
34
+ '-addext', 'subjectAltName = DNS:localhost,IP:127.0.0.1',
35
+ ],
36
+ cwd=temp_dir,
37
+ capture_output=True,
38
+ check=False,
39
+ )
40
+
41
+ if proc.returncode:
42
+ raise RuntimeError(f'Failed to generate temp ssl cert: {proc.stderr=}')
43
+
44
+ return TempSslCert(
45
+ SslCert(
46
+ key_file=os.path.join(temp_dir, 'key.pem'),
47
+ cert_file=os.path.join(temp_dir, 'cert.pem'),
48
+ ),
49
+ temp_dir,
50
+ )
omlish/sockets/bind.py CHANGED
@@ -2,7 +2,10 @@
2
2
  # @omlish-lite
3
3
  """
4
4
  TODO:
5
+ - def parse: (<bind>)?:<port>, unix://, fd://
6
+ - unix chown/chgrp
5
7
  - DupSocketBinder
8
+ - udp
6
9
  """
7
10
  import abc
8
11
  import dataclasses as dc
@@ -20,6 +23,7 @@ from omlish.sockets.addresses import SocketAndAddress
20
23
 
21
24
  SocketBinderT = ta.TypeVar('SocketBinderT', bound='SocketBinder')
22
25
  SocketBinderConfigT = ta.TypeVar('SocketBinderConfigT', bound='SocketBinder.Config')
26
+
23
27
  CanSocketBinderConfig = ta.Union['SocketBinder.Config', int, ta.Tuple[str, int], str] # ta.TypeAlias
24
28
  CanSocketBinder = ta.Union['SocketBinder', CanSocketBinderConfig] # ta.TypeAlias
25
29
 
@@ -308,7 +312,8 @@ class UnixSocketBinder(SocketBinder):
308
312
 
309
313
  if self._config.unlink:
310
314
  try:
311
- os.unlink(self._config.file)
315
+ if stat.S_ISSOCK(os.stat(self._config.file).st_mode):
316
+ os.unlink(self._config.file)
312
317
  except FileNotFoundError:
313
318
  pass
314
319
 
@@ -2,10 +2,12 @@
2
2
  # ruff: noqa: UP006 UP007
3
3
  import abc
4
4
  import contextlib
5
+ import logging
5
6
  import selectors
6
7
  import threading
7
8
  import typing as ta
8
9
 
10
+ from ..addresses import SocketAndAddress
9
11
  from ..bind import SocketBinder
10
12
  from ..io import close_socket_immediately
11
13
  from .handlers import SocketServerHandler
@@ -15,12 +17,15 @@ from .handlers import SocketServerHandler
15
17
 
16
18
 
17
19
  class SocketServer(abc.ABC):
20
+ _DEFAULT_LOGGER = logging.getLogger('.'.join([__name__, 'SocketServer']))
21
+
18
22
  def __init__(
19
23
  self,
20
24
  binder: SocketBinder,
21
25
  handler: SocketServerHandler,
22
26
  *,
23
- on_error: ta.Optional[ta.Callable[[BaseException], None]] = None,
27
+ on_error: ta.Optional[ta.Callable[[BaseException, ta.Optional[SocketAndAddress]], None]] = None,
28
+ error_logger: ta.Optional[logging.Logger] = _DEFAULT_LOGGER,
24
29
  poll_interval: float = .5,
25
30
  shutdown_timeout: ta.Optional[float] = None,
26
31
  ) -> None:
@@ -29,6 +34,7 @@ class SocketServer(abc.ABC):
29
34
  self._binder = binder
30
35
  self._handler = handler
31
36
  self._on_error = on_error
37
+ self._error_logger = error_logger
32
38
  self._poll_interval = poll_interval
33
39
  self._shutdown_timeout = shutdown_timeout
34
40
 
@@ -46,6 +52,15 @@ class SocketServer(abc.ABC):
46
52
 
47
53
  #
48
54
 
55
+ def _handle_error(self, exc: BaseException, conn: ta.Optional[SocketAndAddress] = None) -> None:
56
+ if (error_logger := self._error_logger) is not None:
57
+ error_logger.exception('Error in socket server: %r', conn)
58
+
59
+ if (on_error := self._on_error) is not None:
60
+ on_error(exc, conn)
61
+
62
+ #
63
+
49
64
  class SelectorProtocol(ta.Protocol):
50
65
  def register(self, *args, **kwargs) -> None:
51
66
  raise NotImplementedError
@@ -100,8 +115,7 @@ class SocketServer(abc.ABC):
100
115
  conn = self._binder.accept()
101
116
 
102
117
  except OSError as exc:
103
- if (on_error := self._on_error) is not None:
104
- on_error(exc)
118
+ self._handle_error(exc)
105
119
 
106
120
  return
107
121
 
@@ -109,8 +123,7 @@ class SocketServer(abc.ABC):
109
123
  self._handler(conn)
110
124
 
111
125
  except Exception as exc: # noqa
112
- if (on_error := self._on_error) is not None:
113
- on_error(exc)
126
+ self._handle_error(exc, conn)
114
127
 
115
128
  close_socket_immediately(conn.socket)
116
129
 
File without changes
@@ -0,0 +1,11 @@
1
+ Copyright (c) 2016-2021 Daniel Oaks
2
+ Copyright (c) 2018-2021 Shivaram Lingamneni
3
+
4
+ Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted,
5
+ provided that the above copyright notice and this permission notice appear in all copies.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
8
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
9
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
10
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
11
+ THIS SOFTWARE.