diff --git a/src/py_mini_racer/__init__.py b/src/py_mini_racer/__init__.py index 9f49f622..6a577c06 100644 --- a/src/py_mini_racer/__init__.py +++ b/src/py_mini_racer/__init__.py @@ -1,5 +1,6 @@ from py_mini_racer import py_mini_racer +DEFAULT_V8_FLAGS = py_mini_racer.DEFAULT_V8_FLAGS JSEvalException = py_mini_racer.JSEvalException JSFunction = py_mini_racer.JSFunction JSOOMException = py_mini_racer.JSOOMException @@ -7,8 +8,11 @@ JSParseException = py_mini_racer.JSParseException JSSymbol = py_mini_racer.JSSymbol JSTimeoutException = py_mini_racer.JSTimeoutException +LibAlreadyInitializedError = py_mini_racer.LibAlreadyInitializedError +LibNotFoundError = py_mini_racer.LibNotFoundError MiniRacer = py_mini_racer.MiniRacer StrictMiniRacer = py_mini_racer.StrictMiniRacer +init_mini_racer = py_mini_racer.init_mini_racer __all__ = ["py_mini_racer", "MiniRacer"] diff --git a/src/py_mini_racer/py_mini_racer.py b/src/py_mini_racer/py_mini_racer.py index 366c7065..c0f46a38 100644 --- a/src/py_mini_racer/py_mini_racer.py +++ b/src/py_mini_racer/py_mini_racer.py @@ -11,7 +11,7 @@ from os.path import join as pathjoin from sys import platform, version_info from threading import Lock -from typing import ClassVar +from typing import ClassVar, Iterable, Iterator class MiniRacerBaseException(Exception): # noqa: N818 @@ -25,6 +25,15 @@ def __init__(self, path): super().__init__(f"Native library or dependency not available at {path}") +class LibAlreadyInitializedError(MiniRacerBaseException): + """MiniRacer-wrapped V8 build not found.""" + + def __init__(self): + super().__init__( + "MiniRacer was already initialized before the call to init_mini_racer" + ) + + class JSParseException(MiniRacerBaseException): """JavaScript could not be parsed.""" @@ -82,7 +91,7 @@ def _get_lib_filename(name): return prefix + name + ext -def _build_dll_handle(dll_path): +def _build_dll_handle(dll_path) -> ctypes.CDLL: handle = ctypes.CDLL(dll_path) handle.mr_init_v8.argtypes = [ctypes.c_char_p, ctypes.c_char_p, ctypes.c_char_p] @@ -129,7 +138,7 @@ def _build_dll_handle(dll_path): # modules: _SNAPSHOT_FILENAME = "snapshot_blob.bin" -_V8_FLAGS: list[str] = ["--single-threaded"] +DEFAULT_V8_FLAGS = ("--single-threaded",) def _open_resource_file(filename, exit_stack): @@ -151,7 +160,7 @@ def _check_path(path): @contextmanager -def _open_dll(): +def _open_dll(flags: Iterable[str]) -> Iterator[ctypes.CDLL]: dll_filename = _get_lib_filename("mini_racer") with ExitStack() as exit_stack: @@ -171,15 +180,15 @@ def _open_dll(): _check_path(icu_data_path) _check_path(snapshot_path) - dll = _build_dll_handle(dll_path) + handle = _build_dll_handle(dll_path) - dll.mr_init_v8( - " ".join(_V8_FLAGS).encode("utf-8"), + handle.mr_init_v8( + " ".join(flags).encode("utf-8"), icu_data_path.encode("utf-8"), snapshot_path.encode("utf-8"), ) - yield dll + yield handle _init_lock = Lock() @@ -187,15 +196,27 @@ def _open_dll(): _dll_handle = None -def _get_dll_handle(): +def init_mini_racer( + *, flags: Iterable[str] = DEFAULT_V8_FLAGS, ignore_duplicate_init=False +) -> ctypes.CDLL: + """Initialize py_mini_racer (and V8). + + This function can optionally be used to set V8 flags. This function can be called + at most once, before any instances of MiniRacer are initialized. Instances of + MiniRacer will automatically call this function to initialize MiniRacer and V8. + """ + + global _dll_handle_context_manager # noqa: PLW0603 global _dll_handle # noqa: PLW0603 with _init_lock: if _dll_handle is None: - _dll_handle_context_manager = _open_dll() + _dll_handle_context_manager = _open_dll(flags) _dll_handle = _dll_handle_context_manager.__enter__() # Note: we never call _dll_handle_context_manager.__exit__() because it's # designed as a singleton. But we could if we wanted to! + elif not ignore_duplicate_init: + raise LibAlreadyInitializedError return _dll_handle @@ -212,7 +233,7 @@ class MiniRacer: json_impl: ClassVar[object] = json def __init__(self): - self._dll = _get_dll_handle() + self._dll: ctypes.CDLL = init_mini_racer(ignore_duplicate_init=True) self.ctx = self._dll.mr_init_context() self.lock = Lock() diff --git a/tests/test_init.py b/tests/test_init.py new file mode 100644 index 00000000..b2de7278 --- /dev/null +++ b/tests/test_init.py @@ -0,0 +1,24 @@ +import pytest + +from py_mini_racer import LibAlreadyInitializedError, init_mini_racer + + +def test_init(): + init_mini_racer(ignore_duplicate_init=True) + + with pytest.raises(LibAlreadyInitializedError): + init_mini_racer() + + init_mini_racer(ignore_duplicate_init=True) + + +# Unfortunately while init_mini_racer allows changing V8 flags, it's hard to test +# automatically because only the first use of V8 can set flags. We'd need to +# restart Python between tests. +# Here's a manual test: +# def test_init_flags(): +# from py_mini_racer import DEFAULT_V8_FLAGS, MiniRacer, init_mini_racer +# init_mini_racer(flags=(*DEFAULT_V8_FLAGS, '--no-use-strict')) +# mr = MiniRacer() +# # this would normally fail in strict JS mode because foo is not declared: +# mr.eval('foo = 4')