This fixes a bug in the previous version where if the code does not use top level await but returns a generator or coroutine, the code would try to await it. This sometimes causes strange errors or other unexpected behaviors.

Instead, I switched to a new method of returning by raising an exception. This makes it so the coroutines produced by top level await are returned via a separate channel from return values from the code so we won't confuse them anymore.

I added a new class Executor. On creation, the Executor is given the source code and config options and produces an AST. The caller can then manipulate the AST as they see fit before calling `compile` and then `run` or `run_async`. This passes to the user the most powerful possible interface, and should make it so that Executor is helpful even in complicated use cases.

I put the main parse/compile logic into a separate generator to avoid storing any of the config options on the Executor class. This helps separate out control logic from config options and limit the scope of variables as far as possible.
ENH Improvements to eval_code. This fixes a bug in the previous version where if the code does not use top level await but returns a generator or coroutine, the code would try to await it. This sometimes causes strange errors or other unexpected behaviors.

Instead, I switched to a new method of returning by raising an exception. This makes it so the coroutines produced by top level await are returned via a separate channel from return values from the code so we won't confuse them anymore.

I added a new class Executor. On creation, the Executor is given the source code and config options and produces an AST. The caller can then manipulate the AST as they see fit before calling `compile` and then `run` or `run_async`. This passes to the user the most powerful possible interface, and should make it so that Executor is helpful even in complicated use cases.

I put the main parse/compile logic into a separate generator to avoid storing any of the config options on the Executor class. This helps separate out control logic from config options and limit the scope of variables as far as possible.
from pyodide_build.testing import set_webdriver_script_timeout


class _MockDriver:
    def set_script_timeout(self, value):
        self._timeout = value


class _MockSelenium:
    script_timeout = 2
    driver = _MockDriver()


def test_set_webdriver_script_timeout():
    selenium = _MockSelenium()
    assert not hasattr(selenium.driver, "_timeout")
    with set_webdriver_script_timeout(selenium, script_timeout=10):
        assert selenium.driver._timeout == 10
    assert selenium.driver._timeout == 2

from pyodide_build.testing import set_webdriver_script_timeout


class _MockDriver:
    def set_script_timeout(self, value):
        self._timeout = value


class _MockSelenium:
    script_timeout = 2
    driver = _MockDriver()


def test_set_webdriver_script_timeout():
    selenium = _MockSelenium()
    assert not hasattr(selenium.driver, "_timeout")
    with set_webdriver_script_timeout(selenium, script_timeout=10):
        assert selenium.driver._timeout == 10
    assert selenium.driver._timeout == 2
