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 CHANGED
@@ -1,5 +1,5 @@
1
- __version__ = '0.0.0.dev128'
2
- __revision__ = '032185683e360475ef780c77bea913c965cebcc6'
1
+ __version__ = '0.0.0.dev130'
2
+ __revision__ = '04fd1bb83aa7700c60be8dc97b6530ddf15b6dd7'
3
3
 
4
4
 
5
5
  #
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(frozen=True)
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, []).append(b.provider)
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
- if key == _INJECTOR_INJECTOR_KEY:
482
- return Maybe.just(self)
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
- fn = self._pfm.get(key)
485
- if fn is not None:
486
- return Maybe.just(fn(self))
545
+ if key == _INJECTOR_INJECTOR_KEY:
546
+ return cr.handle_provision(key, Maybe.just(self))
487
547
 
488
- if self._p is not None:
489
- pv = self._p.try_provide(key)
490
- if pv is not None:
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
- return Maybe.empty()
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
- binding = InjectorBinding(key, provider)
737
+ extras: ta.List[InjectorBinding] = []
738
+
739
+ if eager:
740
+ extras.append(bind_injector_eager_key(key))
670
741
 
671
742
  ##
672
743
 
673
- return binding
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,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: omlish
3
- Version: 0.0.0.dev128
3
+ Version: 0.0.0.dev130
4
4
  Summary: omlish
5
5
  Author: wrmsr
6
6
  License: BSD-3-Clause
@@ -1,5 +1,5 @@
1
1
  omlish/.manifests.json,sha256=CxGnj-UiRPlZgmgWoovDWrOnqpSEmBy_kqA7cdfSA3w,1431
2
- omlish/__about__.py,sha256=9lZ5XVEOu3HBl1htFmArMTPb8o6NmnVqnhMIpjv5mhc,3379
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=1mo1GK78Ro5-IC_jUrmNabmt--hz_9v1y5h28K2cqUY,1102
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=NTZl6VXhYDILPMm1zBx-CbqF7Vkpxv9w1JVKqmmUp5w,20407
307
- omlish/lite/io.py,sha256=lcpI1cS_Kn90tvYMg8ZWkSlYloS4RFqXCk-rKyclhdg,3148
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.dev128.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
486
- omlish-0.0.0.dev128.dist-info/METADATA,sha256=lgogkc74fPcQs_ct8BCBruFkyE5sDH9QPyrnz90DRQE,4173
487
- omlish-0.0.0.dev128.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
488
- omlish-0.0.0.dev128.dist-info/entry_points.txt,sha256=Lt84WvRZJskWCAS7xnQGZIeVWksprtUHj0llrvVmod8,35
489
- omlish-0.0.0.dev128.dist-info/top_level.txt,sha256=pePsKdLu7DvtUiecdYXJ78iO80uDNmBlqe-8hOzOmfs,7
490
- omlish-0.0.0.dev128.dist-info/RECORD,,
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,,