| 
									
										
										
										
											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, | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | __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', | 
					
						
							|  |  |  | ] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | _queuemod = None | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def __getattr__(name): | 
					
						
							|  |  |  |     if name in ('Queue', 'QueueEmpty', 'QueueFull', 'create_queue'): | 
					
						
							|  |  |  |         global create_queue, Queue, QueueEmpty, QueueFull | 
					
						
							|  |  |  |         ns = globals() | 
					
						
							|  |  |  |         from .queues import ( | 
					
						
							|  |  |  |             create as create_queue, | 
					
						
							|  |  |  |             Queue, QueueEmpty, QueueFull, | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  |         return ns[name] | 
					
						
							|  |  |  |     else: | 
					
						
							|  |  |  |         raise AttributeError(name) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											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) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def call(self, callable, /): | 
					
						
							|  |  |  |         """Call the object in the interpreter with given args/kwargs.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         Only functions that take no arguments and have no closure | 
					
						
							|  |  |  |         are supported. | 
					
						
							| 
									
										
										
										
											2023-12-12 08:24:31 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-28 16:08:08 -07:00
										 |  |  |         The return value is discarded. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         If the callable raises an exception then the error display | 
					
						
							|  |  |  |         (including full traceback) is send back between the interpreters | 
					
						
							|  |  |  |         and an ExecutionFailed exception is raised, much like what | 
					
						
							|  |  |  |         happens with Interpreter.exec(). | 
					
						
							|  |  |  |         """
 | 
					
						
							|  |  |  |         # XXX Support args and kwargs. | 
					
						
							|  |  |  |         # XXX Support arbitrary callables. | 
					
						
							|  |  |  |         # XXX Support returning the return value (e.g. via pickle). | 
					
						
							| 
									
										
										
										
											2024-04-11 17:23:25 -06:00
										 |  |  |         excinfo = _interpreters.call(self._id, callable, restrict=True) | 
					
						
							| 
									
										
										
										
											2024-02-28 16:08:08 -07:00
										 |  |  |         if excinfo is not None: | 
					
						
							|  |  |  |             raise ExecutionFailed(excinfo) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def call_in_thread(self, callable, /): | 
					
						
							|  |  |  |         """Return a new thread that calls the object in the interpreter.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         The return value and any raised exception are discarded. | 
					
						
							|  |  |  |         """
 | 
					
						
							| 
									
										
										
										
											2023-12-12 08:24:31 -07:00
										 |  |  |         def task(): | 
					
						
							| 
									
										
										
										
											2024-02-28 16:08:08 -07:00
										 |  |  |             self.call(callable) | 
					
						
							| 
									
										
										
										
											2023-12-12 08:24:31 -07:00
										 |  |  |         t = threading.Thread(target=task) | 
					
						
							|  |  |  |         t.start() | 
					
						
							|  |  |  |         return t |