198 lines
5.7 KiB
Python
198 lines
5.7 KiB
Python
|
"""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)
|