ominfra 0.0.0.dev129__py3-none-any.whl → 0.0.0.dev130__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -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"