Skip to content

Commit 818f5b5

Browse files
bpo-32604: Clean up test.support.interpreters. (gh-20926)
There were some minor adjustments needed and a few tests were missing. https://bugs.python.org/issue32604
1 parent c4862e3 commit 818f5b5

File tree

3 files changed

+413
-204
lines changed

3 files changed

+413
-204
lines changed

Lib/test/support/interpreters.py

Lines changed: 93 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Subinterpreters High Level Module."""
22

3+
import time
34
import _xxsubinterpreters as _interpreters
45

56
# aliases:
@@ -19,165 +20,178 @@
1920

2021

2122
def create(*, isolated=True):
22-
"""
23-
Initialize a new (idle) Python interpreter.
24-
"""
23+
"""Return a new (idle) Python interpreter."""
2524
id = _interpreters.create(isolated=isolated)
2625
return Interpreter(id, isolated=isolated)
2726

2827

2928
def list_all():
30-
"""
31-
Get all existing interpreters.
32-
"""
33-
return [Interpreter(id) for id in
34-
_interpreters.list_all()]
29+
"""Return all existing interpreters."""
30+
return [Interpreter(id) for id in _interpreters.list_all()]
3531

3632

3733
def get_current():
38-
"""
39-
Get the currently running interpreter.
40-
"""
34+
"""Return the currently running interpreter."""
4135
id = _interpreters.get_current()
4236
return Interpreter(id)
4337

4438

4539
def get_main():
46-
"""
47-
Get the main interpreter.
48-
"""
40+
"""Return the main interpreter."""
4941
id = _interpreters.get_main()
5042
return Interpreter(id)
5143

5244

5345
class Interpreter:
54-
"""
55-
The Interpreter object represents
56-
a single interpreter.
57-
"""
46+
"""A single Python interpreter."""
5847

5948
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}')
6051
self._id = id
6152
self._isolated = isolated
6253

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+
6368
@property
6469
def id(self):
6570
return self._id
6671

6772
@property
6873
def isolated(self):
6974
if self._isolated is None:
75+
# XXX The low-level function has not been added yet.
76+
# See bpo-....
7077
self._isolated = _interpreters.is_isolated(self._id)
7178
return self._isolated
7279

7380
def is_running(self):
74-
"""
75-
Return whether or not the identified
76-
interpreter is running.
77-
"""
81+
"""Return whether or not the identified interpreter is running."""
7882
return _interpreters.is_running(self._id)
7983

8084
def close(self):
81-
"""
82-
Finalize and destroy the interpreter.
85+
"""Finalize and destroy the interpreter.
8386
84-
Attempting to destroy the current
85-
interpreter results in a RuntimeError.
87+
Attempting to destroy the current interpreter results
88+
in a RuntimeError.
8689
"""
8790
return _interpreters.destroy(self._id)
8891

8992
def run(self, src_str, /, *, channels=None):
90-
"""
91-
Run the given source code in the interpreter.
93+
"""Run the given source code in the interpreter.
94+
9295
This blocks the current Python thread until done.
9396
"""
94-
_interpreters.run_string(self._id, src_str)
97+
_interpreters.run_string(self._id, src_str, channels)
9598

9699

97100
def create_channel():
98-
"""
99-
Create a new channel for passing data between
100-
interpreters.
101-
"""
101+
"""Return (recv, send) for a new cross-interpreter channel.
102102
103+
The channel may be used to pass data safely between interpreters.
104+
"""
103105
cid = _interpreters.channel_create()
104-
return (RecvChannel(cid), SendChannel(cid))
106+
recv, send = RecvChannel(cid), SendChannel(cid)
107+
return recv, send
105108

106109

107110
def list_all_channels():
108-
"""
109-
Get all open channels.
110-
"""
111+
"""Return a list of (recv, send) for all open channels."""
111112
return [(RecvChannel(cid), SendChannel(cid))
112113
for cid in _interpreters.channel_list_all()]
113114

114115

116+
class _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+
115143
_NOT_SET = object()
116144

117145

118-
class RecvChannel:
119-
"""
120-
The RecvChannel object represents
121-
a receiving channel.
122-
"""
146+
class RecvChannel(_ChannelEnd):
147+
"""The receiving end of a cross-interpreter channel."""
123148

124-
def __init__(self, id):
125-
self._id = id
149+
def recv(self, *, _sentinel=object(), _delay=10 / 1000): # 10 milliseconds
150+
"""Return the next object from the channel.
126151
127-
def recv(self, *, _delay=10 / 1000): # 10 milliseconds
128-
"""
129-
Get the next object from the channel,
130-
and wait if none have been sent.
131-
Associate the interpreter with the channel.
152+
This blocks until an object has been sent, if none have been
153+
sent already.
132154
"""
133-
import time
134-
sentinel = object()
135-
obj = _interpreters.channel_recv(self._id, sentinel)
136-
while obj is sentinel:
155+
obj = _interpreters.channel_recv(self._id, _sentinel)
156+
while obj is _sentinel:
137157
time.sleep(_delay)
138-
obj = _interpreters.channel_recv(self._id, sentinel)
158+
obj = _interpreters.channel_recv(self._id, _sentinel)
139159
return obj
140160

141161
def recv_nowait(self, default=_NOT_SET):
142-
"""
143-
Like recv(), but return the default
144-
instead of waiting.
162+
"""Return the next object from the channel.
145163
146-
This function is blocked by a missing low-level
147-
implementation of channel_recv_wait().
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().
148167
"""
149168
if default is _NOT_SET:
150169
return _interpreters.channel_recv(self._id)
151170
else:
152171
return _interpreters.channel_recv(self._id, default)
153172

154173

155-
class SendChannel:
156-
"""
157-
The SendChannel object represents
158-
a sending channel.
159-
"""
160-
161-
def __init__(self, id):
162-
self._id = id
174+
class SendChannel(_ChannelEnd):
175+
"""The sending end of a cross-interpreter channel."""
163176

164177
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.
165181
"""
166-
Send the object (i.e. its data) to the receiving
167-
end of the channel and wait. Associate the interpreter
168-
with the channel.
169-
"""
170-
import time
171182
_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:
172186
time.sleep(2)
173187

174188
def send_nowait(self, obj):
175-
"""
176-
Like send(), but return False if not received.
189+
"""Send the object to the channel's receiving end.
177190
178-
This function is blocked by a missing low-level
179-
implementation of channel_send_wait().
191+
If the object is immediately received then return True
192+
(else False). Otherwise this is the same as send().
180193
"""
181-
182-
_interpreters.channel_send(self._id, obj)
183-
return False
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)

Lib/test/test__xxsubinterpreters.py

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -759,21 +759,9 @@ def test_still_running(self):
759759

760760
class RunStringTests(TestBase):
761761

762-
SCRIPT = dedent("""
763-
with open('{}', 'w') as out:
764-
out.write('{}')
765-
""")
766-
FILENAME = 'spam'
767-
768762
def setUp(self):
769763
super().setUp()
770764
self.id = interpreters.create()
771-
self._fs = None
772-
773-
def tearDown(self):
774-
if self._fs is not None:
775-
self._fs.close()
776-
super().tearDown()
777765

778766
def test_success(self):
779767
script, file = _captured_script('print("it worked!", end="")')

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy