Skip to content

Refactor how py-script are executed, kill scriptQueue store, introduce pyExec#881

Merged
antocuni merged 34 commits into
mainfrom
antocuni/kill-script-queue
Oct 23, 2022
Merged

Refactor how py-script are executed, kill scriptQueue store, introduce pyExec#881
antocuni merged 34 commits into
mainfrom
antocuni/kill-script-queue

Conversation

@antocuni

@antocuni antocuni commented Oct 22, 2022

Copy link
Copy Markdown
Contributor

Yet another refactoring to untangle the old mess.
Highlights:

base.ts, pyscript.ts and pyrepl.ts were a tangled mess of code, in which each of them interacted with the others in non-obvious ways. Now PyScript is no longer a subclass of BaseEvalElement and it is much simpler. I removed code for handling the attributes std-out and std-err because they are no longer needed with the new display() logic.

The logic for executing python code is now in pyexec.ts: so we are decoupling the process of "finding" the python code (handled by the py-script web component) and the logic to actually execute it. This has many advantages, including the fact that it will be more easily usable by other components (e.g. pyrepl). Also, note that it's called pyexec and not pyeval: in the vast majority of cases in Python you have statements to execute, and almost never expressions to evaluate.

I killed the last remaining global store, scriptQueue 🎉. As a bonus effect, now we automatically do the correct thing when a <py-script> tag is dynamically added to the DOM (I added a test for it). I did not remove svelte from packages.json, because I don't fully understand the implications: there are various options which mention svelte in rollup.js and tsconfig.json, so it's probably better to kill it in its own PR.

pyexec.ts is also responsible of handling the default target for display() and correct handling/visualization of exceptions. I fixed/improved/added display/output tests in the process.
I also found a problem though, see issue #878, so I improved the test and marked it as xfail.

I removed BaseEvalElement as the superclass of most components. Now the only class which inherits from it is PyRepl. In a follow-up PR, I plan to merge them into a single class and do more cleanup.

During the refactoring, I killed guidGenerator: now instead of generating random py-* IDs which are very hard to read for humans, we generated py-internal-X IDs, where X is 0, 1, 2, 3, etc. This makes writing tests and debugging much easier.

I improved a lot our test machinery: it turns out that PR #829 broke the ability to use/view sourcemaps inside the playwright browser (at least on my machine).
For some reason chromium is unable to find sourcemaps if you use playwrights internal routing. So I reintroduced the http_server fixture which was removed by that PR, and added a pytest option --no-fake-server to use it instead, useful for debugging. By default we are still using the fakeserver though (which is faster and parallelizable).

Similarly, I added --dev which implies --headed and also automatically open chrome dev tools.

…comment which explains some of the problems of the current approach. But I don't want to tackled it in this PR
When using this option, you use a real http server instead of using
playwright's internal routing.

There are cases in which it's useful, in particular:

1) if you want to test the page on different browser

2) if you want to use the chrome dev tools, because apparently when using the
fake server chromium is unable to locate the sourcemaps

This commit reintroduces some of the code which was killed by PR #829.
…easier to test and to use in the debugger for humans
…eEvalElement. This contains a bit of mechnical copy&paste but I will simplify the code in the next commits
…at we can execute a very simple python code, no need to check the behavior of display()
… using a queue of scripts, we immediately run the python code inside connectedCallback. This has also the advantage that if we dynamically add a <py-script> tag to the DOM, the python code is automatically executed, as the new test demonstrates (the test would have been failed before this commit)
…level because you would need to mock the whole Runtime, and you would end up testing the mock instead of something actually useful. PyScript is battle-tested a lot by integration tests
…out multiple display. It is about implicit display() target, so move it it the correct class and improve the test to be less dependent on the exact <div> id
@antocuni antocuni changed the title WIP: refactor how py-script are executed, kill scriptQueue store, introduce pyExec Refactor how py-script are executed, kill scriptQueue store, introduce pyExec Oct 23, 2022
@antocuni antocuni marked this pull request as ready for review October 23, 2022 17:44

@tedpatrick tedpatrick left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Goodbye stores!
No issues.

Comment thread pyscriptjs/tests/unit/pytitle.test.ts Outdated
this.errorElement = this.outputElement;
}
}
export function make_PyScript(runtime: Runtime) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious - what's the advantage of using a function that defines the class rather than just the class definition?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the fact that I can pass runtime from the outside. Note that runtime is used from connectedCallback, and since this is a method called by the browser, we don't really have any other way to pass it explicitly.
The alternative was to use a global variable as it was before, but we saw that it caused way too many problems.

Comment thread pyscriptjs/src/pyexec.ts
}


function displayPyException(err: any, errElem: HTMLElement) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Love this as a start - having a unified way to intercept and handle errors will be nice, but really good to have this behavior in one place.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes. The next step will be to have an unified place where to display errors when there is no other obvious place (e.g. if you raise an exception from an event handler).

@antocuni antocuni merged commit f9194cc into main Oct 23, 2022
@antocuni antocuni deleted the antocuni/kill-script-queue branch October 23, 2022 21:31

@marimeireles marimeireles left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool PR! :)

Comment thread pyscriptjs/src/main.ts
config: AppConfig;
loader: PyLoader;
runtime: Runtime;
PyScript: any; // XXX would be nice to have a more precise type for the class itself

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[question] Why do we have PyScript here instead of pyscript does this mean something different?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It stands for the PyScript object I guess... and the other ones are function names?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to, this is the PyScript class, which now is created at runtime:

export function make_PyScript(runtime: Runtime) {
class PyScript extends HTMLElement {

so basically, it's the equivalent of what formerly was import { PyScript } from './components/pyscript'

Comment thread pyscriptjs/src/pyexec.ts


// XXX this is used by base.ts but should be removed once we complete the refactoring
export async function pyExecDontHandleErrors(runtime: Runtime, pysrc: string, out: HTMLElement)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[suggestion/question] I don't understand this name. Could you please explain it to me?
This piece of code is evaluating the strings coming from the front-end into python code. Should it be renamed?
Why aren't we catching errors here? Why keep this on base.ts?

@antocuni antocuni Oct 24, 2022

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand this name. Could you please explain it to me?
pyExec executes python code inside a try/catch:

try {
await runtime.run(pysrc);
}
catch (err) {
// XXX: currently we display exceptions in the same position as
// the output. But we probably need a better way to do that,
// e.g. allowing plugins to intercept exceptions and display them
// in a configurable way.
displayPyException(err, outElem);
}

pyExecDontHandleErrors does the same but without the try/catch: this means that if a python-level exception is raised, it will be propagated outside (and thus needs to be intercepted by someone else).

Why aren't we catching errors here? Why keep this on base.ts?

as the comment says, the plan is to kill it eventually and refactor base.ts/pyrepl.ts to use pyExec instead. But the old code was too messy and I didn't want to touch too many things at once, it's what I plan to do next.

Comment thread pyscriptjs/src/utils.ts
Comment on lines +41 to +44
let _uniqueIdCounter = 0;
export function ensureUniqueId(el: HTMLElement) {
if (el.id === "")
el.id = "py-internal-" + _uniqueIdCounter++;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this =)
I think next step is to actually try to remove all of these ids.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes but it's not completely easy because Element.write relies on it. But yes, we should stop passing ids around and start passing real HTMLElement objects instead.

@marimeireles

Copy link
Copy Markdown
Contributor

Thanks @antocuni! ^-^

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

5 participants