backports.interpreters 0.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- backports/interpreters/__init__.py +209 -0
- backports/interpreters/_backend.py +273 -0
- backports/interpreters/_qbackend.py +161 -0
- backports/interpreters/_queues.py +149 -0
- backports_interpreters-0.3.0.dist-info/METADATA +27 -0
- backports_interpreters-0.3.0.dist-info/RECORD +9 -0
- backports_interpreters-0.3.0.dist-info/WHEEL +5 -0
- backports_interpreters-0.3.0.dist-info/licenses/LICENSE +55 -0
- backports_interpreters-0.3.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
"""Backport of the concurrent.interpreters module (PEP 734).
|
|
2
|
+
|
|
3
|
+
On Python 3.14+ this re-exports the standard library module. On 3.8 through
|
|
4
|
+
3.13 it builds the same API on the primitives in :mod:`._backend`.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import sys
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"get_current", "get_main", "create", "list_all", "is_shareable",
|
|
11
|
+
"Interpreter",
|
|
12
|
+
"InterpreterError", "InterpreterNotFoundError", "ExecutionFailed",
|
|
13
|
+
"NotShareableError",
|
|
14
|
+
"create_queue", "Queue", "QueueEmpty", "QueueFull",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
if sys.version_info >= (3, 14):
|
|
19
|
+
# The real thing exists in the standard library.
|
|
20
|
+
from concurrent.interpreters import ( # noqa: F401
|
|
21
|
+
get_current, get_main, create, list_all, is_shareable,
|
|
22
|
+
Interpreter,
|
|
23
|
+
InterpreterError, InterpreterNotFoundError, ExecutionFailed,
|
|
24
|
+
NotShareableError,
|
|
25
|
+
create_queue, Queue, QueueEmpty, QueueFull,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
else:
|
|
29
|
+
import threading
|
|
30
|
+
import weakref
|
|
31
|
+
|
|
32
|
+
from . import _backend as _interpreters
|
|
33
|
+
|
|
34
|
+
InterpreterError = _interpreters.InterpreterError
|
|
35
|
+
InterpreterNotFoundError = _interpreters.InterpreterNotFoundError
|
|
36
|
+
NotShareableError = _interpreters.NotShareableError
|
|
37
|
+
is_shareable = _interpreters.is_shareable
|
|
38
|
+
|
|
39
|
+
from ._queues import (
|
|
40
|
+
create as create_queue,
|
|
41
|
+
Queue, QueueEmpty, QueueFull,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
_EXEC_FAILURE_STR = """
|
|
45
|
+
{superstr}
|
|
46
|
+
|
|
47
|
+
Uncaught in the interpreter:
|
|
48
|
+
|
|
49
|
+
{formatted}
|
|
50
|
+
""".strip()
|
|
51
|
+
|
|
52
|
+
class ExecutionFailed(InterpreterError):
|
|
53
|
+
"""An unhandled exception happened during execution."""
|
|
54
|
+
|
|
55
|
+
def __init__(self, excinfo):
|
|
56
|
+
msg = excinfo.formatted
|
|
57
|
+
if not msg:
|
|
58
|
+
if excinfo.type and excinfo.msg:
|
|
59
|
+
msg = f'{excinfo.type.__name__}: {excinfo.msg}'
|
|
60
|
+
else:
|
|
61
|
+
msg = excinfo.type.__name__ or excinfo.msg
|
|
62
|
+
super().__init__(msg)
|
|
63
|
+
self.excinfo = excinfo
|
|
64
|
+
|
|
65
|
+
def __str__(self):
|
|
66
|
+
try:
|
|
67
|
+
formatted = self.excinfo.errdisplay
|
|
68
|
+
except Exception:
|
|
69
|
+
return super().__str__()
|
|
70
|
+
else:
|
|
71
|
+
return _EXEC_FAILURE_STR.format(
|
|
72
|
+
superstr=super().__str__(),
|
|
73
|
+
formatted=formatted,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
def create():
|
|
77
|
+
"""Return a new (idle) Python interpreter."""
|
|
78
|
+
id = _interpreters.create(reqrefs=True)
|
|
79
|
+
return Interpreter(id, _ownsref=True)
|
|
80
|
+
|
|
81
|
+
def list_all():
|
|
82
|
+
"""Return all existing interpreters."""
|
|
83
|
+
return [Interpreter(id, _whence=whence)
|
|
84
|
+
for id, whence in _interpreters.list_all(require_ready=True)]
|
|
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
|
+
def get_main():
|
|
92
|
+
"""Return the main interpreter."""
|
|
93
|
+
id, whence = _interpreters.get_main()
|
|
94
|
+
assert whence == _interpreters.WHENCE_RUNTIME, repr(whence)
|
|
95
|
+
return Interpreter(id, _whence=whence)
|
|
96
|
+
|
|
97
|
+
_known = weakref.WeakValueDictionary()
|
|
98
|
+
|
|
99
|
+
class Interpreter:
|
|
100
|
+
"""A single Python interpreter.
|
|
101
|
+
|
|
102
|
+
Interpreters not created by this module cannot be modified, so
|
|
103
|
+
close(), prepare_main(), exec() and call() will fail on them.
|
|
104
|
+
"""
|
|
105
|
+
|
|
106
|
+
_WHENCE_TO_STR = {
|
|
107
|
+
_interpreters.WHENCE_UNKNOWN: 'unknown',
|
|
108
|
+
_interpreters.WHENCE_RUNTIME: 'runtime init',
|
|
109
|
+
_interpreters.WHENCE_LEGACY_CAPI: 'legacy C-API',
|
|
110
|
+
_interpreters.WHENCE_CAPI: 'C-API',
|
|
111
|
+
_interpreters.WHENCE_XI: 'cross-interpreter C-API',
|
|
112
|
+
_interpreters.WHENCE_STDLIB: '_interpreters module',
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
def __new__(cls, id, /, _whence=None, _ownsref=None):
|
|
116
|
+
# There is only one instance for any given ID.
|
|
117
|
+
if not isinstance(id, int):
|
|
118
|
+
raise TypeError(f'id must be an int, got {id!r}')
|
|
119
|
+
id = int(id)
|
|
120
|
+
if _whence is None:
|
|
121
|
+
if _ownsref:
|
|
122
|
+
_whence = _interpreters.WHENCE_STDLIB
|
|
123
|
+
else:
|
|
124
|
+
_whence = _interpreters.whence(id)
|
|
125
|
+
assert _whence in cls._WHENCE_TO_STR, repr(_whence)
|
|
126
|
+
if _ownsref is None:
|
|
127
|
+
_ownsref = (_whence == _interpreters.WHENCE_STDLIB)
|
|
128
|
+
try:
|
|
129
|
+
self = _known[id]
|
|
130
|
+
assert hasattr(self, '_ownsref')
|
|
131
|
+
except KeyError:
|
|
132
|
+
self = super().__new__(cls)
|
|
133
|
+
_known[id] = self
|
|
134
|
+
self._id = id
|
|
135
|
+
self._whence = _whence
|
|
136
|
+
self._ownsref = _ownsref
|
|
137
|
+
if _ownsref:
|
|
138
|
+
# May raise InterpreterNotFoundError.
|
|
139
|
+
_interpreters.incref(id)
|
|
140
|
+
return self
|
|
141
|
+
|
|
142
|
+
def __repr__(self):
|
|
143
|
+
return f'{type(self).__name__}({self.id})'
|
|
144
|
+
|
|
145
|
+
def __hash__(self):
|
|
146
|
+
return hash(self._id)
|
|
147
|
+
|
|
148
|
+
def __del__(self):
|
|
149
|
+
self._decref()
|
|
150
|
+
|
|
151
|
+
def _decref(self):
|
|
152
|
+
if not self._ownsref:
|
|
153
|
+
return
|
|
154
|
+
self._ownsref = False
|
|
155
|
+
try:
|
|
156
|
+
_interpreters.decref(self._id)
|
|
157
|
+
except InterpreterNotFoundError:
|
|
158
|
+
pass
|
|
159
|
+
|
|
160
|
+
@property
|
|
161
|
+
def id(self):
|
|
162
|
+
return self._id
|
|
163
|
+
|
|
164
|
+
@property
|
|
165
|
+
def whence(self):
|
|
166
|
+
return self._WHENCE_TO_STR[self._whence]
|
|
167
|
+
|
|
168
|
+
def is_running(self):
|
|
169
|
+
"""Return whether the interpreter is running."""
|
|
170
|
+
return _interpreters.is_running(self._id)
|
|
171
|
+
|
|
172
|
+
def close(self):
|
|
173
|
+
"""Finalize and destroy the interpreter."""
|
|
174
|
+
return _interpreters.destroy(self._id, restrict=True)
|
|
175
|
+
|
|
176
|
+
def prepare_main(self, ns=None, /, **kwargs):
|
|
177
|
+
"""Bind the given values into the interpreter's __main__."""
|
|
178
|
+
ns = dict(ns, **kwargs) if ns is not None else kwargs
|
|
179
|
+
_interpreters.set___main___attrs(self._id, ns, restrict=True)
|
|
180
|
+
|
|
181
|
+
def exec(self, code, /):
|
|
182
|
+
"""Run source code in the interpreter, blocking until it finishes.
|
|
183
|
+
|
|
184
|
+
Raises ExecutionFailed if the code raises something uncaught.
|
|
185
|
+
"""
|
|
186
|
+
excinfo = _interpreters.exec(self._id, code, restrict=True)
|
|
187
|
+
if excinfo is not None:
|
|
188
|
+
raise ExecutionFailed(excinfo)
|
|
189
|
+
|
|
190
|
+
def _call(self, callable, args, kwargs):
|
|
191
|
+
res, excinfo = _interpreters.call(
|
|
192
|
+
self._id, callable, args, kwargs, restrict=True)
|
|
193
|
+
if excinfo is not None:
|
|
194
|
+
raise ExecutionFailed(excinfo)
|
|
195
|
+
return res
|
|
196
|
+
|
|
197
|
+
def call(self, callable, /, *args, **kwargs):
|
|
198
|
+
"""Call the function in the interpreter and return its result.
|
|
199
|
+
|
|
200
|
+
Raises ExecutionFailed if the call raises something uncaught.
|
|
201
|
+
"""
|
|
202
|
+
return self._call(callable, args, kwargs)
|
|
203
|
+
|
|
204
|
+
def call_in_thread(self, callable, /, *args, **kwargs):
|
|
205
|
+
"""Like call(), but run it in a new thread and return the thread."""
|
|
206
|
+
t = threading.Thread(
|
|
207
|
+
target=self._call, args=(callable, args, kwargs))
|
|
208
|
+
t.start()
|
|
209
|
+
return t
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
"""Low level interpreter primitives, normalised across Python versions.
|
|
2
|
+
|
|
3
|
+
On 3.13 the native ``_interpreters`` module is used directly, apart from
|
|
4
|
+
``call()`` which gains its 3.14 arguments and return value here. On 3.8 through
|
|
5
|
+
3.12 everything is emulated over ``_xxsubinterpreters`` by running a small
|
|
6
|
+
pickle/marshal bootstrap and shipping the result back over a channel.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
import sys
|
|
11
|
+
import time
|
|
12
|
+
import types
|
|
13
|
+
import pickle
|
|
14
|
+
import marshal
|
|
15
|
+
|
|
16
|
+
from . import _qbackend
|
|
17
|
+
|
|
18
|
+
_PY = sys.version_info
|
|
19
|
+
|
|
20
|
+
WHENCE_UNKNOWN = 0
|
|
21
|
+
WHENCE_RUNTIME = 1
|
|
22
|
+
WHENCE_LEGACY_CAPI = 2
|
|
23
|
+
WHENCE_CAPI = 3
|
|
24
|
+
WHENCE_XI = 4
|
|
25
|
+
WHENCE_STDLIB = 5
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class _ExcInfo:
|
|
29
|
+
"""Stands in for what _interpreters.capture_exception returns."""
|
|
30
|
+
|
|
31
|
+
def __init__(self, formatted, typename, msg):
|
|
32
|
+
self.formatted = formatted
|
|
33
|
+
self.errdisplay = formatted
|
|
34
|
+
self.type = types.SimpleNamespace(__name__=typename)
|
|
35
|
+
self.msg = msg
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# Runs in the target interpreter and ships a pickled (ok, ...) tuple back home.
|
|
39
|
+
_RUNNER = """
|
|
40
|
+
import pickle as __xi_pk, traceback as __xi_tb, marshal as __xi_ma, types as __xi_ty
|
|
41
|
+
try:
|
|
42
|
+
if __xi_mode == 0:
|
|
43
|
+
exec(compile(__xi_code.decode('utf-8'), '<string>', 'exec'), globals())
|
|
44
|
+
__xi_res = None
|
|
45
|
+
else:
|
|
46
|
+
__xi_fn = __xi_ty.FunctionType(__xi_ma.loads(__xi_code), globals())
|
|
47
|
+
__xi_a, __xi_k = __xi_pk.loads(__xi_callargs)
|
|
48
|
+
__xi_res = __xi_fn(*__xi_a, **__xi_k)
|
|
49
|
+
__xi_ok = True
|
|
50
|
+
except BaseException as __xi_e:
|
|
51
|
+
__xi_ok = False
|
|
52
|
+
__xi_err = (__xi_tb.format_exc(), type(__xi_e).__name__, str(__xi_e))
|
|
53
|
+
if __xi_ok:
|
|
54
|
+
try:
|
|
55
|
+
__xi_payload = __xi_pk.dumps((True, __xi_res))
|
|
56
|
+
except BaseException as __xi_e:
|
|
57
|
+
__xi_payload = __xi_pk.dumps(
|
|
58
|
+
(False, __xi_tb.format_exc(), type(__xi_e).__name__, str(__xi_e)))
|
|
59
|
+
else:
|
|
60
|
+
__xi_payload = __xi_pk.dumps((False,) + __xi_err)
|
|
61
|
+
""" + _qbackend.SUBINTERP_SEND
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _recv_oneshot(tid, timeout=30.0):
|
|
65
|
+
deadline = time.time() + timeout
|
|
66
|
+
while True:
|
|
67
|
+
try:
|
|
68
|
+
return _qbackend.get(tid)
|
|
69
|
+
except _qbackend._Empty:
|
|
70
|
+
if time.time() >= deadline:
|
|
71
|
+
raise InterpreterError("timed out waiting for interpreter result")
|
|
72
|
+
time.sleep(0.0005)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _run_captured(id, *, mode, code, callargs):
|
|
76
|
+
tid = _qbackend.create()
|
|
77
|
+
_qbackend.bind(tid)
|
|
78
|
+
shared = {"__xi_tid": int(tid), "__xi_mode": mode, "__xi_code": code}
|
|
79
|
+
if callargs is not None:
|
|
80
|
+
shared["__xi_callargs"] = callargs
|
|
81
|
+
try:
|
|
82
|
+
err = _run_string(id, _RUNNER, shared)
|
|
83
|
+
if err is not None:
|
|
84
|
+
raise InterpreterError(getattr(err, "formatted", str(err)))
|
|
85
|
+
payload = _recv_oneshot(tid)
|
|
86
|
+
finally:
|
|
87
|
+
_qbackend.destroy(tid)
|
|
88
|
+
data = pickle.loads(payload)
|
|
89
|
+
if data[0]:
|
|
90
|
+
return data[1], None
|
|
91
|
+
return None, _ExcInfo(data[1], data[2], data[3])
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _code_payload(code):
|
|
95
|
+
if isinstance(code, str):
|
|
96
|
+
return 0, code.encode("utf-8")
|
|
97
|
+
if isinstance(code, types.FunctionType):
|
|
98
|
+
code = code.__code__
|
|
99
|
+
if isinstance(code, types.CodeType):
|
|
100
|
+
return 1, marshal.dumps(code)
|
|
101
|
+
raise TypeError("exec() arg must be a string, function, or code object")
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
_PKG_PARENT = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _bind_main(id, ns):
|
|
108
|
+
if not ns:
|
|
109
|
+
return
|
|
110
|
+
# Put our package on the worker's path so it can unpickle a Queue etc.
|
|
111
|
+
shared = {"__xi_path": _PKG_PARENT}
|
|
112
|
+
boot = [
|
|
113
|
+
"import sys as __xi_sys, pickle as __xi_pk",
|
|
114
|
+
"__xi_path in __xi_sys.path or __xi_sys.path.insert(0, __xi_path)",
|
|
115
|
+
]
|
|
116
|
+
for i, (key, value) in enumerate(ns.items()):
|
|
117
|
+
slot = "__xi_b%d" % i
|
|
118
|
+
try:
|
|
119
|
+
shared[slot] = pickle.dumps(value)
|
|
120
|
+
except Exception as exc:
|
|
121
|
+
raise NotShareableError(key) from exc
|
|
122
|
+
boot.append("globals()[%r] = __xi_pk.loads(%s); del %s" % (key, slot, slot))
|
|
123
|
+
boot.append("del __xi_pk, __xi_sys, __xi_path")
|
|
124
|
+
err = _run_string(id, "\n".join(boot), shared)
|
|
125
|
+
if err is not None:
|
|
126
|
+
raise InterpreterError(getattr(err, "formatted", str(err)))
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
if _PY >= (3, 13):
|
|
130
|
+
import _interpreters as _impl
|
|
131
|
+
|
|
132
|
+
InterpreterError = _impl.InterpreterError
|
|
133
|
+
InterpreterNotFoundError = _impl.InterpreterNotFoundError
|
|
134
|
+
NotShareableError = _impl.NotShareableError
|
|
135
|
+
is_shareable = _impl.is_shareable
|
|
136
|
+
|
|
137
|
+
create = _impl.create
|
|
138
|
+
destroy = _impl.destroy
|
|
139
|
+
list_all = _impl.list_all
|
|
140
|
+
get_current = _impl.get_current
|
|
141
|
+
get_main = _impl.get_main
|
|
142
|
+
is_running = _impl.is_running
|
|
143
|
+
whence = _impl.whence
|
|
144
|
+
incref = _impl.incref
|
|
145
|
+
decref = _impl.decref
|
|
146
|
+
exec = _impl.exec
|
|
147
|
+
|
|
148
|
+
def _run_string(id, src, shared):
|
|
149
|
+
return _impl.exec(id, src, shared, restrict=False)
|
|
150
|
+
|
|
151
|
+
def set___main___attrs(id, ns, *, restrict=False):
|
|
152
|
+
# Native set___main___attrs only takes shareables, so go through pickle
|
|
153
|
+
# to also accept plain picklable objects like our Queue.
|
|
154
|
+
_bind_main(id, dict(ns))
|
|
155
|
+
|
|
156
|
+
def call(id, callable, args=None, kwargs=None, *, restrict=False):
|
|
157
|
+
# Native call() here takes no args and returns nothing, so emulate it.
|
|
158
|
+
if not isinstance(callable, types.FunctionType):
|
|
159
|
+
raise TypeError("call() only supports plain functions")
|
|
160
|
+
payload = marshal.dumps(callable.__code__)
|
|
161
|
+
callargs = pickle.dumps((tuple(args or ()), dict(kwargs or {})))
|
|
162
|
+
return _run_captured(id, mode=1, code=payload, callargs=callargs)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
else:
|
|
166
|
+
import _xxsubinterpreters as _impl
|
|
167
|
+
|
|
168
|
+
class InterpreterError(Exception):
|
|
169
|
+
"""A cross-interpreter operation failed."""
|
|
170
|
+
|
|
171
|
+
class InterpreterNotFoundError(InterpreterError):
|
|
172
|
+
"""An interpreter was not found."""
|
|
173
|
+
|
|
174
|
+
class NotShareableError(ValueError):
|
|
175
|
+
"""An object cannot be shared between interpreters."""
|
|
176
|
+
|
|
177
|
+
is_shareable = _impl.is_shareable
|
|
178
|
+
|
|
179
|
+
_refcounts = {}
|
|
180
|
+
# The id object owns the interpreter, so dropping it destroys the
|
|
181
|
+
# interpreter. Hold onto it until we are done.
|
|
182
|
+
_objs = {}
|
|
183
|
+
|
|
184
|
+
def _cid(id):
|
|
185
|
+
return int(id)
|
|
186
|
+
|
|
187
|
+
def incref(id, *, implieslink=False):
|
|
188
|
+
id = _cid(id)
|
|
189
|
+
_refcounts[id] = _refcounts.get(id, 0) + 1
|
|
190
|
+
|
|
191
|
+
def decref(id):
|
|
192
|
+
id = _cid(id)
|
|
193
|
+
count = _refcounts.get(id, 0) - 1
|
|
194
|
+
if count <= 0:
|
|
195
|
+
_refcounts.pop(id, None)
|
|
196
|
+
if id in _objs:
|
|
197
|
+
try:
|
|
198
|
+
_impl.destroy(id)
|
|
199
|
+
except Exception:
|
|
200
|
+
pass
|
|
201
|
+
_objs.pop(id, None)
|
|
202
|
+
else:
|
|
203
|
+
_refcounts[id] = count
|
|
204
|
+
|
|
205
|
+
def whence(id):
|
|
206
|
+
id = _cid(id)
|
|
207
|
+
try:
|
|
208
|
+
if id == int(_impl.get_main()):
|
|
209
|
+
return WHENCE_RUNTIME
|
|
210
|
+
except Exception:
|
|
211
|
+
pass
|
|
212
|
+
return WHENCE_STDLIB if id in _objs else WHENCE_UNKNOWN
|
|
213
|
+
|
|
214
|
+
def create(config="isolated", *, reqrefs=False):
|
|
215
|
+
if _PY >= (3, 9):
|
|
216
|
+
try:
|
|
217
|
+
obj = _impl.create(isolated=(config != "legacy"))
|
|
218
|
+
except TypeError:
|
|
219
|
+
obj = _impl.create()
|
|
220
|
+
else:
|
|
221
|
+
obj = _impl.create()
|
|
222
|
+
id = _cid(obj)
|
|
223
|
+
_objs[id] = obj
|
|
224
|
+
return id
|
|
225
|
+
|
|
226
|
+
def destroy(id, *, restrict=False):
|
|
227
|
+
id = _cid(id)
|
|
228
|
+
try:
|
|
229
|
+
_impl.destroy(id)
|
|
230
|
+
except Exception as exc:
|
|
231
|
+
raise InterpreterNotFoundError(id) from exc
|
|
232
|
+
_objs.pop(id, None)
|
|
233
|
+
_refcounts.pop(id, None)
|
|
234
|
+
|
|
235
|
+
def list_all(*, require_ready=False):
|
|
236
|
+
return [(_cid(i), whence(i)) for i in _impl.list_all()]
|
|
237
|
+
|
|
238
|
+
def get_current():
|
|
239
|
+
id = _cid(_impl.get_current())
|
|
240
|
+
return (id, whence(id))
|
|
241
|
+
|
|
242
|
+
def get_main():
|
|
243
|
+
id = _cid(_impl.get_main())
|
|
244
|
+
return (id, WHENCE_RUNTIME)
|
|
245
|
+
|
|
246
|
+
def is_running(id, *, restrict=False):
|
|
247
|
+
return _impl.is_running(_cid(id))
|
|
248
|
+
|
|
249
|
+
def _run_string(id, src, shared):
|
|
250
|
+
try:
|
|
251
|
+
_impl.run_string(_cid(id), src, shared=shared)
|
|
252
|
+
except _impl.RunFailedError as exc:
|
|
253
|
+
return _ExcInfo(str(exc), "RunFailedError", str(exc))
|
|
254
|
+
return None
|
|
255
|
+
|
|
256
|
+
def set___main___attrs(id, ns, *, restrict=False):
|
|
257
|
+
_bind_main(_cid(id), dict(ns))
|
|
258
|
+
|
|
259
|
+
def exec(id, code, shared=None, *, restrict=False):
|
|
260
|
+
if shared:
|
|
261
|
+
_bind_main(_cid(id), dict(shared))
|
|
262
|
+
mode, payload = _code_payload(code)
|
|
263
|
+
callargs = pickle.dumps(((), {})) if mode == 1 else None
|
|
264
|
+
_res, excinfo = _run_captured(_cid(id), mode=mode, code=payload,
|
|
265
|
+
callargs=callargs)
|
|
266
|
+
return excinfo
|
|
267
|
+
|
|
268
|
+
def call(id, callable, args=None, kwargs=None, *, restrict=False):
|
|
269
|
+
if not isinstance(callable, types.FunctionType):
|
|
270
|
+
raise TypeError("call() only supports plain functions")
|
|
271
|
+
payload = marshal.dumps(callable.__code__)
|
|
272
|
+
callargs = pickle.dumps((tuple(args or ()), dict(kwargs or {})))
|
|
273
|
+
return _run_captured(_cid(id), mode=1, code=payload, callargs=callargs)
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"""Moves pickled bytes between interpreters.
|
|
2
|
+
|
|
3
|
+
Backed by the native ``_interpqueues`` on 3.13 and by interpreter channels on
|
|
4
|
+
3.8 through 3.12. Channels cannot report their size, so ``SUPPORTS_COUNT`` is
|
|
5
|
+
False there and the queue size methods are unavailable.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
_PY = sys.version_info
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class _Empty(Exception):
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class _NotFound(Exception):
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
if _PY >= (3, 13):
|
|
22
|
+
import _interpqueues as _q
|
|
23
|
+
|
|
24
|
+
SUPPORTS_COUNT = True
|
|
25
|
+
|
|
26
|
+
# fmt 0 stores our bytes as-is, unboundop 3 replaces unbound items.
|
|
27
|
+
_FMT = 0
|
|
28
|
+
_UNBOUNDOP = 3
|
|
29
|
+
|
|
30
|
+
QueueError = _q.QueueError
|
|
31
|
+
QueueNotFoundError = _q.QueueNotFoundError
|
|
32
|
+
|
|
33
|
+
# Runs in a target interpreter to send a bytes payload back. Wants
|
|
34
|
+
# __xi_tid and __xi_payload already in scope.
|
|
35
|
+
SUBINTERP_SEND = (
|
|
36
|
+
"import _interpqueues as __xi_t\n"
|
|
37
|
+
"__xi_t.put(__xi_tid, __xi_payload, 0, 3)\n"
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
def create(maxsize=0):
|
|
41
|
+
return _q.create(maxsize, _FMT, _UNBOUNDOP)
|
|
42
|
+
|
|
43
|
+
def destroy(qid):
|
|
44
|
+
try:
|
|
45
|
+
_q.destroy(qid)
|
|
46
|
+
except QueueNotFoundError:
|
|
47
|
+
pass
|
|
48
|
+
|
|
49
|
+
def bind(qid):
|
|
50
|
+
_q.bind(qid)
|
|
51
|
+
|
|
52
|
+
def release(qid):
|
|
53
|
+
try:
|
|
54
|
+
_q.release(qid)
|
|
55
|
+
except QueueNotFoundError:
|
|
56
|
+
pass
|
|
57
|
+
|
|
58
|
+
def put(qid, data):
|
|
59
|
+
_q.put(qid, data, _FMT, _UNBOUNDOP)
|
|
60
|
+
|
|
61
|
+
def get(qid):
|
|
62
|
+
if _q.get_count(qid) == 0:
|
|
63
|
+
raise _Empty
|
|
64
|
+
obj = _q.get(qid)[0]
|
|
65
|
+
return obj
|
|
66
|
+
|
|
67
|
+
def count(qid):
|
|
68
|
+
return _q.get_count(qid)
|
|
69
|
+
|
|
70
|
+
def is_full(qid):
|
|
71
|
+
return _q.is_full(qid)
|
|
72
|
+
|
|
73
|
+
def maxsize(qid):
|
|
74
|
+
return _q.get_maxsize(qid)
|
|
75
|
+
|
|
76
|
+
else:
|
|
77
|
+
if _PY >= (3, 12):
|
|
78
|
+
import _xxinterpchannels as _c
|
|
79
|
+
|
|
80
|
+
_create = _c.create
|
|
81
|
+
_send = _c.send
|
|
82
|
+
_recv = _c.recv
|
|
83
|
+
_destroy = _c.destroy
|
|
84
|
+
_Empty_native = _c.ChannelEmptyError
|
|
85
|
+
_NotFound_native = _c.ChannelNotFoundError
|
|
86
|
+
|
|
87
|
+
SUBINTERP_SEND = (
|
|
88
|
+
"import _xxinterpchannels as __xi_t\n"
|
|
89
|
+
"__xi_t.send(__xi_tid, __xi_payload)\n"
|
|
90
|
+
)
|
|
91
|
+
else:
|
|
92
|
+
import _xxsubinterpreters as _c
|
|
93
|
+
|
|
94
|
+
_create = _c.channel_create
|
|
95
|
+
_send = _c.channel_send
|
|
96
|
+
_recv = _c.channel_recv
|
|
97
|
+
_destroy = _c.channel_destroy
|
|
98
|
+
_Empty_native = _c.ChannelEmptyError
|
|
99
|
+
_NotFound_native = _c.ChannelNotFoundError
|
|
100
|
+
|
|
101
|
+
SUBINTERP_SEND = (
|
|
102
|
+
"import _xxsubinterpreters as __xi_t\n"
|
|
103
|
+
"__xi_t.channel_send(__xi_tid, __xi_payload)\n"
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
SUPPORTS_COUNT = False
|
|
107
|
+
|
|
108
|
+
class QueueError(Exception):
|
|
109
|
+
pass
|
|
110
|
+
|
|
111
|
+
class QueueNotFoundError(QueueError):
|
|
112
|
+
pass
|
|
113
|
+
|
|
114
|
+
# The ChannelID owns the channel, so keep it alive like an interpreter id.
|
|
115
|
+
_chan_objs = {}
|
|
116
|
+
|
|
117
|
+
def create(maxsize=0):
|
|
118
|
+
# Channels are unbounded, so maxsize is ignored here.
|
|
119
|
+
obj = _create()
|
|
120
|
+
cid = int(obj)
|
|
121
|
+
_chan_objs[cid] = obj
|
|
122
|
+
return cid
|
|
123
|
+
|
|
124
|
+
def destroy(qid):
|
|
125
|
+
try:
|
|
126
|
+
_destroy(qid)
|
|
127
|
+
except Exception:
|
|
128
|
+
pass
|
|
129
|
+
_chan_objs.pop(int(qid), None)
|
|
130
|
+
|
|
131
|
+
def bind(qid):
|
|
132
|
+
pass
|
|
133
|
+
|
|
134
|
+
def release(qid):
|
|
135
|
+
try:
|
|
136
|
+
_destroy(qid)
|
|
137
|
+
except Exception:
|
|
138
|
+
pass
|
|
139
|
+
_chan_objs.pop(int(qid), None)
|
|
140
|
+
|
|
141
|
+
def put(qid, data):
|
|
142
|
+
_send(qid, data)
|
|
143
|
+
|
|
144
|
+
def get(qid):
|
|
145
|
+
try:
|
|
146
|
+
return _recv(qid)
|
|
147
|
+
except _Empty_native:
|
|
148
|
+
raise _Empty
|
|
149
|
+
except _NotFound_native as exc:
|
|
150
|
+
raise _NotFound from exc
|
|
151
|
+
|
|
152
|
+
def count(qid):
|
|
153
|
+
raise NotImplementedError(
|
|
154
|
+
"queue size is not available before Python 3.13"
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
def is_full(qid):
|
|
158
|
+
return False
|
|
159
|
+
|
|
160
|
+
def maxsize(qid):
|
|
161
|
+
return 0
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"""The high level Queue, version-independent.
|
|
2
|
+
|
|
3
|
+
Objects are pickled to bytes and handed to :mod:`._qbackend` to cross the
|
|
4
|
+
interpreter boundary.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import time
|
|
8
|
+
import queue
|
|
9
|
+
import pickle
|
|
10
|
+
import weakref
|
|
11
|
+
|
|
12
|
+
from . import _qbackend
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"create", "list_all", "Queue",
|
|
16
|
+
"QueueError", "QueueNotFoundError", "QueueEmpty", "QueueFull",
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class QueueError(Exception):
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class QueueNotFoundError(QueueError):
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class QueueEmpty(QueueError, queue.Empty):
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class QueueFull(QueueError, queue.Full):
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def create(maxsize=0):
|
|
37
|
+
"""Return a new cross-interpreter queue."""
|
|
38
|
+
qid = _qbackend.create(maxsize)
|
|
39
|
+
return Queue(qid, _maxsize=maxsize)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def list_all():
|
|
43
|
+
listing = getattr(_qbackend, "list_all", None)
|
|
44
|
+
if listing is None:
|
|
45
|
+
raise NotImplementedError(
|
|
46
|
+
"listing queues is not available before Python 3.13"
|
|
47
|
+
)
|
|
48
|
+
return [Queue(qid) for qid in listing()]
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
_known_queues = weakref.WeakValueDictionary()
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class Queue:
|
|
55
|
+
"""A cross-interpreter FIFO queue."""
|
|
56
|
+
|
|
57
|
+
def __new__(cls, id, /, _maxsize=0):
|
|
58
|
+
if not isinstance(id, int):
|
|
59
|
+
raise TypeError(f"id must be an int, got {id!r}")
|
|
60
|
+
id = int(id)
|
|
61
|
+
try:
|
|
62
|
+
self = _known_queues[id]
|
|
63
|
+
except KeyError:
|
|
64
|
+
self = super().__new__(cls)
|
|
65
|
+
self._id = id
|
|
66
|
+
self._maxsize = _maxsize
|
|
67
|
+
_known_queues[id] = self
|
|
68
|
+
_qbackend.bind(id)
|
|
69
|
+
return self
|
|
70
|
+
|
|
71
|
+
def __del__(self):
|
|
72
|
+
try:
|
|
73
|
+
_qbackend.release(self._id)
|
|
74
|
+
except Exception:
|
|
75
|
+
pass
|
|
76
|
+
|
|
77
|
+
def __repr__(self):
|
|
78
|
+
return f"{type(self).__name__}({self._id})"
|
|
79
|
+
|
|
80
|
+
def __hash__(self):
|
|
81
|
+
return hash(self._id)
|
|
82
|
+
|
|
83
|
+
def __reduce__(self):
|
|
84
|
+
return (type(self), (self._id,))
|
|
85
|
+
|
|
86
|
+
@property
|
|
87
|
+
def id(self):
|
|
88
|
+
return self._id
|
|
89
|
+
|
|
90
|
+
@property
|
|
91
|
+
def maxsize(self):
|
|
92
|
+
return self._maxsize
|
|
93
|
+
|
|
94
|
+
def qsize(self):
|
|
95
|
+
return _qbackend.count(self._id)
|
|
96
|
+
|
|
97
|
+
def empty(self):
|
|
98
|
+
return self.qsize() == 0
|
|
99
|
+
|
|
100
|
+
def full(self):
|
|
101
|
+
if self._maxsize <= 0:
|
|
102
|
+
return _qbackend.is_full(self._id)
|
|
103
|
+
return self.qsize() >= self._maxsize
|
|
104
|
+
|
|
105
|
+
def put(self, obj, block=True, timeout=None, *, _delay=10 / 1000):
|
|
106
|
+
data = pickle.dumps(obj)
|
|
107
|
+
if not block:
|
|
108
|
+
return self.put_nowait(obj)
|
|
109
|
+
deadline = None if timeout is None else time.time() + timeout
|
|
110
|
+
while True:
|
|
111
|
+
if self._maxsize > 0 and _qbackend.SUPPORTS_COUNT:
|
|
112
|
+
if _qbackend.count(self._id) >= self._maxsize:
|
|
113
|
+
if deadline is not None and time.time() >= deadline:
|
|
114
|
+
raise QueueFull
|
|
115
|
+
time.sleep(_delay)
|
|
116
|
+
continue
|
|
117
|
+
_qbackend.put(self._id, data)
|
|
118
|
+
return
|
|
119
|
+
|
|
120
|
+
def put_nowait(self, obj):
|
|
121
|
+
if self._maxsize > 0 and _qbackend.SUPPORTS_COUNT:
|
|
122
|
+
if _qbackend.count(self._id) >= self._maxsize:
|
|
123
|
+
raise QueueFull
|
|
124
|
+
_qbackend.put(self._id, pickle.dumps(obj))
|
|
125
|
+
|
|
126
|
+
def get(self, block=True, timeout=None, *, _delay=10 / 1000):
|
|
127
|
+
if not block:
|
|
128
|
+
return self.get_nowait()
|
|
129
|
+
deadline = None if timeout is None else time.time() + timeout
|
|
130
|
+
while True:
|
|
131
|
+
try:
|
|
132
|
+
data = _qbackend.get(self._id)
|
|
133
|
+
except _qbackend._Empty:
|
|
134
|
+
if deadline is not None and time.time() >= deadline:
|
|
135
|
+
raise QueueEmpty
|
|
136
|
+
time.sleep(_delay)
|
|
137
|
+
continue
|
|
138
|
+
except _qbackend._NotFound as exc:
|
|
139
|
+
raise QueueNotFoundError(self._id) from exc
|
|
140
|
+
return pickle.loads(data)
|
|
141
|
+
|
|
142
|
+
def get_nowait(self):
|
|
143
|
+
try:
|
|
144
|
+
data = _qbackend.get(self._id)
|
|
145
|
+
except _qbackend._Empty:
|
|
146
|
+
raise QueueEmpty
|
|
147
|
+
except _qbackend._NotFound as exc:
|
|
148
|
+
raise QueueNotFoundError(self._id) from exc
|
|
149
|
+
return pickle.loads(data)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: backports.interpreters
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Backport of the concurrent.interpreters module described in PEP 734
|
|
5
|
+
Home-page: https://github.com/aisk/backports.interpreters
|
|
6
|
+
Author: An Long
|
|
7
|
+
License: PSF-2.0
|
|
8
|
+
Classifier: License :: OSI Approved :: Python Software Foundation License
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
16
|
+
Requires-Python: >=3.8
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Provides-Extra: dev
|
|
19
|
+
Requires-Dist: pytest; extra == "dev"
|
|
20
|
+
Dynamic: author
|
|
21
|
+
Dynamic: classifier
|
|
22
|
+
Dynamic: home-page
|
|
23
|
+
Dynamic: license
|
|
24
|
+
Dynamic: license-file
|
|
25
|
+
Dynamic: provides-extra
|
|
26
|
+
Dynamic: requires-python
|
|
27
|
+
Dynamic: summary
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
backports/interpreters/__init__.py,sha256=FowiJSH4RiFfxScZHV0PCqoO6Idbtaxqy-rueu0365g,7067
|
|
2
|
+
backports/interpreters/_backend.py,sha256=xsDS63Iy6Bn3pl7sDoISO1ALfY41BOnpfUtD7b4q69g,8800
|
|
3
|
+
backports/interpreters/_qbackend.py,sha256=qKoF5Nexf29kzPNnuVYPCAF87TPfhAcLd_k--m0UnnA,3639
|
|
4
|
+
backports/interpreters/_queues.py,sha256=0TmOjAKFa2yUe6v8upQpe_QeTZ8efiO28eQpcE3Bz4o,3938
|
|
5
|
+
backports_interpreters-0.3.0.dist-info/licenses/LICENSE,sha256=UhvnS3nxkXCqd2IJaxOFI43mAD0D4GEeeD46TNF7pE4,3012
|
|
6
|
+
backports_interpreters-0.3.0.dist-info/METADATA,sha256=iDaVP12o2hNGX6UMgu3UA5ywVjMm3RJRmEM1UPhzZhs,926
|
|
7
|
+
backports_interpreters-0.3.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
8
|
+
backports_interpreters-0.3.0.dist-info/top_level.txt,sha256=cGjaLMOoBR1FK0ApojtzWVmViTtJ7JGIK_HwXiEsvtU,10
|
|
9
|
+
backports_interpreters-0.3.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
This package is a backport of the CPython standard library module
|
|
2
|
+
concurrent.interpreters and is distributed under the same license as Python
|
|
3
|
+
itself, the Python Software Foundation License Version 2.
|
|
4
|
+
|
|
5
|
+
Copyright (c) 2020-2026 An Long.
|
|
6
|
+
Copyright © 2001 Python Software Foundation; All Rights Reserved.
|
|
7
|
+
|
|
8
|
+
Summary of changes from the original Python source: the high level API is
|
|
9
|
+
adapted to run on Python 3.8 through 3.13 by emulating the interpreter
|
|
10
|
+
primitives those versions lack. On 3.14+ the standard library module is used
|
|
11
|
+
directly. See the README and git history for details.
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
|
|
15
|
+
--------------------------------------------
|
|
16
|
+
|
|
17
|
+
1. This LICENSE AGREEMENT is between the Python Software Foundation ("PSF"), and
|
|
18
|
+
the Individual or Organization ("Licensee") accessing and otherwise using this
|
|
19
|
+
software ("Python") in source or binary form and its associated documentation.
|
|
20
|
+
|
|
21
|
+
2. Subject to the terms and conditions of this License Agreement, PSF hereby
|
|
22
|
+
grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce,
|
|
23
|
+
analyze, test, perform and/or display publicly, prepare derivative works,
|
|
24
|
+
distribute, and otherwise use Python alone or in any derivative version,
|
|
25
|
+
provided, however, that PSF's License Agreement and PSF's notice of copyright,
|
|
26
|
+
i.e., "Copyright © 2001 Python Software Foundation; All Rights Reserved" are
|
|
27
|
+
retained in Python alone or in any derivative version prepared by Licensee.
|
|
28
|
+
|
|
29
|
+
3. In the event Licensee prepares a derivative work that is based on or
|
|
30
|
+
incorporates Python or any part thereof, and wants to make the derivative work
|
|
31
|
+
available to others as provided herein, then Licensee hereby agrees to include
|
|
32
|
+
in any such work a brief summary of the changes made to Python.
|
|
33
|
+
|
|
34
|
+
4. PSF is making Python available to Licensee on an "AS IS" basis. PSF MAKES NO
|
|
35
|
+
REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF EXAMPLE, BUT NOT
|
|
36
|
+
LIMITATION, PSF MAKES NO AND DISCLAIMS ANY REPRESENTATION OR WARRANTY OF
|
|
37
|
+
MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON
|
|
38
|
+
WILL NOT INFRINGE ANY THIRD PARTY RIGHTS.
|
|
39
|
+
|
|
40
|
+
5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON FOR ANY
|
|
41
|
+
INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A RESULT OF MODIFYING,
|
|
42
|
+
DISTRIBUTING, OR OTHERWISE USING PYTHON, OR ANY DERIVATIVE THEREOF, EVEN IF
|
|
43
|
+
ADVISED OF THE POSSIBILITY THEREOF.
|
|
44
|
+
|
|
45
|
+
6. This License Agreement will automatically terminate upon a material breach of
|
|
46
|
+
its terms and conditions.
|
|
47
|
+
|
|
48
|
+
7. Nothing in this License Agreement shall be deemed to create any relationship
|
|
49
|
+
of agency, partnership, or joint venture between PSF and Licensee. This License
|
|
50
|
+
Agreement does not grant permission to use PSF trademarks or trade name in a
|
|
51
|
+
trademark sense to endorse or promote products or services of Licensee, or any
|
|
52
|
+
third party.
|
|
53
|
+
|
|
54
|
+
8. By copying, installing or otherwise using Python, Licensee agrees to be bound
|
|
55
|
+
by the terms and conditions of this License Agreement.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
backports
|