ominfra 0.0.0.dev129__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.
@@ -1,13 +1,14 @@
1
1
  # ruff: noqa: UP006 UP007
2
- from .types import Dispatcher
2
+ from omlish.lite.fdio.handlers import FdIoHandler
3
+
3
4
  from .types import ProcessOutputDispatcher
4
5
  from .utils.collections import KeyedCollection
5
6
  from .utils.ostypes import Fd
6
7
 
7
8
 
8
- class Dispatchers(KeyedCollection[Fd, Dispatcher]):
9
- def _key(self, v: Dispatcher) -> Fd:
10
- return v.fd
9
+ class Dispatchers(KeyedCollection[Fd, FdIoHandler]):
10
+ def _key(self, v: FdIoHandler) -> Fd:
11
+ return Fd(v.fd())
11
12
 
12
13
  #
13
14
 
@@ -16,9 +17,9 @@ class Dispatchers(KeyedCollection[Fd, Dispatcher]):
16
17
  # note that we *must* call readable() for every dispatcher, as it may have side effects for a given
17
18
  # dispatcher (eg. call handle_listener_state_change for event listener processes)
18
19
  if d.readable():
19
- d.handle_read_event()
20
+ d.on_readable()
20
21
  if d.writable():
21
- d.handle_write_event()
22
+ d.on_writable()
22
23
 
23
24
  #
24
25
 
@@ -60,7 +60,6 @@ class BaseProcessDispatcherImpl(ProcessDispatcher, abc.ABC):
60
60
  def channel(self) -> str:
61
61
  return self._channel
62
62
 
63
- @property
64
63
  def fd(self) -> Fd:
65
64
  return self._fd
66
65
 
@@ -75,7 +74,7 @@ class BaseProcessDispatcherImpl(ProcessDispatcher, abc.ABC):
75
74
  log.debug('fd %s closed, stopped monitoring %s', self._fd, self)
76
75
  self._closed = True
77
76
 
78
- def handle_error(self) -> None:
77
+ def on_error(self, exc: ta.Optional[BaseException] = None) -> None:
79
78
  nil, t, v, tbinfo = compact_traceback()
80
79
 
81
80
  log.critical('uncaptured python exception, closing channel %s (%s:%s %s)', repr(self), t, v, tbinfo)
