client.py 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. """A client for in-process kernels."""
  2. # -----------------------------------------------------------------------------
  3. # Copyright (C) 2012 The IPython Development Team
  4. #
  5. # Distributed under the terms of the BSD License. The full license is in
  6. # the file LICENSE, distributed as part of this software.
  7. # -----------------------------------------------------------------------------
  8. # -----------------------------------------------------------------------------
  9. # Imports
  10. # -----------------------------------------------------------------------------
  11. from __future__ import annotations
  12. import asyncio
  13. from typing import Any
  14. from jupyter_client.client import KernelClient
  15. from jupyter_client.clientabc import KernelClientABC
  16. from jupyter_core.utils import run_sync
  17. # IPython imports
  18. from traitlets import Instance, Type, default
  19. # Local imports
  20. from .channels import InProcessChannel, InProcessHBChannel
  21. # -----------------------------------------------------------------------------
  22. # Main kernel Client class
  23. # -----------------------------------------------------------------------------
  24. class InProcessKernelClient(KernelClient):
  25. """A client for an in-process kernel.
  26. This class implements the interface of
  27. `jupyter_client.clientabc.KernelClientABC` and allows
  28. (asynchronous) frontends to be used seamlessly with an in-process kernel.
  29. See `jupyter_client.client.KernelClient` for docstrings.
  30. """
  31. # The classes to use for the various channels.
  32. shell_channel_class = Type(InProcessChannel) # type:ignore[assignment]
  33. iopub_channel_class = Type(InProcessChannel) # type:ignore[assignment]
  34. stdin_channel_class = Type(InProcessChannel) # type:ignore[assignment]
  35. control_channel_class = Type(InProcessChannel) # type:ignore[assignment]
  36. hb_channel_class = Type(InProcessHBChannel) # type:ignore[assignment]
  37. kernel = Instance("ipykernel.inprocess.ipkernel.InProcessKernel", allow_none=True)
  38. # --------------------------------------------------------------------------
  39. # Channel management methods
  40. # --------------------------------------------------------------------------
  41. @default("blocking_class")
  42. def _default_blocking_class(self):
  43. from .blocking import BlockingInProcessKernelClient
  44. return BlockingInProcessKernelClient
  45. def get_connection_info(self, session: bool = False) -> dict[str, int | str | bytes]:
  46. """Get the connection info for the client."""
  47. d = super().get_connection_info(session=session)
  48. d["kernel"] = self.kernel # type:ignore[assignment]
  49. return d
  50. def start_channels(self, *args, **kwargs):
  51. """Start the channels on the client."""
  52. super().start_channels()
  53. if self.kernel:
  54. self.kernel.frontends.append(self)
  55. @property
  56. def shell_channel(self):
  57. if self._shell_channel is None:
  58. self._shell_channel = self.shell_channel_class(self)
  59. return self._shell_channel
  60. @property
  61. def iopub_channel(self):
  62. if self._iopub_channel is None:
  63. self._iopub_channel = self.iopub_channel_class(self)
  64. return self._iopub_channel
  65. @property
  66. def stdin_channel(self):
  67. if self._stdin_channel is None:
  68. self._stdin_channel = self.stdin_channel_class(self)
  69. return self._stdin_channel
  70. @property
  71. def control_channel(self):
  72. if self._control_channel is None:
  73. self._control_channel = self.control_channel_class(self)
  74. return self._control_channel
  75. @property
  76. def hb_channel(self):
  77. if self._hb_channel is None:
  78. self._hb_channel = self.hb_channel_class(self)
  79. return self._hb_channel
  80. # Methods for sending specific messages
  81. # -------------------------------------
  82. def execute(
  83. self,
  84. code: str,
  85. silent: bool = False,
  86. store_history: bool = True,
  87. user_expressions: dict[str, Any] | None = None,
  88. allow_stdin: bool | None = None,
  89. stop_on_error: bool = True,
  90. ) -> str:
  91. """Execute code on the client."""
  92. if allow_stdin is None:
  93. allow_stdin = self.allow_stdin
  94. content = dict(
  95. code=code,
  96. silent=silent,
  97. store_history=store_history,
  98. user_expressions=user_expressions or {},
  99. allow_stdin=allow_stdin,
  100. )
  101. msg = self.session.msg("execute_request", content)
  102. self._dispatch_to_kernel(msg)
  103. res = msg["header"]["msg_id"]
  104. assert isinstance(res, str)
  105. return res
  106. def complete(self, code, cursor_pos=None):
  107. """Get code completion."""
  108. if cursor_pos is None:
  109. cursor_pos = len(code)
  110. content = dict(code=code, cursor_pos=cursor_pos)
  111. msg = self.session.msg("complete_request", content)
  112. self._dispatch_to_kernel(msg)
  113. return msg["header"]["msg_id"]
  114. def inspect(self, code, cursor_pos=None, detail_level=0):
  115. """Get code inspection."""
  116. if cursor_pos is None:
  117. cursor_pos = len(code)
  118. content = dict(
  119. code=code,
  120. cursor_pos=cursor_pos,
  121. detail_level=detail_level,
  122. )
  123. msg = self.session.msg("inspect_request", content)
  124. self._dispatch_to_kernel(msg)
  125. return msg["header"]["msg_id"]
  126. def history(self, raw=True, output=False, hist_access_type="range", **kwds):
  127. """Get code history."""
  128. content = dict(raw=raw, output=output, hist_access_type=hist_access_type, **kwds)
  129. msg = self.session.msg("history_request", content)
  130. self._dispatch_to_kernel(msg)
  131. return msg["header"]["msg_id"]
  132. def shutdown(self, restart=False):
  133. """Handle shutdown."""
  134. # FIXME: What to do here?
  135. msg = "Cannot shutdown in-process kernel"
  136. raise NotImplementedError(msg)
  137. def kernel_info(self):
  138. """Request kernel info."""
  139. msg = self.session.msg("kernel_info_request")
  140. self._dispatch_to_kernel(msg)
  141. return msg["header"]["msg_id"]
  142. def comm_info(self, target_name=None):
  143. """Request a dictionary of valid comms and their targets."""
  144. content = {} if target_name is None else dict(target_name=target_name)
  145. msg = self.session.msg("comm_info_request", content)
  146. self._dispatch_to_kernel(msg)
  147. return msg["header"]["msg_id"]
  148. def input(self, string):
  149. """Handle kernel input."""
  150. if self.kernel is None:
  151. msg = "Cannot send input reply. No kernel exists."
  152. raise RuntimeError(msg)
  153. self.kernel.raw_input_str = string
  154. def is_complete(self, code):
  155. """Handle an is_complete request."""
  156. msg = self.session.msg("is_complete_request", {"code": code})
  157. self._dispatch_to_kernel(msg)
  158. return msg["header"]["msg_id"]
  159. def _dispatch_to_kernel(self, msg):
  160. """Send a message to the kernel and handle a reply."""
  161. kernel = self.kernel
  162. if kernel is None:
  163. msg = "Cannot send request. No kernel exists."
  164. raise RuntimeError(msg)
  165. stream = kernel.shell_stream
  166. self.session.send(stream, msg)
  167. msg_parts = stream.recv_multipart()
  168. if run_sync is not None:
  169. dispatch_shell = run_sync(kernel.dispatch_shell)
  170. dispatch_shell(msg_parts)
  171. else:
  172. loop = asyncio.get_event_loop() # type:ignore[unreachable]
  173. loop.run_until_complete(kernel.dispatch_shell(msg_parts))
  174. _idents, reply_msg = self.session.recv(stream, copy=False)
  175. self.shell_channel.call_handlers_later(reply_msg)
  176. def get_shell_msg(self, block=True, timeout=None):
  177. """Get a shell message."""
  178. return self.shell_channel.get_msg(block, timeout)
  179. def get_iopub_msg(self, block=True, timeout=None):
  180. """Get an iopub message."""
  181. return self.iopub_channel.get_msg(block, timeout)
  182. def get_stdin_msg(self, block=True, timeout=None):
  183. """Get a stdin message."""
  184. return self.stdin_channel.get_msg(block, timeout)
  185. def get_control_msg(self, block=True, timeout=None):
  186. """Get a control message."""
  187. return self.control_channel.get_msg(block, timeout)
  188. # -----------------------------------------------------------------------------
  189. # ABC Registration
  190. # -----------------------------------------------------------------------------
  191. KernelClientABC.register(InProcessKernelClient)