8000 gh-76785: Multiple Interpreters in the Stdlib (PEP 554) by nanjekyejoannah · Pull Request #18817 · python/cpython · GitHub
[go: up one dir, main page]

Skip to content

gh-76785: Multiple Interpreters in the Stdlib (PEP 554) #18817

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 21 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Add tests
  • Loading branch information
nanjekyejoannah committed Sep 13, 2019
commit 9ab54eb47764f1508b23936375cc890f93c9c074
14 changes: 8 additions & 6 deletions Doc/library/interpreters.rst
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
:mod:`interpreters` --- High-level Sub-interpreters Module
:mod:`interpreters` --- High-level Subinterpreters Module
==========================================================

.. module:: interpreters
:synopsis: High-level Sub-Interpreters Module.
:synopsis: High-level SubInterpreters Module.

**Source code:** :source:`Lib/interpreters.py`

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the module going to be provisional still in 3.9 or is this going to be part of the stable API? If it's going to be provisional, I think it could include a note in the header to explain that.

Expand Down Expand Up @@ -56,8 +56,9 @@ The RecvChannel object represents a recieving channel.

.. method:: release()

No longer associate the current interpreter with the channel
(on the sending end).
Close the channel for the current interpreter. 'send' and 'recv' (bool) may
be used to indicate the ends to close. By default both ends are closed.
Closing an already closed end is a noop.

.. method:: close(force=False)

Expand Down Expand Up @@ -93,8 +94,9 @@ The SendChannel object represents a sending channel.

.. method:: release()

No longer associate the current interpreter with the channel
(on the sending end).
Close the channel for the current interpreter. 'send' and 'recv' (bool) may
be used to indicate the ends to close. By default both ends are closed.
Closing an already closed end is a noop.

.. method:: close(force=False)

Expand Down
32 changes: 24 additions & 8 deletions Lib/interpreters.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
import _interpreters
import logger

__all__ = _all__ = ['create', 'list_all', 'get_current', 'get_main',
'run_string', 'destroy']
__all__ = ['Interpreter', 'SendChannel', 'RecvChannel', 'is_shareable',
'create_channel', 'list_all_channels', 'list_all', 'get_current',
'create']


class Interpreter:
Expand Down Expand Up @@ -59,7 +60,6 @@ def __init__(self, id):
def recv(self, timeout=2):
""" channel_recv() -> obj


Get the next object from the channel,
and wait if none have been sent.
Associate the interpreter with the channel.
Expand All @@ -69,6 +69,7 @@ def recv(self, timeout=2):
wait(timeout)
obj = obj = _interpreters.channel_recv(self.id)

# Pending: See issue 52 on multi-core python project
associate_interp_to_channel(interpId, Cid)

return obj
Expand All @@ -80,13 +81,28 @@ def recv_nowait(self, default=None):
"""
return _interpreters.channel_recv(self.id)

def send_buffer(self, obj):
""" send_buffer(obj)

Send the object's buffer to the receiving end of the channel
and wait. Associate the interpreter with the channel.
"""
pass

def send_buffer_nowait(self, obj):
""" send_buffer_nowait(obj)

Like send_buffer(), but return False if not received.
"""
pass

def release(self):
""" release()

No longer associate the current interpreterwith the channel
(on the sending end).
"""
pass
return _interpreters.(self.id)

def close(self, force=False):
"""close(force=False)
Expand All @@ -102,14 +118,14 @@ def __init__(self, id):
self.id = id
self.interpreters = _interpreters.list_all()

def send(self, obj, timeout=2):
def send(self, obj):
""" send(obj)

Send the object (i.e. its data) to the receiving end of the channel
and wait. Associate the interpreter with the channel.
"""
obj = _interpreters.channel_send(self.id, obj)
wait(timeout)
wait(2)
associate_interp_to_channel(interpId, Cid)

def send_nowait(self, obj):
Expand All @@ -127,10 +143,10 @@ def send_nowait(self, obj):
def release(self):
""" release()

No longer associate the current interpreterwith the channel
No longer associate the current interpreter with the channel
(on the sending end).
"""
pass
return _interpreters.channel_release(self.id)

def close(self, force=False):
""" close(force=False)
Expand Down
176 changes: 106 additions & 70 deletions Lib/test/test_interpreters.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,50 +23,86 @@ def clean_up_interpreters():
except RuntimeError:
pass # already destroyed


class LowLevelStub:
# set these as appropriate in tests
errors = ()
return_create = ()
...
def __init__(self):
self._calls = []
def _add_call(self, name, args=(), kwargs=None):
self.calls.append(
(name, args, kwargs or {}))
def _maybe_error(self):
if not self.errors:
return
err = self.errors.pop(0)
if err is not None:
raise err
def check_calls(self, test, expected):
test.assertEqual(self._calls, expected)
for returns in [self.errors, self.return_create, ...]:
test.assertEqual(tuple(returns), ()) # make sure all were used