@@ -291,7 +290,7 @@ class ProcessOutputDispatcherImpl(BaseProcessDispatcherImpl, ProcessOutputDispat
291
290
  return False
292
291
  return True
293
292
 
294
- def handle_read_event(self) -> None:
293
+ def on_readable(self) -> None:
295
294
  data = read_fd(self._fd)
296
295
  self._output_buffer += data
297
296
  self.record_output()
@@ -329,15 +328,12 @@ class ProcessInputDispatcherImpl(BaseProcessDispatcherImpl, ProcessInputDispatch
329
328
  return True
330
329
  return False
331
330
 
332
- def readable(self) -> bool:
333
- return False
334
-
335
331
  def flush(self) -> None:
336
332
  # other code depends on this raising EPIPE if the pipe is closed
337
333
  sent = os.write(self._fd, as_bytes(self._input_buffer))
338
334
  self._input_buffer = self._input_buffer[sent:]
339
335
 
340
- def handle_write_event(self) -> None:
336
+ def on_writable(self) -> None:
341
337
  if self._input_buffer:
342
338
  try:
343
339
  self.flush()
@@ -2,15 +2,20 @@
2
2
  import typing as ta
3
3
 
4
4
  from .configs import ProcessGroupConfig
5
+ from .dispatchers import Dispatchers
5
6
  from .events import EventCallbacks
6
7
  from .events import ProcessGroupAddedEvent
7
8
  from .events import ProcessGroupRemovedEvent
9
+ from .types import HasDispatchers
8
10
  from .types import Process
9
11
  from .types import ProcessGroup
10
12
  from .utils.collections import KeyedCollectionAccessors
11
13
 
12
14
 
13
- class ProcessGroupManager(KeyedCollectionAccessors[str, ProcessGroup]):
15
+ class ProcessGroupManager(
16
+ KeyedCollectionAccessors[str, ProcessGroup],
17
+ HasDispatchers,
18
+ ):
14
19
  def __init__(
15
20
  self,
16
21
  *,
@@ -34,6 +39,16 @@ class ProcessGroupManager(KeyedCollectionAccessors[str, ProcessGroup]):
34
39
 
35
40
  #
36
41
 
42
+ def get_dispatchers(self) -> Dispatchers:
43
+ return Dispatchers(
44
+ d
45
+ for g in self
46
+ for p in g
47
+ for d in p.get_dispatchers()
48
+ )
49
+
50
+ #
51
+
37
52
  def add(self, group: ProcessGroup) -> None:
38
53
  if (name := group.name) in self._by_name:
39
54
  raise KeyError(f'Process group already exists: {name}')
@@ -0,0 +1,130 @@
1
+ # ruff: noqa: U006 UP007
2
+ import json
3
+ import socket
4
+ import typing as ta
5
+
6
+ from omlish.lite.check import check_not_none
7
+ from omlish.lite.fdio.corohttp import CoroHttpServerConnectionFdIoHandler
8
+ from omlish.lite.fdio.handlers import SocketFdIoHandler
9
+ from omlish.lite.http.handlers import HttpHandler
10
+ from omlish.lite.http.handlers import HttpHandlerRequest
11
+ from omlish.lite.http.handlers import HttpHandlerResponse
12
+ from omlish.lite.json import JSON_PRETTY_KWARGS
13
+ from omlish.lite.socket import SocketAddress
14
+
15
+ from .dispatchers import Dispatchers
16
+ from .groups import ProcessGroupManager
17
+ from .types import HasDispatchers
18
+
19
+
20
+ ##
21
+
22
+
23
+ class SocketServerFdIoHandler(SocketFdIoHandler):
24
+ def __init__(
25
+ self,
26
+ addr: SocketAddress,
27
+ on_connect: ta.Callable[[socket.socket, SocketAddress], None],
28
+ ) -> None:
29
+ sock = socket.create_server(addr)
30
+ sock.setblocking(False)
31
+
32
+ super().__init__(addr, sock)
33
+
34
+ self._on_connect = on_connect
35
+
36
+ sock.listen(1)
37
+
38
+ def readable(self) -> bool:
39
+ return True
40
+
41
+ def on_readable(self) -> None:
42
+ cli_sock, cli_addr = check_not_none(self._sock).accept()
43
+ cli_sock.setblocking(False)
44
+
45
+ self._on_connect(cli_sock, cli_addr)
46
+
47
+
48
+ ##
49
+
50
+
51
+ class HttpServer(HasDispatchers):
52
+ class Address(ta.NamedTuple):
53
+ a: SocketAddress
54
+
55
+ class Handler(ta.NamedTuple):
56
+ h: HttpHandler
57
+
58
+ def __init__(
59
+ self,
60
+ handler: Handler,
61
+ addr: Address = Address(('localhost', 8000)),
62
+ ) -> None:
63
+ super().__init__()
64
+
65
+ self._handler = handler.h
66
+ self._addr = addr.a
67
+
68
+ self._server = SocketServerFdIoHandler(self._addr, self._on_connect)
69
+
70
+ self._conns: ta.List[CoroHttpServerConnectionFdIoHandler] = []
71
+
72
+ def get_dispatchers(self) -> Dispatchers:
73
+ l = []
74
+ for c in self._conns:
75
+ if not c.closed:
76
+ l.append(c)
77
+ self._conns = l
78
+ return Dispatchers([
79
+ self._server,
80
+ *l,
81
+ ])
82
+
83
+ def _on_connect(self, sock: socket.socket, addr: SocketAddress) -> None:
84
+ conn = CoroHttpServerConnectionFdIoHandler(
85
+ addr,
86
+ sock,
87
+ self._handler,
88
+ )
89
+
90
+ self._conns.append(conn)
91
+
92
+
93
+ ##
94
+
95
+
96
+ class SupervisorHttpHandler:
97
+ def __init__(
98
+ self,
99
+ *,
100
+ groups: ProcessGroupManager,
101
+ ) -> None:
102
+ super().__init__()
103
+
104
+ self._groups = groups
105
+
106
+ def handle(self, req: HttpHandlerRequest) -> HttpHandlerResponse:
107
+ dct = {
108
+ 'method': req.method,
109
+ 'path': req.path,
110
+ 'data': len(req.data or b''),
111
+ 'groups': {
112
+ g.name: {
113
+ 'processes': {
114
+ p.name: {
115
+ 'pid': p.pid,
116
+ }
117
+ for p in g
118
+ },
119
+ }
120
+ for g in self._groups
121
+ },
122
+ }
123
+
124
+ return HttpHandlerResponse(
125
+ 200,
126
+ data=json.dumps(dct, **JSON_PRETTY_KWARGS).encode('utf-8') + b'\n',
127
+ headers={
128
+ 'Content-Type': 'application/json',
129
+ },
130
+ )
@@ -1,6 +1,11 @@
1
1
  # ruff: noqa: UP006 UP007
2
+ import dataclasses as dc
2
3
  import typing as ta
3
4
 
5
+ from omlish.lite.fdio.kqueue import KqueueFdIoPoller # noqa
6
+ from omlish.lite.fdio.pollers import FdIoPoller
7
+ from omlish.lite.fdio.pollers import PollFdIoPoller # noqa
8
+ from omlish.lite.fdio.pollers import SelectFdIoPoller
4
9
  from omlish.lite.inject import InjectorBindingOrBindings
5
10
  from omlish.lite.inject import InjectorBindings
6
11
  from omlish.lite.inject import inj
@@ -12,9 +17,10 @@ from .events import EventCallbacks
12
17
  from .groups import ProcessGroupManager
13
18
  from .groupsimpl import ProcessFactory
14
19
  from .groupsimpl import ProcessGroupImpl
20
+ from .http import HttpServer
21
+ from .http import SupervisorHttpHandler
22
+ from .io import HasDispatchersList
15
23
  from .io import IoManager
16
- from .poller import Poller
17
- from .poller import get_poller_impl
18
24
  from .process import PidHistory
19
25
  from .processimpl import ProcessImpl
20
26
  from .processimpl import ProcessSpawningFactory
@@ -31,12 +37,24 @@ from .spawningimpl import ProcessSpawningImpl
31
37
  from .supervisor import ProcessGroupFactory
32
38
  from .supervisor import Supervisor
33
39
  from .supervisor import SupervisorStateManagerImpl
40
+ from .types import HasDispatchers
34
41
  from .types import ServerEpoch
35
42
  from .types import SupervisorStateManager
36
43
  from .utils.signals import SignalReceiver
37
44
  from .utils.users import get_user
38
45
 
39
46
 
47
+ @dc.dataclass(frozen=True)
48
+ class _FdIoPollerDaemonizeListener(DaemonizeListener):
49
+ _poller: FdIoPoller
50
+
51
+ def before_daemonize(self) -> None:
52
+ self._poller.close()
53
+
54
+ def after_daemonize(self) -> None:
55
+ self._poller.reopen()
56
+
57
+
40
58
  def bind_server(
41
59
  config: ServerConfig,
42
60
  *,
@@ -46,22 +64,24 @@ def bind_server(
46
64
  lst: ta.List[InjectorBindingOrBindings] = [
47
65
  inj.bind(config),
48
66
 
67
+ inj.bind_array(DaemonizeListener),
49
68
  inj.bind_array_type(DaemonizeListener, DaemonizeListeners),
50
69
 
51
70
  inj.bind(SupervisorSetupImpl, singleton=True),
52
71
  inj.bind(SupervisorSetup, to_key=SupervisorSetupImpl),
53
72
 
54
- inj.bind(DaemonizeListener, array=True, to_key=Poller),
55
-
56
73
  inj.bind(EventCallbacks, singleton=True),
57
74
 
58
75
  inj.bind(SignalReceiver, singleton=True),
59
76
 
60
77
  inj.bind(IoManager, singleton=True),
78
+ inj.bind_array(HasDispatchers),
79
+ inj.bind_array_type(HasDispatchers, HasDispatchersList),
61
80
 
62
81
  inj.bind(SignalHandler, singleton=True),
63
82
 
64
83
  inj.bind(ProcessGroupManager, singleton=True),
84
+ inj.bind(HasDispatchers, array=True, to_key=ProcessGroupManager),
65
85
 
66
86
  inj.bind(Supervisor, singleton=True),
67
87
 
@@ -94,7 +114,26 @@ def bind_server(
94
114
 
95
115
  #
96
116
 
97
- lst.append(inj.bind(get_poller_impl(), key=Poller, singleton=True))
117
+ poller_impl = next(filter(None, [
118
+ KqueueFdIoPoller,
119
+ PollFdIoPoller,
120
+ SelectFdIoPoller,
121
+ ]))
122
+ lst.append(inj.bind(poller_impl, key=FdIoPoller, singleton=True))
123
+ inj.bind(_FdIoPollerDaemonizeListener, array=True, singleton=True)
124
+
125
+ #
126
+
127
+ def _provide_http_handler(s: SupervisorHttpHandler) -> HttpServer.Handler:
128
+ return HttpServer.Handler(s.handle)
129
+
130
+ lst.extend([
131
+ inj.bind(HttpServer, singleton=True, eager=True),
132
+ inj.bind(HasDispatchers, array=True, to_key=HttpServer),
133
+
134
+ inj.bind(SupervisorHttpHandler, singleton=True),
135
+ inj.bind(_provide_http_handler),
136
+ ])
98
137
 
99
138
  #
100
139
 
ominfra/supervisor/io.py CHANGED
@@ -1,58 +1,71 @@
1
1
  # ruff: noqa: UP006 UP007
2
+ import typing as ta
3
+
4
+ from omlish.lite.fdio.pollers import FdIoPoller
2
5
  from omlish.lite.logs import log
3
6
 
4
7
  from .dispatchers import Dispatchers
5
- from .groups import ProcessGroupManager
6
- from .poller import Poller
7
8
  from .types import ExitNow
9
+ from .types import HasDispatchers
10
+ from .utils.ostypes import Fd
8
11
 
9
12
 
10
13
  ##
11
14
 
12
15
 
13
- class IoManager:
16
+ HasDispatchersList = ta.NewType('HasDispatchersList', ta.Sequence[HasDispatchers])
17
+
18
+
19
+ class IoManager(HasDispatchers):
14
20
  def __init__(
15
21
  self,
16
22
  *,
17
- poller: Poller,
18
- process_groups: ProcessGroupManager,
23
+ poller: FdIoPoller,
24
+ has_dispatchers_list: HasDispatchersList,
19
25
  ) -> None:
20
26
  super().__init__()
21
27
 
22
28
  self._poller = poller
23
- self._process_groups = process_groups
29
+ self._has_dispatchers_list = has_dispatchers_list
24
30
 
25
31
  def get_dispatchers(self) -> Dispatchers:
26
32
  return Dispatchers(
27
33
  d
28
- for p in self._process_groups.all_processes()
29
- for d in p.get_dispatchers()
34
+ for hd in self._has_dispatchers_list
35
+ for d in hd.get_dispatchers()
30
36
  )
31
37
 
32
38
  def poll(self) -> None:
33
39
  dispatchers = self.get_dispatchers()
34
40
 
35
- for fd, dispatcher in dispatchers.items():
36
- if dispatcher.readable():
37
- self._poller.register_readable(fd)
38
- if dispatcher.writable():
39
- self._poller.register_writable(fd)
41
+ self._poller.update(
42
+ {fd for fd, d in dispatchers.items() if d.readable()},
43
+ {fd for fd, d in dispatchers.items() if d.writable()},
44
+ )
40
45
 
41
46
  timeout = 1 # this cannot be fewer than the smallest TickEvent (5)
42
- r, w = self._poller.poll(timeout)
47
+ log.info(f'Polling: {timeout=}') # noqa
48
+ polled = self._poller.poll(timeout)
49
+ log.info(f'Polled: {polled=}') # noqa
50
+ if polled.msg is not None:
51
+ log.error(polled.msg)
52
+ if polled.exc is not None:
53
+ log.error('Poll exception: %r', polled.exc)
43
54
 
44
- for fd in r:
55
+ for r in polled.r:
56
+ fd = Fd(r)
45
57
  if fd in dispatchers:
58
+ dispatcher = dispatchers[fd]
46
59
  try:
47
- dispatcher = dispatchers[fd]
48
60
  log.debug('read event caused by %r', dispatcher)
49
- dispatcher.handle_read_event()
61
+ dispatcher.on_readable()
50
62
  if not dispatcher.readable():
51
63
  self._poller.unregister_readable(fd)
52
64
  except ExitNow:
53
65
  raise
54
- except Exception: # noqa
55
- dispatchers[fd].handle_error()
66
+ except Exception as exc: # noqa
67
+ log.exception('Error in dispatcher: %r', dispatcher)
68
+ dispatcher.on_error(exc)
56
69
  else:
57
70
  # if the fd is not in combined map, we should unregister it. otherwise, it will be polled every
58
71
  # time, which may cause 100% cpu usage
@@ -62,18 +75,20 @@ class IoManager:
62
75
  except Exception: # noqa
63
76
  pass
64
77
 
65
- for fd in w:
78
+ for w in polled.w:
79
+ fd = Fd(w)
66
80
  if fd in dispatchers:
81
+ dispatcher = dispatchers[fd]
67
82
  try:
68
- dispatcher = dispatchers[fd]
69
83
  log.debug('write event caused by %r', dispatcher)
70
- dispatcher.handle_write_event()
84
+ dispatcher.on_writable()
71
85
  if not dispatcher.writable():
72
86
  self._poller.unregister_writable(fd)
73
87
  except ExitNow:
74
88
  raise
75
- except Exception: # noqa
76
- dispatchers[fd].handle_error()
89
+ except Exception as exc: # noqa
90
+ log.exception('Error in dispatcher: %r', dispatcher)
91
+ dispatcher.on_error(exc)
77
92
  else:
78
93
  log.debug('unexpected write event from fd %r', fd)
79
94
  try:
@@ -378,6 +378,7 @@ class ProcessImpl(Process):
378
378
  self._last_stop = now
379
379
 
380
380
  if now > self._last_start:
381
+ log.info(f'{now - self._last_start=}') # noqa
381
382
  too_quickly = now - self._last_start < self._config.startsecs
382
383
  else:
383
384
  too_quickly = False
@@ -447,8 +448,6 @@ class ProcessImpl(Process):
447
448
 
448
449
  self._check_and_adjust_for_system_clock_rollback(now)
449
450
 
450
- logger = log
451
-
452
451
  if self._supervisor_states.state > SupervisorState.RESTARTING:
453
452
  # dont start any processes if supervisor is shutting down
454
453
  if state == ProcessState.EXITED:
@@ -480,14 +479,14 @@ class ProcessImpl(Process):
480
479
  self.check_in_state(ProcessState.STARTING)
481
480
  self.change_state(ProcessState.RUNNING)
482
481
  msg = ('entered RUNNING state, process has stayed up for > than %s seconds (startsecs)' % self._config.startsecs) # noqa
483
- logger.info('success: %s %s', self.name, msg)
482
+ log.info('success: %s %s', self.name, msg)
484
483
 
485
484
  if state == ProcessState.BACKOFF:
486
485
  if self._backoff > self._config.startretries:
487
486
  # BACKOFF -> FATAL if the proc has exceeded its number of retries
488
487
  self.give_up()
489
488
  msg = ('entered FATAL state, too many start retries too quickly')
490
- logger.info('gave up: %s %s', self.name, msg)
489
+ log.info('gave up: %s %s', self.name, msg)
491
490
 
492
491
  elif state == ProcessState.STOPPING:
493
492
  time_left = self._delay - now
@@ -7,6 +7,7 @@ import typing as ta
7
7
 
8
8
  from omlish.lite.check import check_isinstance
9
9
  from omlish.lite.check import check_not_none
10
+ from omlish.lite.fdio.handlers import FdIoHandler
10
11
  from omlish.lite.typing import Func3
11
12
 
12
13
  from .configs import ProcessConfig
@@ -29,7 +30,6 @@ from .process import PidHistory
29
30
  from .spawning import ProcessSpawnError
30
31
  from .spawning import ProcessSpawning
31
32
  from .spawning import SpawnedProcess
32
- from .types import Dispatcher
33
33
  from .types import Process
34
34
  from .types import ProcessGroup
35
35
  from .types import ProcessInputDispatcher
@@ -200,7 +200,7 @@ class ProcessSpawningImpl(ProcessSpawning):
200
200
  return exe, args
201
201
 
202
202
  def _make_dispatchers(self, pipes: ProcessPipes) -> Dispatchers:
203
- dispatchers: ta.List[Dispatcher] = []
203
+ dispatchers: ta.List[FdIoHandler] = []
204
204
 
205
205
  if pipes.stdout is not None:
206
206
  dispatchers.append(check_isinstance(self._output_dispatcher_factory(
@@ -5,6 +5,7 @@ import time
5
5
  import typing as ta
6
6
 
7
7
  from omlish.lite.check import check_isinstance
8
+ from omlish.lite.fdio.pollers import FdIoPoller
8
9
  from omlish.lite.logs import log
9
10
  from omlish.lite.typing import Func1
10
11
 
@@ -17,7 +18,6 @@ from .events import SupervisorStoppingEvent
17
18
  from .groups import ProcessGroup
18
19
  from .groups import ProcessGroupManager
19
20
  from .io import IoManager
20
- from .poller import Poller
21
21
  from .process import PidHistory
22
22
  from .setup import SupervisorSetup
23
23
  from .signals import SignalHandler
@@ -66,7 +66,7 @@ class Supervisor:
66
66
  self,
67
67
  *,
68
68
  config: ServerConfig,
69
- poller: Poller,
69
+ poller: FdIoPoller,
70
70
  process_groups: ProcessGroupManager,
71
71
  signal_handler: SignalHandler,
72
72
  event_callbacks: EventCallbacks,
@@ -182,7 +182,9 @@ class Supervisor:
182
182
  #
183
183
 
184
184
  def _run_once(self) -> None:
185
+ now = time.time()
185
186
  self._poll()
187
+ log.info(f'Poll took {time.time() - now}') # noqa
186
188
  self._reap()
187
189
  self._signal_handler.handle_signals()
188
190
  self._tick()
@@ -234,6 +236,7 @@ class Supervisor:
234
236
  return
235
237
 
236
238
  wp = waitpid()
239
+ log.info(f'Waited pid: {wp}') # noqa
237
240
  if wp is None or not wp.pid:
238
241
  return
239
242
 
@@ -3,12 +3,13 @@ import abc
3
3
  import functools
4
4
  import typing as ta
5
5
 
6
+ from omlish.lite.fdio.handlers import FdIoHandler
7
+
6
8
  from .configs import ProcessConfig
7
9
  from .configs import ProcessGroupConfig
8
10
  from .states import ProcessState
9
11
  from .states import SupervisorState
10
12
  from .utils.collections import KeyedCollectionAccessors
11
- from .utils.ostypes import Fd
12
13
  from .utils.ostypes import Pid
13
14
  from .utils.ostypes import Rc
14
15
 
@@ -61,69 +62,18 @@ class SupervisorStateManager(abc.ABC):
61
62
  ##
62
63
 
63
64
 
64
- class Dispatcher(abc.ABC):
65
- @property
65
+ class HasDispatchers(abc.ABC):
66
66
  @abc.abstractmethod
67
- def channel(self) -> str:
67
+ def get_dispatchers(self) -> 'Dispatchers':
68
68
  raise NotImplementedError
69
69
 
70
- @property
71
- @abc.abstractmethod
72
- def fd(self) -> Fd:
73
- raise NotImplementedError
74
70
 
71
+ class ProcessDispatcher(FdIoHandler, abc.ABC):
75
72
  @property
76
73
  @abc.abstractmethod
77
- def closed(self) -> bool:
78
- raise NotImplementedError
79
-
80
- #
81
-
82
- @abc.abstractmethod
83
- def close(self) -> None:
84
- raise NotImplementedError
85
-
86
- @abc.abstractmethod
87
- def handle_error(self) -> None:
88
- raise NotImplementedError
89
-
90
- #
91
-
92
- @abc.abstractmethod
93
- def readable(self) -> bool:
94
- raise NotImplementedError
95
-
96
- @abc.abstractmethod
97
- def writable(self) -> bool:
98
- raise NotImplementedError
99
-
100
- #
101
-
102
- def handle_read_event(self) -> None:
103
- raise TypeError
104
-
105
- def handle_write_event(self) -> None:
106
- raise TypeError
107
-
108
- #
109
-
110
- def handle_connect(self) -> None:
111
- raise TypeError
112
-
113
- def handle_close(self) -> None:
114
- raise TypeError
115
-
116
- def handle_accepted(self, sock, addr) -> None:
117
- raise TypeError
118
-
119
-
120
- class HasDispatchers(abc.ABC):
121
- @abc.abstractmethod
122
- def get_dispatchers(self) -> 'Dispatchers':
74
+ def channel(self) -> str:
123
75
  raise NotImplementedError
124
76
 
125
-
126
- class ProcessDispatcher(Dispatcher, abc.ABC):
127
77
  @property
128
78
  @abc.abstractmethod
129
79
  def process(self) -> 'Process':
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ominfra
3
- Version: 0.0.0.dev129
3
+ Version: 0.0.0.dev130
4
4
  Summary: ominfra
5
5
  Author: wrmsr
6
6
  License: BSD-3-Clause
@@ -12,8 +12,8 @@ Classifier: Operating System :: OS Independent
12
12
  Classifier: Operating System :: POSIX
13
13
  Requires-Python: >=3.12
14
14
  License-File: LICENSE
15
- Requires-Dist: omdev==0.0.0.dev129
16
- Requires-Dist: omlish==0.0.0.dev129
15
+ Requires-Dist: omdev==0.0.0.dev130
16
+ Requires-Dist: omlish==0.0.0.dev130
17
17
  Provides-Extra: all
18
18
  Requires-Dist: paramiko~=3.5; extra == "all"
19
19
  Requires-Dist: asyncssh~=2.18; extra == "all"