Content-Length: 22234 | pFad | http://github.com/python/cpython/pull/113021.patch
thub.com
From 259ced65c6e86518b2def8299cf57f2e689b2779 Mon Sep 17 00:00:00 2001
From: Eric Snow
Date: Mon, 23 Oct 2023 09:57:32 -0600
Subject: [PATCH 1/4] Add Interpreter.bind().
---
Lib/test/support/interpreters/__init__.py | 5 ++
Lib/test/test__xxinterpchannels.py | 4 +-
Lib/test/test__xxsubinterpreters.py | 24 ++++++----
Lib/test/test_interpreters/test_api.py | 57 +++++++++++++++++++++++
Lib/test/test_interpreters/utils.py | 6 ++-
Modules/_xxsubinterpretersmodule.c | 54 +++++++++++++++++++++
6 files changed, 137 insertions(+), 13 deletions(-)
diff --git a/Lib/test/support/interpreters/__init__.py b/Lib/test/support/interpreters/__init__.py
index 2d6376deb5907e..8a414ce47c4c1f 100644
--- a/Lib/test/support/interpreters/__init__.py
+++ b/Lib/test/support/interpreters/__init__.py
@@ -130,6 +130,11 @@ def close(self):
"""
return _interpreters.destroy(self._id)
+ def bind(self, ns=None, /, **kwargs):
+ """Bind the given values into the interpreter's __main__."""
+ ns = dict(ns, **kwargs) if ns is not None else kwargs
+ _interpreters.bind(self._id, ns)
+
def exec_sync(self, code, /, channels=None):
"""Run the given source code in the interpreter.
diff --git a/Lib/test/test__xxinterpchannels.py b/Lib/test/test__xxinterpchannels.py
index 13c8a10296e502..aba3296aba3bea 100644
--- a/Lib/test/test__xxinterpchannels.py
+++ b/Lib/test/test__xxinterpchannels.py
@@ -586,12 +586,12 @@ def test_run_string_arg_unresolved(self):
cid = channels.create()
interp = interpreters.create()
+ interpreters.bind(interp, dict(cid=cid.send))
out = _run_output(interp, dedent("""
import _xxinterpchannels as _channels
print(cid.end)
_channels.send(cid, b'spam', blocking=False)
- """),
- dict(cid=cid.send))
+ """))
obj = channels.recv(cid)
self.assertEqual(obj, b'spam')
diff --git a/Lib/test/test__xxsubinterpreters.py b/Lib/test/test__xxsubinterpreters.py
index 260ab64b07cb2d..f9ae59d7229412 100644
--- a/Lib/test/test__xxsubinterpreters.py
+++ b/Lib/test/test__xxsubinterpreters.py
@@ -33,10 +33,10 @@ def _captured_script(script):
return wrapped, open(r, encoding="utf-8")
-def _run_output(interp, request, shared=None):
+def _run_output(interp, request):
script, rpipe = _captured_script(request)
with rpipe:
- interpreters.run_string(interp, script, shared)
+ interpreters.run_string(interp, script)
return rpipe.read()
@@ -630,10 +630,10 @@ def test_shareable_types(self):
]
for obj in objects:
with self.subTest(obj):
+ interpreters.bind(interp, dict(obj=obj))
interpreters.run_string(
interp,
f'assert(obj == {obj!r})',
- shared=dict(obj=obj),
)
def test_os_exec(self):
@@ -721,7 +721,8 @@ def test_with_shared(self):
with open({w}, 'wb') as chan:
pickle.dump(ns, chan)
""")
- interpreters.run_string(self.id, script, shared)
+ interpreters.bind(self.id, shared)
+ interpreters.run_string(self.id, script)
with open(r, 'rb') as chan:
ns = pickle.load(chan)
@@ -742,7 +743,8 @@ def test_shared_overwrites(self):
ns2 = dict(vars())
del ns2['__builtins__']
""")
- interpreters.run_string(self.id, script, shared)
+ interpreters.bind(self.id, shared)
+ interpreters.run_string(self.id, script)
r, w = os.pipe()
script = dedent(f"""
@@ -773,7 +775,8 @@ def test_shared_overwrites_default_vars(self):
with open({w}, 'wb') as chan:
pickle.dump(ns, chan)
""")
- interpreters.run_string(self.id, script, shared)
+ interpreters.bind(self.id, shared)
+ interpreters.run_string(self.id, script)
with open(r, 'rb') as chan:
ns = pickle.load(chan)
@@ -1036,7 +1039,8 @@ def script():
with open(w, 'w', encoding="utf-8") as spipe:
with contextlib.redirect_stdout(spipe):
print('it worked!', end='')
- interpreters.run_func(self.id, script, shared=dict(w=w))
+ interpreters.bind(self.id, dict(w=w))
+ interpreters.run_func(self.id, script)
with open(r, encoding="utf-8") as outfile:
out = outfile.read()
@@ -1052,7 +1056,8 @@ def script():
with contextlib.redirect_stdout(spipe):
print('it worked!', end='')
def f():
- interpreters.run_func(self.id, script, shared=dict(w=w))
+ interpreters.bind(self.id, dict(w=w))
+ interpreters.run_func(self.id, script)
t = threading.Thread(target=f)
t.start()
t.join()
@@ -1072,7 +1077,8 @@ def script():
with contextlib.redirect_stdout(spipe):
print('it worked!', end='')
code = script.__code__
- interpreters.run_func(self.id, code, shared=dict(w=w))
+ interpreters.bind(self.id, dict(w=w))
+ interpreters.run_func(self.id, code)
with open(r, encoding="utf-8") as outfile:
out = outfile.read()
diff --git a/Lib/test/test_interpreters/test_api.py b/Lib/test/test_interpreters/test_api.py
index e4ae9d005b5282..25e42b660923da 100644
--- a/Lib/test/test_interpreters/test_api.py
+++ b/Lib/test/test_interpreters/test_api.py
@@ -452,6 +452,63 @@ def task():
self.assertEqual(os.read(r_interp, 1), FINISHED)
+class TestInterpreterBind(TestBase):
+
+ def test_empty(self):
+ interp = interpreters.create()
+ with self.assertRaises(ValueError):
+ interp.bind()
+
+ def test_dict(self):
+ values = {'spam': 42, 'eggs': 'ham'}
+ interp = interpreters.create()
+ interp.bind(values)
+ out = _run_output(interp, dedent("""
+ print(spam, eggs)
+ """))
+ self.assertEqual(out.strip(), '42 ham')
+
+ def test_tuple(self):
+ values = {'spam': 42, 'eggs': 'ham'}
+ values = tuple(values.items())
+ interp = interpreters.create()
+ interp.bind(values)
+ out = _run_output(interp, dedent("""
+ print(spam, eggs)
+ """))
+ self.assertEqual(out.strip(), '42 ham')
+
+ def test_kwargs(self):
+ values = {'spam': 42, 'eggs': 'ham'}
+ interp = interpreters.create()
+ interp.bind(**values)
+ out = _run_output(interp, dedent("""
+ print(spam, eggs)
+ """))
+ self.assertEqual(out.strip(), '42 ham')
+
+ def test_dict_and_kwargs(self):
+ values = {'spam': 42, 'eggs': 'ham'}
+ interp = interpreters.create()
+ interp.bind(values, foo='bar')
+ out = _run_output(interp, dedent("""
+ print(spam, eggs, foo)
+ """))
+ self.assertEqual(out.strip(), '42 ham bar')
+
+ def test_not_shareable(self):
+ interp = interpreters.create()
+ # XXX TypeError?
+ with self.assertRaises(ValueError):
+ interp.bind(spam={'spam': 'eggs', 'foo': 'bar'})
+
+ # Make sure neither was actually bound.
+ with self.assertRaises(interpreters.ExecFailure):
+ interp.exec_sync('print(foo)')
+ with self.assertRaises(interpreters.ExecFailure):
+ interp.exec_sync('print(spam)')
+
+
class TestInterpreterExecSync(TestBase):
def test_success(self):
diff --git a/Lib/test/test_interpreters/utils.py b/Lib/test/test_interpreters/utils.py
index 623c8737b79831..720f26a3836c9e 100644
--- a/Lib/test/test_interpreters/utils.py
+++ b/Lib/test/test_interpreters/utils.py
@@ -29,10 +29,12 @@ def clean_up_interpreters():
pass # already destroyed
-def _run_output(interp, request, channels=None):
+def _run_output(interp, request, init=None):
script, rpipe = _captured_script(request)
with rpipe:
- interp.exec_sync(script, channels=channels)
+ if init:
+ interp.bind(init)
+ interp.exec_sync(script)
return rpipe.read()
diff --git a/Modules/_xxsubinterpretersmodule.c b/Modules/_xxsubinterpretersmodule.c
index 37959e953ee4f5..4182b3964dce11 100644
--- a/Modules/_xxsubinterpretersmodule.c
+++ b/Modules/_xxsubinterpretersmodule.c
@@ -685,6 +685,58 @@ PyDoc_STRVAR(get_main_doc,
\n\
Return the ID of main interpreter.");
+static PyObject *
+interp_bind(PyObject *self, PyObject *args)
+{
+ PyObject *id, *updates;
+ if (!PyArg_ParseTuple(args, "OO:" MODULE_NAME ".bind", &id, &updates)) {
+ return NULL;
+ }
+
+ // Look up the interpreter.
+ PyInterpreterState *interp = PyInterpreterID_LookUp(id);
+ if (interp == NULL) {
+ return NULL;
+ }
+
+ // Check the updates.
+ if (updates != Py_None) {
+ Py_ssize_t size = PyObject_Size(updates);
+ if (size < 0) {
+ return NULL;
+ }
+ if (size == 0) {
+ PyErr_SetString(PyExc_ValueError,
+ "arg 2 must be a non-empty mapping");
+ return NULL;
+ }
+ }
+
+ _PyXI_session session = {0};
+
+ // Prep and switch interpreters, including apply the updates.
+ if (_PyXI_Enter(&session, interp, updates) < 0) {
+ if (!PyErr_Occurred()) {
+ _PyXI_ApplyCapturedException(&session);
+ assert(PyErr_Occurred());
+ }
+ else {
+ assert(!_PyXI_HasCapturedException(&session));
+ }
+ return NULL;
+ }
+
+ // Clean up and switch back.
+ _PyXI_Exit(&session);
+
+ Py_RETURN_NONE;
+}
+
+PyDoc_STRVAR(bind_doc,
+"bind(id, ns)\n\
+\n\
+Bind the given attributes in the interpreter's __main__ module.");
+
static PyUnicodeObject *
convert_script_arg(PyObject *arg, const char *fname, const char *displayname,
const char *expected)
@@ -1033,6 +1085,8 @@ static PyMethodDef module_functions[] = {
{"run_func", _PyCFunction_CAST(interp_run_func),
METH_VARARGS | METH_KEYWORDS, run_func_doc},
+ {"bind", _PyCFunction_CAST(interp_bind),
+ METH_VARARGS, bind_doc},
{"is_shareable", _PyCFunction_CAST(object_is_shareable),
METH_VARARGS | METH_KEYWORDS, is_shareable_doc},
From 0555fb4744e869f8c90a247d953c3a85d1d5ba23 Mon Sep 17 00:00:00 2001
From: Eric Snow
Date: Mon, 23 Oct 2023 10:22:35 -0600
Subject: [PATCH 2/4] _interpreters.bind() ->
_interpreters.set___main___attrs()
---
Lib/test/support/interpreters/__init__.py | 8 ++++++--
Lib/test/test__xxinterpchannels.py | 2 +-
Lib/test/test__xxsubinterpreters.py | 14 +++++++-------
Modules/_xxsubinterpretersmodule.c | 14 ++++++++------
4 files changed, 22 insertions(+), 16 deletions(-)
diff --git a/Lib/test/support/interpreters/__init__.py b/Lib/test/support/interpreters/__init__.py
index 8a414ce47c4c1f..f9f4758b58b36e 100644
--- a/Lib/test/support/interpreters/__init__.py
+++ b/Lib/test/support/interpreters/__init__.py
@@ -130,10 +130,14 @@ def close(self):
"""
return _interpreters.destroy(self._id)
+ # XXX setattr?
def bind(self, ns=None, /, **kwargs):
- """Bind the given values into the interpreter's __main__."""
+ """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
- _interpreters.bind(self._id, ns)
+ _interpreters.set___main___attrs(self._id, ns)
def exec_sync(self, code, /, channels=None):
"""Run the given source code in the interpreter.
diff --git a/Lib/test/test__xxinterpchannels.py b/Lib/test/test__xxinterpchannels.py
index aba3296aba3bea..cc2ed7849b0c0f 100644
--- a/Lib/test/test__xxinterpchannels.py
+++ b/Lib/test/test__xxinterpchannels.py
@@ -586,7 +586,7 @@ def test_run_string_arg_unresolved(self):
cid = channels.create()
interp = interpreters.create()
- interpreters.bind(interp, dict(cid=cid.send))
+ interpreters.set___main___attrs(interp, dict(cid=cid.send))
out = _run_output(interp, dedent("""
import _xxinterpchannels as _channels
print(cid.end)
diff --git a/Lib/test/test__xxsubinterpreters.py b/Lib/test/test__xxsubinterpreters.py
index f9ae59d7229412..a76e4d0ade5b8a 100644
--- a/Lib/test/test__xxsubinterpreters.py
+++ b/Lib/test/test__xxsubinterpreters.py
@@ -630,7 +630,7 @@ def test_shareable_types(self):
]
for obj in objects:
with self.subTest(obj):
- interpreters.bind(interp, dict(obj=obj))
+ interpreters.set___main___attrs(interp, dict(obj=obj))
interpreters.run_string(
interp,
f'assert(obj == {obj!r})',
@@ -721,7 +721,7 @@ def test_with_shared(self):
with open({w}, 'wb') as chan:
pickle.dump(ns, chan)
""")
- interpreters.bind(self.id, shared)
+ interpreters.set___main___attrs(self.id, shared)
interpreters.run_string(self.id, script)
with open(r, 'rb') as chan:
ns = pickle.load(chan)
@@ -743,7 +743,7 @@ def test_shared_overwrites(self):
ns2 = dict(vars())
del ns2['__builtins__']
""")
- interpreters.bind(self.id, shared)
+ interpreters.set___main___attrs(self.id, shared)
interpreters.run_string(self.id, script)
r, w = os.pipe()
@@ -775,7 +775,7 @@ def test_shared_overwrites_default_vars(self):
with open({w}, 'wb') as chan:
pickle.dump(ns, chan)
""")
- interpreters.bind(self.id, shared)
+ interpreters.set___main___attrs(self.id, shared)
interpreters.run_string(self.id, script)
with open(r, 'rb') as chan:
ns = pickle.load(chan)
@@ -1039,7 +1039,7 @@ def script():
with open(w, 'w', encoding="utf-8") as spipe:
with contextlib.redirect_stdout(spipe):
print('it worked!', end='')
- interpreters.bind(self.id, dict(w=w))
+ interpreters.set___main___attrs(self.id, dict(w=w))
interpreters.run_func(self.id, script)
with open(r, encoding="utf-8") as outfile:
@@ -1056,7 +1056,7 @@ def script():
with contextlib.redirect_stdout(spipe):
print('it worked!', end='')
def f():
- interpreters.bind(self.id, dict(w=w))
+ interpreters.set___main___attrs(self.id, dict(w=w))
interpreters.run_func(self.id, script)
t = threading.Thread(target=f)
t.start()
@@ -1077,7 +1077,7 @@ def script():
with contextlib.redirect_stdout(spipe):
print('it worked!', end='')
code = script.__code__
- interpreters.bind(self.id, dict(w=w))
+ interpreters.set___main___attrs(self.id, dict(w=w))
interpreters.run_func(self.id, code)
with open(r, encoding="utf-8") as outfile:
diff --git a/Modules/_xxsubinterpretersmodule.c b/Modules/_xxsubinterpretersmodule.c
index 4182b3964dce11..4bb54c93b0a61b 100644
--- a/Modules/_xxsubinterpretersmodule.c
+++ b/Modules/_xxsubinterpretersmodule.c
@@ -686,10 +686,12 @@ PyDoc_STRVAR(get_main_doc,
Return the ID of main interpreter.");
static PyObject *
-interp_bind(PyObject *self, PyObject *args)
+interp_set___main___attrs(PyObject *self, PyObject *args)
{
PyObject *id, *updates;
- if (!PyArg_ParseTuple(args, "OO:" MODULE_NAME ".bind", &id, &updates)) {
+ if (!PyArg_ParseTuple(args, "OO:" MODULE_NAME ".set___main___attrs",
+ &id, &updates))
+ {
return NULL;
}
@@ -732,8 +734,8 @@ interp_bind(PyObject *self, PyObject *args)
Py_RETURN_NONE;
}
-PyDoc_STRVAR(bind_doc,
-"bind(id, ns)\n\
+PyDoc_STRVAR(set___main___attrs_doc,
+"set___main___attrs(id, ns)\n\
\n\
Bind the given attributes in the interpreter's __main__ module.");
@@ -1085,8 +1087,8 @@ static PyMethodDef module_functions[] = {
{"run_func", _PyCFunction_CAST(interp_run_func),
METH_VARARGS | METH_KEYWORDS, run_func_doc},
- {"bind", _PyCFunction_CAST(interp_bind),
- METH_VARARGS, bind_doc},
+ {"set___main___attrs", _PyCFunction_CAST(interp_set___main___attrs),
+ METH_VARARGS, set___main___attrs_doc},
{"is_shareable", _PyCFunction_CAST(object_is_shareable),
METH_VARARGS | METH_KEYWORDS, is_shareable_doc},
From c32eff3758423ffe7f166f7746c74d02483bcdf5 Mon Sep 17 00:00:00 2001
From: Eric Snow
Date: Tue, 12 Dec 2023 09:54:57 -0700
Subject: [PATCH 3/4] bind() -> prepare_main()
---
Lib/test/support/interpreters/__init__.py | 3 +--
Lib/test/test_interpreters/test_api.py | 14 +++++++-------
Lib/test/test_interpreters/utils.py | 2 +-
3 files changed, 9 insertions(+), 10 deletions(-)
diff --git a/Lib/test/support/interpreters/__init__.py b/Lib/test/support/interpreters/__init__.py
index f9f4758b58b36e..312d6623827709 100644
--- a/Lib/test/support/interpreters/__init__.py
+++ b/Lib/test/support/interpreters/__init__.py
@@ -130,8 +130,7 @@ def close(self):
"""
return _interpreters.destroy(self._id)
- # XXX setattr?
- def bind(self, ns=None, /, **kwargs):
+ def prepare_main(self, ns=None, /, **kwargs):
"""Bind the given values into the interpreter's __main__.
The values must be shareable.
diff --git a/Lib/test/test_interpreters/test_api.py b/Lib/test/test_interpreters/test_api.py
index 25e42b660923da..b702338c3de1ad 100644
--- a/Lib/test/test_interpreters/test_api.py
+++ b/Lib/test/test_interpreters/test_api.py
@@ -452,17 +452,17 @@ def task():
self.assertEqual(os.read(r_interp, 1), FINISHED)
-class TestInterpreterBind(TestBase):
+class TestInterpreterPrepareMain(TestBase):
def test_empty(self):
interp = interpreters.create()
with self.assertRaises(ValueError):
- interp.bind()
+ interp.prepare_main()
def test_dict(self):
values = {'spam': 42, 'eggs': 'ham'}
interp = interpreters.create()
- interp.bind(values)
+ interp.prepare_main(values)
out = _run_output(interp, dedent("""
print(spam, eggs)
"""))
@@ -472,7 +472,7 @@ def test_tuple(self):
values = {'spam': 42, 'eggs': 'ham'}
values = tuple(values.items())
interp = interpreters.create()
- interp.bind(values)
+ interp.prepare_main(values)
out = _run_output(interp, dedent("""
print(spam, eggs)
"""))
@@ -481,7 +481,7 @@ def test_tuple(self):
def test_kwargs(self):
values = {'spam': 42, 'eggs': 'ham'}
interp = interpreters.create()
- interp.bind(**values)
+ interp.prepare_main(**values)
out = _run_output(interp, dedent("""
print(spam, eggs)
"""))
@@ -490,7 +490,7 @@ def test_kwargs(self):
def test_dict_and_kwargs(self):
values = {'spam': 42, 'eggs': 'ham'}
interp = interpreters.create()
- interp.bind(values, foo='bar')
+ interp.prepare_main(values, foo='bar')
out = _run_output(interp, dedent("""
print(spam, eggs, foo)
"""))
@@ -500,7 +500,7 @@ def test_not_shareable(self):
interp = interpreters.create()
# XXX TypeError?
with self.assertRaises(ValueError):
- interp.bind(spam={'spam': 'eggs', 'foo': 'bar'})
+ interp.prepare_main(spam={'spam': 'eggs', 'foo': 'bar'})
# Make sure neither was actually bound.
with self.assertRaises(interpreters.ExecFailure):
diff --git a/Lib/test/test_interpreters/utils.py b/Lib/test/test_interpreters/utils.py
index 720f26a3836c9e..11b6f126dff0f4 100644
--- a/Lib/test/test_interpreters/utils.py
+++ b/Lib/test/test_interpreters/utils.py
@@ -33,7 +33,7 @@ def _run_output(interp, request, init=None):
script, rpipe = _captured_script(request)
with rpipe:
if init:
- interp.bind(init)
+ interp.prepare_main(init)
interp.exec_sync(script)
return rpipe.read()
From d9703e086af6a9b1626a16e4f6ad396dc33e6674 Mon Sep 17 00:00:00 2001
From: Eric Snow
Date: Tue, 12 Dec 2023 10:01:28 -0700
Subject: [PATCH 4/4] Drop the init arg to exec_sync().
---
Lib/test/support/interpreters/__init__.py | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/Lib/test/support/interpreters/__init__.py b/Lib/test/support/interpreters/__init__.py
index 312d6623827709..9cd1c3de0274d2 100644
--- a/Lib/test/support/interpreters/__init__.py
+++ b/Lib/test/support/interpreters/__init__.py
@@ -138,7 +138,7 @@ def prepare_main(self, ns=None, /, **kwargs):
ns = dict(ns, **kwargs) if ns is not None else kwargs
_interpreters.set___main___attrs(self._id, ns)
- def exec_sync(self, code, /, channels=None):
+ def exec_sync(self, code, /):
"""Run the given source code in the interpreter.
This is essentially the same as calling the builtin "exec"
@@ -156,13 +156,13 @@ def exec_sync(self, code, /, channels=None):
that time, the previous interpreter is allowed to run
in other threads.
"""
- excinfo = _interpreters.exec(self._id, code, channels)
+ excinfo = _interpreters.exec(self._id, code)
if excinfo is not None:
raise ExecFailure(excinfo)
- def run(self, code, /, channels=None):
+ def run(self, code, /):
def task():
- self.exec_sync(code, channels=channels)
+ self.exec_sync(code)
t = threading.Thread(target=task)
t.start()
return t
--- a PPN by Garber Painting Akron. With Image Size Reduction included!Fetched URL: http://github.com/python/cpython/pull/113021.patch
Alternative Proxies:
Alternative Proxy
pFad Proxy
pFad v3 Proxy
pFad v4 Proxy