| """Subinterpreters High Level Module.""" |
| |
| import time |
| import _xxsubinterpreters as _interpreters |
| |
| # aliases: |
| from _xxsubinterpreters import ( |
| ChannelError, ChannelNotFoundError, ChannelEmptyError, |
| is_shareable, |
| ) |
| |
| |
| __all__ = [ |
| 'Interpreter', 'get_current', 'get_main', 'create', 'list_all', |
| 'SendChannel', 'RecvChannel', |
| 'create_channel', 'list_all_channels', 'is_shareable', |
| 'ChannelError', 'ChannelNotFoundError', |
| 'ChannelEmptyError', |
| ] |
| |
| |
| def create(*, isolated=True): |
| """Return a new (idle) Python interpreter.""" |
| id = _interpreters.create(isolated=isolated) |
| return Interpreter(id, isolated=isolated) |
| |
| |
| def list_all(): |
| """Return all existing interpreters.""" |
| return [Interpreter(id) for id in _interpreters.list_all()] |
| |
| |
| def get_current(): |
| """Return the currently running interpreter.""" |
| id = _interpreters.get_current() |
| return Interpreter(id) |
| |
| |
| def get_main(): |
| """Return the main interpreter.""" |
| id = _interpreters.get_main() |
| return Interpreter(id) |
| |
| |
| class Interpreter: |
| """A single Python interpreter.""" |
| |
| def __init__(self, id, *, isolated=None): |
| if not isinstance(id, (int, _interpreters.InterpreterID)): |
| raise TypeError(f'id must be an int, got {id!r}') |
| self._id = id |
| self._isolated = isolated |
| |
| def __repr__(self): |
| data = dict(id=int(self._id), isolated=self._isolated) |
| kwargs = (f'{k}={v!r}' for k, v in data.items()) |
| return f'{type(self).__name__}({", ".join(kwargs)})' |
| |
| def __hash__(self): |
| return hash(self._id) |
| |
| def __eq__(self, other): |
| if not isinstance(other, Interpreter): |
| return NotImplemented |
| else: |
| return other._id == self._id |
| |
| @property |
| def id(self): |
| return self._id |
| |
| @property |
| def isolated(self): |
| if self._isolated is None: |
| # XXX The low-level function has not been added yet. |
| # See bpo-.... |
| self._isolated = _interpreters.is_isolated(self._id) |
| return self._isolated |
| |
| def is_running(self): |
| """Return whether or not the identified interpreter is running.""" |
| return _interpreters.is_running(self._id) |
| |
| def close(self): |
| """Finalize and destroy the interpreter. |
| |
| Attempting to destroy the current interpreter results |
| in a RuntimeError. |
| """ |
| return _interpreters.destroy(self._id) |
| |
| def run(self, src_str, /, *, channels=None): |
| """Run the given source code in the interpreter. |
| |
| This blocks the current Python thread until done. |
| """ |
| _interpreters.run_string(self._id, src_str, channels) |
| |
| |
| def create_channel(): |
| """Return (recv, send) for a new cross-interpreter channel. |
| |
| The channel may be used to pass data safely between interpreters. |
| """ |
| cid = _interpreters.channel_create() |
| recv, send = RecvChannel(cid), SendChannel(cid) |
| return recv, send |
| |
| |
| def list_all_channels(): |
| """Return a list of (recv, send) for all open channels.""" |
| return [(RecvChannel(cid), SendChannel(cid)) |
| for cid in _interpreters.channel_list_all()] |
| |
| |
| class _ChannelEnd: |
| """The base class for RecvChannel and SendChannel.""" |
| |
| def __init__(self, id): |
| if not isinstance(id, (int, _interpreters.ChannelID)): |
| raise TypeError(f'id must be an int, got {id!r}') |
| self._id = id |
| |
| def __repr__(self): |
| return f'{type(self).__name__}(id={int(self._id)})' |
| |
| def __hash__(self): |
| return hash(self._id) |
| |
| def __eq__(self, other): |
| if isinstance(self, RecvChannel): |
| if not isinstance(other, RecvChannel): |
| return NotImplemented |
| elif not isinstance(other, SendChannel): |
| return NotImplemented |
| return other._id == self._id |
| |
| @property |
| def id(self): |
| return self._id |
| |
| |
| _NOT_SET = object() |
| |
| |
| class RecvChannel(_ChannelEnd): |
| """The receiving end of a cross-interpreter channel.""" |
| |
| def recv(self, *, _sentinel=object(), _delay=10 / 1000): # 10 milliseconds |
| """Return the next object from the channel. |
| |
| This blocks until an object has been sent, if none have been |
| sent already. |
| """ |
| obj = _interpreters.channel_recv(self._id, _sentinel) |
| while obj is _sentinel: |
| time.sleep(_delay) |
| obj = _interpreters.channel_recv(self._id, _sentinel) |
| return obj |
| |
| def recv_nowait(self, default=_NOT_SET): |
| """Return the next object from the channel. |
| |
| If none have been sent then return the default if one |
| is provided or fail with ChannelEmptyError. Otherwise this |
| is the same as recv(). |
| """ |
| if default is _NOT_SET: |
| return _interpreters.channel_recv(self._id) |
| else: |
| return _interpreters.channel_recv(self._id, default) |
| |
| |
| class SendChannel(_ChannelEnd): |
| """The sending end of a cross-interpreter channel.""" |
| |
| def send(self, obj): |
| """Send the object (i.e. its data) to the channel's receiving end. |
| |
| This blocks until the object is received. |
| """ |
| _interpreters.channel_send(self._id, obj) |
| # XXX We are missing a low-level channel_send_wait(). |
| # See bpo-32604 and gh-19829. |
| # Until that shows up we fake it: |
| time.sleep(2) |
| |
| def send_nowait(self, obj): |
| """Send the object to the channel's receiving end. |
| |
| If the object is immediately received then return True |
| (else False). Otherwise this is the same as send(). |
| """ |
| # XXX Note that at the moment channel_send() only ever returns |
| # None. This should be fixed when channel_send_wait() is added. |
| # See bpo-32604 and gh-19829. |
| return _interpreters.channel_send(self._id, obj) |