Skip to content

Commit 1588665

Browse files
committed
Add test.support.interpreters at 3.13.2
1 parent b81ae9b commit 1588665

File tree

4 files changed

+930
-0
lines changed

4 files changed

+930
-0
lines changed
Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
"""Subinterpreters High Level Module."""
2+
3+
import threading
4+
import weakref
5+
import _interpreters
6+
7+
# aliases:
8+
from _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+
25+
def __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+
41+
Uncaught in the interpreter:
42+
43+
{formatted}
44+
""".strip()
45+
46+
class 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+
74+
def create():
75+
"""Return a new (idle) Python interpreter."""
76+
id = _interpreters.create(reqrefs=True)
77+
return Interpreter(id, _ownsref=True)
78+
79+
80+
def 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+
86+
def get_current():
87+
"""Return the currently running interpreter."""
88+
id, whence = _interpreters.get_current()
89+
return Interpreter(id, _whence=whence)
90+
91+
92+
def 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+
101+
class 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
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
"""Common code between queues and channels."""
2+
3+
4+
class ItemInterpreterDestroyed(Exception):
5+
"""Raised when trying to get an item whose interpreter was destroyed."""
6+
7+
8+
class classonly:
9+
"""A non-data descriptor that makes a value only visible on the class.
10+
11+
This is like the "classmethod" builtin, but does not show up on
12+
instances of the class. It may be used as a decorator.
13+
"""
14+
15+
def __init__(self, value):
16+
self.value = value
17+
self.getter = classmethod(value).__get__
18+
self.name = None
19+
20+
def __set_name__(self, cls, name):
21+
if self.name is not None:
22+
raise TypeError('already used')
23+
self.name = name
24+
25+
def __get__(self, obj, cls):
26+
if obj is not None:
27+
raise AttributeError(self.name)
28+
# called on the class
29+
return self.getter(None, cls)
30+
31+
32+
class UnboundItem:
33+
"""Represents a cross-interpreter item no longer bound to an interpreter.
34+
35+
An item is unbound when the interpreter that added it to the
36+
cross-interpreter container is destroyed.
37+
"""
38+
39+
__slots__ = ()
40+
41+
@classonly
42+
def singleton(cls, kind, module, name='UNBOUND'):
43+
doc = cls.__doc__.replace('cross-interpreter container', kind)
44+
doc = doc.replace('cross-interpreter', kind)
45+
subclass = type(
46+
f'Unbound{kind.capitalize()}Item',
47+
(cls,),
48+
dict(
49+
_MODULE=module,
50+
_NAME=name,
51+
__doc__=doc,
52+
),
53+
)
54+
return object.__new__(subclass)
55+
56+
_MODULE = __name__
57+
_NAME = 'UNBOUND'
58+
59+
def __new__(cls):
60+
raise Exception(f'use {cls._MODULE}.{cls._NAME}')
61+
62+
def __repr__(self):
63+
return f'{self._MODULE}.{self._NAME}'
64+
# return f'interpreters.queues.UNBOUND'
65+
66+
67+
UNBOUND = object.__new__(UnboundItem)
68+
UNBOUND_ERROR = object()
69+
UNBOUND_REMOVE = object()
70+
71+
_UNBOUND_CONSTANT_TO_FLAG = {
72+
UNBOUND_REMOVE: 1,
73+
UNBOUND_ERROR: 2,
74+
UNBOUND: 3,
75+
}
76+
_UNBOUND_FLAG_TO_CONSTANT = {v: k
77+
for k, v in _UNBOUND_CONSTANT_TO_FLAG.items()}
78+
79+
80+
def serialize_unbound(unbound):
81+
op = unbound
82+
try:
83+
flag = _UNBOUND_CONSTANT_TO_FLAG[op]
84+
except KeyError:
85+
raise NotImplementedError(f'unsupported unbound replacement op {op!r}')
86+
return flag,
87+
88+
89+
def resolve_unbound(flag, exctype_destroyed):
90+
try:
91+
op = _UNBOUND_FLAG_TO_CONSTANT[flag]
92+
except KeyError:
93+
raise NotImplementedError(f'unsupported unbound replacement op {flag!r}')
94+
if op is UNBOUND_REMOVE:
95+
# "remove" not possible here
96+
raise NotImplementedError
97+
elif op is UNBOUND_ERROR:
98+
raise exctype_destroyed("item's original interpreter destroyed")
99+
elif op is UNBOUND:
100+
return UNBOUND
101+
else:
102+
raise NotImplementedError(repr(op))

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy