omlish 0.0.0.dev128__py3-none-any.whl → 0.0.0.dev130__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/lite/check.py +37 -0
- omlish/lite/fdio/__init__.py +0 -0
- omlish/lite/fdio/corohttp.py +135 -0
- omlish/lite/fdio/handlers.py +67 -0
- omlish/lite/fdio/kqueue.py +102 -0
- omlish/lite/fdio/manager.py +48 -0
- omlish/lite/fdio/pollers.py +212 -0
- omlish/lite/inject.py +127 -16
- omlish/lite/io.py +98 -0
- {omlish-0.0.0.dev128.dist-info → omlish-0.0.0.dev130.dist-info}/METADATA +1 -1
- {omlish-0.0.0.dev128.dist-info → omlish-0.0.0.dev130.dist-info}/RECORD +16 -10
- {omlish-0.0.0.dev128.dist-info → omlish-0.0.0.dev130.dist-info}/LICENSE +0 -0
- {omlish-0.0.0.dev128.dist-info → omlish-0.0.0.dev130.dist-info}/WHEEL +0 -0
- {omlish-0.0.0.dev128.dist-info → omlish-0.0.0.dev130.dist-info}/entry_points.txt +0 -0
- {omlish-0.0.0.dev128.dist-info → omlish-0.0.0.dev130.dist-info}/top_level.txt +0 -0
omlish/__about__.py
CHANGED
omlish/lite/check.py
CHANGED
@@ -3,6 +3,7 @@ import typing as ta
|
|
3
3
|
|
4
4
|
|
5
5
|
T = ta.TypeVar('T')
|
6
|
+
SizedT = ta.TypeVar('SizedT', bound=ta.Sized)
|
6
7
|
|
7
8
|
|
8
9
|
def check_isinstance(v: ta.Any, spec: ta.Union[ta.Type[T], tuple]) -> T:
|
@@ -57,6 +58,42 @@ def check_not_equal(l: T, r: T) -> T:
|
|
57
58
|
return l
|
58
59
|
|
59
60
|
|
61
|
+
def check_is(l: T, r: T) -> T:
|
62
|
+
if l is not r:
|
63
|
+
raise ValueError(l, r)
|
64
|
+
return l
|
65
|
+
|
66
|
+
|
67
|
+
def check_is_not(l: T, r: ta.Any) -> T:
|
68
|
+
if l is r:
|
69
|
+
raise ValueError(l, r)
|
70
|
+
return l
|
71
|
+
|
72
|
+
|
73
|
+
def check_in(v: T, c: ta.Container[T]) -> T:
|
74
|
+
if v not in c:
|
75
|
+
raise ValueError(v, c)
|
76
|
+
return v
|
77
|
+
|
78
|
+
|
79
|
+
def check_not_in(v: T, c: ta.Container[T]) -> T:
|
80
|
+
if v in c:
|
81
|
+
raise ValueError(v, c)
|
82
|
+
return v
|
83
|
+
|
84
|
+
|
60
85
|
def check_single(vs: ta.Iterable[T]) -> T:
|
61
86
|
[v] = vs
|
62
87
|
return v
|
88
|
+
|
89
|
+
|
90
|
+
def check_empty(v: SizedT) -> SizedT:
|
91
|
+
if len(v):
|
92
|
+
raise ValueError(v)
|
93
|
+
return v
|
94
|
+
|
95
|
+
|
96
|
+
def check_non_empty(v: SizedT) -> SizedT:
|
97
|
+
if not len(v):
|
98
|
+
raise ValueError(v)
|
99
|
+
return v
|
File without changes
|
@@ -0,0 +1,135 @@
|
|
1
|
+
import socket
|
2
|
+
import typing as ta
|
3
|
+
|
4
|
+
from ..check import check_isinstance
|
5
|
+
from ..check import check_none
|
6
|
+
from ..check import check_not_none
|
7
|
+
from ..check import check_state
|
8
|
+
from ..http.coroserver import CoroHttpServer
|
9
|
+
from ..http.handlers import HttpHandler
|
10
|
+
from ..io import IncrementalWriteBuffer
|
11
|
+
from ..io import ReadableListBuffer
|
12
|
+
from ..socket import SocketAddress
|
13
|
+
from .handlers import SocketFdIoHandler
|
14
|
+
|
15
|
+
|
16
|
+
class CoroHttpServerConnectionFdIoHandler(SocketFdIoHandler):
|
17
|
+
def __init__(
|
18
|
+
self,
|
19
|
+
addr: SocketAddress,
|
20
|
+
sock: socket.socket,
|
21
|
+
handler: HttpHandler,
|
22
|
+
*,
|
23
|
+
read_size: int = 0x10000,
|
24
|
+
write_size: int = 0x10000,
|
25
|
+
) -> None:
|
26
|
+
check_state(not sock.getblocking())
|
27
|
+
|
28
|
+
super().__init__(addr, sock)
|
29
|
+
|
30
|
+
self._handler = handler
|
31
|
+
self._read_size = read_size
|
32
|
+
self._write_size = write_size
|
33
|
+
|
34
|
+
self._read_buf = ReadableListBuffer()
|
35
|
+
self._write_buf: IncrementalWriteBuffer | None = None
|
36
|
+
|
37
|
+
self._coro_srv = CoroHttpServer(
|
38
|
+
addr,
|
39
|
+
handler=self._handler,
|
40
|
+
)
|
41
|
+
self._srv_coro: ta.Optional[ta.Generator[CoroHttpServer.Io, ta.Optional[bytes], None]] = self._coro_srv.coro_handle() # noqa
|
42
|
+
|
43
|
+
self._cur_io: CoroHttpServer.Io | None = None
|
44
|
+
self._next_io()
|
45
|
+
|
46
|
+
#
|
47
|
+
|
48
|
+
def _next_io(self) -> None: # noqa
|
49
|
+
coro = check_not_none(self._srv_coro)
|
50
|
+
|
51
|
+
d: bytes | None = None
|
52
|
+
o = self._cur_io
|
53
|
+
while True:
|
54
|
+
if o is None:
|
55
|
+
try:
|
56
|
+
if d is not None:
|
57
|
+
o = coro.send(d)
|
58
|
+
d = None
|
59
|
+
else:
|
60
|
+
o = next(coro)
|
61
|
+
except StopIteration:
|
62
|
+
self.close()
|
63
|
+
o = None
|
64
|
+
break
|
65
|
+
|
66
|
+
if isinstance(o, CoroHttpServer.AnyLogIo):
|
67
|
+
print(o)
|
68
|
+
o = None
|
69
|
+
|
70
|
+
elif isinstance(o, CoroHttpServer.ReadIo):
|
71
|
+
if (d := self._read_buf.read(o.sz)) is None:
|
72
|
+
break
|
73
|
+
o = None
|
74
|
+
|
75
|
+
elif isinstance(o, CoroHttpServer.ReadLineIo):
|
76
|
+
if (d := self._read_buf.read_until(b'\n')) is None:
|
77
|
+
break
|
78
|
+
o = None
|
79
|
+
|
80
|
+
elif isinstance(o, CoroHttpServer.WriteIo):
|
81
|
+
check_none(self._write_buf)
|
82
|
+
self._write_buf = IncrementalWriteBuffer(o.data, write_size=self._write_size)
|
83
|
+
break
|
84
|
+
|
85
|
+
else:
|
86
|
+
raise TypeError(o)
|
87
|
+
|
88
|
+
self._cur_io = o
|
89
|
+
|
90
|
+
#
|
91
|
+
|
92
|
+
def readable(self) -> bool:
|
93
|
+
return True
|
94
|
+
|
95
|
+
def writable(self) -> bool:
|
96
|
+
return self._write_buf is not None
|
97
|
+
|
98
|
+
#
|
99
|
+
|
100
|
+
def on_readable(self) -> None:
|
101
|
+
try:
|
102
|
+
buf = check_not_none(self._sock).recv(self._read_size)
|
103
|
+
except BlockingIOError:
|
104
|
+
return
|
105
|
+
except ConnectionResetError:
|
106
|
+
self.close()
|
107
|
+
return
|
108
|
+
if not buf:
|
109
|
+
self.close()
|
110
|
+
return
|
111
|
+
|
112
|
+
self._read_buf.feed(buf)
|
113
|
+
|
114
|
+
if isinstance(self._cur_io, CoroHttpServer.AnyReadIo):
|
115
|
+
self._next_io()
|
116
|
+
|
117
|
+
def on_writable(self) -> None:
|
118
|
+
check_isinstance(self._cur_io, CoroHttpServer.WriteIo)
|
119
|
+
wb = check_not_none(self._write_buf)
|
120
|
+
while wb.rem > 0:
|
121
|
+
def send(d: bytes) -> int:
|
122
|
+
try:
|
123
|
+
return check_not_none(self._sock).send(d)
|
124
|
+
except ConnectionResetError:
|
125
|
+
self.close()
|
126
|
+
return 0
|
127
|
+
except BlockingIOError:
|
128
|
+
return 0
|
129
|
+
if not wb.write(send):
|
130
|
+
break
|
131
|
+
|
132
|
+
if wb.rem < 1:
|
133
|
+
self._write_buf = None
|
134
|
+
self._cur_io = None
|
135
|
+
self._next_io()
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
import abc
|
3
|
+
import socket
|
4
|
+
import typing as ta
|
5
|
+
|
6
|
+
from ..check import check_not_none
|
7
|
+
from ..socket import SocketAddress
|
8
|
+
|
9
|
+
|
10
|
+
class FdIoHandler(abc.ABC):
|
11
|
+
@abc.abstractmethod
|
12
|
+
def fd(self) -> int:
|
13
|
+
raise NotImplementedError
|
14
|
+
|
15
|
+
#
|
16
|
+
|
17
|
+
@property
|
18
|
+
@abc.abstractmethod
|
19
|
+
def closed(self) -> bool:
|
20
|
+
raise NotImplementedError
|
21
|
+
|
22
|
+
@abc.abstractmethod
|
23
|
+
def close(self) -> None:
|
24
|
+
raise NotImplementedError
|
25
|
+
|
26
|
+
#
|
27
|
+
|
28
|
+
def readable(self) -> bool:
|
29
|
+
return False
|
30
|
+
|
31
|
+
def writable(self) -> bool:
|
32
|
+
return False
|
33
|
+
|
34
|
+
#
|
35
|
+
|
36
|
+
def on_readable(self) -> None:
|
37
|
+
raise TypeError
|
38
|
+
|
39
|
+
def on_writable(self) -> None:
|
40
|
+
raise TypeError
|
41
|
+
|
42
|
+
def on_error(self, exc: ta.Optional[BaseException] = None) -> None: # noqa
|
43
|
+
pass
|
44
|
+
|
45
|
+
|
46
|
+
class SocketFdIoHandler(FdIoHandler, abc.ABC):
|
47
|
+
def __init__(
|
48
|
+
self,
|
49
|
+
addr: SocketAddress,
|
50
|
+
sock: socket.socket,
|
51
|
+
) -> None:
|
52
|
+
super().__init__()
|
53
|
+
|
54
|
+
self._addr = addr
|
55
|
+
self._sock: ta.Optional[socket.socket] = sock
|
56
|
+
|
57
|
+
def fd(self) -> int:
|
58
|
+
return check_not_none(self._sock).fileno()
|
59
|
+
|
60
|
+
@property
|
61
|
+
def closed(self) -> bool:
|
62
|
+
return self._sock is None
|
63
|
+
|
64
|
+
def close(self) -> None:
|
65
|
+
if self._sock is not None:
|
66
|
+
self._sock.close()
|
67
|
+
self._sock = None
|
@@ -0,0 +1,102 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
import errno
|
3
|
+
import select
|
4
|
+
import sys
|
5
|
+
import typing as ta
|
6
|
+
|
7
|
+
from .pollers import FdIoPoller
|
8
|
+
|
9
|
+
|
10
|
+
KqueueFdIoPoller: ta.Optional[ta.Type[FdIoPoller]]
|
11
|
+
if sys.platform == 'darwin' or sys.platform.startswith('freebsd'):
|
12
|
+
|
13
|
+
class _KqueueFdIoPoller(FdIoPoller):
|
14
|
+
DEFAULT_MAX_EVENTS = 1000
|
15
|
+
|
16
|
+
def __init__(
|
17
|
+
self,
|
18
|
+
*,
|
19
|
+
max_events: int = DEFAULT_MAX_EVENTS,
|
20
|
+
) -> None:
|
21
|
+
super().__init__()
|
22
|
+
|
23
|
+
self._max_events = max_events
|
24
|
+
|
25
|
+
self._kqueue: ta.Optional[ta.Any] = None
|
26
|
+
|
27
|
+
#
|
28
|
+
|
29
|
+
def _get_kqueue(self) -> 'select.kqueue':
|
30
|
+
if (kq := self._kqueue) is not None:
|
31
|
+
return kq
|
32
|
+
kq = select.kqueue()
|
33
|
+
self._kqueue = kq
|
34
|
+
return kq
|
35
|
+
|
36
|
+
def close(self) -> None:
|
37
|
+
if self._kqueue is not None:
|
38
|
+
self._kqueue.close()
|
39
|
+
self._kqueue = None
|
40
|
+
|
41
|
+
def reopen(self) -> None:
|
42
|
+
for fd in self._readable:
|
43
|
+
self._register_readable(fd)
|
44
|
+
for fd in self._writable:
|
45
|
+
self._register_writable(fd)
|
46
|
+
|
47
|
+
#
|
48
|
+
|
49
|
+
def _register_readable(self, fd: int) -> None:
|
50
|
+
self._control(fd, select.KQ_FILTER_READ, select.KQ_EV_ADD)
|
51
|
+
|
52
|
+
def _register_writable(self, fd: int) -> None:
|
53
|
+
self._control(fd, select.KQ_FILTER_WRITE, select.KQ_EV_ADD)
|
54
|
+
|
55
|
+
def _unregister_readable(self, fd: int) -> None:
|
56
|
+
self._control(fd, select.KQ_FILTER_READ, select.KQ_EV_DELETE)
|
57
|
+
|
58
|
+
def _unregister_writable(self, fd: int) -> None:
|
59
|
+
self._control(fd, select.KQ_FILTER_WRITE, select.KQ_EV_DELETE)
|
60
|
+
|
61
|
+
def _control(self, fd: int, filter: int, flags: int) -> None: # noqa
|
62
|
+
ke = select.kevent(fd, filter=filter, flags=flags)
|
63
|
+
kq = self._get_kqueue()
|
64
|
+
try:
|
65
|
+
kq.control([ke], 0)
|
66
|
+
|
67
|
+
except OSError as exc:
|
68
|
+
if exc.errno == errno.EBADF:
|
69
|
+
# log.debug('EBADF encountered in kqueue. Invalid file descriptor %s', ke.ident)
|
70
|
+
pass
|
71
|
+
elif exc.errno == errno.ENOENT:
|
72
|
+
# Can happen when trying to remove an already closed socket
|
73
|
+
pass
|
74
|
+
else:
|
75
|
+
raise
|
76
|
+
|
77
|
+
#
|
78
|
+
|
79
|
+
def poll(self, timeout: ta.Optional[float]) -> FdIoPoller.PollResult:
|
80
|
+
kq = self._get_kqueue()
|
81
|
+
try:
|
82
|
+
kes = kq.control(None, self._max_events, timeout)
|
83
|
+
|
84
|
+
except OSError as exc:
|
85
|
+
if exc.errno == errno.EINTR:
|
86
|
+
return FdIoPoller.PollResult(msg='EINTR encountered in poll', exc=exc)
|
87
|
+
else:
|
88
|
+
raise
|
89
|
+
|
90
|
+
r: ta.List[int] = []
|
91
|
+
w: ta.List[int] = []
|
92
|
+
for ke in kes:
|
93
|
+
if ke.filter == select.KQ_FILTER_READ:
|
94
|
+
r.append(ke.ident)
|
95
|
+
if ke.filter == select.KQ_FILTER_WRITE:
|
96
|
+
w.append(ke.ident)
|
97
|
+
|
98
|
+
return FdIoPoller.PollResult(r, w)
|
99
|
+
|
100
|
+
KqueueFdIoPoller = _KqueueFdIoPoller
|
101
|
+
else:
|
102
|
+
KqueueFdIoPoller = None
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
import typing as ta
|
3
|
+
|
4
|
+
from .handlers import FdIoHandler
|
5
|
+
from .pollers import FdIoPoller
|
6
|
+
|
7
|
+
|
8
|
+
class FdIoManager:
|
9
|
+
def __init__(
|
10
|
+
self,
|
11
|
+
poller: FdIoPoller,
|
12
|
+
) -> None:
|
13
|
+
super().__init__()
|
14
|
+
|
15
|
+
self._poller = poller
|
16
|
+
|
17
|
+
self._handlers: ta.Dict[int, FdIoHandler] = {} # Preserves insertion order
|
18
|
+
|
19
|
+
def register(self, h: FdIoHandler) -> None:
|
20
|
+
if (hid := id(h)) in self._handlers:
|
21
|
+
raise KeyError(h)
|
22
|
+
self._handlers[hid] = h
|
23
|
+
|
24
|
+
def unregister(self, h: FdIoHandler) -> None:
|
25
|
+
del self._handlers[id(h)]
|
26
|
+
|
27
|
+
def poll(self, *, timeout: float = 1.) -> None:
|
28
|
+
hs = list(self._handlers.values())
|
29
|
+
rd = {h.fd(): h for h in hs if h.readable()}
|
30
|
+
wd = {h.fd(): h for h in hs if h.writable()}
|
31
|
+
|
32
|
+
self._poller.update(set(rd), set(wd))
|
33
|
+
|
34
|
+
pr = self._poller.poll(timeout)
|
35
|
+
|
36
|
+
for f in pr.r:
|
37
|
+
if not (h := rd[f]).closed:
|
38
|
+
h.on_readable()
|
39
|
+
for f in pr.w:
|
40
|
+
if not (h := wd[f]).closed:
|
41
|
+
h.on_writable()
|
42
|
+
|
43
|
+
hs = list(self._handlers.values())
|
44
|
+
nh = {}
|
45
|
+
for h in hs:
|
46
|
+
if not h.closed:
|
47
|
+
nh[id(h)] = h
|
48
|
+
self._handlers = nh
|
@@ -0,0 +1,212 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
import abc
|
3
|
+
import dataclasses as dc
|
4
|
+
import errno
|
5
|
+
import select
|
6
|
+
import typing as ta
|
7
|
+
|
8
|
+
|
9
|
+
##
|
10
|
+
|
11
|
+
|
12
|
+
class FdIoPoller(abc.ABC):
|
13
|
+
def __init__(self) -> None:
|
14
|
+
super().__init__()
|
15
|
+
|
16
|
+
self._readable: ta.Set[int] = set()
|
17
|
+
self._writable: ta.Set[int] = set()
|
18
|
+
|
19
|
+
#
|
20
|
+
|
21
|
+
def close(self) -> None: # noqa
|
22
|
+
pass
|
23
|
+
|
24
|
+
def reopen(self) -> None: # noqa
|
25
|
+
pass
|
26
|
+
|
27
|
+
#
|
28
|
+
|
29
|
+
@property
|
30
|
+
@ta.final
|
31
|
+
def readable(self) -> ta.AbstractSet[int]:
|
32
|
+
return self._readable
|
33
|
+
|
34
|
+
@property
|
35
|
+
@ta.final
|
36
|
+
def writable(self) -> ta.AbstractSet[int]:
|
37
|
+
return self._writable
|
38
|
+
|
39
|
+
#
|
40
|
+
|
41
|
+
@ta.final
|
42
|
+
def register_readable(self, fd: int) -> bool:
|
43
|
+
if fd in self._readable:
|
44
|
+
return False
|
45
|
+
self._readable.add(fd)
|
46
|
+
self._register_readable(fd)
|
47
|
+
return True
|
48
|
+
|
49
|
+
@ta.final
|
50
|
+
def register_writable(self, fd: int) -> bool:
|
51
|
+
if fd in self._writable:
|
52
|
+
return False
|
53
|
+
self._writable.add(fd)
|
54
|
+
self._register_writable(fd)
|
55
|
+
return True
|
56
|
+
|
57
|
+
@ta.final
|
58
|
+
def unregister_readable(self, fd: int) -> bool:
|
59
|
+
if fd not in self._readable:
|
60
|
+
return False
|
61
|
+
self._readable.discard(fd)
|
62
|
+
self._unregister_readable(fd)
|
63
|
+
return True
|
64
|
+
|
65
|
+
@ta.final
|
66
|
+
def unregister_writable(self, fd: int) -> bool:
|
67
|
+
if fd not in self._writable:
|
68
|
+
return False
|
69
|
+
self._writable.discard(fd)
|
70
|
+
self._unregister_writable(fd)
|
71
|
+
return True
|
72
|
+
|
73
|
+
#
|
74
|
+
|
75
|
+
def _register_readable(self, fd: int) -> None: # noqa
|
76
|
+
pass
|
77
|
+
|
78
|
+
def _register_writable(self, fd: int) -> None: # noqa
|
79
|
+
pass
|
80
|
+
|
81
|
+
def _unregister_readable(self, fd: int) -> None: # noqa
|
82
|
+
pass
|
83
|
+
|
84
|
+
def _unregister_writable(self, fd: int) -> None: # noqa
|
85
|
+
pass
|
86
|
+
|
87
|
+
#
|
88
|
+
|
89
|
+
def update(
|
90
|
+
self,
|
91
|
+
r: ta.AbstractSet[int],
|
92
|
+
w: ta.AbstractSet[int],
|
93
|
+
) -> None:
|
94
|
+
for f in r - self._readable:
|
95
|
+
self.register_readable(f)
|
96
|
+
for f in w - self._writable:
|
97
|
+
self.register_writable(f)
|
98
|
+
for f in self._readable - r:
|
99
|
+
self.unregister_readable(f)
|
100
|
+
for f in self._writable - w:
|
101
|
+
self.unregister_writable(f)
|
102
|
+
|
103
|
+
#
|
104
|
+
|
105
|
+
@dc.dataclass(frozen=True)
|
106
|
+
class PollResult:
|
107
|
+
r: ta.Sequence[int] = ()
|
108
|
+
w: ta.Sequence[int] = ()
|
109
|
+
|
110
|
+
inv: ta.Sequence[int] = ()
|
111
|
+
|
112
|
+
msg: ta.Optional[str] = None
|
113
|
+
exc: ta.Optional[BaseException] = None
|
114
|
+
|
115
|
+
@abc.abstractmethod
|
116
|
+
def poll(self, timeout: ta.Optional[float]) -> PollResult:
|
117
|
+
raise NotImplementedError
|
118
|
+
|
119
|
+
|
120
|
+
##
|
121
|
+
|
122
|
+
|
123
|
+
class SelectFdIoPoller(FdIoPoller):
|
124
|
+
def poll(self, timeout: ta.Optional[float]) -> FdIoPoller.PollResult:
|
125
|
+
try:
|
126
|
+
r, w, x = select.select(
|
127
|
+
self._readable,
|
128
|
+
self._writable,
|
129
|
+
[],
|
130
|
+
timeout,
|
131
|
+
)
|
132
|
+
|
133
|
+
except OSError as exc:
|
134
|
+
if exc.errno == errno.EINTR:
|
135
|
+
return FdIoPoller.PollResult(msg='EINTR encountered in poll', exc=exc)
|
136
|
+
elif exc.errno == errno.EBADF:
|
137
|
+
return FdIoPoller.PollResult(msg='EBADF encountered in poll', exc=exc)
|
138
|
+
else:
|
139
|
+
raise
|
140
|
+
|
141
|
+
return FdIoPoller.PollResult(r, w)
|
142
|
+
|
143
|
+
|
144
|
+
##
|
145
|
+
|
146
|
+
|
147
|
+
PollFdIoPoller: ta.Optional[ta.Type[FdIoPoller]]
|
148
|
+
if hasattr(select, 'poll'):
|
149
|
+
|
150
|
+
class _PollFdIoPoller(FdIoPoller):
|
151
|
+
def __init__(self) -> None:
|
152
|
+
super().__init__()
|
153
|
+
|
154
|
+
self._poller = select.poll()
|
155
|
+
|
156
|
+
#
|
157
|
+
|
158
|
+
_READ = select.POLLIN | select.POLLPRI | select.POLLHUP
|
159
|
+
_WRITE = select.POLLOUT
|
160
|
+
|
161
|
+
def _register_readable(self, fd: int) -> None:
|
162
|
+
self._update_registration(fd)
|
163
|
+
|
164
|
+
def _register_writable(self, fd: int) -> None:
|
165
|
+
self._update_registration(fd)
|
166
|
+
|
167
|
+
def _unregister_readable(self, fd: int) -> None:
|
168
|
+
self._update_registration(fd)
|
169
|
+
|
170
|
+
def _unregister_writable(self, fd: int) -> None:
|
171
|
+
self._update_registration(fd)
|
172
|
+
|
173
|
+
def _update_registration(self, fd: int) -> None:
|
174
|
+
r = fd in self._readable
|
175
|
+
w = fd in self._writable
|
176
|
+
if r or w:
|
177
|
+
self._poller.register(fd, (self._READ if r else 0) | (self._WRITE if w else 0))
|
178
|
+
else:
|
179
|
+
self._poller.unregister(fd)
|
180
|
+
|
181
|
+
#
|
182
|
+
|
183
|
+
def poll(self, timeout: ta.Optional[float]) -> FdIoPoller.PollResult:
|
184
|
+
polled: ta.List[ta.Tuple[int, int]]
|
185
|
+
try:
|
186
|
+
polled = self._poller.poll(timeout * 1000 if timeout is not None else None)
|
187
|
+
|
188
|
+
except OSError as exc:
|
189
|
+
if exc.errno == errno.EINTR:
|
190
|
+
return FdIoPoller.PollResult(msg='EINTR encountered in poll', exc=exc)
|
191
|
+
else:
|
192
|
+
raise
|
193
|
+
|
194
|
+
r: ta.List[int] = []
|
195
|
+
w: ta.List[int] = []
|
196
|
+
inv: ta.List[int] = []
|
197
|
+
for fd, mask in polled:
|
198
|
+
if mask & select.POLLNVAL:
|
199
|
+
self._poller.unregister(fd)
|
200
|
+
self._readable.discard(fd)
|
201
|
+
self._writable.discard(fd)
|
202
|
+
inv.append(fd)
|
203
|
+
continue
|
204
|
+
if mask & self._READ:
|
205
|
+
r.append(fd)
|
206
|
+
if mask & self._WRITE:
|
207
|
+
w.append(fd)
|
208
|
+
return FdIoPoller.PollResult(r, w, inv=inv)
|
209
|
+
|
210
|
+
PollFdIoPoller = _PollFdIoPoller
|
211
|
+
else:
|
212
|
+
PollFdIoPoller = None
|
omlish/lite/inject.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# ruff: noqa: UP006 UP007
|
2
2
|
import abc
|
3
|
+
import contextlib
|
3
4
|
import dataclasses as dc
|
4
5
|
import functools
|
5
6
|
import inspect
|
@@ -7,7 +8,9 @@ import types
|
|
7
8
|
import typing as ta
|
8
9
|
import weakref
|
9
10
|
|
11
|
+
from .check import check_in
|
10
12
|
from .check import check_isinstance
|
13
|
+
from .check import check_not_in
|
11
14
|
from .check import check_not_isinstance
|
12
15
|
from .check import check_not_none
|
13
16
|
from .maybes import Maybe
|
@@ -124,7 +127,7 @@ class InjectorError(Exception):
|
|
124
127
|
pass
|
125
128
|
|
126
129
|
|
127
|
-
@dc.dataclass(
|
130
|
+
@dc.dataclass()
|
128
131
|
class InjectorKeyError(InjectorError):
|
129
132
|
key: InjectorKey
|
130
133
|
|
@@ -132,16 +135,18 @@ class InjectorKeyError(InjectorError):
|
|
132
135
|
name: ta.Optional[str] = None
|
133
136
|
|
134
137
|
|
135
|
-
@dc.dataclass(frozen=True)
|
136
138
|
class UnboundInjectorKeyError(InjectorKeyError):
|
137
139
|
pass
|
138
140
|
|
139
141
|
|
140
|
-
@dc.dataclass(frozen=True)
|
141
142
|
class DuplicateInjectorKeyError(InjectorKeyError):
|
142
143
|
pass
|
143
144
|
|
144
145
|
|
146
|
+
class CyclicDependencyInjectorKeyError(InjectorKeyError):
|
147
|
+
pass
|
148
|
+
|
149
|
+
|
145
150
|
###
|
146
151
|
# keys
|
147
152
|
|
@@ -315,7 +320,11 @@ def build_injector_provider_map(bs: InjectorBindings) -> ta.Mapping[InjectorKey,
|
|
315
320
|
|
316
321
|
for b in bs.bindings():
|
317
322
|
if b.key.array:
|
318
|
-
am.setdefault(b.key, [])
|
323
|
+
al = am.setdefault(b.key, [])
|
324
|
+
if isinstance(b.provider, ArrayInjectorProvider):
|
325
|
+
al.extend(b.provider.ps)
|
326
|
+
else:
|
327
|
+
al.append(b.provider)
|
319
328
|
else:
|
320
329
|
if b.key in pm:
|
321
330
|
raise KeyError(b.key)
|
@@ -463,6 +472,14 @@ def build_injection_kwargs_target(
|
|
463
472
|
_INJECTOR_INJECTOR_KEY: InjectorKey[Injector] = InjectorKey(Injector)
|
464
473
|
|
465
474
|
|
475
|
+
@dc.dataclass(frozen=True)
|
476
|
+
class _InjectorEager:
|
477
|
+
key: InjectorKey
|
478
|
+
|
479
|
+
|
480
|
+
_INJECTOR_EAGER_ARRAY_KEY: InjectorKey[_InjectorEager] = InjectorKey(_InjectorEager, array=True)
|
481
|
+
|
482
|
+
|
466
483
|
class _Injector(Injector):
|
467
484
|
def __init__(self, bs: InjectorBindings, p: ta.Optional[Injector] = None) -> None:
|
468
485
|
super().__init__()
|
@@ -475,22 +492,69 @@ class _Injector(Injector):
|
|
475
492
|
if _INJECTOR_INJECTOR_KEY in self._pfm:
|
476
493
|
raise DuplicateInjectorKeyError(_INJECTOR_INJECTOR_KEY)
|
477
494
|
|
495
|
+
self.__cur_req: ta.Optional[_Injector._Request] = None
|
496
|
+
|
497
|
+
if _INJECTOR_EAGER_ARRAY_KEY in self._pfm:
|
498
|
+
for e in self.provide(_INJECTOR_EAGER_ARRAY_KEY):
|
499
|
+
self.provide(e.key)
|
500
|
+
|
501
|
+
class _Request:
|
502
|
+
def __init__(self, injector: '_Injector') -> None:
|
503
|
+
super().__init__()
|
504
|
+
self._injector = injector
|
505
|
+
self._provisions: ta.Dict[InjectorKey, Maybe] = {}
|
506
|
+
self._seen_keys: ta.Set[InjectorKey] = set()
|
507
|
+
|
508
|
+
def handle_key(self, key: InjectorKey) -> Maybe[Maybe]:
|
509
|
+
try:
|
510
|
+
return Maybe.just(self._provisions[key])
|
511
|
+
except KeyError:
|
512
|
+
pass
|
513
|
+
if key in self._seen_keys:
|
514
|
+
raise CyclicDependencyInjectorKeyError(key)
|
515
|
+
self._seen_keys.add(key)
|
516
|
+
return Maybe.empty()
|
517
|
+
|
518
|
+
def handle_provision(self, key: InjectorKey, mv: Maybe) -> Maybe:
|
519
|
+
check_in(key, self._seen_keys)
|
520
|
+
check_not_in(key, self._provisions)
|
521
|
+
self._provisions[key] = mv
|
522
|
+
return mv
|
523
|
+
|
524
|
+
@contextlib.contextmanager
|
525
|
+
def _current_request(self) -> ta.Generator[_Request, None, None]:
|
526
|
+
if (cr := self.__cur_req) is not None:
|
527
|
+
yield cr
|
528
|
+
return
|
529
|
+
|
530
|
+
cr = self._Request(self)
|
531
|
+
try:
|
532
|
+
self.__cur_req = cr
|
533
|
+
yield cr
|
534
|
+
finally:
|
535
|
+
self.__cur_req = None
|
536
|
+
|
478
537
|
def try_provide(self, key: ta.Any) -> Maybe[ta.Any]:
|
479
538
|
key = as_injector_key(key)
|
480
539
|
|
481
|
-
|
482
|
-
|
540
|
+
cr: _Injector._Request
|
541
|
+
with self._current_request() as cr:
|
542
|
+
if (rv := cr.handle_key(key)).present:
|
543
|
+
return rv.must()
|
483
544
|
|
484
|
-
|
485
|
-
|
486
|
-
return Maybe.just(fn(self))
|
545
|
+
if key == _INJECTOR_INJECTOR_KEY:
|
546
|
+
return cr.handle_provision(key, Maybe.just(self))
|
487
547
|
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
return Maybe.empty()
|
548
|
+
fn = self._pfm.get(key)
|
549
|
+
if fn is not None:
|
550
|
+
return cr.handle_provision(key, Maybe.just(fn(self)))
|
492
551
|
|
493
|
-
|
552
|
+
if self._p is not None:
|
553
|
+
pv = self._p.try_provide(key)
|
554
|
+
if pv is not None:
|
555
|
+
return cr.handle_provision(key, Maybe.empty())
|
556
|
+
|
557
|
+
return cr.handle_provision(key, Maybe.empty())
|
494
558
|
|
495
559
|
def provide(self, key: ta.Any) -> ta.Any:
|
496
560
|
v = self.try_provide(key)
|
@@ -591,6 +655,8 @@ class InjectorBinder:
|
|
591
655
|
to_key: ta.Any = None,
|
592
656
|
|
593
657
|
singleton: bool = False,
|
658
|
+
|
659
|
+
eager: bool = False,
|
594
660
|
) -> InjectorBindingOrBindings:
|
595
661
|
if obj is None or obj is inspect.Parameter.empty:
|
596
662
|
raise TypeError(obj)
|
@@ -664,13 +730,21 @@ class InjectorBinder:
|
|
664
730
|
if singleton:
|
665
731
|
provider = SingletonInjectorProvider(provider)
|
666
732
|
|
733
|
+
binding = InjectorBinding(key, provider)
|
734
|
+
|
667
735
|
##
|
668
736
|
|
669
|
-
|
737
|
+
extras: ta.List[InjectorBinding] = []
|
738
|
+
|
739
|
+
if eager:
|
740
|
+
extras.append(bind_injector_eager_key(key))
|
670
741
|
|
671
742
|
##
|
672
743
|
|
673
|
-
|
744
|
+
if extras:
|
745
|
+
return as_injector_bindings(binding, *extras)
|
746
|
+
else:
|
747
|
+
return binding
|
674
748
|
|
675
749
|
|
676
750
|
###
|
@@ -693,6 +767,26 @@ def make_injector_factory(
|
|
693
767
|
return outer
|
694
768
|
|
695
769
|
|
770
|
+
def bind_injector_array(
|
771
|
+
obj: ta.Any = None,
|
772
|
+
*,
|
773
|
+
tag: ta.Any = None,
|
774
|
+
) -> InjectorBindingOrBindings:
|
775
|
+
key = as_injector_key(obj)
|
776
|
+
if tag is not None:
|
777
|
+
if key.tag is not None:
|
778
|
+
raise ValueError('Must not specify multiple tags')
|
779
|
+
key = dc.replace(key, tag=tag)
|
780
|
+
|
781
|
+
if key.array:
|
782
|
+
raise ValueError('Key must not be array')
|
783
|
+
|
784
|
+
return InjectorBinding(
|
785
|
+
dc.replace(key, array=True),
|
786
|
+
ArrayInjectorProvider([]),
|
787
|
+
)
|
788
|
+
|
789
|
+
|
696
790
|
def make_injector_array_type(
|
697
791
|
ele: ta.Union[InjectorKey, InjectorKeyCls],
|
698
792
|
cls: U,
|
@@ -714,6 +808,10 @@ def make_injector_array_type(
|
|
714
808
|
return inner
|
715
809
|
|
716
810
|
|
811
|
+
def bind_injector_eager_key(key: ta.Any) -> InjectorBinding:
|
812
|
+
return InjectorBinding(_INJECTOR_EAGER_ARRAY_KEY, ConstInjectorProvider(_InjectorEager(as_injector_key(key))))
|
813
|
+
|
814
|
+
|
717
815
|
##
|
718
816
|
|
719
817
|
|
@@ -768,6 +866,8 @@ class Injection:
|
|
768
866
|
to_key: ta.Any = None,
|
769
867
|
|
770
868
|
singleton: bool = False,
|
869
|
+
|
870
|
+
eager: bool = False,
|
771
871
|
) -> InjectorBindingOrBindings:
|
772
872
|
return InjectorBinder.bind(
|
773
873
|
obj,
|
@@ -782,6 +882,8 @@ class Injection:
|
|
782
882
|
to_key=to_key,
|
783
883
|
|
784
884
|
singleton=singleton,
|
885
|
+
|
886
|
+
eager=eager,
|
785
887
|
)
|
786
888
|
|
787
889
|
# helpers
|
@@ -795,6 +897,15 @@ class Injection:
|
|
795
897
|
) -> InjectorBindingOrBindings:
|
796
898
|
return cls.bind(make_injector_factory(fn, cls_, ann))
|
797
899
|
|
900
|
+
@classmethod
|
901
|
+
def bind_array(
|
902
|
+
cls,
|
903
|
+
obj: ta.Any = None,
|
904
|
+
*,
|
905
|
+
tag: ta.Any = None,
|
906
|
+
) -> InjectorBindingOrBindings:
|
907
|
+
return bind_injector_array(obj, tag=tag)
|
908
|
+
|
798
909
|
@classmethod
|
799
910
|
def bind_array_type(
|
800
911
|
cls,
|
omlish/lite/io.py
CHANGED
@@ -3,6 +3,7 @@ import io
|
|
3
3
|
import typing as ta
|
4
4
|
|
5
5
|
from .check import check_isinstance
|
6
|
+
from .check import check_non_empty
|
6
7
|
from .check import check_not_none
|
7
8
|
from .strings import attr_repr
|
8
9
|
|
@@ -126,3 +127,100 @@ class DelimitingBuffer:
|
|
126
127
|
p = i + remaining_buf_capacity
|
127
128
|
yield self.Incomplete(self._append_and_reset(data[i:p]))
|
128
129
|
i = p
|
130
|
+
|
131
|
+
|
132
|
+
class ReadableListBuffer:
|
133
|
+
def __init__(self) -> None:
|
134
|
+
super().__init__()
|
135
|
+
self._lst: list[bytes] = []
|
136
|
+
|
137
|
+
def feed(self, d: bytes) -> None:
|
138
|
+
if d:
|
139
|
+
self._lst.append(d)
|
140
|
+
|
141
|
+
def _chop(self, i: int, e: int) -> bytes:
|
142
|
+
lst = self._lst
|
143
|
+
d = lst[i]
|
144
|
+
|
145
|
+
o = b''.join([
|
146
|
+
*lst[:i],
|
147
|
+
d[:e],
|
148
|
+
])
|
149
|
+
|
150
|
+
self._lst = [
|
151
|
+
*([d[e:]] if e < len(d) else []),
|
152
|
+
*lst[i + 1:],
|
153
|
+
]
|
154
|
+
|
155
|
+
return o
|
156
|
+
|
157
|
+
def read(self, n: ta.Optional[int] = None) -> ta.Optional[bytes]:
|
158
|
+
if n is None:
|
159
|
+
o = b''.join(self._lst)
|
160
|
+
self._lst = []
|
161
|
+
return o
|
162
|
+
|
163
|
+
if not (lst := self._lst):
|
164
|
+
return None
|
165
|
+
|
166
|
+
c = 0
|
167
|
+
for i, d in enumerate(lst):
|
168
|
+
r = n - c
|
169
|
+
if (l := len(d)) >= r:
|
170
|
+
return self._chop(i, r)
|
171
|
+
c += l
|
172
|
+
|
173
|
+
return None
|
174
|
+
|
175
|
+
def read_until(self, delim: bytes = b'\n') -> ta.Optional[bytes]:
|
176
|
+
if not (lst := self._lst):
|
177
|
+
return None
|
178
|
+
|
179
|
+
for i, d in enumerate(lst):
|
180
|
+
if (p := d.find(delim)) >= 0:
|
181
|
+
return self._chop(i, p + len(delim))
|
182
|
+
|
183
|
+
return None
|
184
|
+
|
185
|
+
|
186
|
+
class IncrementalWriteBuffer:
|
187
|
+
def __init__(
|
188
|
+
self,
|
189
|
+
data: bytes,
|
190
|
+
*,
|
191
|
+
write_size: int = 0x10000,
|
192
|
+
) -> None:
|
193
|
+
super().__init__()
|
194
|
+
|
195
|
+
check_non_empty(data)
|
196
|
+
self._len = len(data)
|
197
|
+
self._write_size = write_size
|
198
|
+
|
199
|
+
self._lst = [
|
200
|
+
data[i:i + write_size]
|
201
|
+
for i in range(0, len(data), write_size)
|
202
|
+
]
|
203
|
+
self._pos = 0
|
204
|
+
|
205
|
+
@property
|
206
|
+
def rem(self) -> int:
|
207
|
+
return self._len - self._pos
|
208
|
+
|
209
|
+
def write(self, fn: ta.Callable[[bytes], int]) -> int:
|
210
|
+
lst = check_non_empty(self._lst)
|
211
|
+
|
212
|
+
t = 0
|
213
|
+
for i, d in enumerate(lst): # noqa
|
214
|
+
n = fn(check_non_empty(d))
|
215
|
+
if not n:
|
216
|
+
break
|
217
|
+
t += n
|
218
|
+
|
219
|
+
if t:
|
220
|
+
self._lst = [
|
221
|
+
*([d[n:]] if n < len(d) else []),
|
222
|
+
*lst[i + 1:],
|
223
|
+
]
|
224
|
+
self._pos += t
|
225
|
+
|
226
|
+
return t
|
@@ -1,5 +1,5 @@
|
|
1
1
|
omlish/.manifests.json,sha256=CxGnj-UiRPlZgmgWoovDWrOnqpSEmBy_kqA7cdfSA3w,1431
|
2
|
-
omlish/__about__.py,sha256=
|
2
|
+
omlish/__about__.py,sha256=PvbOOiIxO1ksGKnYclis2IJG4h8EtRZ-Qy1gtTkvgI4,3379
|
3
3
|
omlish/__init__.py,sha256=SsyiITTuK0v74XpKV8dqNaCmjOlan1JZKrHQv5rWKPA,253
|
4
4
|
omlish/argparse.py,sha256=cqKGAqcxuxv_s62z0gq29L9KAvg_3-_rFvXKjVpRJjo,8126
|
5
5
|
omlish/c3.py,sha256=ubu7lHwss5V4UznbejAI0qXhXahrU01MysuHOZI9C4U,8116
|
@@ -300,11 +300,11 @@ omlish/lifecycles/states.py,sha256=zqMOU2ZU-MDNnWuwauM3_anIAiXM8LoBDElDEraptFg,1
|
|
300
300
|
omlish/lifecycles/transitions.py,sha256=qQtFby-h4VzbvgaUqT2NnbNumlcOx9FVVADP9t83xj4,1939
|
301
301
|
omlish/lite/__init__.py,sha256=Y3l4WY4JRi2uLG6kgbGp93fuGfkxkKwZDvhsa0Rwgtk,15
|
302
302
|
omlish/lite/cached.py,sha256=Fs-ljXVJmHBjAaHc-JuJXMEV4MNSX5c_KHZIM3AEaIw,694
|
303
|
-
omlish/lite/check.py,sha256=
|
303
|
+
omlish/lite/check.py,sha256=pQC412ffe_Zh7eHa4C1UYn6fA71Ls1vpVM0ZIOroPAY,1765
|
304
304
|
omlish/lite/contextmanagers.py,sha256=_jfNdpYvxkbKwyjQLbK-o69W89GoEuUfl_NrCosE9lU,1308
|
305
305
|
omlish/lite/docker.py,sha256=3IVZZtIm7-UdB2SwArmN_MosTva1_KifyYp3YWjODbE,337
|
306
|
-
omlish/lite/inject.py,sha256=
|
307
|
-
omlish/lite/io.py,sha256=
|
306
|
+
omlish/lite/inject.py,sha256=aRRmFb6azTKF208ogYwVCEopNZx7496Ta1GZmL_IKBA,23716
|
307
|
+
omlish/lite/io.py,sha256=3ECgUXdRnXyS6pGTSoVr6oB4moI38EpWxTq08zaTM-U,5339
|
308
308
|
omlish/lite/journald.py,sha256=f5Y2Q6-6O3iK_7MoGiwZwoQEOcP7LfkxxQNUR9tMjJM,3882
|
309
309
|
omlish/lite/json.py,sha256=7-02Ny4fq-6YAu5ynvqoijhuYXWpLmfCI19GUeZnb1c,740
|
310
310
|
omlish/lite/logs.py,sha256=1pcGu0ekhVCcLUckLSP16VccnAoprjtl5Vkdfm7y1Wg,6184
|
@@ -319,6 +319,12 @@ omlish/lite/socketserver.py,sha256=Esy9dAo9dPnNavNx5hW52YZi5hv504a8XQUudMrPs2A,1
|
|
319
319
|
omlish/lite/strings.py,sha256=QURcE4-1pKVW8eT_5VCJpXaHDWR2dW2pYOChTJnZDiQ,1504
|
320
320
|
omlish/lite/subprocesses.py,sha256=_YwUpvfaC2pV5TMC9-Ivuw1Ao-YxteD3a1NQwGERft4,3380
|
321
321
|
omlish/lite/typing.py,sha256=U3-JaEnkDSYxK4tsu_MzUn3RP6qALBe5FXQXpD-licE,1090
|
322
|
+
omlish/lite/fdio/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
323
|
+
omlish/lite/fdio/corohttp.py,sha256=5kzuM1cssA2D7rfknHFxylpjBNfA5tQSPkIRY2Ilapo,3825
|
324
|
+
omlish/lite/fdio/handlers.py,sha256=ukUiwF8-UCr4mzTTfOaTipC0k3k7THiHnohVdYfH69o,1341
|
325
|
+
omlish/lite/fdio/kqueue.py,sha256=4SvSMwNdkXpQbkMhyLqiZIjFGPUGSgD22yr9mpxT2fk,3153
|
326
|
+
omlish/lite/fdio/manager.py,sha256=-gMVzk4B1YTZS-d2TdM12woUme37pcNVUxNTiLe91lA,1250
|
327
|
+
omlish/lite/fdio/pollers.py,sha256=zSFi19SGQ2WKtMO_ONiqaJ_mbQmhrK59zgH5CqMYcu8,5434
|
322
328
|
omlish/lite/http/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
323
329
|
omlish/lite/http/coroserver.py,sha256=aBaYjP80yQHQxPxwi7PTYHub-fdRDKsMnB-tM8lBc2o,18095
|
324
330
|
omlish/lite/http/handlers.py,sha256=Yu0P3nqz-frklwCM2PbiWvoJNE-NqeTFLBvpNpqcdtA,753
|
@@ -482,9 +488,9 @@ omlish/text/glyphsplit.py,sha256=Ug-dPRO7x-OrNNr8g1y6DotSZ2KH0S-VcOmUobwa4B0,329
|
|
482
488
|
omlish/text/indent.py,sha256=6Jj6TFY9unaPa4xPzrnZemJ-fHsV53IamP93XGjSUHs,1274
|
483
489
|
omlish/text/parts.py,sha256=7vPF1aTZdvLVYJ4EwBZVzRSy8XB3YqPd7JwEnNGGAOo,6495
|
484
490
|
omlish/text/random.py,sha256=jNWpqiaKjKyTdMXC-pWAsSC10AAP-cmRRPVhm59ZWLk,194
|
485
|
-
omlish-0.0.0.
|
486
|
-
omlish-0.0.0.
|
487
|
-
omlish-0.0.0.
|
488
|
-
omlish-0.0.0.
|
489
|
-
omlish-0.0.0.
|
490
|
-
omlish-0.0.0.
|
491
|
+
omlish-0.0.0.dev130.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
|
492
|
+
omlish-0.0.0.dev130.dist-info/METADATA,sha256=1UOC8pu3kIMCWi1vb1n83h4VZjHWkmZpcmwpEzxERlk,4173
|
493
|
+
omlish-0.0.0.dev130.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
|
494
|
+
omlish-0.0.0.dev130.dist-info/entry_points.txt,sha256=Lt84WvRZJskWCAS7xnQGZIeVWksprtUHj0llrvVmod8,35
|
495
|
+
omlish-0.0.0.dev130.dist-info/top_level.txt,sha256=pePsKdLu7DvtUiecdYXJ78iO80uDNmBlqe-8hOzOmfs,7
|
496
|
+
omlish-0.0.0.dev130.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|