diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 334ea19..85660f8 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -5,10 +5,12 @@ on: [push, pull_request] jobs: tests: name: Run tests - runs-on: ubuntu-latest + runs-on: ${{ matrix.os }}-latest strategy: + fail-fast: false matrix: - python-version: ["3.8", "3.9", "3.10", "3.11"] + os: ["ubuntu", "macos", "windows"] + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12.0-rc.3"] steps: - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} @@ -18,9 +20,11 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install -U setuptools flake8 pytest - python setup.py install + python -m pip install -U setuptools wheel + - name: Install package under test + run: | + python -m pip install -vv .[test] - name: Test with pytest run: | - python -c 'import pyduktape2; ctx = pyduktape2.DuktapeContext(); ctx.eval_js("print(\"Duktape Version: \" + Duktape.version)")' - pytest + python -c "import pyduktape2; ctx = pyduktape2.DuktapeContext(); ctx.eval_js('''print('Duktape Version: ' + Duktape.version)''')" + python -m pytest -vv --tb=long diff --git a/MANIFEST.in b/MANIFEST.in index 67672b4..e1d9df7 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -9,3 +9,5 @@ include vendor/duk_print_alert.c include vendor/duk_print_alert.h include vendor/AUTHORS.txt include vendor/LICENSE.txt + +recursive-include tests *.js diff --git a/README.rst b/README.rst index 12c0d34..ed8a20a 100644 --- a/README.rst +++ b/README.rst @@ -1,195 +1,195 @@ -Introduction -############ - -Pyduktape is a python wrapper around `Duktape `_, -an embeddable Javascript interpreter. - -On top of the interpreter wrapper, pyduktape offers easy integration -between the Python and the Javascript environments. You can pass -Python objects to Javascript, call methods on them and access their -attributes. Similarly, you can pass Javascript objects to Python. - -Objects are never copied or serialized. Instead, they are passed -between the two environments using proxy objects. Proxy objects -delegate the execution to the original object environment. - -Threading -######### - -It is possible to invoke Javascript code from multiple threads. Each -thread will need to use its own embedded interpreter. Javascript -objects returned to the Python environment will only be usable on the -same thread that created them. The runtime always checks this -condition automatically, and raises a ``DuktapeThreadError`` if it's -violated. - -Getting Started -############### - -Installation ------------- - -To install from pypi:: - - $ pip install pyduktape2 - -To install the latest version from github:: - - $ pip install git+https://github.com/phith0n/pyduktape2 - -Running Javascript code ------------------------ - -To run Javascript code, you need to create an execution context and -use the method ``eval_js``:: - - import pyduktape2 - - context = pyduktape2.DuktapeContext() - context.eval_js("print(Duktape.version);") - -Each execution context starts its own interpreter. Each context is -independent, and tied to the Python thread that created it. Memory is -automatically managed. - -To evaluate external Javascript files, use ``eval_js_file``:: - - // helloWorld.js - print('Hello, World!'); - - # in the Python interpreter - import pyduktape2 - - context = pyduktape2.DuktapeContext() - context.eval_js_file('helloWorld.js') - -Pyduktape supports Javascript modules:: - - // js/helloWorld.js - exports.sayHello = function () { - print('Hello, World!'); - }; - - // js/main.js - var helloWorld = require('js/helloWorld'); - helloWorld.sayHello(); - - # in the Python interpreter - import pyduktape2 - - context = pyduktape2.DuktapeContext() - context.eval_js_file('js/main') - -The ``.js`` extension is automatically added if missing. Relative -paths are relative to the current working directory, but you can -change the base path using ``set_base_path``:: - - # js/helloWorld.js - print('Hello, World!'); - - # in the Python interpreter - import pyduktape2 - - context = pyduktape2.DuktapeContext() - context.set_base_path('js') - context.eval_js_file('helloWorld') - -Python and Javascript integration ---------------------------------- - -You can use ``set_globals`` to set Javascript global variables:: - - import pyduktape2 - - def say_hello(to): - print 'Hello, {}!'.format(to) - - context = pyduktape2.DuktapeContext() - context.set_globals(sayHello=say_hello, world='World') - context.eval_js("sayHello(world);") - -You can use ``get_global`` to access Javascript global variables:: - - import pyduktape2 - - context = pyduktape2.DuktapeContext() - context.eval_js("var helloWorld = 'Hello, World!';") - print context.get_global('helloWorld') - -``eval_js`` returns the value of the last expression:: - - import pyduktape2 - - context = pyduktape2.DuktapeContext() - hello_world = context.eval_js("var helloWorld = 'Hello, World!'; helloWorld") - print hello_world - -You can seamlessly use Python objects and functions within Javascript -code. There are some limitations, though: any Python callable can -only be used as a function, and other attributes cannot be -accessed. Primitive types (int, float, string, None) are converted to -equivalent Javascript primitives. The following code shows how to -interact with a Python object from Javascript:: - - import pyduktape2 - - class Hello(object): - def __init__(self, what): - self.what = what - - def say(self): - print('Hello, {}!'.format(self.what)) - - context = pyduktape2.DuktapeContext() - context.set_globals(Hello=Hello) - context.eval_js("var helloWorld = Hello('World'); helloWorld.say();") - -In the same way, you can use Javascript objects in Python. You can -use the special method `new` to instantiate an object:: - - import pyduktape2 - - context = pyduktape2.DuktapeContext() - Hello = context.eval_js(""" - function Hello(what) { - this.what = what; - } - - Hello.prototype.say = function () { - print('Hello, ' + this.what + '!'); - }; - - Hello - """) - - hello_world = Hello.new('World') - hello_world.say() - -You can use Python lists and dicts from Javascript, and viceversa:: - - import pyduktape2 - - context = pyduktape2.DuktapeContext() - res = context.eval_js('[1, 2, 3]') - - for item in res: - print(item) - - context.set_globals(lst=[4, 5, 6]) - context.eval_js('for (var i = 0; i < lst.length; i++) { print(lst[i]); }') - - res = context.eval_js('var x = {a: 1, b: 2}; x') - for key, val in res.items(): - print(key, '=', val) - res.c = 3 - context.eval_js('print(x.c);') - - context.set_globals(x=dict(a=1, b=2)) - context.eval_js(""" - var items = x.items(); - for (var i = 0; i < items.length; i++) { - print(items[i][0] + ' = ' + items[i][1]); - } - """) - context.set_globals(x=dict(a=1, b=2)) - context.eval_js('for (var k in x) { print(k + ' = ' + x[k]); }') +Introduction +############ + +Pyduktape is a python wrapper around `Duktape `_, +an embeddable Javascript interpreter. + +On top of the interpreter wrapper, pyduktape offers easy integration +between the Python and the Javascript environments. You can pass +Python objects to Javascript, call methods on them and access their +attributes. Similarly, you can pass Javascript objects to Python. + +Objects are never copied or serialized. Instead, they are passed +between the two environments using proxy objects. Proxy objects +delegate the execution to the original object environment. + +Threading +######### + +It is possible to invoke Javascript code from multiple threads. Each +thread will need to use its own embedded interpreter. Javascript +objects returned to the Python environment will only be usable on the +same thread that created them. The runtime always checks this +condition automatically, and raises a ``DuktapeThreadError`` if it's +violated. + +Getting Started +############### + +Installation +------------ + +To install from pypi:: + + $ pip install pyduktape2 + +To install the latest version from github:: + + $ pip install git+https://github.com/phith0n/pyduktape2 + +Running Javascript code +----------------------- + +To run Javascript code, you need to create an execution context and +use the method ``eval_js``:: + + import pyduktape2 + + context = pyduktape2.DuktapeContext() + context.eval_js("print(Duktape.version);") + +Each execution context starts its own interpreter. Each context is +independent, and tied to the Python thread that created it. Memory is +automatically managed. + +To evaluate external Javascript files, use ``eval_js_file``:: + + // helloWorld.js + print('Hello, World!'); + + # in the Python interpreter + import pyduktape2 + + context = pyduktape2.DuktapeContext() + context.eval_js_file('helloWorld.js') + +Pyduktape supports Javascript modules:: + + // js/helloWorld.js + exports.sayHello = function () { + print('Hello, World!'); + }; + + // js/main.js + var helloWorld = require('js/helloWorld'); + helloWorld.sayHello(); + + # in the Python interpreter + import pyduktape2 + + context = pyduktape2.DuktapeContext() + context.eval_js_file('js/main') + +The ``.js`` extension is automatically added if missing. Relative +paths are relative to the current working directory, but you can +change the base path using ``set_base_path``:: + + # js/helloWorld.js + print('Hello, World!'); + + # in the Python interpreter + import pyduktape2 + + context = pyduktape2.DuktapeContext() + context.set_base_path('js') + context.eval_js_file('helloWorld') + +Python and Javascript integration +--------------------------------- + +You can use ``set_globals`` to set Javascript global variables:: + + import pyduktape2 + + def say_hello(to): + print 'Hello, {}!'.format(to) + + context = pyduktape2.DuktapeContext() + context.set_globals(sayHello=say_hello, world='World') + context.eval_js("sayHello(world);") + +You can use ``get_global`` to access Javascript global variables:: + + import pyduktape2 + + context = pyduktape2.DuktapeContext() + context.eval_js("var helloWorld = 'Hello, World!';") + print context.get_global('helloWorld') + +``eval_js`` returns the value of the last expression:: + + import pyduktape2 + + context = pyduktape2.DuktapeContext() + hello_world = context.eval_js("var helloWorld = 'Hello, World!'; helloWorld") + print hello_world + +You can seamlessly use Python objects and functions within Javascript +code. There are some limitations, though: any Python callable can +only be used as a function, and other attributes cannot be +accessed. Primitive types (int, float, string, None) are converted to +equivalent Javascript primitives. The following code shows how to +interact with a Python object from Javascript:: + + import pyduktape2 + + class Hello(object): + def __init__(self, what): + self.what = what + + def say(self): + print('Hello, {}!'.format(self.what)) + + context = pyduktape2.DuktapeContext() + context.set_globals(Hello=Hello) + context.eval_js("var helloWorld = Hello('World'); helloWorld.say();") + +In the same way, you can use Javascript objects in Python. You can +use the special method `new` to instantiate an object:: + + import pyduktape2 + + context = pyduktape2.DuktapeContext() + Hello = context.eval_js(""" + function Hello(what) { + this.what = what; + } + + Hello.prototype.say = function () { + print('Hello, ' + this.what + '!'); + }; + + Hello + """) + + hello_world = Hello.new('World') + hello_world.say() + +You can use Python lists and dicts from Javascript, and viceversa:: + + import pyduktape2 + + context = pyduktape2.DuktapeContext() + res = context.eval_js('[1, 2, 3]') + + for item in res: + print(item) + + context.set_globals(lst=[4, 5, 6]) + context.eval_js('for (var i = 0; i < lst.length; i++) { print(lst[i]); }') + + res = context.eval_js('var x = {a: 1, b: 2}; x') + for key, val in res.items(): + print(key, '=', val) + res.c = 3 + context.eval_js('print(x.c);') + + context.set_globals(x=dict(a=1, b=2)) + context.eval_js(""" + var items = x.items(); + for (var i = 0; i < items.length; i++) { + print(items[i][0] + ' = ' + items[i][1]); + } + """) + context.set_globals(x=dict(a=1, b=2)) + context.eval_js('for (var k in x) { print(k + ' = ' + x[k]); }') diff --git a/pyduktape2.pyx b/pyduktape2.pyx index 58b2fbf..03a5805 100644 --- a/pyduktape2.pyx +++ b/pyduktape2.pyx @@ -1,3 +1,5 @@ +#cython: language_level=3 + import contextlib import os import threading diff --git a/setup.py b/setup.py index 86541f4..d985d87 100644 --- a/setup.py +++ b/setup.py @@ -33,6 +33,7 @@ ], packages=find_packages(exclude=['tests']), setup_requires=['setuptools>=18.0', 'Cython<3'], + extras_require={'test': ['pytest']}, test_suite='tests', ext_modules=extensions, include_package_data=True, diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index 83f94a0..0000000 --- a/tests/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -import unittest - -class TestCase(unittest.TestCase): - pass diff --git a/tests/js/test0.js b/tests/js/test0.js index cde68b9..c05cf75 100644 --- a/tests/js/test0.js +++ b/tests/js/test0.js @@ -1 +1 @@ -var res = 1 + 1; +var res = 1 + 1; diff --git a/tests/js/test1.js b/tests/js/test1.js index c1aafd4..59aa6ff 100644 --- a/tests/js/test1.js +++ b/tests/js/test1.js @@ -1,2 +1,2 @@ -exports.a = 1; -exports.b = 2; +exports.a = 1; +exports.b = 2; diff --git a/tests/js/test2.js b/tests/js/test2.js index c7fc679..dec5733 100644 --- a/tests/js/test2.js +++ b/tests/js/test2.js @@ -1,2 +1,2 @@ -var test1 = require('js/test1'); -var res = test1.a + test1.b; +var test1 = require('js/test1'); +var res = test1.a + test1.b; diff --git a/tests/test_context.py b/tests/test_context.py index b115707..eda6ee1 100644 --- a/tests/test_context.py +++ b/tests/test_context.py @@ -3,7 +3,7 @@ import unittest from threading import Thread, Lock -from tests import TestCase +from unittest import TestCase from pyduktape2 import DuktapeContext, JSError, JSProxy, DuktapeThreadError