1  """Subinterpreters High Level Module."""
       2  
       3  import time
       4  import _xxsubinterpreters as _interpreters
       5  import _xxinterpchannels as _channels
       6  
       7  # aliases:
       8  from _xxsubinterpreters import is_shareable, RunFailedError
       9  from _xxinterpchannels import (
      10      ChannelError, ChannelNotFoundError, ChannelEmptyError,
      11  )
      12  
      13  
      14  __all__ = [
      15      'Interpreter', 'get_current', 'get_main', 'create', 'list_all',
      16      'SendChannel', 'RecvChannel',
      17      'create_channel', 'list_all_channels', 'is_shareable',
      18      'ChannelError', 'ChannelNotFoundError',
      19      'ChannelEmptyError',
      20      ]
      21  
      22  
      23  def create(*, isolated=True):
      24      """Return a new (idle) Python interpreter."""
      25      id = _interpreters.create(isolated=isolated)
      26      return Interpreter(id, isolated=isolated)
      27  
      28  
      29  def list_all():
      30      """Return all existing interpreters."""
      31      return [Interpreter(id) for id in _interpreters.list_all()]
      32  
      33  
      34  def get_current():
      35      """Return the currently running interpreter."""
      36      id = _interpreters.get_current()
      37      return Interpreter(id)
      38  
      39  
      40  def get_main():
      41      """Return the main interpreter."""
      42      id = _interpreters.get_main()
      43      return Interpreter(id)
      44  
      45  
      46  class ESC[4;38;5;81mInterpreter:
      47      """A single Python interpreter."""
      48  
      49      def __init__(self, id, *, isolated=None):
      50          if not isinstance(id, (int, _interpreters.InterpreterID)):
      51              raise TypeError(f'id must be an int, got {id!r}')
      52          self._id = id
      53          self._isolated = isolated
      54  
      55      def __repr__(self):
      56          data = dict(id=int(self._id), isolated=self._isolated)
      57          kwargs = (f'{k}={v!r}' for k, v in data.items())
      58          return f'{type(self).__name__}({", ".join(kwargs)})'
      59  
      60      def __hash__(self):
      61          return hash(self._id)
      62  
      63      def __eq__(self, other):
      64          if not isinstance(other, Interpreter):
      65              return NotImplemented
      66          else:
      67              return other._id == self._id
      68  
      69      @property
      70      def id(self):
      71          return self._id
      72  
      73      @property
      74      def isolated(self):
      75          if self._isolated is None:
      76              # XXX The low-level function has not been added yet.
      77              # See bpo-....
      78              self._isolated = _interpreters.is_isolated(self._id)
      79          return self._isolated
      80  
      81      def is_running(self):
      82          """Return whether or not the identified interpreter is running."""
      83          return _interpreters.is_running(self._id)
      84  
      85      def close(self):
      86          """Finalize and destroy the interpreter.
      87  
      88          Attempting to destroy the current interpreter results
      89          in a RuntimeError.
      90          """
      91          return _interpreters.destroy(self._id)
      92  
      93      def run(self, src_str, /, *, channels=None):
      94          """Run the given source code in the interpreter.
      95  
      96          This blocks the current Python thread until done.
      97          """
      98          _interpreters.run_string(self._id, src_str, channels)
      99  
     100  
     101  def create_channel():
     102      """Return (recv, send) for a new cross-interpreter channel.
     103  
     104      The channel may be used to pass data safely between interpreters.
     105      """
     106      cid = _channels.create()
     107      recv, send = RecvChannel(cid), SendChannel(cid)
     108      return recv, send
     109  
     110  
     111  def list_all_channels():
     112      """Return a list of (recv, send) for all open channels."""
     113      return [(RecvChannel(cid), SendChannel(cid))
     114              for cid in _channels.list_all()]
     115  
     116  
     117  class ESC[4;38;5;81m_ChannelEnd:
     118      """The base class for RecvChannel and SendChannel."""
     119  
     120      def __init__(self, id):
     121          if not isinstance(id, (int, _channels.ChannelID)):
     122              raise TypeError(f'id must be an int, got {id!r}')
     123          self._id = id
     124  
     125      def __repr__(self):
     126          return f'{type(self).__name__}(id={int(self._id)})'
     127  
     128      def __hash__(self):
     129          return hash(self._id)
     130  
     131      def __eq__(self, other):
     132          if isinstance(self, RecvChannel):
     133              if not isinstance(other, RecvChannel):
     134                  return NotImplemented
     135          elif not isinstance(other, SendChannel):
     136              return NotImplemented
     137          return other._id == self._id
     138  
     139      @property
     140      def id(self):
     141          return self._id
     142  
     143  
     144  _NOT_SET = object()
     145  
     146  
     147  class ESC[4;38;5;81mRecvChannel(ESC[4;38;5;149m_ChannelEnd):
     148      """The receiving end of a cross-interpreter channel."""
     149  
     150      def recv(self, *, _sentinel=object(), _delay=10 / 1000):  # 10 milliseconds
     151          """Return the next object from the channel.
     152  
     153          This blocks until an object has been sent, if none have been
     154          sent already.
     155          """
     156          obj = _channels.recv(self._id, _sentinel)
     157          while obj is _sentinel:
     158              time.sleep(_delay)
     159              obj = _channels.recv(self._id, _sentinel)
     160          return obj
     161  
     162      def recv_nowait(self, default=_NOT_SET):
     163          """Return the next object from the channel.
     164  
     165          If none have been sent then return the default if one
     166          is provided or fail with ChannelEmptyError.  Otherwise this
     167          is the same as recv().
     168          """
     169          if default is _NOT_SET:
     170              return _channels.recv(self._id)
     171          else:
     172              return _channels.recv(self._id, default)
     173  
     174  
     175  class ESC[4;38;5;81mSendChannel(ESC[4;38;5;149m_ChannelEnd):
     176      """The sending end of a cross-interpreter channel."""
     177  
     178      def send(self, obj):
     179          """Send the object (i.e. its data) to the channel's receiving end.
     180  
     181          This blocks until the object is received.
     182          """
     183          _channels.send(self._id, obj)
     184          # XXX We are missing a low-level channel_send_wait().
     185          # See bpo-32604 and gh-19829.
     186          # Until that shows up we fake it:
     187          time.sleep(2)
     188  
     189      def send_nowait(self, obj):
     190          """Send the object to the channel's receiving end.
     191  
     192          If the object is immediately received then return True
     193          (else False).  Otherwise this is the same as send().
     194          """
     195          # XXX Note that at the moment channel_send() only ever returns
     196          # None.  This should be fixed when channel_send_wait() is added.
     197          # See bpo-32604 and gh-19829.
     198          return _channels.send(self._id, obj)