• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1"""Subinterpreters High Level Module."""
2
3import threading
4import weakref
5import _interpreters
6
7# aliases:
8from _interpreters import (
9    InterpreterError, InterpreterNotFoundError, NotShareableError,
10    is_shareable,
11)
12
13
14__all__ = [
15    'get_current', 'get_main', 'create', 'list_all', 'is_shareable',
16    'Interpreter',
17    'InterpreterError', 'InterpreterNotFoundError', 'ExecutionFailed',
18    'NotShareableError',
19    'create_queue', 'Queue', 'QueueEmpty', 'QueueFull',
20]
21
22
23_queuemod = None
24
25def __getattr__(name):
26    if name in ('Queue', 'QueueEmpty', 'QueueFull', 'create_queue'):
27        global create_queue, Queue, QueueEmpty, QueueFull
28        ns = globals()
29        from .queues import (
30            create as create_queue,
31            Queue, QueueEmpty, QueueFull,
32        )
33        return ns[name]
34    else:
35        raise AttributeError(name)
36
37
38_EXEC_FAILURE_STR = """
39{superstr}
40
41Uncaught in the interpreter:
42
43{formatted}
44""".strip()
45
46class ExecutionFailed(InterpreterError):
47    """An unhandled exception happened during execution.
48
49    This is raised from Interpreter.exec() and Interpreter.call().
50    """
51
52    def __init__(self, excinfo):
53        msg = excinfo.formatted
54        if not msg:
55            if excinfo.type and excinfo.msg:
56                msg = f'{excinfo.type.__name__}: {excinfo.msg}'
57            else:
58                msg = excinfo.type.__name__ or excinfo.msg
59        super().__init__(msg)
60        self.excinfo = excinfo
61
62    def __str__(self):
63        try:
64            formatted = self.excinfo.errdisplay
65        except Exception:
66            return super().__str__()
67        else:
68            return _EXEC_FAILURE_STR.format(
69                superstr=super().__str__(),
70                formatted=formatted,
71            )
72
73
74def create():
75    """Return a new (idle) Python interpreter."""
76    id = _interpreters.create(reqrefs=True)
77    return Interpreter(id, _ownsref=True)
78
79
80def list_all():
81    """Return all existing interpreters."""
82    return [Interpreter(id, _whence=whence)
83            for id, whence in _interpreters.list_all(require_ready=True)]
84
85
86def get_current():
87    """Return the currently running interpreter."""
88    id, whence = _interpreters.get_current()
89    return Interpreter(id, _whence=whence)
90
91
92def get_main():
93    """Return the main interpreter."""
94    id, whence = _interpreters.get_main()
95    assert whence == _interpreters.WHENCE_RUNTIME, repr(whence)
96    return Interpreter(id, _whence=whence)
97
98
99_known = weakref.WeakValueDictionary()
100
101class Interpreter:
102    """A single Python interpreter.
103
104    Attributes:
105
106    "id" - the unique process-global ID number for the interpreter
107    "whence" - indicates where the interpreter was created
108
109    If the interpreter wasn't created by this module
110    then any method that modifies the interpreter will fail,
111    i.e. .close(), .prepare_main(), .exec(), and .call()
112    """
113
114    _WHENCE_TO_STR = {
115       _interpreters.WHENCE_UNKNOWN: 'unknown',
116       _interpreters.WHENCE_RUNTIME: 'runtime init',
117       _interpreters.WHENCE_LEGACY_CAPI: 'legacy C-API',
118       _interpreters.WHENCE_CAPI: 'C-API',
119       _interpreters.WHENCE_XI: 'cross-interpreter C-API',
120       _interpreters.WHENCE_STDLIB: '_interpreters module',
121    }
122
123    def __new__(cls, id, /, _whence=None, _ownsref=None):
124        # There is only one instance for any given ID.
125        if not isinstance(id, int):
126            raise TypeError(f'id must be an int, got {id!r}')
127        id = int(id)
128        if _whence is None:
129            if _ownsref:
130                _whence = _interpreters.WHENCE_STDLIB
131            else:
132                _whence = _interpreters.whence(id)
133        assert _whence in cls._WHENCE_TO_STR, repr(_whence)
134        if _ownsref is None:
135            _ownsref = (_whence == _interpreters.WHENCE_STDLIB)
136        try:
137            self = _known[id]
138            assert hasattr(self, '_ownsref')
139        except KeyError:
140            self = super().__new__(cls)
141            _known[id] = self
142            self._id = id
143            self._whence = _whence
144            self._ownsref = _ownsref
145            if _ownsref:
146                # This may raise InterpreterNotFoundError:
147                _interpreters.incref(id)
148        return self
149
150    def __repr__(self):
151        return f'{type(self).__name__}({self.id})'
152
153    def __hash__(self):
154        return hash(self._id)
155
156    def __del__(self):
157        self._decref()
158
159    # for pickling:
160    def __getnewargs__(self):
161        return (self._id,)
162
163    # for pickling:
164    def __getstate__(self):
165        return None
166
167    def _decref(self):
168        if not self._ownsref:
169            return
170        self._ownsref = False
171        try:
172            _interpreters.decref(self._id)
173        except InterpreterNotFoundError:
174            pass
175
176    @property
177    def id(self):
178        return self._id
179
180    @property
181    def whence(self):
182        return self._WHENCE_TO_STR[self._whence]
183
184    def is_running(self):
185        """Return whether or not the identified interpreter is running."""
186        return _interpreters.is_running(self._id)
187
188    # Everything past here is available only to interpreters created by
189    # interpreters.create().
190
191    def close(self):
192        """Finalize and destroy the interpreter.
193
194        Attempting to destroy the current interpreter results
195        in an InterpreterError.
196        """
197        return _interpreters.destroy(self._id, restrict=True)
198
199    def prepare_main(self, ns=None, /, **kwargs):
200        """Bind the given values into the interpreter's __main__.
201
202        The values must be shareable.
203        """
204        ns = dict(ns, **kwargs) if ns is not None else kwargs
205        _interpreters.set___main___attrs(self._id, ns, restrict=True)
206
207    def exec(self, code, /):
208        """Run the given source code in the interpreter.
209
210        This is essentially the same as calling the builtin "exec"
211        with this interpreter, using the __dict__ of its __main__
212        module as both globals and locals.
213
214        There is no return value.
215
216        If the code raises an unhandled exception then an ExecutionFailed
217        exception is raised, which summarizes the unhandled exception.
218        The actual exception is discarded because objects cannot be
219        shared between interpreters.
220
221        This blocks the current Python thread until done.  During
222        that time, the previous interpreter is allowed to run
223        in other threads.
224        """
225        excinfo = _interpreters.exec(self._id, code, restrict=True)
226        if excinfo is not None:
227            raise ExecutionFailed(excinfo)
228
229    def call(self, callable, /):
230        """Call the object in the interpreter with given args/kwargs.
231
232        Only functions that take no arguments and have no closure
233        are supported.
234
235        The return value is discarded.
236
237        If the callable raises an exception then the error display
238        (including full traceback) is send back between the interpreters
239        and an ExecutionFailed exception is raised, much like what
240        happens with Interpreter.exec().
241        """
242        # XXX Support args and kwargs.
243        # XXX Support arbitrary callables.
244        # XXX Support returning the return value (e.g. via pickle).
245        excinfo = _interpreters.call(self._id, callable, restrict=True)
246        if excinfo is not None:
247            raise ExecutionFailed(excinfo)
248
249    def call_in_thread(self, callable, /):
250        """Return a new thread that calls the object in the interpreter.
251
252        The return value and any raised exception are discarded.
253        """
254        def task():
255            self.call(callable)
256        t = threading.Thread(target=task)
257        t.start()
258        return t
259