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