Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
22 changes: 22 additions & 0 deletions Doc/library/concurrent.interpreters.rst
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,16 @@ Interpreter objects
Generally, :class:`Interpreter` shouldn't be called directly.
Instead, use :func:`create` or one of the other module functions.

In addition to interpreters created by this module (via
:func:`create`), an :class:`!Interpreter` object may wrap
interpreters not created by the module. Some methods fail for such
interpreters, as noted below for each such method.

An interpreter may be marked as currently executing code in its
:mod:`!__main__` module. See :meth:`~Interpreter.is_running`.
Some methods of a running :class:`!interpreter` fail, as noted
below for each such method.

.. attribute:: id

(read-only)
Expand Down Expand Up @@ -271,21 +281,33 @@ Interpreter objects
Some objects are actually shared and some are copied efficiently,
but most are copied via :mod:`pickle`. See :ref:`interp-object-sharing`.

This method fails for unsupported modules.
It also fails for running modules.

.. method:: exec(code, /, dedent=True)

Run the given source code in the interpreter (in the current thread).

This method fails for unsupported modules.
It also fails for running modules.

.. method:: call(callable, /, *args, **kwargs)

Return the result of calling running the given function in the
interpreter (in the current thread).

This method fails for unsupported modules.
It also fails for running modules.

.. _interp-call-in-thread:

.. method:: call_in_thread(callable, /, *args, **kwargs)

Run the given function in the interpreter (in a new thread).

This method fails for unsupported modules.
It also fails for running modules.

Exceptions
^^^^^^^^^^

Expand Down
74 changes: 61 additions & 13 deletions Lib/test/test_interpreters/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -717,18 +717,18 @@ def test_created_with_capi(self):
with self.subTest('running __main__ (from self)'):
with self.interpreter_from_capi() as interpid:
with self.assertRaisesRegex(ExecutionFailed,
'InterpreterError.*unrecognized'):
'InterpreterError.*not supported'):
self.run_from_capi(interpid, script, main=True)

with self.subTest('running, but not __main__ (from self)'):
with self.assertRaisesRegex(ExecutionFailed,
'InterpreterError.*unrecognized'):
'InterpreterError.*not supported'):
self.run_temp_from_capi(script)

