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.
@@ -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,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -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