omlish 0.0.0.dev219__py3-none-any.whl → 0.0.0.dev221__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 +2 -2
- omlish/algorithm/__init__.py +0 -0
- omlish/algorithm/all.py +13 -0
- omlish/algorithm/distribute.py +46 -0
- omlish/algorithm/toposort.py +26 -0
- omlish/algorithm/unify.py +31 -0
- omlish/antlr/dot.py +13 -6
- omlish/collections/__init__.py +0 -2
- omlish/collections/utils.py +0 -46
- omlish/dataclasses/__init__.py +1 -0
- omlish/dataclasses/impl/api.py +10 -0
- omlish/dataclasses/impl/fields.py +8 -1
- omlish/dataclasses/impl/init.py +1 -1
- omlish/dataclasses/impl/main.py +1 -1
- omlish/dataclasses/impl/metaclass.py +112 -29
- omlish/dataclasses/impl/overrides.py +53 -0
- omlish/dataclasses/impl/params.py +3 -0
- omlish/dataclasses/impl/reflect.py +17 -5
- omlish/dataclasses/impl/simple.py +0 -42
- omlish/docker/oci/building.py +122 -0
- omlish/docker/oci/data.py +62 -8
- omlish/docker/oci/datarefs.py +98 -0
- omlish/docker/oci/loading.py +120 -0
- omlish/docker/oci/media.py +44 -14
- omlish/docker/oci/repositories.py +72 -0
- omlish/graphs/trees.py +2 -1
- omlish/http/coro/server.py +53 -24
- omlish/http/{simple.py → coro/simple.py} +17 -17
- omlish/http/handlers.py +8 -0
- omlish/io/fileno.py +11 -0
- omlish/lang/__init__.py +4 -1
- omlish/lang/cached.py +0 -1
- omlish/lang/classes/__init__.py +3 -1
- omlish/lang/classes/abstract.py +14 -1
- omlish/lang/classes/restrict.py +5 -5
- omlish/lang/classes/virtual.py +0 -1
- omlish/lang/clsdct.py +0 -1
- omlish/lang/contextmanagers.py +0 -8
- omlish/lang/descriptors.py +0 -1
- omlish/lang/maybes.py +0 -1
- omlish/lang/objects.py +0 -2
- omlish/secrets/ssl.py +9 -0
- omlish/secrets/tempssl.py +50 -0
- omlish/sockets/bind.py +6 -1
- omlish/sockets/server/server.py +18 -5
- omlish/specs/irc/__init__.py +0 -0
- omlish/specs/irc/format/LICENSE +11 -0
- omlish/specs/irc/format/__init__.py +61 -0
- omlish/specs/irc/format/consts.py +6 -0
- omlish/specs/irc/format/errors.py +30 -0
- omlish/specs/irc/format/message.py +18 -0
- omlish/specs/irc/format/nuh.py +52 -0
- omlish/specs/irc/format/parsing.py +155 -0
- omlish/specs/irc/format/rendering.py +150 -0
- omlish/specs/irc/format/tags.py +99 -0
- omlish/specs/irc/format/utils.py +27 -0
- omlish/specs/irc/numerics/__init__.py +0 -0
- omlish/specs/irc/numerics/formats.py +94 -0
- omlish/specs/irc/numerics/numerics.py +808 -0
- omlish/specs/irc/numerics/types.py +59 -0
- {omlish-0.0.0.dev219.dist-info → omlish-0.0.0.dev221.dist-info}/METADATA +1 -1
- {omlish-0.0.0.dev219.dist-info → omlish-0.0.0.dev221.dist-info}/RECORD +66 -38
- {omlish-0.0.0.dev219.dist-info → omlish-0.0.0.dev221.dist-info}/LICENSE +0 -0
- {omlish-0.0.0.dev219.dist-info → omlish-0.0.0.dev221.dist-info}/WHEEL +0 -0
- {omlish-0.0.0.dev219.dist-info → omlish-0.0.0.dev221.dist-info}/entry_points.txt +0 -0
- {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(
|
198
|
+
ts = list(alg.mut_toposort(tsd))
|
198
199
|
root = d(check.single(ts[0]))
|
199
200
|
|
200
201
|
return cls(
|
omlish/http/coro/server.py
CHANGED
@@ -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
|
-
|
421
|
-
gen = self.coro_handle_one()
|
424
|
+
return self._coro_run_handler(self._coro_handle_one())
|
422
425
|
|
423
|
-
|
424
|
-
|
425
|
-
|
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
|
-
|
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
|
-
|
556
|
+
try:
|
557
|
+
# Build internal response
|
534
558
|
|
535
|
-
|
536
|
-
|
559
|
+
response_headers = handler_response.headers or {}
|
560
|
+
response_data = handler_response.data
|
537
561
|
|
538
|
-
|
539
|
-
|
540
|
-
|
562
|
+
headers: ta.List[CoroHttpServer._Header] = [
|
563
|
+
*self._make_default_headers(),
|
564
|
+
]
|
541
565
|
|
542
|
-
|
543
|
-
|
566
|
+
for k, v in response_headers.items():
|
567
|
+
headers.append(self._Header(k, v))
|
544
568
|
|
545
|
-
|
546
|
-
|
569
|
+
if handler_response.close_connection and 'Connection' not in headers:
|
570
|
+
headers.append(self._Header('Connection', 'close'))
|
547
571
|
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
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
|
9
|
-
from
|
10
|
-
from
|
11
|
-
from
|
12
|
-
from
|
13
|
-
from
|
14
|
-
from
|
15
|
-
from
|
16
|
-
from
|
17
|
-
from
|
18
|
-
from
|
19
|
-
from
|
20
|
-
from
|
21
|
-
from
|
22
|
-
from
|
23
|
-
from .
|
24
|
-
from .
|
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
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
|
-
|
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
omlish/lang/classes/__init__.py
CHANGED
@@ -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
|
-
|
15
|
+
FinalTypeError,
|
14
16
|
NoBool,
|
15
17
|
NotInstantiable,
|
16
18
|
NotPicklable,
|
omlish/lang/classes/abstract.py
CHANGED
@@ -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
|
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]]:
|
omlish/lang/classes/restrict.py
CHANGED
@@ -9,7 +9,7 @@ from .abstract import is_abstract
|
|
9
9
|
##
|
10
10
|
|
11
11
|
|
12
|
-
class
|
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
|
31
|
+
raise FinalTypeError(base)
|
32
32
|
elif base is Final:
|
33
33
|
continue
|
34
34
|
elif Final in base.__mro__:
|
35
|
-
raise
|
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
|
43
|
+
raise FinalTypeError(a) from None
|
44
44
|
if is_abstract(v):
|
45
|
-
raise
|
45
|
+
raise FinalTypeError(a)
|
46
46
|
|
47
47
|
|
48
48
|
##
|
omlish/lang/classes/virtual.py
CHANGED
omlish/lang/clsdct.py
CHANGED
omlish/lang/contextmanagers.py
CHANGED
@@ -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
|
|
omlish/lang/descriptors.py
CHANGED
@@ -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
omlish/lang/objects.py
CHANGED
omlish/secrets/ssl.py
ADDED
@@ -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.
|
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
|
|
omlish/sockets/server/server.py
CHANGED
@@ -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
|
-
|
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
|
-
|
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.
|