From 54d10d35dc9fd9aaa62e4c0442c7562770d66c36 Mon Sep 17 00:00:00 2001 From: Jeff Glass Date: Wed, 11 Jan 2023 07:51:12 -0600 Subject: [PATCH 01/28] Add before, after REPL hooks --- pyscriptjs/src/components/elements.ts | 5 +++-- pyscriptjs/src/components/pyrepl.ts | 3 ++- pyscriptjs/src/main.ts | 3 ++- pyscriptjs/src/plugin.ts | 32 +++++++++++++++++++++++++++ 4 files changed, 39 insertions(+), 4 deletions(-) diff --git a/pyscriptjs/src/components/elements.ts b/pyscriptjs/src/components/elements.ts index 7524cbd8033..57bd2e332c2 100644 --- a/pyscriptjs/src/components/elements.ts +++ b/pyscriptjs/src/components/elements.ts @@ -1,10 +1,11 @@ +import type { PyScriptApp } from '../main'; import { InterpreterClient } from '../interpreter_client'; import { make_PyRepl } from './pyrepl'; import { make_PyWidget } from './pywidget'; -function createCustomElements(interpreter: InterpreterClient) { +function createCustomElements(interpreter: InterpreterClient, app: PyScriptApp) { const PyWidget = make_PyWidget(interpreter); - const PyRepl = make_PyRepl(interpreter); + const PyRepl = make_PyRepl(interpreter, app); /* eslint-disable @typescript-eslint/no-unused-vars */ const xPyRepl = customElements.define('py-repl', PyRepl); diff --git a/pyscriptjs/src/components/pyrepl.ts b/pyscriptjs/src/components/pyrepl.ts index 48840ed1510..52af0259b46 100644 --- a/pyscriptjs/src/components/pyrepl.ts +++ b/pyscriptjs/src/components/pyrepl.ts @@ -10,11 +10,12 @@ import { getAttribute, ensureUniqueId, htmlDecode } from '../utils'; import { pyExec, pyDisplay } from '../pyexec'; import { getLogger } from '../logger'; import { InterpreterClient } from '../interpreter_client'; +import type { PyScriptApp } from '../main'; const logger = getLogger('py-repl'); const RUNBUTTON = ``; -export function make_PyRepl(interpreter: InterpreterClient) { +export function make_PyRepl(interpreter: InterpreterClient, app: PyScriptApp) { /* High level structure of py-repl DOM, and the corresponding JS names. this diff --git a/pyscriptjs/src/main.ts b/pyscriptjs/src/main.ts index cd442cb9b63..072c3352332 100644 --- a/pyscriptjs/src/main.ts +++ b/pyscriptjs/src/main.ts @@ -189,8 +189,9 @@ export class PyScriptApp { this.logStatus('Initializing web components...'); // lifecycle (8) - createCustomElements(interpreter); + //Takes a runtime and a reference to the PyScriptApp (to access plugins) + createCustomElements(interpreter, this); await initHandlers(interpreter); // NOTE: interpreter message is used by integration tests to know that diff --git a/pyscriptjs/src/plugin.ts b/pyscriptjs/src/plugin.ts index e8dc737fc4f..d4e79e1f313 100644 --- a/pyscriptjs/src/plugin.ts +++ b/pyscriptjs/src/plugin.ts @@ -80,6 +80,26 @@ export class Plugin { /* empty */ } + /** The source of the tag has been fetched and its output-element determined; + * we're about to evaluate the source using the provided runtime + * + * @param runtime The Runtime object that will be used to evaluated the Python source code + * @param src {string} The Python source code to be evaluated + * @param outEl The element that the result of the REPL evaluation will be output to. + * @param pyReplTag The HTML tag the originated the evaluation + */ + beforePyReplExec(runtime, src, outEl, pyReplTag){} + + /** + * + * @param runtime The Runtime object that will be used to evaluated the Python source code + * @param src {string} The Python source code to be evaluated + * @param outEl The element that the result of the REPL evaluation will be output to. + * @param pyReplTag The HTML tag the originated the evaluation + * @param result The result of evaluating the Python (if any) + */ + afterPyReplExec(runtime, src, outEl, pyReplTag, result){} + /** Startup complete. The interpreter is initialized and ready, user * scripts have been executed: the main initialization logic ends here and * the page is ready to accept user interactions. @@ -158,6 +178,18 @@ export class PluginManager { for (const p of this._pythonPlugins) p.afterPyScriptExec?.callKwargs(options); } + beforePyReplExec(runtime, src, outEl, pyReplTag){ + for (const p of this._plugins) p.beforePyReplExec(runtime, src, outEl, pyReplTag); + + for (const p of this._pythonPlugins) p.beforePyReplExec?.(runtime, src, outEl, pyReplTag); + } + + afterPyReplExec(runtime, src, outEl, pyReplTag, result){ + for (const p of this._plugins) p.afterPyReplExec(runtime, src, outEl, pyReplTag, result); + + for (const p of this._pythonPlugins) p.afterPyReplExec?.(runtime, src, outEl, pyReplTag, result); + } + onUserError(error: UserError) { for (const p of this._plugins) p.onUserError?.(error); From 62c15959830925a79421b731c268470c021c0320 Mon Sep 17 00:00:00 2001 From: Jeff Glass Date: Wed, 11 Jan 2023 08:52:11 -0600 Subject: [PATCH 02/28] Basic stdout/stderr handling --- pyscriptjs/src/components/pyrepl.ts | 30 +++++++++++------------ pyscriptjs/src/plugins/stdiodirector.ts | 32 +++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 16 deletions(-) diff --git a/pyscriptjs/src/components/pyrepl.ts b/pyscriptjs/src/components/pyrepl.ts index 52af0259b46..77049624a88 100644 --- a/pyscriptjs/src/components/pyrepl.ts +++ b/pyscriptjs/src/components/pyrepl.ts @@ -11,6 +11,7 @@ import { pyExec, pyDisplay } from '../pyexec'; import { getLogger } from '../logger'; import { InterpreterClient } from '../interpreter_client'; import type { PyScriptApp } from '../main'; +import { Stdio } from '../stdio'; const logger = getLogger('py-repl'); const RUNBUTTON = ``; @@ -32,6 +33,8 @@ export function make_PyRepl(interpreter: InterpreterClient, app: PyScriptApp) { shadow: ShadowRoot; outDiv: HTMLElement; editor: EditorView; + stdout_manager: Stdio | null; + stderr_manager: Stdio | null; constructor() { super(); @@ -167,8 +170,9 @@ export function make_PyRepl(interpreter: InterpreterClient, app: PyScriptApp) { outEl.innerHTML = ''; // execute the python code - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + app.plugins.beforePyReplExec(interpreter, pySrc, outEl, this); const pyResult = (await pyExec(interpreter, pySrc, outEl)).result; + app.plugins.afterPyReplExec(interpreter, pySrc, outEl, this, pyResult); // display the value of the last evaluated expression (REPL-style) if (pyResult !== undefined) { @@ -207,27 +211,21 @@ export function make_PyRepl(interpreter: InterpreterClient, app: PyScriptApp) { const nextExecId = parseInt(lastExecId) + 1; const newPyRepl = document.createElement('py-repl'); - newPyRepl.setAttribute('root', this.getAttribute('root')); - newPyRepl.id = this.getAttribute('root') + '-' + nextExecId.toString(); - - if (this.hasAttribute('auto-generate')) { - newPyRepl.setAttribute('auto-generate', ''); - this.removeAttribute('auto-generate'); - } - - const outputMode = getAttribute(this, 'output-mode'); - if (outputMode) { - newPyRepl.setAttribute('output-mode', outputMode); - } - const addReplAttribute = (attribute: string) => { + //Attributes to be copied from old REPL to auto-generated REPL + for (const attribute of ['root', 'output-mode', 'output', 'stderr']){ const attr = getAttribute(this, attribute); if (attr) { newPyRepl.setAttribute(attribute, attr); } - }; + } + + newPyRepl.id = this.getAttribute('root') + '-' + nextExecId.toString(); - addReplAttribute('output'); + if (this.hasAttribute('auto-generate')) { + newPyRepl.setAttribute('auto-generate', ''); + this.removeAttribute('auto-generate'); + } newPyRepl.setAttribute('exec-id', nextExecId.toString()); if (this.parentElement) { diff --git a/pyscriptjs/src/plugins/stdiodirector.ts b/pyscriptjs/src/plugins/stdiodirector.ts index 2cb379ac419..49148146e4e 100644 --- a/pyscriptjs/src/plugins/stdiodirector.ts +++ b/pyscriptjs/src/plugins/stdiodirector.ts @@ -58,4 +58,36 @@ export class StdioDirector extends Plugin { options.pyScriptTag.stderr_manager = null; } } + + beforePyReplExec(runtime: any, src: any, outEl: any, pyReplTag: any): void { + let output_targeted_io; + if (pyReplTag.hasAttribute("output")){ + output_targeted_io = new TargetedStdio(pyReplTag, "output", true, true); + } + else { + output_targeted_io = new TargetedStdio(pyReplTag.outDiv, "id", true, true); + } + + pyReplTag.stdout_manager = output_targeted_io; + this._stdioMultiplexer.addListener(output_targeted_io); + + + if (pyReplTag.hasAttribute("stderr")){ + const stderr_targeted_io = new TargetedStdio(pyReplTag, "stderr", false, true); + pyReplTag.stderr_manager = stderr_targeted_io; + this._stdioMultiplexer.addListener(stderr_targeted_io); + } + + } + + afterPyReplExec(runtime: any, src: any, outEl: any, pyReplTag: any, result: any): void { + if (pyReplTag.stdout_manager != null){ + this._stdioMultiplexer.removeListener(pyReplTag.stdout_manager) + pyReplTag.stdout_manager = null + } + if (pyReplTag.stderr_manager != null){ + this._stdioMultiplexer.removeListener(pyReplTag.stderr_manager) + pyReplTag.stderr_manager = null + } + } } From 52d3c5dc20113e2033692f9d17c07f45d2097417 Mon Sep 17 00:00:00 2001 From: Jeff Glass Date: Wed, 11 Jan 2023 09:18:58 -0600 Subject: [PATCH 03/28] Re-introduce 'output-mode' attribute for py-repl --- pyscriptjs/src/components/pyrepl.ts | 3 --- pyscriptjs/src/plugins/stdiodirector.ts | 11 +++++++++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/pyscriptjs/src/components/pyrepl.ts b/pyscriptjs/src/components/pyrepl.ts index 77049624a88..c5814eaafc1 100644 --- a/pyscriptjs/src/components/pyrepl.ts +++ b/pyscriptjs/src/components/pyrepl.ts @@ -166,9 +166,6 @@ export function make_PyRepl(interpreter: InterpreterClient, app: PyScriptApp) { return; } - // clear the old output before executing the new code - outEl.innerHTML = ''; - // execute the python code app.plugins.beforePyReplExec(interpreter, pySrc, outEl, this); const pyResult = (await pyExec(interpreter, pySrc, outEl)).result; diff --git a/pyscriptjs/src/plugins/stdiodirector.ts b/pyscriptjs/src/plugins/stdiodirector.ts index 49148146e4e..4cf149df516 100644 --- a/pyscriptjs/src/plugins/stdiodirector.ts +++ b/pyscriptjs/src/plugins/stdiodirector.ts @@ -60,6 +60,14 @@ export class StdioDirector extends Plugin { } beforePyReplExec(runtime: any, src: any, outEl: any, pyReplTag: any): void { + //Handle 'output-mode' attribute (removed in PR #881/f9194cc8, restored here) + if (pyReplTag.getAttribute('output-mode') != 'append'){ + outEl.innerHTML = '' + } + + // Handle 'output' attribute; defaults to writing stdout to the existing outEl + // If 'output' attribute is used, the DOM element with the specified ID receives + // -both- sys.stdout and sys.stderr let output_targeted_io; if (pyReplTag.hasAttribute("output")){ output_targeted_io = new TargetedStdio(pyReplTag, "output", true, true); @@ -67,11 +75,10 @@ export class StdioDirector extends Plugin { else { output_targeted_io = new TargetedStdio(pyReplTag.outDiv, "id", true, true); } - pyReplTag.stdout_manager = output_targeted_io; this._stdioMultiplexer.addListener(output_targeted_io); - + //Handle 'stderr' attribute; if (pyReplTag.hasAttribute("stderr")){ const stderr_targeted_io = new TargetedStdio(pyReplTag, "stderr", false, true); pyReplTag.stderr_manager = stderr_targeted_io; From 7692b5629012a8e5b75ebd99b363572b9193b18c Mon Sep 17 00:00:00 2001 From: Jeff Glass Date: Wed, 25 Jan 2023 09:26:46 -0600 Subject: [PATCH 04/28] Adjust to use options/kwargs --- pyscriptjs/src/components/pyrepl.ts | 4 +-- pyscriptjs/src/plugin.ts | 35 +++++++++++++------------ pyscriptjs/src/plugins/stdiodirector.ts | 34 ++++++++++++------------ 3 files changed, 37 insertions(+), 36 deletions(-) diff --git a/pyscriptjs/src/components/pyrepl.ts b/pyscriptjs/src/components/pyrepl.ts index c5814eaafc1..8f47c63a79f 100644 --- a/pyscriptjs/src/components/pyrepl.ts +++ b/pyscriptjs/src/components/pyrepl.ts @@ -167,9 +167,9 @@ export function make_PyRepl(interpreter: InterpreterClient, app: PyScriptApp) { } // execute the python code - app.plugins.beforePyReplExec(interpreter, pySrc, outEl, this); + app.plugins.beforePyReplExec({interpreter: interpreter, src: pySrc, outEl: outEl, pyReplTag: this}); const pyResult = (await pyExec(interpreter, pySrc, outEl)).result; - app.plugins.afterPyReplExec(interpreter, pySrc, outEl, this, pyResult); + app.plugins.afterPyReplExec({interpreter: interpreter, src: pySrc, outEl: outEl, pyReplTag: this, result: pyResult}); // display the value of the last evaluated expression (REPL-style) if (pyResult !== undefined) { diff --git a/pyscriptjs/src/plugin.ts b/pyscriptjs/src/plugin.ts index d4e79e1f313..56c96f38b9c 100644 --- a/pyscriptjs/src/plugin.ts +++ b/pyscriptjs/src/plugin.ts @@ -8,6 +8,7 @@ import type { UserError } from './exceptions'; import { getLogger } from './logger'; import { make_PyScript } from './components/pyscript'; import { InterpreterClient } from './interpreter_client'; +import { interpreter } from './main'; const logger = getLogger('plugin'); type PyScriptTag = InstanceType>; @@ -83,22 +84,22 @@ export class Plugin { /** The source of the tag has been fetched and its output-element determined; * we're about to evaluate the source using the provided runtime * - * @param runtime The Runtime object that will be used to evaluated the Python source code - * @param src {string} The Python source code to be evaluated - * @param outEl The element that the result of the REPL evaluation will be output to. - * @param pyReplTag The HTML tag the originated the evaluation + * @param options.runtime The Runtime object that will be used to evaluated the Python source code + * @param options.src {string} The Python source code to be evaluated + * @param options.outEl The element that the result of the REPL evaluation will be output to. + * @param options.pyReplTag The HTML tag the originated the evaluation */ - beforePyReplExec(runtime, src, outEl, pyReplTag){} + beforePyReplExec(options: {interpreter: InterpreterClient, src: string, outEl: HTMLElement, pyReplTag: any}){} /** * - * @param runtime The Runtime object that will be used to evaluated the Python source code - * @param src {string} The Python source code to be evaluated - * @param outEl The element that the result of the REPL evaluation will be output to. - * @param pyReplTag The HTML tag the originated the evaluation - * @param result The result of evaluating the Python (if any) + * @param options.runtime The Runtime object that will be used to evaluated the Python source code + * @param options.src {string} The Python source code to be evaluated + * @param options.outEl The element that the result of the REPL evaluation will be output to. + * @param options.pyReplTag The HTML tag the originated the evaluation + * @param options.result The result of evaluating the Python (if any) */ - afterPyReplExec(runtime, src, outEl, pyReplTag, result){} + afterPyReplExec(options: {interpreter: InterpreterClient, src: string, outEl: HTMLElement, pyReplTag: HTMLElement, result: any}){} /** Startup complete. The interpreter is initialized and ready, user * scripts have been executed: the main initialization logic ends here and @@ -178,16 +179,16 @@ export class PluginManager { for (const p of this._pythonPlugins) p.afterPyScriptExec?.callKwargs(options); } - beforePyReplExec(runtime, src, outEl, pyReplTag){ - for (const p of this._plugins) p.beforePyReplExec(runtime, src, outEl, pyReplTag); + beforePyReplExec(options: {interpreter: InterpreterClient, src: string, outEl: HTMLElement, pyReplTag: any}){ + for (const p of this._plugins) p.beforePyReplExec(options); - for (const p of this._pythonPlugins) p.beforePyReplExec?.(runtime, src, outEl, pyReplTag); + for (const p of this._pythonPlugins) p.beforePyReplExec?.callKwargs(options); } - afterPyReplExec(runtime, src, outEl, pyReplTag, result){ - for (const p of this._plugins) p.afterPyReplExec(runtime, src, outEl, pyReplTag, result); + afterPyReplExec(options: {interpreter: InterpreterClient, src: string, outEl, pyReplTag, result}){ + for (const p of this._plugins) p.afterPyReplExec(options); - for (const p of this._pythonPlugins) p.afterPyReplExec?.(runtime, src, outEl, pyReplTag, result); + for (const p of this._pythonPlugins) p.afterPyReplExec?.callKwargs(options); } onUserError(error: UserError) { diff --git a/pyscriptjs/src/plugins/stdiodirector.ts b/pyscriptjs/src/plugins/stdiodirector.ts index 4cf149df516..4498c5a0634 100644 --- a/pyscriptjs/src/plugins/stdiodirector.ts +++ b/pyscriptjs/src/plugins/stdiodirector.ts @@ -59,42 +59,42 @@ export class StdioDirector extends Plugin { } } - beforePyReplExec(runtime: any, src: any, outEl: any, pyReplTag: any): void { + beforePyReplExec(options: {runtime: Interpreter, src: string, outEl: HTMLElement, pyReplTag: any}): void { //Handle 'output-mode' attribute (removed in PR #881/f9194cc8, restored here) - if (pyReplTag.getAttribute('output-mode') != 'append'){ - outEl.innerHTML = '' + if (options.pyReplTag.getAttribute('output-mode') != 'append'){ + options.outEl.innerHTML = '' } // Handle 'output' attribute; defaults to writing stdout to the existing outEl // If 'output' attribute is used, the DOM element with the specified ID receives // -both- sys.stdout and sys.stderr let output_targeted_io; - if (pyReplTag.hasAttribute("output")){ - output_targeted_io = new TargetedStdio(pyReplTag, "output", true, true); + if (options.pyReplTag.hasAttribute("output")){ + output_targeted_io = new TargetedStdio(options.pyReplTag, "output", true, true); } else { - output_targeted_io = new TargetedStdio(pyReplTag.outDiv, "id", true, true); + output_targeted_io = new TargetedStdio(options.pyReplTag.outDiv, "id", true, true); } - pyReplTag.stdout_manager = output_targeted_io; + options.pyReplTag.stdout_manager = output_targeted_io; this._stdioMultiplexer.addListener(output_targeted_io); //Handle 'stderr' attribute; - if (pyReplTag.hasAttribute("stderr")){ - const stderr_targeted_io = new TargetedStdio(pyReplTag, "stderr", false, true); - pyReplTag.stderr_manager = stderr_targeted_io; + if (options.pyReplTag.hasAttribute("stderr")){ + const stderr_targeted_io = new TargetedStdio(options.pyReplTag, "stderr", false, true); + options.pyReplTag.stderr_manager = stderr_targeted_io; this._stdioMultiplexer.addListener(stderr_targeted_io); } } - afterPyReplExec(runtime: any, src: any, outEl: any, pyReplTag: any, result: any): void { - if (pyReplTag.stdout_manager != null){ - this._stdioMultiplexer.removeListener(pyReplTag.stdout_manager) - pyReplTag.stdout_manager = null + afterPyReplExec(options: {runtime: any, src: any, outEl: any, pyReplTag: any, result: any}): void { + if (options.pyReplTag.stdout_manager != null){ + this._stdioMultiplexer.removeListener(options.pyReplTag.stdout_manager) + options.pyReplTag.stdout_manager = null } - if (pyReplTag.stderr_manager != null){ - this._stdioMultiplexer.removeListener(pyReplTag.stderr_manager) - pyReplTag.stderr_manager = null + if (options.pyReplTag.stderr_manager != null){ + this._stdioMultiplexer.removeListener(options.pyReplTag.stderr_manager) + options.pyReplTag.stderr_manager = null } } } From be949d4be6fb65a4193e057da8a4eebe563f0f8b Mon Sep 17 00:00:00 2001 From: Jeff Glass Date: Thu, 26 Jan 2023 08:55:09 -0600 Subject: [PATCH 05/28] runtime -> interpreter --- pyscriptjs/src/plugins/stdiodirector.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyscriptjs/src/plugins/stdiodirector.ts b/pyscriptjs/src/plugins/stdiodirector.ts index 4498c5a0634..6c93963b9f4 100644 --- a/pyscriptjs/src/plugins/stdiodirector.ts +++ b/pyscriptjs/src/plugins/stdiodirector.ts @@ -59,7 +59,7 @@ export class StdioDirector extends Plugin { } } - beforePyReplExec(options: {runtime: Interpreter, src: string, outEl: HTMLElement, pyReplTag: any}): void { + beforePyReplExec(options: {interpreter: Interpreter, src: string, outEl: HTMLElement, pyReplTag: any}): void { //Handle 'output-mode' attribute (removed in PR #881/f9194cc8, restored here) if (options.pyReplTag.getAttribute('output-mode') != 'append'){ options.outEl.innerHTML = '' From b2d2d9cf86160e555c72bdc6175e16b7bc00f782 Mon Sep 17 00:00:00 2001 From: Jeff Glass Date: Thu, 26 Jan 2023 08:55:43 -0600 Subject: [PATCH 06/28] Add plugin execution tests --- pyscriptjs/tests/integration/test_plugins.py | 57 +++++++++++++++++++- 1 file changed, 55 insertions(+), 2 deletions(-) diff --git a/pyscriptjs/tests/integration/test_plugins.py b/pyscriptjs/tests/integration/test_plugins.py index e51eaac04df..39821106d13 100644 --- a/pyscriptjs/tests/integration/test_plugins.py +++ b/pyscriptjs/tests/integration/test_plugins.py @@ -1,3 +1,5 @@ +from textwrap import dedent + from .support import PyScriptTest # Source code of a simple plugin that creates a Custom Element for testing purposes @@ -54,7 +56,7 @@ def onUserError(self, config): # Source of script that defines a plugin with only beforePyScriptExec and # afterPyScriptExec methods -EXEC_HOOKS_PLUGIN_CODE = """ +PYSCRIPT_HOOKS_PLUGIN_CODE = """ from pyscript import Plugin from js import console @@ -195,6 +197,8 @@ def test_execution_hooks(self): "beforeLaunch", "beforePyScriptExec", "afterPyScriptExec", + "beforePyReplExec", + "afterPyReplExec", ] # EXPECT it to log the correct logs for the events it intercepts @@ -211,7 +215,7 @@ def test_execution_hooks(self): @prepare_test( "exec_test_logger", - EXEC_HOOKS_PLUGIN_CODE, + PYSCRIPT_HOOKS_PLUGIN_CODE, template=HTML_TEMPLATE_NO_TAG + "\nx=2; x", ) def test_pyscript_exec_hooks(self): @@ -231,6 +235,55 @@ def test_pyscript_exec_hooks(self): assert "after_id:pyid" in log_lines assert "result:2" in log_lines + # Source of script that defines a plugin with only beforePyScriptExec and + # afterPyScriptExec methods + PYREPL_HOOKS_PLUGIN_CODE = dedent( + """ + from pyscript import Plugin + from js import console + + console.warn("This is in pyrepl hooks file") + + class PyReplTestLogger(Plugin): + + def beforePyReplExec(self, interpreter, outEl, src, pyReplTag): + console.log(f'beforePyReplExec called') + console.log(f'before_src:{src}') + console.log(f'before_id:{pyReplTag.id}') + + def afterPyReplExec(self, interpreter, src, outEl, pyReplTag, result): + console.log(f'afterPyReplExec called') + console.log(f'after_src:{src}') + console.log(f'after_id:{pyReplTag.id}') + console.log(f'result:{result}') + + + plugin = PyReplTestLogger() + """ + ) + + @prepare_test( + "pyrepl_test_logger", + PYREPL_HOOKS_PLUGIN_CODE, + template=HTML_TEMPLATE_NO_TAG + "\nx=2; x", + ) + def test_pyrepl_exec_hooks(self): + py_repl = self.page.locator("py-repl") + py_repl.locator("button").click() + + log_lines: list[str] = self.console.log.lines + + assert "beforePyReplExec called" in log_lines + assert "afterPyReplExec called" in log_lines + + # These could be made better with a utility function that found log lines + # that match a filter function, or start with something + assert "before_src:x=2; x" in log_lines + assert "before_id:pyid" in log_lines + assert "after_src:x=2; x" in log_lines + assert "after_id:pyid" in log_lines + assert "result:2" in log_lines + @prepare_test("no_plugin", NO_PLUGIN_CODE) def test_no_plugin_attribute_error(self): """ From dd2cc99e67bcd73cfe7bc5d79c4a1805ced3a086 Mon Sep 17 00:00:00 2001 From: Jeff Glass Date: Thu, 26 Jan 2023 09:14:01 -0600 Subject: [PATCH 07/28] Add test for output attribute on repl --- pyscriptjs/tests/integration/test_py_repl.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/pyscriptjs/tests/integration/test_py_repl.py b/pyscriptjs/tests/integration/test_py_repl.py index 5d42fbd9f80..89e0c4b56aa 100644 --- a/pyscriptjs/tests/integration/test_py_repl.py +++ b/pyscriptjs/tests/integration/test_py_repl.py @@ -315,3 +315,23 @@ def test_multiple_repls_mixed_display_order(self): assert self.page.inner_text("#py-internal-1-1-repl-output") == "second children" assert self.page.inner_text("#py-internal-0-1-repl-output") == "first children" + def test_repl_output_attribute(self): + # Test that output attribute sends stdout and display() + # To the element with the given ID + self.pyscript_run( + """ +
+ + print('print from py-repl') + display('display from py-repl') + + + """ + ) + + py_repl = self.page.locator("py-repl") + py_repl.locator("button").click() + + target = self.page.locator("#repl-target") + assert "print from py-repl" in target.text_content() + assert "display from py-repl" in target.text_content() From 0a84e07f1c02b240e71a72cd578d1aa953097cf1 Mon Sep 17 00:00:00 2001 From: Jeff Glass Date: Thu, 26 Jan 2023 09:52:00 -0600 Subject: [PATCH 08/28] Add async test --- pyscriptjs/src/plugin.ts | 4 +- pyscriptjs/src/plugins/stdiodirector.ts | 2 +- pyscriptjs/tests/integration/test_py_repl.py | 42 ++++++++++++++++++++ 3 files changed, 45 insertions(+), 3 deletions(-) diff --git a/pyscriptjs/src/plugin.ts b/pyscriptjs/src/plugin.ts index 56c96f38b9c..a19f2ac2582 100644 --- a/pyscriptjs/src/plugin.ts +++ b/pyscriptjs/src/plugin.ts @@ -84,7 +84,7 @@ export class Plugin { /** The source of the tag has been fetched and its output-element determined; * we're about to evaluate the source using the provided runtime * - * @param options.runtime The Runtime object that will be used to evaluated the Python source code + * @param options.interpreter The Runtime object that will be used to evaluated the Python source code * @param options.src {string} The Python source code to be evaluated * @param options.outEl The element that the result of the REPL evaluation will be output to. * @param options.pyReplTag The HTML tag the originated the evaluation @@ -93,7 +93,7 @@ export class Plugin { /** * - * @param options.runtime The Runtime object that will be used to evaluated the Python source code + * @param options.interpreter The Runtime object that will be used to evaluated the Python source code * @param options.src {string} The Python source code to be evaluated * @param options.outEl The element that the result of the REPL evaluation will be output to. * @param options.pyReplTag The HTML tag the originated the evaluation diff --git a/pyscriptjs/src/plugins/stdiodirector.ts b/pyscriptjs/src/plugins/stdiodirector.ts index 6c93963b9f4..5924beda411 100644 --- a/pyscriptjs/src/plugins/stdiodirector.ts +++ b/pyscriptjs/src/plugins/stdiodirector.ts @@ -87,7 +87,7 @@ export class StdioDirector extends Plugin { } - afterPyReplExec(options: {runtime: any, src: any, outEl: any, pyReplTag: any, result: any}): void { + afterPyReplExec(options: {interpreter: any, src: any, outEl: any, pyReplTag: any, result: any}): void { if (options.pyReplTag.stdout_manager != null){ this._stdioMultiplexer.removeListener(options.pyReplTag.stdout_manager) options.pyReplTag.stdout_manager = null diff --git a/pyscriptjs/tests/integration/test_py_repl.py b/pyscriptjs/tests/integration/test_py_repl.py index 89e0c4b56aa..8ffa45337bb 100644 --- a/pyscriptjs/tests/integration/test_py_repl.py +++ b/pyscriptjs/tests/integration/test_py_repl.py @@ -335,3 +335,45 @@ def test_repl_output_attribute(self): target = self.page.locator("#repl-target") assert "print from py-repl" in target.text_content() assert "display from py-repl" in target.text_content() + + self.assert_no_banners() + + def test_repl_output_display_async(self): + # py-repls running async code are not expected to + # send stdout element + self.pyscript_run( + """ +
+ + import asyncio + import js + + async def print_it(): + await asyncio.sleep(1) + print('print from py-repl') + + + async def display_it(): + display('display from py-repl') + await asyncio.sleep(2) + + async def done(): + await asyncio.sleep(3) + js.console.log("DONE") + + + + asyncio.ensure_future(print_it()); + asyncio.ensure_future(display_it()); + asyncio.ensure_future(done()); + + """ + ) + + py_repl = self.page.locator("py-repl") + py_repl.locator("button").click() + + self.wait_for_console("DONE") + + assert self.page.locator("#repl-target").text_content() == "" + self.assert_no_banners() From 859cb4accc2d72b9ef1fc93082a941d83b502ec6 Mon Sep 17 00:00:00 2001 From: Jeff Glass Date: Thu, 26 Jan 2023 09:58:09 -0600 Subject: [PATCH 09/28] Stub remaining integration tests --- pyscriptjs/tests/integration/test_py_repl.py | 39 +++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/pyscriptjs/tests/integration/test_py_repl.py b/pyscriptjs/tests/integration/test_py_repl.py index 8ffa45337bb..5b3083cd9e0 100644 --- a/pyscriptjs/tests/integration/test_py_repl.py +++ b/pyscriptjs/tests/integration/test_py_repl.py @@ -1,4 +1,5 @@ import platform +import pytest from .support import PyScriptTest, wait_for_render @@ -340,7 +341,7 @@ def test_repl_output_attribute(self): def test_repl_output_display_async(self): # py-repls running async code are not expected to - # send stdout element + # send display to element element self.pyscript_run( """
@@ -377,3 +378,39 @@ async def done(): assert self.page.locator("#repl-target").text_content() == "" self.assert_no_banners() + + @pytest.mark.xfail(reason="Test not yet written") + def test_repl_stdio_dynamic_tags(self): + # Test that creating py-repl tags via Python still leaves + # stdio targets working + assert False + + @pytest.mark.xfail(reason="Test not yet written") + def test_repl_output_id_errors(self): + # Test that using an ID not present on the page as the Output + # Attribute creates exactly 1 warning banner per missing id + assert False + + @pytest.mark.xfail(reason="Test not yet written") + def test_repl_stderr_id_errors(self): + # Test that using an ID not present on the page as the Output + # Attribute creates exactly 1 warning banner per missing id + assert False + + @pytest.mark.xfail(reason="Test not yet written") + def test_repl_output_stderr(self): + # Test that stderr works, and routes to the same location as stdout + # Also, repls with the stderr attribute route to an additional location + assert False + + @pytest.mark.xfail(reason="Test not yet written") + def test_repl_output_attribute_change(self): + # If the user changes the 'output' attribute of a tag mid-execution, + # Output should no longer go to the selected div and a warning should appear + assert False + + @pytest.mark.xfail(reason="Test not yet written") + def test_repl_output_element_id_change(self): + # If the user changes the ID of the targeted DOM element mid-execution, + # Output should no longer go to the selected element and a warning should appear + assert False From 91c25e013843898a6053efad8f2e80d6e39267b5 Mon Sep 17 00:00:00 2001 From: Jeff Glass Date: Fri, 27 Jan 2023 09:25:33 -0600 Subject: [PATCH 10/28] Remove dedent --- pyscriptjs/tests/integration/test_plugins.py | 36 +++++++++----------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/pyscriptjs/tests/integration/test_plugins.py b/pyscriptjs/tests/integration/test_plugins.py index 39821106d13..675759f016a 100644 --- a/pyscriptjs/tests/integration/test_plugins.py +++ b/pyscriptjs/tests/integration/test_plugins.py @@ -1,5 +1,3 @@ -from textwrap import dedent - from .support import PyScriptTest # Source code of a simple plugin that creates a Custom Element for testing purposes @@ -237,30 +235,28 @@ def test_pyscript_exec_hooks(self): # Source of script that defines a plugin with only beforePyScriptExec and # afterPyScriptExec methods - PYREPL_HOOKS_PLUGIN_CODE = dedent( - """ - from pyscript import Plugin - from js import console + PYREPL_HOOKS_PLUGIN_CODE = """ + from pyscript import Plugin + from js import console - console.warn("This is in pyrepl hooks file") + console.warn("This is in pyrepl hooks file") - class PyReplTestLogger(Plugin): + class PyReplTestLogger(Plugin): - def beforePyReplExec(self, interpreter, outEl, src, pyReplTag): - console.log(f'beforePyReplExec called') - console.log(f'before_src:{src}') - console.log(f'before_id:{pyReplTag.id}') + def beforePyReplExec(self, interpreter, outEl, src, pyReplTag): + console.log(f'beforePyReplExec called') + console.log(f'before_src:{src}') + console.log(f'before_id:{pyReplTag.id}') - def afterPyReplExec(self, interpreter, src, outEl, pyReplTag, result): - console.log(f'afterPyReplExec called') - console.log(f'after_src:{src}') - console.log(f'after_id:{pyReplTag.id}') - console.log(f'result:{result}') + def afterPyReplExec(self, interpreter, src, outEl, pyReplTag, result): + console.log(f'afterPyReplExec called') + console.log(f'after_src:{src}') + console.log(f'after_id:{pyReplTag.id}') + console.log(f'result:{result}') - plugin = PyReplTestLogger() - """ - ) + plugin = PyReplTestLogger() + """ @prepare_test( "pyrepl_test_logger", From 813bb48d8c51bf5a0d468d1cca8cc4217d0e66b8 Mon Sep 17 00:00:00 2001 From: Jeff Glass Date: Fri, 27 Jan 2023 09:25:53 -0600 Subject: [PATCH 11/28] Add dynamic-tag test --- pyscriptjs/tests/integration/test_py_repl.py | 29 +++++++++++++++++--- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/pyscriptjs/tests/integration/test_py_repl.py b/pyscriptjs/tests/integration/test_py_repl.py index 5b3083cd9e0..017511360ed 100644 --- a/pyscriptjs/tests/integration/test_py_repl.py +++ b/pyscriptjs/tests/integration/test_py_repl.py @@ -379,11 +379,32 @@ async def done(): assert self.page.locator("#repl-target").text_content() == "" self.assert_no_banners() - @pytest.mark.xfail(reason="Test not yet written") def test_repl_stdio_dynamic_tags(self): - # Test that creating py-repl tags via Python still leaves - # stdio targets working - assert False + self.pyscript_run( + """ +
+
+ + import js + + print("first.") + + # Using string, since no clean way to write to the + # code contents of the CodeMirror in a PyRepl + newTag = 'print("second.")' + js.document.body.innerHTML += newTag + + """ + ) + + py_repl = self.page.locator("py-repl") + py_repl.locator("button").click() + + assert self.page.locator("#first").text_content() == "first." + + second_repl = self.page.locator("py-repl#second-repl") + second_repl.locator("button").click() + assert self.page.locator("#second").text_content() == "second." @pytest.mark.xfail(reason="Test not yet written") def test_repl_output_id_errors(self): From cc4bbad13f6eba2dfba9034dfc687319146b99a0 Mon Sep 17 00:00:00 2001 From: Jeff Glass Date: Fri, 27 Jan 2023 12:18:57 -0600 Subject: [PATCH 12/28] More tests --- pyscriptjs/src/components/pyrepl.ts | 5 +- pyscriptjs/tests/integration/test_plugins.py | 50 +++--- pyscriptjs/tests/integration/test_py_repl.py | 166 ++++++++++++++++--- 3 files changed, 174 insertions(+), 47 deletions(-) diff --git a/pyscriptjs/src/components/pyrepl.ts b/pyscriptjs/src/components/pyrepl.ts index 8f47c63a79f..848dd494c92 100644 --- a/pyscriptjs/src/components/pyrepl.ts +++ b/pyscriptjs/src/components/pyrepl.ts @@ -6,7 +6,7 @@ import { keymap, Command } from '@codemirror/view'; import { defaultKeymap } from '@codemirror/commands'; import { oneDarkTheme } from '@codemirror/theme-one-dark'; -import { getAttribute, ensureUniqueId, htmlDecode } from '../utils'; +import { getAttribute, ensureUniqueId, htmlDecode, createSingularWarning } from '../utils'; import { pyExec, pyDisplay } from '../pyexec'; import { getLogger } from '../logger'; import { InterpreterClient } from '../interpreter_client'; @@ -188,8 +188,7 @@ export function make_PyRepl(interpreter: InterpreterClient, app: PyScriptApp) { if (outputID !== null) { const el = document.getElementById(outputID); if (el === null) { - const err = `py-repl ERROR: cannot find the output element #${outputID} in the DOM`; - this.outDiv.innerText = err; + createSingularWarning(`output = "${this.getAttribute("output")}" does not match the id of any element on the page.`) return undefined; } return el; diff --git a/pyscriptjs/tests/integration/test_plugins.py b/pyscriptjs/tests/integration/test_plugins.py index 675759f016a..a1d32bac593 100644 --- a/pyscriptjs/tests/integration/test_plugins.py +++ b/pyscriptjs/tests/integration/test_plugins.py @@ -75,6 +75,31 @@ def afterPyScriptExec(self, interpreter, src, pyScriptTag, result): plugin = ExecTestLogger() """ +# Source of script that defines a plugin with only beforePyScriptExec and +# afterPyScriptExec methods +PYREPL_HOOKS_PLUGIN_CODE = """ +from pyscript import Plugin +from js import console + +console.warn("This is in pyrepl hooks file") + +class PyReplTestLogger(Plugin): + + def beforePyReplExec(self, interpreter, outEl, src, pyReplTag): + console.log(f'beforePyReplExec called') + console.log(f'before_src:{src}') + console.log(f'before_id:{pyReplTag.id}') + + def afterPyReplExec(self, interpreter, src, outEl, pyReplTag, result): + console.log(f'afterPyReplExec called') + console.log(f'after_src:{src}') + console.log(f'after_id:{pyReplTag.id}') + console.log(f'result:{result}') + + +plugin = PyReplTestLogger() +""" + # Source of a script that doesn't call define a `plugin` attribute NO_PLUGIN_CODE = """ from pyscript import Plugin @@ -233,31 +258,6 @@ def test_pyscript_exec_hooks(self): assert "after_id:pyid" in log_lines assert "result:2" in log_lines - # Source of script that defines a plugin with only beforePyScriptExec and - # afterPyScriptExec methods - PYREPL_HOOKS_PLUGIN_CODE = """ - from pyscript import Plugin - from js import console - - console.warn("This is in pyrepl hooks file") - - class PyReplTestLogger(Plugin): - - def beforePyReplExec(self, interpreter, outEl, src, pyReplTag): - console.log(f'beforePyReplExec called') - console.log(f'before_src:{src}') - console.log(f'before_id:{pyReplTag.id}') - - def afterPyReplExec(self, interpreter, src, outEl, pyReplTag, result): - console.log(f'afterPyReplExec called') - console.log(f'after_src:{src}') - console.log(f'after_id:{pyReplTag.id}') - console.log(f'result:{result}') - - - plugin = PyReplTestLogger() - """ - @prepare_test( "pyrepl_test_logger", PYREPL_HOOKS_PLUGIN_CODE, diff --git a/pyscriptjs/tests/integration/test_py_repl.py b/pyscriptjs/tests/integration/test_py_repl.py index 017511360ed..283d2736769 100644 --- a/pyscriptjs/tests/integration/test_py_repl.py +++ b/pyscriptjs/tests/integration/test_py_repl.py @@ -254,11 +254,15 @@ def test_output_attribute_does_not_exist(self): ) py_repl = self.page.locator("py-repl") py_repl.locator("button").click() - # - out_div = py_repl.locator("div.py-repl-output") - msg = "py-repl ERROR: cannot find the output element #I-dont-exist in the DOM" - assert out_div.all_inner_texts()[0] == msg - assert "I will not be executed" not in self.console.log.text + + banner = self.page.query_selector_all(".py-warning") + assert len(banner) == 1 + + banner_content = banner[0].inner_text() + expected = ( + 'output = "I-dont-exist" does not match the id of any element on the page.' + ) + assert banner_content == expected def test_auto_generate(self): self.pyscript_run( @@ -406,32 +410,156 @@ def test_repl_stdio_dynamic_tags(self): second_repl.locator("button").click() assert self.page.locator("#second").text_content() == "second." - @pytest.mark.xfail(reason="Test not yet written") def test_repl_output_id_errors(self): - # Test that using an ID not present on the page as the Output - # Attribute creates exactly 1 warning banner per missing id - assert False + self.pyscript_run( + """ + + print("bad.") + print("bad.") + + + + print("bad.") + + """ + ) + py_repls = self.page.query_selector_all("py-repl") + for repl in py_repls: + repl.query_selector_all("button")[0].click() + + banner = self.page.query_selector_all(".py-warning") + assert len(banner) == 1 + + banner_content = banner[0].inner_text() + expected = ( + 'output = "not-on-page" does not match the id of any element on the page.' + ) + + assert banner_content == expected - @pytest.mark.xfail(reason="Test not yet written") def test_repl_stderr_id_errors(self): - # Test that using an ID not present on the page as the Output - # Attribute creates exactly 1 warning banner per missing id - assert False + self.pyscript_run( + """ + + import sys + print("bad.", file=sys.stderr) + print("bad.", file=sys.stderr) + + + + print("bad.", file=sys.stderr) + + """ + ) + py_repls = self.page.query_selector_all("py-repl") + for repl in py_repls: + repl.query_selector_all("button")[0].click() + + banner = self.page.query_selector_all(".py-warning") + assert len(banner) == 1 + + banner_content = banner[0].inner_text() + expected = ( + 'stderr = "not-on-page" does not match the id of any element on the page.' + ) + + assert banner_content == expected - @pytest.mark.xfail(reason="Test not yet written") def test_repl_output_stderr(self): # Test that stderr works, and routes to the same location as stdout # Also, repls with the stderr attribute route to an additional location - assert False + self.pyscript_run( + """ +
+
+ + import sys + print("one.", file=sys.stderr) + print("two.") + + """ + ) + + py_repl = self.page.locator("py-repl") + py_repl.locator("button").click() + + assert self.page.locator("#stdout-div").text_content() == "one.two." + assert self.page.locator("#stderr-div").text_content() == "one." + self.assert_no_banners() - @pytest.mark.xfail(reason="Test not yet written") def test_repl_output_attribute_change(self): # If the user changes the 'output' attribute of a tag mid-execution, # Output should no longer go to the selected div and a warning should appear - assert False + self.pyscript_run( + """ +
+
+ + + print("one.") + + # Change the 'output' attribute of this tag + import js + this_tag = js.document.getElementById("repl-tag") + + this_tag.setAttribute("output", "second") + print("two.") + + this_tag.setAttribute("output", "third") + print("three.") + + """ + ) + + py_repl = self.page.locator("py-repl") + py_repl.locator("button").click() + + assert self.page.locator("#first").text_content() == "one." + assert self.page.locator("#second").text_content() == "two." + + expected_alert_banner_msg = ( + 'output = "third" does not match the id of any element on the page.' + ) + + alert_banner = self.page.locator(".alert-banner") + assert expected_alert_banner_msg in alert_banner.inner_text() - @pytest.mark.xfail(reason="Test not yet written") def test_repl_output_element_id_change(self): # If the user changes the ID of the targeted DOM element mid-execution, # Output should no longer go to the selected element and a warning should appear - assert False + self.pyscript_run( + """ +
+
+ + + print("one.") + + # Change the ID of the targeted DIV to something else + import js + target_tag = js.document.getElementById("first") + + # should fail and show banner + target_tag.setAttribute("id", "second") + print("two.") + + # But changing both the 'output' attribute and the id of the target + # should work + target_tag.setAttribute("id", "third") + js.document.getElementById("pyscript-tag").setAttribute("output", "third") + print("three.") + + """ + ) + + py_repl = self.page.locator("py-repl") + py_repl.locator("button").click() + + # Note the ID of the div has changed by the time of this assert + assert self.page.locator("#third").text_content() == "one.three." + + expected_alert_banner_msg = ( + 'output = "first" does not match the id of any element on the page.' + ) + alert_banner = self.page.locator(".alert-banner") + assert expected_alert_banner_msg in alert_banner.inner_text() From 92f720a98f4c722dbca6ab33ba25075041beac8c Mon Sep 17 00:00:00 2001 From: Jeff Glass Date: Sun, 29 Jan 2023 10:35:19 -0600 Subject: [PATCH 13/28] 'Output=' doesn't affect display() --- pyscriptjs/src/components/pyrepl.ts | 24 +---------------- pyscriptjs/tests/integration/test_py_repl.py | 28 ++++---------------- 2 files changed, 6 insertions(+), 46 deletions(-) diff --git a/pyscriptjs/src/components/pyrepl.ts b/pyscriptjs/src/components/pyrepl.ts index 848dd494c92..6f4d1d3931a 100644 --- a/pyscriptjs/src/components/pyrepl.ts +++ b/pyscriptjs/src/components/pyrepl.ts @@ -156,15 +156,7 @@ export function make_PyRepl(interpreter: InterpreterClient, app: PyScriptApp) { */ async execute(): Promise { const pySrc = this.getPySrc(); - - // determine the output element - const outEl = this.getOutputElement(); - if (outEl === undefined) { - // this happens if we specified output="..." but we couldn't - // find the ID. We already displayed an error message inside - // getOutputElement, stop the execution. - return; - } + const outEl = this.outDiv // execute the python code app.plugins.beforePyReplExec({interpreter: interpreter, src: pySrc, outEl: outEl, pyReplTag: this}); @@ -183,20 +175,6 @@ export function make_PyRepl(interpreter: InterpreterClient, app: PyScriptApp) { return this.editor.state.doc.toString(); } - getOutputElement(): HTMLElement { - const outputID = getAttribute(this, 'output'); - if (outputID !== null) { - const el = document.getElementById(outputID); - if (el === null) { - createSingularWarning(`output = "${this.getAttribute("output")}" does not match the id of any element on the page.`) - return undefined; - } - return el; - } else { - return this.outDiv; - } - } - // XXX the autogenerate logic is very messy. We should redo it, and it // should be the default. autogenerateMaybe(): void { diff --git a/pyscriptjs/tests/integration/test_py_repl.py b/pyscriptjs/tests/integration/test_py_repl.py index 283d2736769..7a7eb6e23db 100644 --- a/pyscriptjs/tests/integration/test_py_repl.py +++ b/pyscriptjs/tests/integration/test_py_repl.py @@ -220,26 +220,6 @@ def test_hide_previous_error_after_successful_run(self): self.page.keyboard.press("Shift+Enter") assert out_div.all_inner_texts()[0] == "hello" - def test_output_attribute(self): - self.pyscript_run( - """ - - display('hello world') - -
-
- """ - ) - py_repl = self.page.locator("py-repl") - py_repl.locator("button").click() - # - # check that we did NOT write to py-repl-output - out_div = py_repl.locator("div.py-repl-output") - assert out_div.inner_text() == "" - # check that we are using mydiv instead - mydiv = self.page.locator("#mydiv") - assert mydiv.all_inner_texts()[0] == "hello world" - def test_output_attribute_does_not_exist(self): """ If we try to use an attribute which doesn't exist, we display an error @@ -321,8 +301,8 @@ def test_multiple_repls_mixed_display_order(self): assert self.page.inner_text("#py-internal-1-1-repl-output") == "second children" assert self.page.inner_text("#py-internal-0-1-repl-output") == "first children" def test_repl_output_attribute(self): - # Test that output attribute sends stdout and display() - # To the element with the given ID + # Test that output attribute sends stdout to the element + # with the given ID, but not display() self.pyscript_run( """
@@ -339,7 +319,9 @@ def test_repl_output_attribute(self): target = self.page.locator("#repl-target") assert "print from py-repl" in target.text_content() - assert "display from py-repl" in target.text_content() + + out_div = py_repl.locator("div.py-repl-output") + assert out_div.all_inner_texts()[0] == "display from py-repl" self.assert_no_banners() From 48043e50ceaabce112c8c74cd81845770c0d5dde Mon Sep 17 00:00:00 2001 From: Jeff Glass Date: Sun, 29 Jan 2023 11:24:53 -0600 Subject: [PATCH 14/28] Adjust behavior of repl results --- pyscriptjs/src/components/pyrepl.ts | 5 --- pyscriptjs/src/plugins/stdiodirector.ts | 32 +++++++++++++++++--- pyscriptjs/tests/integration/test_py_repl.py | 21 +++++++++++++ 3 files changed, 49 insertions(+), 9 deletions(-) diff --git a/pyscriptjs/src/components/pyrepl.ts b/pyscriptjs/src/components/pyrepl.ts index 6f4d1d3931a..70bae8d171e 100644 --- a/pyscriptjs/src/components/pyrepl.ts +++ b/pyscriptjs/src/components/pyrepl.ts @@ -163,11 +163,6 @@ export function make_PyRepl(interpreter: InterpreterClient, app: PyScriptApp) { const pyResult = (await pyExec(interpreter, pySrc, outEl)).result; app.plugins.afterPyReplExec({interpreter: interpreter, src: pySrc, outEl: outEl, pyReplTag: this, result: pyResult}); - // display the value of the last evaluated expression (REPL-style) - if (pyResult !== undefined) { - pyDisplay(interpreter, pyResult, { target: outEl.id }); - } - this.autogenerateMaybe(); } diff --git a/pyscriptjs/src/plugins/stdiodirector.ts b/pyscriptjs/src/plugins/stdiodirector.ts index 5924beda411..e26e6aa1e04 100644 --- a/pyscriptjs/src/plugins/stdiodirector.ts +++ b/pyscriptjs/src/plugins/stdiodirector.ts @@ -1,7 +1,9 @@ -import { Plugin } from '../plugin'; -import { TargetedStdio, StdioMultiplexer } from '../stdio'; +import { Plugin } from "../plugin"; +import { TargetedStdio, StdioMultiplexer } from "../stdio"; +import type { InterpreterClient } from "../interpreter_client"; +import { createSingularWarning } from '../utils' import { make_PyScript } from '../components/pyscript'; -import { InterpreterClient } from '../interpreter_client'; +import { pyDisplay } from '../pyexec' type PyScriptTag = InstanceType>; @@ -59,7 +61,7 @@ export class StdioDirector extends Plugin { } } - beforePyReplExec(options: {interpreter: Interpreter, src: string, outEl: HTMLElement, pyReplTag: any}): void { + beforePyReplExec(options: {interpreter: InterpreterClient, src: string, outEl: HTMLElement, pyReplTag: any}): void { //Handle 'output-mode' attribute (removed in PR #881/f9194cc8, restored here) if (options.pyReplTag.getAttribute('output-mode') != 'append'){ options.outEl.innerHTML = '' @@ -88,6 +90,28 @@ export class StdioDirector extends Plugin { } afterPyReplExec(options: {interpreter: any, src: any, outEl: any, pyReplTag: any, result: any}): void { + // display the value of the last evaluated expression (REPL-style) + if (options.result !== undefined) { + + + const outputId = options.pyReplTag.getAttribute("output") + if (outputId) { + // 'output' attribute also used as location to send + // result of REPL + if (document.getElementById(outputId)){ + pyDisplay(options.interpreter, options.result, { target: outputId }); + } + else { //no matching element on page + createSingularWarning(`output = "${outputId}" does not match the id of any element on the page.`) + } + + } + else { + // 'otuput atribuite not provided + pyDisplay(options.interpreter, options.result, { target: options.outEl.id }); + } + } + if (options.pyReplTag.stdout_manager != null){ this._stdioMultiplexer.removeListener(options.pyReplTag.stdout_manager) options.pyReplTag.stdout_manager = null diff --git a/pyscriptjs/tests/integration/test_py_repl.py b/pyscriptjs/tests/integration/test_py_repl.py index 7a7eb6e23db..3f01f6cc24b 100644 --- a/pyscriptjs/tests/integration/test_py_repl.py +++ b/pyscriptjs/tests/integration/test_py_repl.py @@ -110,6 +110,27 @@ def test_show_last_expression(self): out_div = py_repl.locator("div.py-repl-output") assert out_div.all_inner_texts()[0] == "42" + def test_show_last_expression_with_output(self): + """ + Test that we display() the value of the last expression, as you would + expect by a REPL + """ + self.pyscript_run( + """ +
+ + 42 + + """ + ) + py_repl = self.page.locator("py-repl") + py_repl.locator("button").click() + out_div = py_repl.locator("div.py-repl-output") + assert out_div.all_inner_texts()[0] == "" + + out_div = self.page.locator("#repl-target") + assert out_div.all_inner_texts()[0] == "42" + def test_run_clears_previous_output(self): """ Check that we clear the previous output of the cell before executing it From ae0bdcf3282f55b642f8490539db3cba5316639a Mon Sep 17 00:00:00 2001 From: Jeff Glass Date: Sun, 29 Jan 2023 11:25:02 -0600 Subject: [PATCH 15/28] Documentation --- docs/reference/elements/py-repl.md | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/docs/reference/elements/py-repl.md b/docs/reference/elements/py-repl.md index 3005db20634..14d87572a58 100644 --- a/docs/reference/elements/py-repl.md +++ b/docs/reference/elements/py-repl.md @@ -7,24 +7,42 @@ The `` element provides a REPL(Read Eval Print Loop) to evaluate multi- | attribute | type | default | description | |-------------------|---------|---------|---------------------------------------| | **auto-generate** | boolean | | Auto-generates REPL after evaluation | -| **output** | string | | The element to write output into | +| **output-mode** | string | "" | Determines whether the output element is cleared prior to writing output | +| **output** | string | | The id of the element to write `stdout` and `stderr` to | +| **stderr** | string | | The id of the element to write `stderr` to | -### Examples -#### `` element set to auto-generate +### `auto-generate` +If a \ tag has the `auto-generate` attribute, upon execution, another \ tag will be created and added to the DOM as a sibling of the current tag. + +### `output-mode` +By default, the element which displays the output from a REPL is cleared (`innerHTML` set to "") prior to each new execution of the REPL. If `output-mode` == "append", that element is not cleared, and the output is appended instead. + +### `output` +The ID of an element in the DOM that `stdout` (e.g. `print()`), `stderr`, and the results of executing the repl are written to. Defaults to an automatically-generated \ as the next sibling of the REPL itself. + +### `stderr` +The ID of an element in the DOM that `stderr` will be written to. Defaults to None, though writes to `stderr` will still appear in the location specified by `output`. + +## Examples + +### `` element set to auto-generate ```html ``` -#### `` element with output +### `` element with output + +The following will write "Hello! World!" to the div with id `replOutput`. ```html
- hello = "Hello world!" + print("Hello!") + hello = "World!" hello ``` -Note that if we `print` any element in the repl, the output will be printed in the [`py-terminal`](../plugins/py-terminal.md) if is enabled. +Note that if we `print` any element in the repl (or otherwise write to `sys.stdout`), the output will be printed in the [`py-terminal`](../plugins/py-terminal.md) if is enabled. From 47896abe8f1e5bc6774a9a7d0deed89b1eda3ab2 Mon Sep 17 00:00:00 2001 From: Jeff Glass Date: Sun, 29 Jan 2023 11:25:08 -0600 Subject: [PATCH 16/28] Changelog --- docs/changelog.md | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/docs/changelog.md b/docs/changelog.md index e7fa75acf30..406c026994a 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -21,34 +21,44 @@ Enhancements 2023.01.1 ========= -Features + +### Features -------- +#### <py-script> tags - Restored the `output` attribute of <py-script> tags to route `sys.stdout` to a DOM element with the given ID. ([#1063](https://github.com/pyscript/pyscript/pull/1063)) - Added a `stderr` attribute of <py-script> tags to route `sys.stderr` to a DOM element with the given ID. ([#1063](https://github.com/pyscript/pyscript/pull/1063)) -Bug fixes ---------- +#### <py-repl tags> +- The `output` attribute of $lt;py-repl$gt; tags now specifies the id of the DOM element that `sys.stdout`, `sys.stderr`, and the results of a REPL execution are written to. It no longer affects the location of calls to `display()` +- Added a `stderr` attribute of <py-repl> tags to route `sys.stderr` to a DOM element with the given ID. ([#1106](https://github.com/pyscript/pyscript/pull/1106)) +- Resored the `output-mode` attribute of <py-repl> tags. If `output-mode` == 'append', the DOM element where output is printed is _not_ cleared before writing new results. + +#### Plugins +- Plugins may now implement the `beforePyReplExec()` and `afterPyReplExec()` hooks, which are called immediately before and after code in a <py-repl> tag is executed. ([#1106](https://github.com/pyscript/pyscript/pull/1106)) + +### Bug fixes + - Fixed an issue where `pyscript` would not be available when using the minified version of PyScript. ([#1054](https://github.com/pyscript/pyscript/pull/1054)) - Fixed missing closing tag when rendering an image with `display`. ([#1058](https://github.com/pyscript/pyscript/pull/1058)) - Fixed a bug where Python plugins methods were being executed twice. ([#1064](https://github.com/pyscript/pyscript/pull/1064)) -Enhancements ------------- +### Enhancements + - When adding a `py-` attribute to an element but didn't added an `id` attribute, PyScript will now generate a random ID for the element instead of throwing an error which caused the splash screen to not shutdown. ([#1122](https://github.com/pyscript/pyscript/pull/1122)) - You can now disable the splashscreen by setting `enabled = false` in your `py-config` under the `[splashscreen]` configuration section. ([#1138](https://github.com/pyscript/pyscript/pull/1138)) -Documentation -------------- +### Documentation + - Fixed 'Direct usage of document is deprecated' warning in the getting started guide. ([#1052](https://github.com/pyscript/pyscript/pull/1052)) - Added reference documentation for the `py-splashscreen` plugin ([#1138](https://github.com/pyscript/pyscript/pull/1138)) - Adds doc for installing tests ([#1156](https://github.com/pyscript/pyscript/pull/1156)) - Adds docs for custom Pyscript attributes (`py-*`) that allow you to add event listeners to an element ([#1125](https://github.com/pyscript/pyscript/pull/1125)) -Deprecations and Removals -------------------------- +### Deprecations and Removals + - The py-config `runtimes` to specify an interpreter has been deprecated. The `interpreters` config should be used instead. ([#1082](https://github.com/pyscript/pyscript/pull/1082)) - The attributes `pys-onClick` and `pys-onKeyDown` have been deprecated, but the warning was only shown in the console. An alert banner will now be shown on the page if the attributes are used. They will be removed in the next release. ([#1084](https://github.com/pyscript/pyscript/pull/1084)) From c4381f7df84f331142a2a945a5d4ccdcaf954a44 Mon Sep 17 00:00:00 2001 From: Jeff Glass Date: Mon, 6 Feb 2023 07:45:44 -0600 Subject: [PATCH 17/28] Cleanup --- docs/reference/elements/py-repl.md | 2 +- pyscriptjs/src/plugins/stdiodirector.ts | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/reference/elements/py-repl.md b/docs/reference/elements/py-repl.md index 14d87572a58..244211e54d7 100644 --- a/docs/reference/elements/py-repl.md +++ b/docs/reference/elements/py-repl.md @@ -45,4 +45,4 @@ The following will write "Hello! World!" to the div with id `replOutput`.
``` -Note that if we `print` any element in the repl (or otherwise write to `sys.stdout`), the output will be printed in the [`py-terminal`](../plugins/py-terminal.md) if is enabled. +Note that if we `print` from the REPL (or otherwise write to `sys.stdout`), the output will be printed in the [`py-terminal`](../plugins/py-terminal.md) if is enabled. diff --git a/pyscriptjs/src/plugins/stdiodirector.ts b/pyscriptjs/src/plugins/stdiodirector.ts index e26e6aa1e04..2d7027926da 100644 --- a/pyscriptjs/src/plugins/stdiodirector.ts +++ b/pyscriptjs/src/plugins/stdiodirector.ts @@ -63,6 +63,7 @@ export class StdioDirector extends Plugin { beforePyReplExec(options: {interpreter: InterpreterClient, src: string, outEl: HTMLElement, pyReplTag: any}): void { //Handle 'output-mode' attribute (removed in PR #881/f9194cc8, restored here) + //If output-mode == 'append', don't clear target tag before writing if (options.pyReplTag.getAttribute('output-mode') != 'append'){ options.outEl.innerHTML = '' } @@ -93,18 +94,18 @@ export class StdioDirector extends Plugin { // display the value of the last evaluated expression (REPL-style) if (options.result !== undefined) { - - const outputId = options.pyReplTag.getAttribute("output") - if (outputId) { + + const outputId: string | undefined = options.pyReplTag.getAttribute("output") + if (outputId) { // 'output' attribute also used as location to send // result of REPL if (document.getElementById(outputId)){ pyDisplay(options.interpreter, options.result, { target: outputId }); - } + } else { //no matching element on page createSingularWarning(`output = "${outputId}" does not match the id of any element on the page.`) } - + } else { // 'otuput atribuite not provided From eee2fe12ed8dcd837deeb3bd414c1a3332e0c635 Mon Sep 17 00:00:00 2001 From: Jeff Glass Date: Mon, 6 Feb 2023 07:51:49 -0600 Subject: [PATCH 18/28] Merge from main --- .pre-commit-config.yaml | 60 ++++++++++++++++---- docs/changelog.md | 2 +- docs/development/developing.md | 10 +++- pyscriptjs/src/plugin.ts | 1 - pyscriptjs/src/plugins/python/py_markdown.py | 1 + pyscriptjs/tests/py-unit/conftest.py | 4 ++ 6 files changed, 64 insertions(+), 14 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c02e07a7ba9..f837a9dad4b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -46,14 +46,52 @@ repos: - id: prettier args: [--tab-width, "4"] - - repo: https://github.com/pre-commit/mirrors-eslint - rev: v8.35.0 - hooks: - - id: eslint - files: pyscriptjs/src/.*\.[jt]sx?$ # *.js, *.jsx, *.ts and *.tsx - types: [file] - additional_dependencies: - - eslint@8.25.0 - - typescript@4.8.4 - - "@typescript-eslint/eslint-plugin@5.39.0" - - "@typescript-eslint/parser@5.39.0" +- repo: https://github.com/PyCQA/flake8 + rev: 6.0.0 + hooks: + - id: flake8 # See 'setup.cfg' for args + additional_dependencies: [flake8-bugbear, flake8-comprehensions] + +- repo: https://github.com/pycqa/isort + rev: 5.12.0 + hooks: + - id: isort + name: isort (python) + args: [--profile, black] + +- repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks + rev: v2.6.0 + hooks: + - id: pretty-format-yaml + args: [--autofix, --indent, '4'] + exclude: .github/ISSUE_TEMPLATE/.*\.yml$ + +- repo: https://github.com/asottile/pyupgrade + rev: v3.3.1 + hooks: + - id: pyupgrade + args: + - --py310-plus + +- repo: https://github.com/pre-commit/mirrors-eslint + rev: v8.33.0 + hooks: + - id: eslint + files: pyscriptjs/src/.*\.[jt]sx?$ # *.js, *.jsx, *.ts and *.tsx + types: [file] + additional_dependencies: + - eslint@8.25.0 + - typescript@4.8.4 + - '@typescript-eslint/eslint-plugin@5.39.0' + - '@typescript-eslint/parser@5.39.0' + +# Commented out until mdformat-myst supports custom extensions +# See https://github.com/executablebooks/mdformat-myst/pull/9 +# - repo: https://github.com/executablebooks/mdformat +# rev: 0.7.14 # Use the ref you want to point at +# hooks: +# - id: mdformat +# additional_dependencies: +# - mdformat-gfm +# - mdformat-myst +# - mdformat-black diff --git a/docs/changelog.md b/docs/changelog.md index 406c026994a..b1a85f0563f 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -32,7 +32,7 @@ Enhancements #### <py-repl tags> - The `output` attribute of $lt;py-repl$gt; tags now specifies the id of the DOM element that `sys.stdout`, `sys.stderr`, and the results of a REPL execution are written to. It no longer affects the location of calls to `display()` - Added a `stderr` attribute of <py-repl> tags to route `sys.stderr` to a DOM element with the given ID. ([#1106](https://github.com/pyscript/pyscript/pull/1106)) -- Resored the `output-mode` attribute of <py-repl> tags. If `output-mode` == 'append', the DOM element where output is printed is _not_ cleared before writing new results. +- Resored the `output-mode` attribute of <py-repl> tags. If `output-mode` == 'append', the DOM element where output is printed is _not_ cleared before writing new results. #### Plugins - Plugins may now implement the `beforePyReplExec()` and `afterPyReplExec()` hooks, which are called immediately before and after code in a <py-repl> tag is executed. ([#1106](https://github.com/pyscript/pyscript/pull/1106)) diff --git a/docs/development/developing.md b/docs/development/developing.md index 19c624c2819..cd23982c290 100644 --- a/docs/development/developing.md +++ b/docs/development/developing.md @@ -84,7 +84,15 @@ Documentation ## Quick guide to pytest -We make heavy usage of `pytest`. Here is a quick guide and collection of +## Rebasing changes + +Sometimes you might be asked to rebase main into your branch. Please refer to this [section on git rebase from GitHub docs](https://docs.github.com/en/get-started/using-git/about-git-rebase). + +If you need help with anything, feel free to reach out and ask for help! + +## pytest quick guide + +We make a heavy usage of `pytest`. Here is a quick guide and collection of useful options: - To run all tests in the current directory and subdirectories: `pytest` diff --git a/pyscriptjs/src/plugin.ts b/pyscriptjs/src/plugin.ts index a19f2ac2582..d8ec7dc4297 100644 --- a/pyscriptjs/src/plugin.ts +++ b/pyscriptjs/src/plugin.ts @@ -8,7 +8,6 @@ import type { UserError } from './exceptions'; import { getLogger } from './logger'; import { make_PyScript } from './components/pyscript'; import { InterpreterClient } from './interpreter_client'; -import { interpreter } from './main'; const logger = getLogger('plugin'); type PyScriptTag = InstanceType>; diff --git a/pyscriptjs/src/plugins/python/py_markdown.py b/pyscriptjs/src/plugins/python/py_markdown.py index 95322459424..d371753c339 100644 --- a/pyscriptjs/src/plugins/python/py_markdown.py +++ b/pyscriptjs/src/plugins/python/py_markdown.py @@ -3,6 +3,7 @@ from js import console from markdown import markdown + from pyscript import Plugin console.warn( diff --git a/pyscriptjs/tests/py-unit/conftest.py b/pyscriptjs/tests/py-unit/conftest.py index d4eb3a78fc2..6001d3ee51c 100644 --- a/pyscriptjs/tests/py-unit/conftest.py +++ b/pyscriptjs/tests/py-unit/conftest.py @@ -6,6 +6,10 @@ pyscriptjs = Path(__file__).parents[2] +import pytest + +# current working directory +base_path = pathlib.Path().absolute() # add pyscript folder to path python_source = pyscriptjs / "src" / "python" sys.path.append(str(python_source)) From 21689443640708ee92ff8437edacdb7e320fe1fa Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 6 Feb 2023 14:52:54 +0000 Subject: [PATCH 19/28] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- pyscriptjs/src/plugins/python/py_markdown.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyscriptjs/src/plugins/python/py_markdown.py b/pyscriptjs/src/plugins/python/py_markdown.py index d371753c339..95322459424 100644 --- a/pyscriptjs/src/plugins/python/py_markdown.py +++ b/pyscriptjs/src/plugins/python/py_markdown.py @@ -3,7 +3,6 @@ from js import console from markdown import markdown - from pyscript import Plugin console.warn( From 56b777064cbe465e7a3a30d59869720960c2cc27 Mon Sep 17 00:00:00 2001 From: Jeff Glass Date: Mon, 20 Mar 2023 08:33:16 -0500 Subject: [PATCH 20/28] Address comments --- pyscriptjs/src/plugin.ts | 10 +++++----- pyscriptjs/src/plugins/stdiodirector.ts | 4 +--- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/pyscriptjs/src/plugin.ts b/pyscriptjs/src/plugin.ts index d8ec7dc4297..591eb663c82 100644 --- a/pyscriptjs/src/plugin.ts +++ b/pyscriptjs/src/plugin.ts @@ -55,7 +55,7 @@ export class Plugin { /** The source of a > tag has been fetched, and we're about * to evaluate that source using the provided interpreter. * - * @param options.interpreter The Interpreter object that will be used to evaluated the Python source code + * @param options.interpreter The Interpreter object that will be used to evaluate the Python source code * @param options.src {string} The Python source code to be evaluated * @param options.pyScriptTag The HTML tag that originated the evaluation */ @@ -66,7 +66,7 @@ export class Plugin { /** The Python in a has just been evaluated, but control * has not been ceded back to the JavaScript event loop yet * - * @param options.interpreter The Interpreter object that will be used to evaluated the Python source code + * @param options.interpreter The Interpreter object that will be used to evaluate the Python source code * @param options.src {string} The Python source code to be evaluated * @param options.pyScriptTag The HTML tag that originated the evaluation * @param options.result The returned result of evaluating the Python (if any) @@ -81,9 +81,9 @@ export class Plugin { } /** The source of the tag has been fetched and its output-element determined; - * we're about to evaluate the source using the provided runtime + * we're about to evaluate the source using the provided interpreter * - * @param options.interpreter The Runtime object that will be used to evaluated the Python source code + * @param options.interpreter The interpreter object that will be used to evaluated the Python source code * @param options.src {string} The Python source code to be evaluated * @param options.outEl The element that the result of the REPL evaluation will be output to. * @param options.pyReplTag The HTML tag the originated the evaluation @@ -92,7 +92,7 @@ export class Plugin { /** * - * @param options.interpreter The Runtime object that will be used to evaluated the Python source code + * @param options.interpreter The interpreter object that will be used to evaluated the Python source code * @param options.src {string} The Python source code to be evaluated * @param options.outEl The element that the result of the REPL evaluation will be output to. * @param options.pyReplTag The HTML tag the originated the evaluation diff --git a/pyscriptjs/src/plugins/stdiodirector.ts b/pyscriptjs/src/plugins/stdiodirector.ts index 2d7027926da..c8450fd86a4 100644 --- a/pyscriptjs/src/plugins/stdiodirector.ts +++ b/pyscriptjs/src/plugins/stdiodirector.ts @@ -91,10 +91,8 @@ export class StdioDirector extends Plugin { } afterPyReplExec(options: {interpreter: any, src: any, outEl: any, pyReplTag: any, result: any}): void { - // display the value of the last evaluated expression (REPL-style) + // display the value of the last-evaluated expression in the REPL if (options.result !== undefined) { - - const outputId: string | undefined = options.pyReplTag.getAttribute("output") if (outputId) { // 'output' attribute also used as location to send From 856d61b28498fdb1c2a6cbf8242c3d00d83821f7 Mon Sep 17 00:00:00 2001 From: Jeff Glass Date: Mon, 20 Mar 2023 09:14:43 -0500 Subject: [PATCH 21/28] Merge from main --- .pre-commit-config.yaml | 60 ++++--------------- docs/development/developing.md | 10 +--- pyscriptjs/src/components/elements.ts | 2 +- pyscriptjs/src/components/pyrepl.ts | 17 ++++-- pyscriptjs/src/plugin.ts | 14 ++++- pyscriptjs/src/plugins/stdiodirector.ts | 62 +++++++++++--------- pyscriptjs/tests/integration/test_py_repl.py | 27 +++++++++ pyscriptjs/tests/py-unit/conftest.py | 4 +- 8 files changed, 101 insertions(+), 95 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f837a9dad4b..c02e07a7ba9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -46,52 +46,14 @@ repos: - id: prettier args: [--tab-width, "4"] -- repo: https://github.com/PyCQA/flake8 - rev: 6.0.0 - hooks: - - id: flake8 # See 'setup.cfg' for args - additional_dependencies: [flake8-bugbear, flake8-comprehensions] - -- repo: https://github.com/pycqa/isort - rev: 5.12.0 - hooks: - - id: isort - name: isort (python) - args: [--profile, black] - -- repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks - rev: v2.6.0 - hooks: - - id: pretty-format-yaml - args: [--autofix, --indent, '4'] - exclude: .github/ISSUE_TEMPLATE/.*\.yml$ - -- repo: https://github.com/asottile/pyupgrade - rev: v3.3.1 - hooks: - - id: pyupgrade - args: - - --py310-plus - -- repo: https://github.com/pre-commit/mirrors-eslint - rev: v8.33.0 - hooks: - - id: eslint - files: pyscriptjs/src/.*\.[jt]sx?$ # *.js, *.jsx, *.ts and *.tsx - types: [file] - additional_dependencies: - - eslint@8.25.0 - - typescript@4.8.4 - - '@typescript-eslint/eslint-plugin@5.39.0' - - '@typescript-eslint/parser@5.39.0' - -# Commented out until mdformat-myst supports custom extensions -# See https://github.com/executablebooks/mdformat-myst/pull/9 -# - repo: https://github.com/executablebooks/mdformat -# rev: 0.7.14 # Use the ref you want to point at -# hooks: -# - id: mdformat -# additional_dependencies: -# - mdformat-gfm -# - mdformat-myst -# - mdformat-black + - repo: https://github.com/pre-commit/mirrors-eslint + rev: v8.35.0 + hooks: + - id: eslint + files: pyscriptjs/src/.*\.[jt]sx?$ # *.js, *.jsx, *.ts and *.tsx + types: [file] + additional_dependencies: + - eslint@8.25.0 + - typescript@4.8.4 + - "@typescript-eslint/eslint-plugin@5.39.0" + - "@typescript-eslint/parser@5.39.0" diff --git a/docs/development/developing.md b/docs/development/developing.md index cd23982c290..19c624c2819 100644 --- a/docs/development/developing.md +++ b/docs/development/developing.md @@ -84,15 +84,7 @@ Documentation ## Quick guide to pytest -## Rebasing changes - -Sometimes you might be asked to rebase main into your branch. Please refer to this [section on git rebase from GitHub docs](https://docs.github.com/en/get-started/using-git/about-git-rebase). - -If you need help with anything, feel free to reach out and ask for help! - -## pytest quick guide - -We make a heavy usage of `pytest`. Here is a quick guide and collection of +We make heavy usage of `pytest`. Here is a quick guide and collection of useful options: - To run all tests in the current directory and subdirectories: `pytest` diff --git a/pyscriptjs/src/components/elements.ts b/pyscriptjs/src/components/elements.ts index 57bd2e332c2..9c24841b7ce 100644 --- a/pyscriptjs/src/components/elements.ts +++ b/pyscriptjs/src/components/elements.ts @@ -1,5 +1,5 @@ -import type { PyScriptApp } from '../main'; import { InterpreterClient } from '../interpreter_client'; +import type { PyScriptApp } from '../main'; import { make_PyRepl } from './pyrepl'; import { make_PyWidget } from './pywidget'; diff --git a/pyscriptjs/src/components/pyrepl.ts b/pyscriptjs/src/components/pyrepl.ts index 70bae8d171e..ccd8c850e8d 100644 --- a/pyscriptjs/src/components/pyrepl.ts +++ b/pyscriptjs/src/components/pyrepl.ts @@ -16,7 +16,7 @@ import { Stdio } from '../stdio'; const logger = getLogger('py-repl'); const RUNBUTTON = ``; -export function make_PyRepl(interpreter: InterpreterClient, app: PyScriptApp) { +export function make_PyRepl(interpreter: InterpreterClient, app: PyScriptApp) { /* High level structure of py-repl DOM, and the corresponding JS names. this @@ -156,12 +156,19 @@ export function make_PyRepl(interpreter: InterpreterClient, app: PyScriptApp) { */ async execute(): Promise { const pySrc = this.getPySrc(); - const outEl = this.outDiv + const outEl = this.outDiv; // execute the python code - app.plugins.beforePyReplExec({interpreter: interpreter, src: pySrc, outEl: outEl, pyReplTag: this}); + app.plugins.beforePyReplExec({ interpreter: interpreter, src: pySrc, outEl: outEl, pyReplTag: this }); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const pyResult = (await pyExec(interpreter, pySrc, outEl)).result; - app.plugins.afterPyReplExec({interpreter: interpreter, src: pySrc, outEl: outEl, pyReplTag: this, result: pyResult}); + app.plugins.afterPyReplExec({ + interpreter: interpreter, + src: pySrc, + outEl: outEl, + pyReplTag: this, + result: pyResult, // eslint-disable-line @typescript-eslint/no-unsafe-assignment + }); this.autogenerateMaybe(); } @@ -182,7 +189,7 @@ export function make_PyRepl(interpreter: InterpreterClient, app: PyScriptApp) { const newPyRepl = document.createElement('py-repl'); //Attributes to be copied from old REPL to auto-generated REPL - for (const attribute of ['root', 'output-mode', 'output', 'stderr']){ + for (const attribute of ['root', 'output-mode', 'output', 'stderr']) { const attr = getAttribute(this, attribute); if (attr) { newPyRepl.setAttribute(attribute, attr); diff --git a/pyscriptjs/src/plugin.ts b/pyscriptjs/src/plugin.ts index 591eb663c82..e219ee42470 100644 --- a/pyscriptjs/src/plugin.ts +++ b/pyscriptjs/src/plugin.ts @@ -88,7 +88,9 @@ export class Plugin { * @param options.outEl The element that the result of the REPL evaluation will be output to. * @param options.pyReplTag The HTML tag the originated the evaluation */ - beforePyReplExec(options: {interpreter: InterpreterClient, src: string, outEl: HTMLElement, pyReplTag: any}){} + beforePyReplExec(options: { interpreter: InterpreterClient; src: string; outEl: HTMLElement; pyReplTag: any }) { + /* empty */ + } /** * @@ -98,7 +100,15 @@ export class Plugin { * @param options.pyReplTag The HTML tag the originated the evaluation * @param options.result The result of evaluating the Python (if any) */ - afterPyReplExec(options: {interpreter: InterpreterClient, src: string, outEl: HTMLElement, pyReplTag: HTMLElement, result: any}){} + afterPyReplExec(options: { + interpreter: InterpreterClient; + src: string; + outEl: HTMLElement; + pyReplTag: HTMLElement; + result: any; + }) { + /* empty */ + } /** Startup complete. The interpreter is initialized and ready, user * scripts have been executed: the main initialization logic ends here and diff --git a/pyscriptjs/src/plugins/stdiodirector.ts b/pyscriptjs/src/plugins/stdiodirector.ts index c8450fd86a4..7c620c6c448 100644 --- a/pyscriptjs/src/plugins/stdiodirector.ts +++ b/pyscriptjs/src/plugins/stdiodirector.ts @@ -4,6 +4,7 @@ import type { InterpreterClient } from "../interpreter_client"; import { createSingularWarning } from '../utils' import { make_PyScript } from '../components/pyscript'; import { pyDisplay } from '../pyexec' +import { make_PyRepl } from "../components/pyrepl"; type PyScriptTag = InstanceType>; @@ -61,63 +62,70 @@ export class StdioDirector extends Plugin { } } - beforePyReplExec(options: {interpreter: InterpreterClient, src: string, outEl: HTMLElement, pyReplTag: any}): void { + beforePyReplExec(options: { + interpreter: InterpreterClient; + src: string; + outEl: HTMLElement; + pyReplTag: InstanceType>; + }): void { //Handle 'output-mode' attribute (removed in PR #881/f9194cc8, restored here) //If output-mode == 'append', don't clear target tag before writing - if (options.pyReplTag.getAttribute('output-mode') != 'append'){ - options.outEl.innerHTML = '' + if (options.pyReplTag.getAttribute('output-mode') != 'append') { + options.outEl.innerHTML = ''; } // Handle 'output' attribute; defaults to writing stdout to the existing outEl // If 'output' attribute is used, the DOM element with the specified ID receives // -both- sys.stdout and sys.stderr - let output_targeted_io; - if (options.pyReplTag.hasAttribute("output")){ - output_targeted_io = new TargetedStdio(options.pyReplTag, "output", true, true); - } - else { - output_targeted_io = new TargetedStdio(options.pyReplTag.outDiv, "id", true, true); + let output_targeted_io: TargetedStdio; + if (options.pyReplTag.hasAttribute('output')) { + output_targeted_io = new TargetedStdio(options.pyReplTag, 'output', true, true); + } else { + output_targeted_io = new TargetedStdio(options.pyReplTag.outDiv, 'id', true, true); } options.pyReplTag.stdout_manager = output_targeted_io; this._stdioMultiplexer.addListener(output_targeted_io); //Handle 'stderr' attribute; - if (options.pyReplTag.hasAttribute("stderr")){ - const stderr_targeted_io = new TargetedStdio(options.pyReplTag, "stderr", false, true); + if (options.pyReplTag.hasAttribute('stderr')) { + const stderr_targeted_io = new TargetedStdio(options.pyReplTag, 'stderr', false, true); options.pyReplTag.stderr_manager = stderr_targeted_io; this._stdioMultiplexer.addListener(stderr_targeted_io); } - } - afterPyReplExec(options: {interpreter: any, src: any, outEl: any, pyReplTag: any, result: any}): void { + afterPyReplExec(options: { + interpreter: InterpreterClient; + src: string; + outEl: HTMLElement; + pyReplTag: InstanceType>; + result: any; // eslint-disable-line @typescript-eslint/no-explicit-any + }): void { // display the value of the last-evaluated expression in the REPL if (options.result !== undefined) { - const outputId: string | undefined = options.pyReplTag.getAttribute("output") + const outputId: string | undefined = options.pyReplTag.getAttribute('output'); if (outputId) { // 'output' attribute also used as location to send // result of REPL - if (document.getElementById(outputId)){ + if (document.getElementById(outputId)) { pyDisplay(options.interpreter, options.result, { target: outputId }); + } else { + //no matching element on page + createSingularWarning(`output = "${outputId}" does not match the id of any element on the page.`); } - else { //no matching element on page - createSingularWarning(`output = "${outputId}" does not match the id of any element on the page.`) - } - - } - else { + } else { // 'otuput atribuite not provided pyDisplay(options.interpreter, options.result, { target: options.outEl.id }); } } - if (options.pyReplTag.stdout_manager != null){ - this._stdioMultiplexer.removeListener(options.pyReplTag.stdout_manager) - options.pyReplTag.stdout_manager = null + if (options.pyReplTag.stdout_manager != null) { + this._stdioMultiplexer.removeListener(options.pyReplTag.stdout_manager); + options.pyReplTag.stdout_manager = null; } - if (options.pyReplTag.stderr_manager != null){ - this._stdioMultiplexer.removeListener(options.pyReplTag.stderr_manager) - options.pyReplTag.stderr_manager = null + if (options.pyReplTag.stderr_manager != null) { + this._stdioMultiplexer.removeListener(options.pyReplTag.stderr_manager); + options.pyReplTag.stderr_manager = null; } } } diff --git a/pyscriptjs/tests/integration/test_py_repl.py b/pyscriptjs/tests/integration/test_py_repl.py index 3f01f6cc24b..8ea24a15d15 100644 --- a/pyscriptjs/tests/integration/test_py_repl.py +++ b/pyscriptjs/tests/integration/test_py_repl.py @@ -566,3 +566,30 @@ def test_repl_output_element_id_change(self): ) alert_banner = self.page.locator(".alert-banner") assert expected_alert_banner_msg in alert_banner.inner_text() + + def test_multiple_repls_mixed_display_order(self): + """ + Displaying several outputs that don't obey the order in which the original + repl displays were created using the auto_generate attr + """ + self.pyscript_run( + """ + display("root first") + display("root second") + """ + ) + + second_py_repl = self.page.get_by_text("root second") + second_py_repl.click() + self.page.keyboard.press("Shift+Enter") + self.page.keyboard.type("display('second children')") + self.page.keyboard.press("Shift+Enter") + + first_py_repl = self.page.get_by_text("root first") + first_py_repl.click() + self.page.keyboard.press("Shift+Enter") + self.page.keyboard.type("display('first children')") + self.page.keyboard.press("Shift+Enter") + + assert self.page.inner_text("#py-internal-1-1-repl-output") == "second children" + assert self.page.inner_text("#py-internal-0-1-repl-output") == "first children" diff --git a/pyscriptjs/tests/py-unit/conftest.py b/pyscriptjs/tests/py-unit/conftest.py index 6001d3ee51c..c6c6e419952 100644 --- a/pyscriptjs/tests/py-unit/conftest.py +++ b/pyscriptjs/tests/py-unit/conftest.py @@ -8,8 +8,8 @@ import pytest -# current working directory -base_path = pathlib.Path().absolute() +pyscriptjs = Path(__file__).parents[2] + # add pyscript folder to path python_source = pyscriptjs / "src" / "python" sys.path.append(str(python_source)) From a8deeeaeebf1e2bcbf168a29e0e698dc865f4c1d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 22 Mar 2023 15:17:01 +0000 Subject: [PATCH 22/28] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- pyscriptjs/src/plugin.ts | 4 ++-- pyscriptjs/src/plugins/stdiodirector.ts | 12 ++++++------ pyscriptjs/tests/integration/test_py_repl.py | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pyscriptjs/src/plugin.ts b/pyscriptjs/src/plugin.ts index e219ee42470..fad343e05c9 100644 --- a/pyscriptjs/src/plugin.ts +++ b/pyscriptjs/src/plugin.ts @@ -188,13 +188,13 @@ export class PluginManager { for (const p of this._pythonPlugins) p.afterPyScriptExec?.callKwargs(options); } - beforePyReplExec(options: {interpreter: InterpreterClient, src: string, outEl: HTMLElement, pyReplTag: any}){ + beforePyReplExec(options: { interpreter: InterpreterClient; src: string; outEl: HTMLElement; pyReplTag: any }) { for (const p of this._plugins) p.beforePyReplExec(options); for (const p of this._pythonPlugins) p.beforePyReplExec?.callKwargs(options); } - afterPyReplExec(options: {interpreter: InterpreterClient, src: string, outEl, pyReplTag, result}){ + afterPyReplExec(options: { interpreter: InterpreterClient; src: string; outEl; pyReplTag; result }) { for (const p of this._plugins) p.afterPyReplExec(options); for (const p of this._pythonPlugins) p.afterPyReplExec?.callKwargs(options); diff --git a/pyscriptjs/src/plugins/stdiodirector.ts b/pyscriptjs/src/plugins/stdiodirector.ts index 7c620c6c448..9f7c463ccd7 100644 --- a/pyscriptjs/src/plugins/stdiodirector.ts +++ b/pyscriptjs/src/plugins/stdiodirector.ts @@ -1,10 +1,10 @@ -import { Plugin } from "../plugin"; -import { TargetedStdio, StdioMultiplexer } from "../stdio"; -import type { InterpreterClient } from "../interpreter_client"; -import { createSingularWarning } from '../utils' +import { Plugin } from '../plugin'; +import { TargetedStdio, StdioMultiplexer } from '../stdio'; +import type { InterpreterClient } from '../interpreter_client'; +import { createSingularWarning } from '../utils'; import { make_PyScript } from '../components/pyscript'; -import { pyDisplay } from '../pyexec' -import { make_PyRepl } from "../components/pyrepl"; +import { pyDisplay } from '../pyexec'; +import { make_PyRepl } from '../components/pyrepl'; type PyScriptTag = InstanceType>; diff --git a/pyscriptjs/tests/integration/test_py_repl.py b/pyscriptjs/tests/integration/test_py_repl.py index 8ea24a15d15..bf8ce5ae494 100644 --- a/pyscriptjs/tests/integration/test_py_repl.py +++ b/pyscriptjs/tests/integration/test_py_repl.py @@ -1,5 +1,4 @@ import platform -import pytest from .support import PyScriptTest, wait_for_render @@ -321,6 +320,7 @@ def test_multiple_repls_mixed_display_order(self): assert self.page.inner_text("#py-internal-1-1-repl-output") == "second children" assert self.page.inner_text("#py-internal-0-1-repl-output") == "first children" + def test_repl_output_attribute(self): # Test that output attribute sends stdout to the element # with the given ID, but not display() From 2e904a81b2c2dc334087fd47543e717706c5e160 Mon Sep 17 00:00:00 2001 From: Jeff Glass Date: Wed, 22 Mar 2023 10:35:08 -0500 Subject: [PATCH 23/28] Remove unused imports --- pyscriptjs/src/components/pyrepl.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyscriptjs/src/components/pyrepl.ts b/pyscriptjs/src/components/pyrepl.ts index ccd8c850e8d..e560fade7ae 100644 --- a/pyscriptjs/src/components/pyrepl.ts +++ b/pyscriptjs/src/components/pyrepl.ts @@ -6,8 +6,8 @@ import { keymap, Command } from '@codemirror/view'; import { defaultKeymap } from '@codemirror/commands'; import { oneDarkTheme } from '@codemirror/theme-one-dark'; -import { getAttribute, ensureUniqueId, htmlDecode, createSingularWarning } from '../utils'; -import { pyExec, pyDisplay } from '../pyexec'; +import { getAttribute, ensureUniqueId, htmlDecode } from '../utils'; +import { pyExec } from '../pyexec'; import { getLogger } from '../logger'; import { InterpreterClient } from '../interpreter_client'; import type { PyScriptApp } from '../main'; From 982f47743ad1e9c457a7077f24a50b7651640d7f Mon Sep 17 00:00:00 2001 From: mariana Date: Wed, 22 Mar 2023 16:39:41 +0100 Subject: [PATCH 24/28] Formats changelog --- docs/changelog.md | 35 +++++++++++++++-------------------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/docs/changelog.md b/docs/changelog.md index b1a85f0563f..96675b81eb1 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -7,6 +7,12 @@ Features -------- - Added a `docked` field and attribute for the `` custom element, enabled by default when the terminal is in `auto` mode, and able to dock the terminal at the bottom of the page with auto scroll on new code execution. +- Restored the `output` attribute of `py-script` tags to route `sys.stdout` to a DOM element with the given ID. ([#1063](https://github.com/pyscript/pyscript/pull/1063)) +- Added a `stderr` attribute of `py-script` tags to route `sys.stderr` to a DOM element with the given ID. ([#1063](https://github.com/pyscript/pyscript/pull/1063)) +- The `output` attribute of $lt;py-repl$gt; tags now specifies the id of the DOM element that `sys.stdout`, `sys.stderr`, and the results of a REPL execution are written to. It no longer affects the location of calls to `display()` +- Added a `stderr` attribute of `py-repl` tags to route `sys.stderr` to a DOM element with the given ID. ([#1106](https://github.com/pyscript/pyscript/pull/1106)) +- Resored the `output-mode` attribute of `py-repl` tags. If `output-mode` == 'append', the DOM element where output is printed is _not_ cleared before writing new results. +- Plugins may now implement the `beforePyReplExec()` and `afterPyReplExec()` hooks, which are called immediately before and after code in a `py-repl` tag is executed. ([#1106](https://github.com/pyscript/pyscript/pull/1106)) Bug fixes --------- @@ -22,43 +28,32 @@ Enhancements ========= -### Features +Features -------- -#### <py-script> tags -- Restored the `output` attribute of <py-script> tags to route `sys.stdout` to a DOM element with the given ID. ([#1063](https://github.com/pyscript/pyscript/pull/1063)) -- Added a `stderr` attribute of <py-script> tags to route `sys.stderr` to a DOM element with the given ID. ([#1063](https://github.com/pyscript/pyscript/pull/1063)) - -#### <py-repl tags> -- The `output` attribute of $lt;py-repl$gt; tags now specifies the id of the DOM element that `sys.stdout`, `sys.stderr`, and the results of a REPL execution are written to. It no longer affects the location of calls to `display()` -- Added a `stderr` attribute of <py-repl> tags to route `sys.stderr` to a DOM element with the given ID. ([#1106](https://github.com/pyscript/pyscript/pull/1106)) -- Resored the `output-mode` attribute of <py-repl> tags. If `output-mode` == 'append', the DOM element where output is printed is _not_ cleared before writing new results. - -#### Plugins -- Plugins may now implement the `beforePyReplExec()` and `afterPyReplExec()` hooks, which are called immediately before and after code in a <py-repl> tag is executed. ([#1106](https://github.com/pyscript/pyscript/pull/1106)) - -### Bug fixes - +Bug fixes +--------- - Fixed an issue where `pyscript` would not be available when using the minified version of PyScript. ([#1054](https://github.com/pyscript/pyscript/pull/1054)) - Fixed missing closing tag when rendering an image with `display`. ([#1058](https://github.com/pyscript/pyscript/pull/1058)) - Fixed a bug where Python plugins methods were being executed twice. ([#1064](https://github.com/pyscript/pyscript/pull/1064)) -### Enhancements +Enhancements +------------ - When adding a `py-` attribute to an element but didn't added an `id` attribute, PyScript will now generate a random ID for the element instead of throwing an error which caused the splash screen to not shutdown. ([#1122](https://github.com/pyscript/pyscript/pull/1122)) - You can now disable the splashscreen by setting `enabled = false` in your `py-config` under the `[splashscreen]` configuration section. ([#1138](https://github.com/pyscript/pyscript/pull/1138)) -### Documentation - +Documentation +------------- - Fixed 'Direct usage of document is deprecated' warning in the getting started guide. ([#1052](https://github.com/pyscript/pyscript/pull/1052)) - Added reference documentation for the `py-splashscreen` plugin ([#1138](https://github.com/pyscript/pyscript/pull/1138)) - Adds doc for installing tests ([#1156](https://github.com/pyscript/pyscript/pull/1156)) - Adds docs for custom Pyscript attributes (`py-*`) that allow you to add event listeners to an element ([#1125](https://github.com/pyscript/pyscript/pull/1125)) -### Deprecations and Removals - +Deprecations and Removals +------------------------- - The py-config `runtimes` to specify an interpreter has been deprecated. The `interpreters` config should be used instead. ([#1082](https://github.com/pyscript/pyscript/pull/1082)) - The attributes `pys-onClick` and `pys-onKeyDown` have been deprecated, but the warning was only shown in the console. An alert banner will now be shown on the page if the attributes are used. They will be removed in the next release. ([#1084](https://github.com/pyscript/pyscript/pull/1084)) From d775649577b129e617a893404823dc26ebf2190b Mon Sep 17 00:00:00 2001 From: mariana Date: Wed, 22 Mar 2023 17:10:59 +0100 Subject: [PATCH 25/28] Linting --- pyscriptjs/tests/integration/test_py_repl.py | 27 -------------------- pyscriptjs/tests/py-unit/conftest.py | 9 +++---- 2 files changed, 3 insertions(+), 33 deletions(-) diff --git a/pyscriptjs/tests/integration/test_py_repl.py b/pyscriptjs/tests/integration/test_py_repl.py index bf8ce5ae494..64221e8df4b 100644 --- a/pyscriptjs/tests/integration/test_py_repl.py +++ b/pyscriptjs/tests/integration/test_py_repl.py @@ -566,30 +566,3 @@ def test_repl_output_element_id_change(self): ) alert_banner = self.page.locator(".alert-banner") assert expected_alert_banner_msg in alert_banner.inner_text() - - def test_multiple_repls_mixed_display_order(self): - """ - Displaying several outputs that don't obey the order in which the original - repl displays were created using the auto_generate attr - """ - self.pyscript_run( - """ - display("root first") - display("root second") - """ - ) - - second_py_repl = self.page.get_by_text("root second") - second_py_repl.click() - self.page.keyboard.press("Shift+Enter") - self.page.keyboard.type("display('second children')") - self.page.keyboard.press("Shift+Enter") - - first_py_repl = self.page.get_by_text("root first") - first_py_repl.click() - self.page.keyboard.press("Shift+Enter") - self.page.keyboard.type("display('first children')") - self.page.keyboard.press("Shift+Enter") - - assert self.page.inner_text("#py-internal-1-1-repl-output") == "second children" - assert self.page.inner_text("#py-internal-0-1-repl-output") == "first children" diff --git a/pyscriptjs/tests/py-unit/conftest.py b/pyscriptjs/tests/py-unit/conftest.py index c6c6e419952..9b9f999ec95 100644 --- a/pyscriptjs/tests/py-unit/conftest.py +++ b/pyscriptjs/tests/py-unit/conftest.py @@ -2,12 +2,13 @@ import sys from pathlib import Path +# patch pyscript module where needed +import pyscript # noqa: E402 +import pyscript_plugins_tester as ppt # noqa: E402 import pytest pyscriptjs = Path(__file__).parents[2] -import pytest - pyscriptjs = Path(__file__).parents[2] # add pyscript folder to path @@ -18,10 +19,6 @@ python_plugins_source = pyscriptjs / "src" / "plugins" / "python" sys.path.append(str(python_plugins_source)) -# patch pyscript module where needed -import pyscript # noqa: E402 -import pyscript_plugins_tester as ppt # noqa: E402 - pyscript.define_custom_element = ppt.define_custom_element From 5bf2da8d44e0db48a3875720a3760f18fb0f0185 Mon Sep 17 00:00:00 2001 From: mariana Date: Wed, 22 Mar 2023 19:01:18 +0100 Subject: [PATCH 26/28] Linting correctly --- pyscriptjs/tests/py-unit/conftest.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/pyscriptjs/tests/py-unit/conftest.py b/pyscriptjs/tests/py-unit/conftest.py index 9b9f999ec95..7e236c4c4b2 100644 --- a/pyscriptjs/tests/py-unit/conftest.py +++ b/pyscriptjs/tests/py-unit/conftest.py @@ -2,13 +2,12 @@ import sys from pathlib import Path -# patch pyscript module where needed -import pyscript # noqa: E402 -import pyscript_plugins_tester as ppt # noqa: E402 import pytest pyscriptjs = Path(__file__).parents[2] +import pytest # noqa + pyscriptjs = Path(__file__).parents[2] # add pyscript folder to path @@ -19,6 +18,10 @@ python_plugins_source = pyscriptjs / "src" / "plugins" / "python" sys.path.append(str(python_plugins_source)) +# patch pyscript module where needed +import pyscript # noqa: E402 +import pyscript_plugins_tester as ppt # noqa: E402 + pyscript.define_custom_element = ppt.define_custom_element From 9d84d8613b9fecae99d76c1edbe3b4ae159c90c3 Mon Sep 17 00:00:00 2001 From: Jeff Glass Date: Wed, 22 Mar 2023 18:44:10 -0500 Subject: [PATCH 27/28] Adjust changelog formatting --- docs/changelog.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/changelog.md b/docs/changelog.md index 96675b81eb1..71c5b42d7b2 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -6,12 +6,20 @@ Features -------- + +### <py-terminal> - Added a `docked` field and attribute for the `` custom element, enabled by default when the terminal is in `auto` mode, and able to dock the terminal at the bottom of the page with auto scroll on new code execution. + +### <py-script> - Restored the `output` attribute of `py-script` tags to route `sys.stdout` to a DOM element with the given ID. ([#1063](https://github.com/pyscript/pyscript/pull/1063)) - Added a `stderr` attribute of `py-script` tags to route `sys.stderr` to a DOM element with the given ID. ([#1063](https://github.com/pyscript/pyscript/pull/1063)) -- The `output` attribute of $lt;py-repl$gt; tags now specifies the id of the DOM element that `sys.stdout`, `sys.stderr`, and the results of a REPL execution are written to. It no longer affects the location of calls to `display()` + +### <py-repl> +- The `output` attribute of `py-repl` tags now specifies the id of the DOM element that `sys.stdout`, `sys.stderr`, and the results of a REPL execution are written to. It no longer affects the location of calls to `display()` - Added a `stderr` attribute of `py-repl` tags to route `sys.stderr` to a DOM element with the given ID. ([#1106](https://github.com/pyscript/pyscript/pull/1106)) - Resored the `output-mode` attribute of `py-repl` tags. If `output-mode` == 'append', the DOM element where output is printed is _not_ cleared before writing new results. + +### Plugins - Plugins may now implement the `beforePyReplExec()` and `afterPyReplExec()` hooks, which are called immediately before and after code in a `py-repl` tag is executed. ([#1106](https://github.com/pyscript/pyscript/pull/1106)) Bug fixes From 41884c06d7ea2effa42a8f2298596d23ea7c0c2c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 22 Mar 2023 23:45:54 +0000 Subject: [PATCH 28/28] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- docs/changelog.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/changelog.md b/docs/changelog.md index 17fc3ab36c1..cfdc4bd5e9b 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -19,7 +19,7 @@ Features - Added a `stderr` attribute of `py-repl` tags to route `sys.stderr` to a DOM element with the given ID. ([#1106](https://github.com/pyscript/pyscript/pull/1106)) - Resored the `output-mode` attribute of `py-repl` tags. If `output-mode` == 'append', the DOM element where output is printed is _not_ cleared before writing new results. -### Plugins +### Plugins - Plugins may now implement the `beforePyReplExec()` and `afterPyReplExec()` hooks, which are called immediately before and after code in a `py-repl` tag is executed. ([#1106](https://github.com/pyscript/pyscript/pull/1106)) Bug fixes 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