diff --git a/docs/project/changelog.md b/docs/project/changelog.md index 79af07498..35e351461 100644 --- a/docs/project/changelog.md +++ b/docs/project/changelog.md @@ -15,6 +15,10 @@ myst: ## Unreleased +- {{ Enhancement }} `runPython` and `runPythonAsync` now accept a `locals` + argument. + {pr}`3618` + - {{ Fix }} If the `locals` argument to `eval_code` or `eval_code_async` is `None` it now uses `locals=globals` as the documentation says. {pr}`3580` diff --git a/docs/usage/faq.md b/docs/usage/faq.md index 208e7e861..41f48f555 100644 --- a/docs/usage/faq.md +++ b/docs/usage/faq.md @@ -366,10 +366,10 @@ To make your own version of {js:func}`~pyodide.runPython` you could do: ```pyodide const my_eval_code = pyodide.runPython(` from pyodide.code import eval_code - def my_eval_code(code, ns): + def my_eval_code(code, globals=None, locals=None): extra_info = None - result = eval_code(code, ns) - return ns["extra_info"], result + result = eval_code(code, globals, locals) + return globals["extra_info"], result my_eval_code `) diff --git a/src/js/api.ts b/src/js/api.ts index 3f072b4bd..0a9f7ba9a 100644 --- a/src/js/api.ts +++ b/src/js/api.ts @@ -64,17 +64,19 @@ API.runPythonInternal = function (code: string): any { * @param options * @param options.globals An optional Python dictionary to use as the globals. * Defaults to :js:attr:`pyodide.globals`. + * @param options.locals An optional Python dictionary to use as the locals. + * Defaults to the same as ``globals``. * @returns The result of the Python code translated to JavaScript. See the * documentation for :py:func:`~pyodide.code.eval_code` for more info. */ export function runPython( code: string, - options: { globals?: PyProxy } = {}, + options: { globals?: PyProxy; locals?: PyProxy } = {}, ): any { if (!options.globals) { options.globals = API.globals; } - return API.pyodide_code.eval_code(code, options.globals); + return API.pyodide_code.eval_code(code, options.globals, options.locals); } API.runPython = runPython; @@ -182,17 +184,23 @@ export async function loadPackagesFromImports( * @param options * @param options.globals An optional Python dictionary to use as the globals. * Defaults to :js:attr:`pyodide.globals`. + * @param options.locals An optional Python dictionary to use as the locals. + * Defaults to the same as ``globals``. * @returns The result of the Python code translated to JavaScript. * @async */ export async function runPythonAsync( code: string, - options: { globals?: PyProxy } = {}, + options: { globals?: PyProxy; locals?: PyProxy } = {}, ): Promise { if (!options.globals) { options.globals = API.globals; } - return await API.pyodide_code.eval_code_async(code, options.globals); + return await API.pyodide_code.eval_code_async( + code, + options.globals, + options.locals, + ); } API.runPythonAsync = runPythonAsync; diff --git a/src/tests/test_pyodide.py b/src/tests/test_pyodide.py index f10962e5f..4c5541bb9 100644 --- a/src/tests/test_pyodide.py +++ b/src/tests/test_pyodide.py @@ -302,8 +302,8 @@ def test_monkeypatch_eval_code(selenium): import pyodide old_eval_code = pyodide.code.eval_code x = 3 - def eval_code(code, ns): - return [ns["x"], old_eval_code(code, ns)] + def eval_code(code, globals=None, locals=None): + return [globals["x"], old_eval_code(code, globals, locals)] pyodide.code.eval_code = eval_code """ ) @@ -584,6 +584,26 @@ def test_run_python_js_error(selenium): ) +def test_run_python_locals(selenium): + selenium.run_js( + """ + let dict = pyodide.globals.get("dict"); + let locals = dict([["x", 7]]); + let globals = dict([["x", 5], ["y", 29]]); + dict.destroy(); + let result = pyodide.runPython("z = 13; x + y", {locals, globals}); + assert(() => locals.get("z") === 13); + assert(() => locals.has("x")); + let result2 = pyodide.runPython("del x; x + y", {locals, globals}); + assert(() => !locals.has("x")); + assert(() => result === 7 + 29); + assert(() => result2 === 5 + 29); + locals.destroy(); + globals.destroy(); + """ + ) + + def test_create_once_callable(selenium): selenium.run_js( """