diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 76074704..d1fcb1bc 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -88,6 +88,24 @@ jobs: component: clippy - run: cargo clippy --lib --tests --target wasm32-wasi -- -D warnings -D clippy::all working-directory: ./lib + - run: cargo clippy --target wasm32-wasi --features abi3 --no-default-features -- -D warnings -D clippy::all + working-directory: ./python + + clippy_wasm_emscripten: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + submodules: true + - uses: ./.github/actions/setup-rust + with: + version: 1.74.1 + target: wasm32-unknown-emscripten + component: clippy + - run: cargo clippy --lib --tests --target wasm32-unknown-emscripten -- -D warnings -D clippy::all + working-directory: ./lib + - run: cargo clippy --target wasm32-unknown-emscripten --features abi3 -- -D warnings -D clippy::all + working-directory: ./python clippy_wasm_unknown: runs-on: ubuntu-latest @@ -346,6 +364,36 @@ jobs: - run: python -m unittest working-directory: ./python/tests + python_pyodide: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + submodules: true + - uses: actions/setup-python@v4 + with: + python-version: "3.11" + cache: pip + cache-dependency-path: '**/requirements.dev.txt' + - uses: ./.github/actions/setup-rust + with: + version: nightly + target: wasm32-unknown-emscripten + - run: | + pip install pyodide-build + echo EMSCRIPTEN_VERSION=$(pyodide config get emscripten_version) >> $GITHUB_ENV + - uses: mymindstorm/setup-emsdk@v13 + with: + version: ${{ env.EMSCRIPTEN_VERSION }} + - run: pyodide build + working-directory: ./python + - run: | + pyodide venv venv + source venv/bin/activate + pip install --no-index --find-links=../dist/ pyoxigraph + python -m unittest + working-directory: ./python/tests + typos: runs-on: ubuntu-latest steps: diff --git a/python/Cargo.toml b/python/Cargo.toml index a63d6c89..94c5bbf1 100644 --- a/python/Cargo.toml +++ b/python/Cargo.toml @@ -27,7 +27,10 @@ pyo3 = { version = "0.20.1", features = ["extension-module"] } [target.'cfg(any(target_family = "windows", target_os = "macos", target_os = "ios"))'.dependencies] oxigraph = { path = "../lib", features = ["http-client-native-tls"] } -[target.'cfg(not(any(target_family = "windows", target_os = "macos", target_os = "ios")))'.dependencies] +[target.'cfg(target_family = "wasm")'.dependencies] +oxigraph.path = "../lib" + +[target.'cfg(not(any(target_family = "windows", target_os = "macos", target_os = "ios", target_family = "wasm")))'.dependencies] oxigraph = { path = "../lib", features = ["http-client-rustls-native"] } [lints] diff --git a/python/src/store.rs b/python/src/store.rs index ccc56e35..cbb4f975 100644 --- a/python/src/store.rs +++ b/python/src/store.rs @@ -48,6 +48,7 @@ pub struct PyStore { #[pymethods] impl PyStore { + #[cfg(not(target_family = "wasm"))] #[new] #[pyo3(signature = (path = None))] fn new(path: Option, py: Python<'_>) -> PyResult { @@ -63,6 +64,16 @@ impl PyStore { }) } + #[cfg(target_family = "wasm")] + #[new] + fn new(py: Python<'_>) -> PyResult { + py.allow_threads(|| { + Ok(Self { + inner: Store::new().map_err(map_storage_error)?, + }) + }) + } + /// Opens a read-only store from disk. /// /// Opening as read-only while having an other process writing the database is undefined behavior. @@ -73,6 +84,7 @@ impl PyStore { /// :return: the opened store. /// :rtype: Store /// :raises OSError: if the target directory contains invalid data or could not be accessed. + #[cfg(not(target_family = "wasm"))] #[staticmethod] fn read_only(path: &str, py: Python<'_>) -> PyResult { py.allow_threads(|| { @@ -97,6 +109,7 @@ impl PyStore { /// :return: the opened store. /// :rtype: Store /// :raises OSError: if the target directories contain invalid data or could not be accessed. + #[cfg(not(target_family = "wasm"))] #[staticmethod] #[pyo3(signature = (primary_path, secondary_path = None))] fn secondary( @@ -173,6 +186,7 @@ impl PyStore { /// >>> store.bulk_extend([Quad(NamedNode('http://example.com'), NamedNode('http://example.com/p'), Literal('1'), NamedNode('http://example.com/g'))]) /// >>> list(store) /// [ predicate= object=> graph_name=>] + #[cfg(not(target_family = "wasm"))] fn bulk_extend(&self, quads: &PyAny) -> PyResult<()> { self.inner .bulk_loader() @@ -695,6 +709,7 @@ impl PyStore { /// /// :rtype: None /// :raises OSError: if an error happens during the flush. + #[cfg(not(target_family = "wasm"))] fn flush(&self, py: Python<'_>) -> PyResult<()> { py.allow_threads(|| self.inner.flush().map_err(map_storage_error)) } @@ -705,6 +720,7 @@ impl PyStore { /// /// :rtype: None /// :raises OSError: if an error happens during the optimization. + #[cfg(not(target_family = "wasm"))] fn optimize(&self, py: Python<'_>) -> PyResult<()> { py.allow_threads(|| self.inner.optimize().map_err(map_storage_error)) } @@ -730,6 +746,7 @@ impl PyStore { /// :type target_directory: str or os.PathLike[str] /// :rtype: None /// :raises OSError: if an error happens during the backup. + #[cfg(not(target_family = "wasm"))] fn backup(&self, target_directory: PathBuf, py: Python<'_>) -> PyResult<()> { py.allow_threads(|| { self.inner diff --git a/python/tests/test_store.py b/python/tests/test_store.py index 9b662ad6..abda28ca 100644 --- a/python/tests/test_store.py +++ b/python/tests/test_store.py @@ -1,4 +1,5 @@ import gc +import sys import unittest from io import BytesIO, StringIO, UnsupportedOperation from pathlib import Path @@ -26,6 +27,7 @@ bar = NamedNode("http://bar") baz = NamedNode("http://baz") triple = Triple(foo, foo, foo) graph = NamedNode("http://graph") +is_wasm = sys.platform == "emscripten" class TestStore(unittest.TestCase): @@ -49,6 +51,7 @@ class TestStore(unittest.TestCase): ) self.assertEqual(len(store), 2) + @unittest.skipIf(is_wasm, "Not supported with WASM") def test_bulk_extend(self) -> None: store = Store() store.bulk_extend( @@ -244,6 +247,7 @@ class TestStore(unittest.TestCase): store.update("DELETE WHERE { ?v ?v ?v }") self.assertEqual(len(store), 0) + @unittest.skipIf(is_wasm, "Not supported with WASM") def test_update_load(self) -> None: store = Store() store.update("LOAD ") @@ -381,6 +385,7 @@ class TestStore(unittest.TestCase): self.assertEqual(list(store.named_graphs()), []) self.assertEqual(list(store), []) + @unittest.skipIf(is_wasm, "Not supported with WASM") def test_read_only(self) -> None: quad = Quad(foo, bar, baz, graph) with TemporaryDirectory() as dir: @@ -391,6 +396,7 @@ class TestStore(unittest.TestCase): store = Store.read_only(dir) self.assertEqual(list(store), [quad]) + @unittest.skipIf(is_wasm, "Not supported with WASM") def test_secondary(self) -> None: quad = Quad(foo, bar, baz, graph) with TemporaryDirectory() as dir: