python (3.12.0)
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)