##// END OF EJS Templates
Creating an ABC for kernel managers and channels.
Brian Granger -
Show More
@@ -0,0 +1,399 b''
1 """Abstract base classes for kernel manager and channels."""
2
3 #-----------------------------------------------------------------------------
4 # Copyright (C) 2013 The IPython Development Team
5 #
6 # Distributed under the terms of the BSD License. The full license is in
7 # the file COPYING, distributed as part of this software.
8 #-----------------------------------------------------------------------------
9
10 #-----------------------------------------------------------------------------
11 # Imports
12 #-----------------------------------------------------------------------------
13
14 # Standard library imports.
15 import abc
16
17 #-----------------------------------------------------------------------------
18 # Channels
19 #-----------------------------------------------------------------------------
20
21
22 class ChannelABC(object):
23 """A base class for all channel ABCs."""
24
25 __metaclass__ = abc.ABCMeta
26
27 @abc.abstractmethod
28 def start(self):
29 pass
30
31 @abc.abstractmethod
32 def stop(self):
33 pass
34
35 @abc.abstractmethod
36 def is_alive(self):
37 pass
38
39
40 class ShellChannelABC(ChannelABC):
41 """The DEALER channel for issues request/replies to the kernel.
42 """
43
44 @abc.abstractproperty
45 def allow_stdin(self):
46 pass
47
48 @abc.abstractmethod
49 def execute(self, code, silent=False, store_history=True,
50 user_variables=None, user_expressions=None, allow_stdin=None):
51 """Execute code in the kernel.
52
53 Parameters
54 ----------
55 code : str
56 A string of Python code.
57
58 silent : bool, optional (default False)
59 If set, the kernel will execute the code as quietly possible, and
60 will force store_history to be False.
61
62 store_history : bool, optional (default True)
63 If set, the kernel will store command history. This is forced
64 to be False if silent is True.
65
66 user_variables : list, optional
67 A list of variable names to pull from the user's namespace. They
68 will come back as a dict with these names as keys and their
69 :func:`repr` as values.
70
71 user_expressions : dict, optional
72 A dict mapping names to expressions to be evaluated in the user's
73 dict. The expression values are returned as strings formatted using
74 :func:`repr`.
75
76 allow_stdin : bool, optional (default self.allow_stdin)
77 Flag for whether the kernel can send stdin requests to frontends.
78
79 Some frontends (e.g. the Notebook) do not support stdin requests.
80 If raw_input is called from code executed from such a frontend, a
81 StdinNotImplementedError will be raised.
82
83 Returns
84 -------
85 The msg_id of the message sent.
86 """
87 pass
88
89 @abc.abstractmethod
90 def complete(self, text, line, cursor_pos, block=None):
91 """Tab complete text in the kernel's namespace.
92
93 Parameters
94 ----------
95 text : str
96 The text to complete.
97 line : str
98 The full line of text that is the surrounding context for the
99 text to complete.
100 cursor_pos : int
101 The position of the cursor in the line where the completion was
102 requested.
103 block : str, optional
104 The full block of code in which the completion is being requested.
105
106 Returns
107 -------
108 The msg_id of the message sent.
109 """
110 pass
111
112 @abc.abstractmethod
113 def object_info(self, oname, detail_level=0):
114 """Get metadata information about an object.
115
116 Parameters
117 ----------
118 oname : str
119 A string specifying the object name.
120 detail_level : int, optional
121 The level of detail for the introspection (0-2)
122
123 Returns
124 -------
125 The msg_id of the message sent.
126 """
127 pass
128
129 @abc.abstractmethod
130 def history(self, raw=True, output=False, hist_access_type='range', **kwargs):
131 """Get entries from the history list.
132
133 Parameters
134 ----------
135 raw : bool
136 If True, return the raw input.
137 output : bool
138 If True, then return the output as well.
139 hist_access_type : str
140 'range' (fill in session, start and stop params), 'tail' (fill in n)
141 or 'search' (fill in pattern param).
142
143 session : int
144 For a range request, the session from which to get lines. Session
145 numbers are positive integers; negative ones count back from the
146 current session.
147 start : int
148 The first line number of a history range.
149 stop : int
150 The final (excluded) line number of a history range.
151
152 n : int
153 The number of lines of history to get for a tail request.
154
155 pattern : str
156 The glob-syntax pattern for a search request.
157
158 Returns
159 -------
160 The msg_id of the message sent.
161 """
162 pass
163
164 @abc.abstractmethod
165 def kernel_info(self):
166 """Request kernel info."""
167 pass
168
169 @abc.abstractmethod
170 def shutdown(self, restart=False):
171 """Request an immediate kernel shutdown.
172
173 Upon receipt of the (empty) reply, client code can safely assume that
174 the kernel has shut down and it's safe to forcefully terminate it if
175 it's still alive.
176
177 The kernel will send the reply via a function registered with Python's
178 atexit module, ensuring it's truly done as the kernel is done with all
179 normal operation.
180 """
181 pass
182
183
184 class IOPubChannelABC(ChannelABC):
185 """The SUB channel which listens for messages that the kernel publishes."""
186
187
188 @abc.abstractmethod
189 def flush(self, timeout=1.0):
190 """Immediately processes all pending messages on the SUB channel.
191
192 Callers should use this method to ensure that :method:`call_handlers`
193 has been called for all messages that have been received on the
194 0MQ SUB socket of this channel.
195
196 This method is thread safe.
197
198 Parameters
199 ----------
200 timeout : float, optional
201 The maximum amount of time to spend flushing, in seconds. The
202 default is one second.
203 """
204 pass
205
206
207 class StdInChannelABC(ChannelABC):
208 """A reply channel to handle raw_input requests that the kernel makes."""
209
210 @abc.abstractmethod
211 def input(self, string):
212 """Send a string of raw input to the kernel."""
213 pass
214
215
216 class HBChannelABC(ChannelABC):
217 """The heartbeat channel which monitors the kernel heartbeat."""
218
219 @abc.abstractproperty
220 def time_to_dead(self):
221 pass
222
223 @abc.abstractmethod
224 def pause(self):
225 """Pause the heartbeat."""
226 pass
227
228 @abc.abstractmethod
229 def unpause(self):
230 """Unpause the heartbeat."""
231 pass
232
233 @abc.abstractmethod
234 def is_beating(self):
235 """Is the heartbeat running and responsive (and not paused)."""
236 pass
237
238
239 #-----------------------------------------------------------------------------
240 # Main kernel manager class
241 #-----------------------------------------------------------------------------
242
243 class KernelManagerABC(object):
244 """ Manages a kernel for a frontend."""
245
246 @abc.abstractproperty
247 def kernel(self):
248 pass
249
250 @abc.abstractproperty
251 def shell_channel_class(self):
252 pass
253
254 @abc.abstractproperty
255 def iopub_channel_class(self):
256 pass
257
258 @abc.abstractproperty
259 def hb_channel_class(self):
260 pass
261
262 @abc.abstractproperty
263 def stdin_channel_class(self):
264 pass
265
266 #--------------------------------------------------------------------------
267 # Channel management methods:
268 #--------------------------------------------------------------------------
269
270 @abc.abstractmethod
271 def start_channels(self, shell=True, iopub=True, stdin=True, hb=True):
272 """Starts the channels for this kernel.
273
274 This will create the channels if they do not exist and then start
275 them. If port numbers of 0 are being used (random ports) then you
276 must first call :method:`start_kernel`. If the channels have been
277 stopped and you call this, :class:`RuntimeError` will be raised.
278 """
279 pass
280
281 @abc.abstractmethod
282 def stop_channels(self):
283 """Stops all the running channels for this kernel."""
284 pass
285
286 @abc.abstractproperty
287 def channels_running(self):
288 """Are any of the channels created and running?"""
289 pass
290
291 @abc.abstractproperty
292 def shell_channel(self):
293 """Get the REQ socket channel object to make requests of the kernel."""
294 pass
295
296 @abc.abstractproperty
297 def iopub_channel(self):
298 """Get the SUB socket channel object."""
299 pass
300
301 @abc.abstractproperty
302 def stdin_channel(self):
303 """Get the REP socket channel object to handle stdin (raw_input)."""
304 pass
305
306 @abc.abstractproperty
307 def hb_channel(self):
308 """Get the heartbeat socket channel object to check that the
309 kernel is alive."""
310 pass
311
312 #--------------------------------------------------------------------------
313 # Kernel management.
314 #--------------------------------------------------------------------------
315
316 @abc.abstractmethod
317 def start_kernel(self, **kw):
318 """Starts a kernel process and configures the manager to use it.
319
320 If random ports (port=0) are being used, this method must be called
321 before the channels are created.
322
323 Parameters:
324 -----------
325 launcher : callable, optional (default None)
326 A custom function for launching the kernel process (generally a
327 wrapper around ``entry_point.base_launch_kernel``). In most cases,
328 it should not be necessary to use this parameter.
329
330 **kw : optional
331 See respective options for IPython and Python kernels.
332 """
333 pass
334
335 @abc.abstractmethod
336 def shutdown_kernel(self, now=False, restart=False):
337 """ Attempts to the stop the kernel process cleanly."""
338 pass
339
340 @abc.abstractmethod
341 def restart_kernel(self, now=False, **kw):
342 """Restarts a kernel with the arguments that were used to launch it.
343
344 If the old kernel was launched with random ports, the same ports will be
345 used for the new kernel.
346
347 Parameters
348 ----------
349 now : bool, optional
350 If True, the kernel is forcefully restarted *immediately*, without
351 having a chance to do any cleanup action. Otherwise the kernel is
352 given 1s to clean up before a forceful restart is issued.
353
354 In all cases the kernel is restarted, the only difference is whether
355 it is given a chance to perform a clean shutdown or not.
356
357 **kw : optional
358 Any options specified here will replace those used to launch the
359 kernel.
360 """
361 pass
362
363 @abc.abstractproperty
364 def has_kernel(self):
365 """Returns whether a kernel process has been specified for the kernel
366 manager.
367 """
368 pass
369
370 @abc.abstractmethod
371 def kill_kernel(self):
372 """ Kill the running kernel.
373
374 This method blocks until the kernel process has terminated.
375 """
376 pass
377
378 @abc.abstractmethod
379 def interrupt_kernel(self):
380 """ Interrupts the kernel.
381
382 Unlike ``signal_kernel``, this operation is well supported on all
383 platforms.
384 """
385 pass
386
387 @abc.abstractmethod
388 def signal_kernel(self, signum):
389 """ Sends a signal to the kernel.
390
391 Note that since only SIGTERM is supported on Windows, this function is
392 only useful on Unix systems.
393 """
394 pass
395
396 @abc.abstractproperty
397 def is_alive(self):
398 """Is the kernel process still running?"""
399 pass
@@ -17,8 +17,6 b' Authors:'
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 import os
19 import os
20 import signal
21 import sys
22 import uuid
20 import uuid
23
21
24 import zmq
22 import zmq
@@ -94,7 +92,7 b' class MultiKernelManager(LoggingConfigurable):'
94 )
92 )
95 km.start_kernel(**kwargs)
93 km.start_kernel(**kwargs)
96 # start just the shell channel, needed for graceful restart
94 # start just the shell channel, needed for graceful restart
97 km.start_channels(shell=True, sub=False, stdin=False, hb=False)
95 km.start_channels(shell=True, iopub=False, stdin=False, hb=False)
98 self._kernels[kernel_id] = km
96 self._kernels[kernel_id] = km
99 return kernel_id
97 return kernel_id
100
98
@@ -14,10 +14,6 b' Useful for test suites and blocking terminal interfaces.'
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 from __future__ import print_function
15 from __future__ import print_function
16
16
17 # Standard library imports.
18 import Queue
19 from threading import Event
20
21 # Local imports.
17 # Local imports.
22 from IPython.utils.io import raw_print
18 from IPython.utils.io import raw_print
23 from IPython.utils.traitlets import Type
19 from IPython.utils.traitlets import Type
@@ -15,6 +15,11 b''
15 from IPython.config.loader import Config
15 from IPython.config.loader import Config
16 from IPython.inprocess.socket import DummySocket
16 from IPython.inprocess.socket import DummySocket
17 from IPython.utils.traitlets import HasTraits, Any, Instance, Type
17 from IPython.utils.traitlets import HasTraits, Any, Instance, Type
18 from IPython.zmq.kernelmanagerabc import (
19 ShellChannelABC, IOPubChannelABC,
20 HBChannelABC, StdInChannelABC,
21 KernelManagerABC
22 )
18
23
19 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
20 # Channel classes
25 # Channel classes
@@ -322,15 +327,15 b' class InProcessKernelManager(HasTraits):'
322 _hb_channel = Any
327 _hb_channel = Any
323
328
324 #--------------------------------------------------------------------------
329 #--------------------------------------------------------------------------
325 # Channel management methods:
330 # Channel management methods.
326 #--------------------------------------------------------------------------
331 #--------------------------------------------------------------------------
327
332
328 def start_channels(self, shell=True, sub=True, stdin=True, hb=True):
333 def start_channels(self, shell=True, iopub=True, stdin=True, hb=True):
329 """ Starts the channels for this kernel.
334 """ Starts the channels for this kernel.
330 """
335 """
331 if shell:
336 if shell:
332 self.shell_channel.start()
337 self.shell_channel.start()
333 if sub:
338 if iopub:
334 self.iopub_channel.start()
339 self.iopub_channel.start()
335 if stdin:
340 if stdin:
336 self.stdin_channel.start()
341 self.stdin_channel.start()
@@ -358,6 +363,35 b' class InProcessKernelManager(HasTraits):'
358 return (self.shell_channel.is_alive() or self.iopub_channel.is_alive() or
363 return (self.shell_channel.is_alive() or self.iopub_channel.is_alive() or
359 self.stdin_channel.is_alive() or self.hb_channel.is_alive())
364 self.stdin_channel.is_alive() or self.hb_channel.is_alive())
360
365
366 @property
367 def shell_channel(self):
368 """Get the REQ socket channel object to make requests of the kernel."""
369 if self._shell_channel is None:
370 self._shell_channel = self.shell_channel_class(self)
371 return self._shell_channel
372
373 @property
374 def iopub_channel(self):
375 """Get the SUB socket channel object."""
376 if self._iopub_channel is None:
377 self._iopub_channel = self.iopub_channel_class(self)
378 return self._iopub_channel
379
380 @property
381 def stdin_channel(self):
382 """Get the REP socket channel object to handle stdin (raw_input)."""
383 if self._stdin_channel is None:
384 self._stdin_channel = self.stdin_channel_class(self)
385 return self._stdin_channel
386
387 @property
388 def hb_channel(self):
389 """Get the heartbeat socket channel object to check that the
390 kernel is alive."""
391 if self._hb_channel is None:
392 self._hb_channel = self.hb_channel_class(self)
393 return self._hb_channel
394
361 #--------------------------------------------------------------------------
395 #--------------------------------------------------------------------------
362 # Kernel management methods:
396 # Kernel management methods:
363 #--------------------------------------------------------------------------
397 #--------------------------------------------------------------------------
@@ -409,35 +443,13 b' class InProcessKernelManager(HasTraits):'
409 """ Is the kernel process still running? """
443 """ Is the kernel process still running? """
410 return True
444 return True
411
445
412 #--------------------------------------------------------------------------
413 # Channels used for communication with the kernel:
414 #--------------------------------------------------------------------------
415
416 @property
417 def shell_channel(self):
418 """Get the REQ socket channel object to make requests of the kernel."""
419 if self._shell_channel is None:
420 self._shell_channel = self.shell_channel_class(self)
421 return self._shell_channel
422
423 @property
424 def iopub_channel(self):
425 """Get the SUB socket channel object."""
426 if self._iopub_channel is None:
427 self._iopub_channel = self.iopub_channel_class(self)
428 return self._iopub_channel
429
446
430 @property
447 #-----------------------------------------------------------------------------
431 def stdin_channel(self):
448 # ABC Registration
432 """Get the REP socket channel object to handle stdin (raw_input)."""
449 #-----------------------------------------------------------------------------
433 if self._stdin_channel is None:
434 self._stdin_channel = self.stdin_channel_class(self)
435 return self._stdin_channel
436
450
437 @property
451 ShellChannelABC.register(InProcessShellChannel)
438 def hb_channel(self):
452 IOPubChannelABC.register(InProcessIOPubChannel)
439 """Get the heartbeat socket channel object to check that the
453 HBChannelABC.register(InProcessHBChannel)
440 kernel is alive."""
454 StdInChannelABC.register(InProcessStdInChannel)
441 if self._hb_channel is None:
455 KernelManagerABC.register(InProcessKernelManager)
442 self._hb_channel = self.hb_channel_class(self)
443 return self._hb_channel
@@ -13,6 +13,8 b' Useful for test suites and blocking terminal interfaces.'
13 # Imports
13 # Imports
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15
15
16 import Queue
17
16 from IPython.utils.traitlets import Type
18 from IPython.utils.traitlets import Type
17 from kernelmanager import KernelManager, IOPubChannel, HBChannel, \
19 from kernelmanager import KernelManager, IOPubChannel, HBChannel, \
18 ShellChannel, StdInChannel
20 ShellChannel, StdInChannel
@@ -81,4 +83,4 b' class BlockingKernelManager(KernelManager):'
81 iopub_channel_class = Type(BlockingIOPubChannel)
83 iopub_channel_class = Type(BlockingIOPubChannel)
82 stdin_channel_class = Type(BlockingStdInChannel)
84 stdin_channel_class = Type(BlockingStdInChannel)
83 hb_channel_class = Type(BlockingHBChannel)
85 hb_channel_class = Type(BlockingHBChannel)
84
86
@@ -34,15 +34,20 b' from zmq import ZMQError'
34 from zmq.eventloop import ioloop, zmqstream
34 from zmq.eventloop import ioloop, zmqstream
35
35
36 # Local imports.
36 # Local imports.
37 from IPython.config.loader import Config
38 from IPython.config.configurable import Configurable
37 from IPython.config.configurable import Configurable
39 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
38 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
40 from IPython.utils.traitlets import (
39 from IPython.utils.traitlets import (
41 HasTraits, Any, Instance, Type, Unicode, Integer, Bool, CaselessStrEnum
40 Any, Instance, Type, Unicode, Integer, Bool, CaselessStrEnum
42 )
41 )
43 from IPython.utils.py3compat import str_to_bytes
42 from IPython.utils.py3compat import str_to_bytes
44 from IPython.zmq.entry_point import write_connection_file
43 from IPython.zmq.entry_point import write_connection_file
45 from session import Session
44 from session import Session
45 from IPython.zmq.kernelmanagerabc import (
46 ShellChannelABC, IOPubChannelABC,
47 HBChannelABC, StdInChannelABC,
48 KernelManagerABC
49 )
50
46
51
47 #-----------------------------------------------------------------------------
52 #-----------------------------------------------------------------------------
48 # Constants and exceptions
53 # Constants and exceptions
@@ -698,7 +703,7 b' class KernelManager(Configurable):'
698 # Channel management methods:
703 # Channel management methods:
699 #--------------------------------------------------------------------------
704 #--------------------------------------------------------------------------
700
705
701 def start_channels(self, shell=True, sub=True, stdin=True, hb=True):
706 def start_channels(self, shell=True, iopub=True, stdin=True, hb=True):
702 """Starts the channels for this kernel.
707 """Starts the channels for this kernel.
703
708
704 This will create the channels if they do not exist and then start
709 This will create the channels if they do not exist and then start
@@ -708,7 +713,7 b' class KernelManager(Configurable):'
708 """
713 """
709 if shell:
714 if shell:
710 self.shell_channel.start()
715 self.shell_channel.start()
711 if sub:
716 if iopub:
712 self.iopub_channel.start()
717 self.iopub_channel.start()
713 if stdin:
718 if stdin:
714 self.stdin_channel.start()
719 self.stdin_channel.start()
@@ -736,8 +741,56 b' class KernelManager(Configurable):'
736 return (self.shell_channel.is_alive() or self.iopub_channel.is_alive() or
741 return (self.shell_channel.is_alive() or self.iopub_channel.is_alive() or
737 self.stdin_channel.is_alive() or self.hb_channel.is_alive())
742 self.stdin_channel.is_alive() or self.hb_channel.is_alive())
738
743
744 def _make_url(self, port):
745 """make a zmq url with a port"""
746 if self.transport == 'tcp':
747 return "tcp://%s:%i" % (self.ip, port)
748 else:
749 return "%s://%s-%s" % (self.transport, self.ip, port)
750
751 @property
752 def shell_channel(self):
753 """Get the REQ socket channel object to make requests of the kernel."""
754 if self._shell_channel is None:
755 self._shell_channel = self.shell_channel_class(self.context,
756 self.session,
757 self._make_url(self.shell_port),
758 )
759 return self._shell_channel
760
761 @property
762 def iopub_channel(self):
763 """Get the SUB socket channel object."""
764 if self._iopub_channel is None:
765 self._iopub_channel = self.iopub_channel_class(self.context,
766 self.session,
767 self._make_url(self.iopub_port),
768 )
769 return self._iopub_channel
770
771 @property
772 def stdin_channel(self):
773 """Get the REP socket channel object to handle stdin (raw_input)."""
774 if self._stdin_channel is None:
775 self._stdin_channel = self.stdin_channel_class(self.context,
776 self.session,
777 self._make_url(self.stdin_port),
778 )
779 return self._stdin_channel
780
781 @property
782 def hb_channel(self):
783 """Get the heartbeat socket channel object to check that the
784 kernel is alive."""
785 if self._hb_channel is None:
786 self._hb_channel = self.hb_channel_class(self.context,
787 self.session,
788 self._make_url(self.hb_port),
789 )
790 return self._hb_channel
791
739 #--------------------------------------------------------------------------
792 #--------------------------------------------------------------------------
740 # Kernel process management methods:
793 # Connection and ipc file management.
741 #--------------------------------------------------------------------------
794 #--------------------------------------------------------------------------
742
795
743 def cleanup_connection_file(self):
796 def cleanup_connection_file(self):
@@ -796,7 +849,11 b' class KernelManager(Configurable):'
796 self.hb_port = cfg['hb_port']
849 self.hb_port = cfg['hb_port']
797
850
798 self._connection_file_written = True
851 self._connection_file_written = True
799
852
853 #--------------------------------------------------------------------------
854 # Kernel management.
855 #--------------------------------------------------------------------------
856
800 def start_kernel(self, **kw):
857 def start_kernel(self, **kw):
801 """Starts a kernel process and configures the manager to use it.
858 """Starts a kernel process and configures the manager to use it.
802
859
@@ -985,54 +1042,13 b' class KernelManager(Configurable):'
985 # so naively return True
1042 # so naively return True
986 return True
1043 return True
987
1044
988 #--------------------------------------------------------------------------
989 # Channels used for communication with the kernel:
990 #--------------------------------------------------------------------------
991
992 def _make_url(self, port):
993 """make a zmq url with a port"""
994 if self.transport == 'tcp':
995 return "tcp://%s:%i" % (self.ip, port)
996 else:
997 return "%s://%s-%s" % (self.transport, self.ip, port)
998
999 @property
1000 def shell_channel(self):
1001 """Get the REQ socket channel object to make requests of the kernel."""
1002 if self._shell_channel is None:
1003 self._shell_channel = self.shell_channel_class(self.context,
1004 self.session,
1005 self._make_url(self.shell_port),
1006 )
1007 return self._shell_channel
1008
1009 @property
1010 def iopub_channel(self):
1011 """Get the SUB socket channel object."""
1012 if self._iopub_channel is None:
1013 self._iopub_channel = self.iopub_channel_class(self.context,
1014 self.session,
1015 self._make_url(self.iopub_port),
1016 )
1017 return self._iopub_channel
1018
1045
1019 @property
1046 #-----------------------------------------------------------------------------
1020 def stdin_channel(self):
1047 # ABC Registration
1021 """Get the REP socket channel object to handle stdin (raw_input)."""
1048 #-----------------------------------------------------------------------------
1022 if self._stdin_channel is None:
1023 self._stdin_channel = self.stdin_channel_class(self.context,
1024 self.session,
1025 self._make_url(self.stdin_port),
1026 )
1027 return self._stdin_channel
1028
1049
1029 @property
1050 ShellChannelABC.register(ShellChannel)
1030 def hb_channel(self):
1051 IOPubChannelABC.register(IOPubChannel)
1031 """Get the heartbeat socket channel object to check that the
1052 HBChannelABC.register(HBChannel)
1032 kernel is alive."""
1053 StdInChannelABC.register(StdInChannel)
1033 if self._hb_channel is None:
1054 KernelManagerABC.register(KernelManager)
1034 self._hb_channel = self.hb_channel_class(self.context,
1035 self.session,
1036 self._make_url(self.hb_port),
1037 )
1038 return self._hb_channel
General Comments 0
You need to be logged in to leave comments. Login now