# the stubbed methods
def create(self):
self._add_call('create')
self._maybe_error()
return self.return_create.pop(0)
def list_all(self):
...
def get_current(self):
...
def get_main(self):
...
def destroy(self, id):
...
def run_string(self, id, text, ...):
...

def _run_output(interp, request, shared=None):
script, rpipe = _captured_script(request)
with rpipe:
interpreters.run_string(interp, script, shared)
return rpipe.read()

class TestBase(unittest.TestCase):

def tearDown(self):
clean_up_interpreters()

class TestInterpreter(TestBase):

def test_is_running(self):
interp = interpreters.Interpreter(1)
self.assertEqual(True, interp.is_running())

def test_destroy(self):
interp = interpreters.Interpreter(1)
interp2 = interpreters.Interpreter(2)
interp.destroy()
ids = interpreters.list_all()
self.assertEqual(ids, [interp2.id])

def test_run(self):
interp = interpreters.Interpreter(1)
interp.run(dedent(f"""
import _interpreters
_interpreters.channel_send({cid}, b'spam')
"""))
out = _run_output(id2, dedent(f"""
import _interpreters
obj = _interpreters.channel_recv({cid})
_interpreters.channel_release({cid})
print(repr(obj))
"""))
self.assertEqual(out.strip(), "b'spam'")

class RecvChannelTest(TestBase):

def test_release(self):
import _interpreters as interpreters

chanl = interpreters.RecvChannel(1)
interpreters.channel_send(chanl.id, b'spam')
interpreters.channel_recv(cid)
chanl.release(cid)

with self.assertRaises(interpreters.ChannelClosedError):
interpreters.channel_send(cid, b'eggs')
with self.assertRaises(interpreters.ChannelClosedError):
interpreters.channel_recv(cid)

def test_close(self):
chanl = interpreters.RecvChannel(1)
chanl.close()
with self.assertRaises(interpreters.ChannelClosedError):
interpreters.channel_recv(fix.cid)

class SendChannelTest(TestBase):

def test_release(self):
import _interpreters as interpreters

chanl = interpreters.SendChannel(1)
interpreters.channel_send(chanl.id, b'spam')
interpreters.channel_recv(cid)
chanl.release(cid)

with self.assertRaises(interpreters.ChannelClosedError):
interpreters.channel_send(cid, b'eggs')
with self.assertRaises(interpreters.ChannelClosedError):
interpreters.channel_recv(cid)

def test_close(self):
chanl = interpreters.RecvChannel(1)
chanl.close()
with self.assertRaises(interpreters.ChannelClosedError):
interpreters.channel_recv(fix.cid)


class ListAllTests(TestBase):

Expand All @@ -75,51 +111,51 @@ def test_initial(self):
ids = interpreters.list_all()
self.assertEqual(ids, [main])


class GetCurrentTests(TestBase):

def test_get_current(self):
main = interpreters.get_main()
cur = interpreters.get_current()
self.assertEqual(cur, main)


class GetMainTests(TestBase):

def test_get_main(self):
expected, * = interpreters.list_all()
main = interpreters.get_main()
self.assertEqual(main, expected)


class CreateTests(TestBase):

def test_create(self):
interp = interpreters.create()
self.assertIn(interp, interpreters.list_all())


class DestroyTests(TestBase):

def test_destroy(self):
id1 = interpreters.create()
id2 = interpreters.create()
id3 = interpreters.create()
self.assertIn(id2, interpreters.list_all())
interpreters.destroy(id2)
self.assertNotIn(id2, interpreters.list_all())


class RunStringTests(TestBase):

def test_run_string(self):
script, file = _captured_script('print("it worked!", end="")')
id = interpreters.create()
with file:
interpreters.run_string(id, script, None)
out = file.read()

self.assertEqual(out, 'it worked!')
class ExceptionTests(TestBase):

def test_does_not_exist(self):
cid = interpreters.create_channel()
recvCha = interpreters.RecvChannel(cid)
with self.assertRaises(interpreters.ChannelNotFoundError):
interpreters._channel_id(int(cid) + 1)

def test_recv_empty(self):
cid = interpreters.create_channel()
recvCha = interpreters.RecvChannel(cid)
with self.assertRaises(interpreters.ChannelEmptyError):
recvCha.recv()

def test_channel_not_empty(self):
cid = interpreters.create_channel()
sendCha = interpreters.SendChannel(cid)
sendCha.send(b'spam')
sendCha.send(b'ham')

with self.assertRaises(interpreters.ChannelNotEmptyError):
sendCha.close()

def test_channel_closed(self):
cid = interpreters.channel_create()
sendCha = interpreters.SendChannel(cid)
sendCha.send(b'spam')
sendCha.send(b'ham')
sendCha.close()

with self.assertRaises(interpreters.ChannelClosedError):
sendCha.send(b'spam')


if __name__ == '__main__':
Expand Down
0