2023-12-12 08:24:31 -07:00
|
|
|
"""Subinterpreters High Level Module."""
|
|
|
|
|
|
|
|
import threading
|
|
|
|
import weakref
|
2024-04-24 10:18:24 -06:00
|
|
|
import _interpreters
|
2023-12-12 08:24:31 -07:00
|
|
|
|
|
|
|
# aliases:
|
2024-04-24 10:18:24 -06:00
|
|
|
from _interpreters import (
|
2024-02-28 16:08:08 -07:00
|
|
|
InterpreterError, InterpreterNotFoundError, NotShareableError,
|
2023-12-12 08:24:31 -07:00
|
|
|
is_shareable,
|
|
|
|
)
|
2025-06-11 17:35:48 -06:00
|
|
|
from ._queues import (
|
|
|
|
create as create_queue,
|
|
|
|
Queue, QueueEmpty, QueueFull,
|
|
|
|
)
|
2023-12-12 08:24:31 -07:00
|
|
|
|
|
|
|
|
|
|
|
__all__ = [
|
|
|
|
'get_current', 'get_main', 'create', 'list_all', 'is_shareable',
|
|
|
|
'Interpreter',
|
2024-02-28 16:08:08 -07:00
|
|
|
'InterpreterError', 'InterpreterNotFoundError', 'ExecutionFailed',
|
|
|
|
'NotShareableError',
|
2023-12-12 08:24:31 -07:00
|
|
|
'create_queue', 'Queue', 'QueueEmpty', 'QueueFull',
|
|
|
|
]
|
|
|
|
|
|
|
|
|
2023-12-12 17:00:54 -07:00
|
|
|
_EXEC_FAILURE_STR = """
|
|
|
|
{superstr}
|
|
|
|
|
|
|
|
Uncaught in the interpreter:
|
|
|
|
|
|
|
|
{formatted}
|
|
|
|
""".strip()
|
|
|
|
|
2024-04-03 10:58:39 -06:00
|
|
|
class ExecutionFailed(InterpreterError):
|
2024-02-28 16:08:08 -07:00
|
|
|
"""An unhandled exception happened during execution.
|
|
|
|
|
|
|
|
This is raised from Interpreter.exec() and Interpreter.call().
|
|
|
|
"""
|
2023-12-12 08:24:31 -07:00
|
|
|
|
|
|
|
def __init__(self, excinfo):
|
|
|
|
msg = excinfo.formatted
|
|
|
|
if not msg:
|
2023-12-12 17:00:54 -07:00
|
|
|
if excinfo.type and excinfo.msg:
|
|
|
|
msg = f'{excinfo.type.__name__}: {excinfo.msg}'
|
2023-12-12 08:24:31 -07:00
|
|
|
else:
|
2023-12-12 17:00:54 -07:00
|
|
|
msg = excinfo.type.__name__ or excinfo.msg
|
2023-12-12 08:24:31 -07:00
|
|
|
super().__init__(msg)
|
2023-12-12 17:00:54 -07:00
|
|
|
self.excinfo = excinfo
|
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
try:
|
2023-12-12 17:31:30 -07:00
|
|
|
formatted = self.excinfo.errdisplay
|
2023-12-12 17:00:54 -07:00
|
|
|
except Exception:
|
|
|
|
return super().__str__()
|
|
|
|
else:
|
|
|
|
return _EXEC_FAILURE_STR.format(
|
|
|
|
superstr=super().__str__(),
|
|
|
|
formatted=formatted,
|
|
|
|
)
|
2023-12-12 08:24:31 -07:00
|
|
|
|
|
|
|
|
|
|
|
def create():
|
|
|
|
"""Return a new (idle) Python interpreter."""
|
2024-04-02 17:16:50 -06:00
|
|
|
id = _interpreters.create(reqrefs=True)
|
2024-04-11 17:23:25 -06:00
|
|
|
return Interpreter(id, _ownsref=True)
|
2023-12-12 08:24:31 -07:00
|
|
|
|
|
|
|
|
|
|
|
def list_all():
|
|
|
|
"""Return all existing interpreters."""
|
2024-04-11 17:23:25 -06:00
|
|
|
return [Interpreter(id, _whence=whence)
|
|
|
|
for id, whence in _interpreters.list_all(require_ready=True)]
|
2023-12-12 08:24:31 -07:00
|
|
|
|
|
|
|
|
|
|
|
def get_current():
|
|
|
|
"""Return the currently running interpreter."""
|
2024-04-11 17:23:25 -06:00
|
|
|
id, whence = _interpreters.get_current()
|
|
|
|
return Interpreter(id, _whence=whence)
|
2023-12-12 08:24:31 -07:00
|
|
|
|
|
|
|
|
|
|
|
def get_main():
|
|
|
|
"""Return the main interpreter."""
|
2024-04-11 17:23:25 -06:00
|
|
|
id, whence = _interpreters.get_main()
|
|
|
|
assert whence == _interpreters.WHENCE_RUNTIME, repr(whence)
|
|
|
|
return Interpreter(id, _whence=whence)
|
2023-12-12 08:24:31 -07:00
|
|
|
|
|
|
|
|
|
|
|
_known = weakref.WeakValueDictionary()
|
|
|
|
|
|
|
|
class Interpreter:
|
2024-04-11 17:23:25 -06:00
|
|
|
"""A single Python interpreter.
|
2023-12-12 08:24:31 -07:00
|
|
|
|
2024-04-11 17:23:25 -06:00
|
|
|
Attributes:
|
|
|
|
|
|
|
|
"id" - the unique process-global ID number for the interpreter
|
|
|
|
"whence" - indicates where the interpreter was created
|
|
|
|
|
|
|
|
If the interpreter wasn't created by this module
|
|
|
|
then any method that modifies the interpreter will fail,
|
|
|
|
i.e. .close(), .prepare_main(), .exec(), and .call()
|
|
|
|
"""
|
|
|
|
|
|
|
|
_WHENCE_TO_STR = {
|
|
|
|
_interpreters.WHENCE_UNKNOWN: 'unknown',
|
|
|
|
_interpreters.WHENCE_RUNTIME: 'runtime init',
|
|
|
|
_interpreters.WHENCE_LEGACY_CAPI: 'legacy C-API',
|
|
|
|
_interpreters.WHENCE_CAPI: 'C-API',
|
|
|
|
_interpreters.WHENCE_XI: 'cross-interpreter C-API',
|
|
|
|
_interpreters.WHENCE_STDLIB: '_interpreters module',
|
|
|
|
}
|
|
|
|
|
|
|
|
def __new__(cls, id, /, _whence=None, _ownsref=None):
|
2023-12-12 08:24:31 -07:00
|
|
|
# There is only one instance for any given ID.
|
|
|
|
if not isinstance(id, int):
|
|
|
|
raise TypeError(f'id must be an int, got {id!r}')
|
|
|
|
id = int(id)
|
2024-04-11 17:23:25 -06:00
|
|
|
if _whence is None:
|
|
|
|
if _ownsref:
|
|
|
|
_whence = _interpreters.WHENCE_STDLIB
|
|
|
|
else:
|
|
|
|
_whence = _interpreters.whence(id)
|
|
|
|
assert _whence in cls._WHENCE_TO_STR, repr(_whence)
|
|
|
|
if _ownsref is None:
|
|
|
|
_ownsref = (_whence == _interpreters.WHENCE_STDLIB)
|
2023-12-12 08:24:31 -07:00
|
|
|
try:
|
|
|
|
self = _known[id]
|
|
|
|
assert hasattr(self, '_ownsref')
|
|
|
|
except KeyError:
|
2024-04-11 17:23:25 -06:00
|
|
|
self = super().__new__(cls)
|
2023-12-12 08:24:31 -07:00
|
|
|
_known[id] = self
|
2024-04-11 17:23:25 -06:00
|
|
|
self._id = id
|
|
|
|
self._whence = _whence
|
|
|
|
self._ownsref = _ownsref
|
|
|
|
if _ownsref:
|
|
|
|
# This may raise InterpreterNotFoundError:
|
|
|
|
_interpreters.incref(id)
|
2023-12-12 08:24:31 -07:00
|
|
|
return self
|
|
|
|
|
|
|
|
def __repr__(self):
|
|
|
|
return f'{type(self).__name__}({self.id})'
|
|
|
|
|
|
|
|
def __hash__(self):
|
|
|
|
return hash(self._id)
|
|
|
|
|
|
|
|
def __del__(self):
|
|
|
|
self._decref()
|
|
|
|
|
2024-03-05 08:54:46 -07:00
|
|
|
# for pickling:
|
|
|
|
def __getnewargs__(self):
|
|
|
|
return (self._id,)
|
|
|
|
|
|
|
|
# for pickling:
|
|
|
|
def __getstate__(self):
|
|
|
|
return None
|
|
|
|
|
2023-12-12 08:24:31 -07:00
|
|
|
def _decref(self):
|
|
|
|
if not self._ownsref:
|
|
|
|
return
|
|
|
|
self._ownsref = False
|
|
|
|
try:
|
2024-04-11 17:23:25 -06:00
|
|
|
_interpreters.decref(self._id)
|
2023-12-12 08:24:31 -07:00
|
|
|
except InterpreterNotFoundError:
|
|
|
|
pass
|
|
|
|
|
|
|
|
@property
|
|
|
|
def id(self):
|
|
|
|
return self._id
|
|
|
|
|
2024-04-11 17:23:25 -06:00
|
|
|
@property
|
|
|
|
def whence(self):
|
|
|
|
return self._WHENCE_TO_STR[self._whence]
|
|
|
|
|
2023-12-12 08:24:31 -07:00
|
|
|
def is_running(self):
|
|
|
|
"""Return whether or not the identified interpreter is running."""
|
|
|
|
return _interpreters.is_running(self._id)
|
|
|
|
|
2024-04-11 17:23:25 -06:00
|
|
|
# Everything past here is available only to interpreters created by
|
|
|
|
# interpreters.create().
|
|
|
|
|
2023-12-12 08:24:31 -07:00
|
|
|
def close(self):
|
|
|
|
"""Finalize and destroy the interpreter.
|
|
|
|
|
|
|
|
Attempting to destroy the current interpreter results
|
2024-04-03 10:58:39 -06:00
|
|
|
in an InterpreterError.
|
2023-12-12 08:24:31 -07:00
|
|
|
"""
|
2024-04-11 17:23:25 -06:00
|
|
|
return _interpreters.destroy(self._id, restrict=True)
|
2023-12-12 08:24:31 -07:00
|
|
|
|
2023-12-12 11:06:06 -07:00
|
|
|
def prepare_main(self, ns=None, /, **kwargs):
|
|
|
|
"""Bind the given values into the interpreter's __main__.
|
|
|
|
|
|
|
|
The values must be shareable.
|
|
|
|
"""
|
|
|
|
ns = dict(ns, **kwargs) if ns is not None else kwargs
|
2024-04-11 17:23:25 -06:00
|
|
|
_interpreters.set___main___attrs(self._id, ns, restrict=True)
|
2023-12-12 11:06:06 -07:00
|
|
|
|
2024-02-28 16:08:08 -07:00
|
|
|
def exec(self, code, /):
|
2023-12-12 08:24:31 -07:00
|
|
|
"""Run the given source code in the interpreter.
|
|
|
|
|
|
|
|
This is essentially the same as calling the builtin "exec"
|
|
|
|
with this interpreter, using the __dict__ of its __main__
|
|
|
|
module as both globals and locals.
|
|
|
|
|
|
|
|
There is no return value.
|
|
|
|
|
2024-02-28 16:08:08 -07:00
|
|
|
If the code raises an unhandled exception then an ExecutionFailed
|
|
|
|
exception is raised, which summarizes the unhandled exception.
|
|
|
|
The actual exception is discarded because objects cannot be
|
|
|
|
shared between interpreters.
|
2023-12-12 08:24:31 -07:00
|
|
|
|
|
|
|
This blocks the current Python thread until done. During
|
|
|
|
that time, the previous interpreter is allowed to run
|
|
|
|
in other threads.
|
|
|
|
"""
|
2024-04-11 17:23:25 -06:00
|
|
|
excinfo = _interpreters.exec(self._id, code, restrict=True)
|
2023-12-12 08:24:31 -07:00
|
|
|
if excinfo is not None:
|
2024-02-28 16:08:08 -07:00
|
|
|
raise ExecutionFailed(excinfo)
|
|
|
|
|
2025-05-30 09:15:00 -06:00
|
|
|
def _call(self, callable, args, kwargs):
|
|
|
|
res, excinfo = _interpreters.call(self._id, callable, args, kwargs, restrict=True)
|
|
|
|
if excinfo is not None:
|
|
|
|
raise ExecutionFailed(excinfo)
|
|
|
|
return res
|
2024-02-28 16:08:08 -07:00
|
|
|
|
2025-05-30 09:15:00 -06:00
|
|
|
def call(self, callable, /, *args, **kwargs):
|
|
|
|
"""Call the object in the interpreter with given args/kwargs.
|
2023-12-12 08:24:31 -07:00
|
|
|
|
2025-05-30 09:15:00 -06:00
|
|
|
Nearly all callables, args, kwargs, and return values are
|
|
|
|
supported. All "shareable" objects are supported, as are
|
|
|
|
"stateless" functions (meaning non-closures that do not use
|
|
|
|
any globals). This method will fall back to pickle.
|
2024-02-28 16:08:08 -07:00
|
|
|
|
|
|
|
If the callable raises an exception then the error display
|
2025-05-30 09:15:00 -06:00
|
|
|
(including full traceback) is sent back between the interpreters
|
2024-02-28 16:08:08 -07:00
|
|
|
and an ExecutionFailed exception is raised, much like what
|
|
|
|
happens with Interpreter.exec().
|
|
|
|
"""
|
2025-05-30 09:15:00 -06:00
|
|
|
return self._call(callable, args, kwargs)
|
2024-02-28 16:08:08 -07:00
|
|
|
|
2025-05-30 09:15:00 -06:00
|
|
|
def call_in_thread(self, callable, /, *args, **kwargs):
|
2024-02-28 16:08:08 -07:00
|
|
|
"""Return a new thread that calls the object in the interpreter.
|
|
|
|
|
|
|
|
The return value and any raised exception are discarded.
|
|
|
|
"""
|
2025-05-30 09:15:00 -06:00
|
|
|
t = threading.Thread(target=self._call, args=(callable, args, kwargs))
|
2023-12-12 08:24:31 -07:00
|
|
|
t.start()
|
|
|
|
return t
|