with self.subTest('running __main__ (from other)'):
with self.interpreter_obj_from_capi() as (interp, interpid):
with self.running_from_capi(interpid, main=True):
with self.assertRaisesRegex(InterpreterError, 'unrecognized'):
with self.assertRaisesRegex(InterpreterError, 'not supported'):
interp.close()
# Make sure it wssn't closed.
self.assertTrue(
Expand All @@ -741,15 +741,15 @@ def test_created_with_capi(self):
with self.subTest('running, but not __main__ (from other)'):
with self.interpreter_obj_from_capi() as (interp, interpid):
with self.running_from_capi(interpid, main=False):
with self.assertRaisesRegex(InterpreterError, 'unrecognized'):
with self.assertRaisesRegex(InterpreterError, 'not supported'):
interp.close()
# Make sure it wssn't closed.
self.assertTrue(
self.interp_exists(interpid))

with self.subTest('not running (from other)'):
with self.interpreter_obj_from_capi() as (interp, interpid):
with self.assertRaisesRegex(InterpreterError, 'unrecognized'):
with self.assertRaisesRegex(InterpreterError, 'not supported'):
interp.close()
self.assertTrue(
self.interp_exists(interpid))
Expand Down Expand Up @@ -819,6 +819,43 @@ def task():

class TestInterpreterPrepareMain(TestBase):

def test_main(self):
interp0 = interpreters.get_main()
if interp0 is interpreters.get_current():
import __main__ as mainmod
mainns = vars(mainmod)
self.assertNotIn('prepare_main_spam', mainns)

with self.subTest('in current'):
try:
with self.assertRaisesRegex(InterpreterError, 'running'):
interp0.prepare_main(prepare_main_spam='spam!!!')
finally:
mainns.pop('prepare_main_spam', None)
self.assertNotIn('prepare_main_spam', mainns)

with self.subTest('in other'):
interp = interpreters.create()
try:
with self.assertRaisesRegex(ExecutionFailed, 'running'):
interp.exec(dedent("""
from concurrent import interpreters
interp0 = interpreters.get_main()
interp0.prepare_main(prepare_main_spam='spam!!!')
"""))
self.assertNotIn('prepare_main_spam', mainns)
finally:
mainns.pop('prepare_main_spam', None)
self.assertNotIn('prepare_main_spam', mainns)
else:
with self.subTest('in other'):
interp = interpreters.create()
interp.exec(dedent("""
from concurrent import interpreters
interp0 = interpreters.get_main()
interp0.prepare_main(prepare_main_spam='spam!!!')
"""))

def test_empty(self):
interp = interpreters.create()
with self.assertRaises(ValueError):
Expand Down Expand Up @@ -883,7 +920,7 @@ def test_running(self):
@requires_test_modules
def test_created_with_capi(self):
with self.interpreter_obj_from_capi() as (interp, interpid):
with self.assertRaisesRegex(InterpreterError, 'unrecognized'):
with self.assertRaisesRegex(InterpreterError, 'not supported'):
interp.prepare_main({'spam': True})
with self.assertRaisesRegex(ExecutionFailed, 'NameError'):
self.run_from_capi(interpid, 'assert spam is True')
Expand All @@ -907,6 +944,11 @@ def test_failure(self):
with self.assertRaises(ExecutionFailed):
interp.exec('raise Exception')

def test_main(self):
interp = interpreters.get_main()
with self.assertRaisesRegex(InterpreterError, 'running'):
interp.exec('print("spam")')

@force_not_colorized
def test_display_preserved_exception(self):
tempdir = self.temp_dir()
Expand Down Expand Up @@ -1056,7 +1098,7 @@ def task():

def test_created_with_capi(self):
with self.interpreter_obj_from_capi() as (interp, _):
with self.assertRaisesRegex(InterpreterError, 'unrecognized'):
with self.assertRaisesRegex(InterpreterError, 'not supported'):
interp.exec('raise Exception("it worked!")')

def test_list_comprehension(self):
Expand Down Expand Up @@ -1256,6 +1298,11 @@ def assert_exceptions_equal(self, exc1, exc2):
self.assertIs(type(exc1), type(exc2))
self.assertEqual(exc1.args, exc2.args)

def test_main(self):
interp = interpreters.get_main()
with self.assertRaisesRegex(InterpreterError, 'running'):
interp.call(call_func_noop)

def test_stateless_funcs(self):
interp = interpreters.create()

Expand Down Expand Up @@ -2224,7 +2271,7 @@ def test_destroy(self):

with self.subTest('from C-API'):
interpid = _testinternalcapi.create_interpreter()
with self.assertRaisesRegex(InterpreterError, 'unrecognized'):
with self.assertRaisesRegex(InterpreterError, 'not supported'):
_interpreters.destroy(interpid, restrict=True)
self.assertTrue(
self.interp_exists(interpid))
Expand Down Expand Up @@ -2268,7 +2315,7 @@ def test_get_config(self):
with self.subTest('from C-API'):
orig = _interpreters.new_config('isolated')
with self.interpreter_from_capi(orig) as interpid:
with self.assertRaisesRegex(InterpreterError, 'unrecognized'):
with self.assertRaisesRegex(InterpreterError, 'not supported'):
_interpreters.get_config(interpid, restrict=True)
config = _interpreters.get_config(interpid)
self.assert_ns_equal(config, orig)
Expand Down Expand Up @@ -2329,7 +2376,7 @@ def test_contextvars_missing(self):

def test_is_running(self):
def check(interpid, expected):
with self.assertRaisesRegex(InterpreterError, 'unrecognized'):
with self.assertRaisesRegex(InterpreterError, 'not supported'):
_interpreters.is_running(interpid, restrict=True)
running = _interpreters.is_running(interpid)
self.assertIs(running, expected)
Expand All @@ -2347,7 +2394,8 @@ def check(interpid, expected):

with self.subTest('main'):
interpid, *_ = _interpreters.get_main()
check(interpid, True)
running = _interpreters.is_running(interpid)
self.assertTrue(running)

with self.subTest('from C-API (running __main__)'):
with self.interpreter_from_capi() as interpid:
Expand Down Expand Up @@ -2394,7 +2442,7 @@ def test_exec(self):

with self.subTest('from C-API'):
with self.interpreter_from_capi() as interpid:
with self.assertRaisesRegex(InterpreterError, 'unrecognized'):
with self.assertRaisesRegex(InterpreterError, 'not supported'):
_interpreters.exec(interpid, 'raise Exception("it worked!")',
restrict=True)
exc = _interpreters.exec(interpid, 'raise Exception("it worked!")')
Expand Down Expand Up @@ -2473,7 +2521,7 @@ def test_set___main___attrs(self):

with self.subTest('from C-API'):
with self.interpreter_from_capi() as interpid:
with self.assertRaisesRegex(InterpreterError, 'unrecognized'):
with self.assertRaisesRegex(InterpreterError, 'not supported'):
_interpreters.set___main___attrs(interpid, {'spam': True},
restrict=True)
_interpreters.set___main___attrs(interpid, {'spam': True})
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Errors and documentation are clearer now relative to interpreters not
created by the :mod:`~concurrent.interpreters` module.
9 changes: 7 additions & 2 deletions Modules/_interpretersmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -755,6 +755,11 @@ resolve_interp(PyObject *idobj, int restricted, int reqready, const char *op)
}
}

if (_Py_IsMainInterpreter(interp)) {
assert(_PyInterpreterState_IsReady(interp));
return interp;
}

if (reqready && !_PyInterpreterState_IsReady(interp)) {
if (idobj == NULL) {
PyErr_Format(PyExc_InterpreterError,
Expand All @@ -770,11 +775,11 @@ resolve_interp(PyObject *idobj, int restricted, int reqready, const char *op)
if (restricted && get_whence(interp) != _PyInterpreterState_WHENCE_STDLIB) {
if (idobj == NULL) {
PyErr_Format(PyExc_InterpreterError,
"cannot %s unrecognized current interpreter", op);
"cannot %s current interpreter (not supported)", op);
}
else {
PyErr_Format(PyExc_InterpreterError,
"cannot %s unrecognized interpreter %R", op, idobj);
"cannot %s interpreter %R (not supported)", op, idobj);
}
return NULL;
}
Expand Down
Loading