From 5b65d0dfd2dca22eb65a7feb7d768769d2352e20 Mon Sep 17 00:00:00 2001 From: Matt McCormick Date: Thu, 20 Aug 2026 22:05:37 -0400 Subject: [PATCH 1/2] fix(itkwasm): support the wasmtime-py 48 preopen_dir signature wasmtime-py 48.0.0 replaced WasiConfig.preopen_dir(path, guest_path, dir_perms, file_perms) with WasiConfig.preopen_dir(path, guest_path, fs_mutable=True) and stopped exporting DirPerms and FilePerms. Because pipeline.py imported those names at module scope, the removal broke plain `import itkwasm` with an ImportError, not just pipelines that perform file I/O. Drop the two imports and pass only the path arguments. Read-write is the default on both sides of the break -- DirPerms.READ_WRITE / FilePerms.READ_WRITE on <= 47, fs_mutable=True on 48 -- so the two-argument call is semantically identical across the supported range and no version shim or change to the `wasmtime >= 28.0.0` floor is needed. Every other wasmtime API used here is unchanged on 48: the Config feature flags, WasiConfig.inherit_* / argv, Module.deserialize_file, and Linker.define_wasi. Add test_pipeline_input_output_files_same_directory to cover the case the existing file test misses. When inputs and outputs live in separate directories, each preopen is exercised read-only or write-only; placing both in one directory collapses them to a single preopen that must serve reads and writes, pinning the read-write default this fix now relies on. Verified the test fails when the preopen is made read-only. Co-Authored-By: Claude Opus 5 (1M context) --- .../core/python/itkwasm/itkwasm/pipeline.py | 7 ++- .../core/python/itkwasm/test/test_pipeline.py | 61 +++++++++++++++++++ 2 files changed, 65 insertions(+), 3 deletions(-) diff --git a/packages/core/python/itkwasm/itkwasm/pipeline.py b/packages/core/python/itkwasm/itkwasm/pipeline.py index 497270611..c91e555c4 100644 --- a/packages/core/python/itkwasm/itkwasm/pipeline.py +++ b/packages/core/python/itkwasm/itkwasm/pipeline.py @@ -46,8 +46,6 @@ WasiConfig, Linker, WasmtimeError, - DirPerms, - FilePerms ) # Get the value of the ITKWASM_CACHE_DIR environment variable @@ -86,7 +84,10 @@ def __init__( wasi_config.argv = args for preopen in preopen_directories: - wasi_config.preopen_dir(preopen, preopen, DirPerms.READ_WRITE, FilePerms.READ_WRITE) + # Read-write access is the default in every supported wasmtime; passing + # it explicitly is not portable -- wasmtime 48 replaced the DirPerms / + # FilePerms arguments with a single fs_mutable flag. + wasi_config.preopen_dir(preopen, preopen) store.set_wasi(wasi_config) diff --git a/packages/core/python/itkwasm/test/test_pipeline.py b/packages/core/python/itkwasm/test/test_pipeline.py index a581ef57f..b3e1d9e7b 100644 --- a/packages/core/python/itkwasm/test/test_pipeline.py +++ b/packages/core/python/itkwasm/test/test_pipeline.py @@ -1,4 +1,5 @@ from pathlib import Path, PurePosixPath +import shutil import tempfile from dataclasses import asdict import sys @@ -138,6 +139,66 @@ def test_pipeline_input_output_files(): assert content[3] == 239 +@pytest.mark.skipif( + sys.platform == "win32", + reason="Windows tempfile resource, https://github.com/bytecodealliance/wasmtime-py/issues/132", +) +def test_pipeline_input_output_files_same_directory(): + """A directory preopened for both input and output is read-write. + + Inputs and outputs in the same directory collapse to a single preopen, so + that one preopen must serve reads and writes. This pins the read-write + default relied on by RunInstance.preopen_dir. + """ + pipeline = Pipeline(test_input_dir / "input-output-files-test.wasi.wasm") + + with tempfile.TemporaryDirectory() as tmpdirname: + tmpdir = Path(tmpdirname) + shutil.copy(test_input_dir / "input.txt", tmpdir / "input.txt") + shutil.copy(test_input_dir / "input.bin", tmpdir / "input.bin") + + input_text_file = PurePosixPath(tmpdir / "input.txt") + input_binary_file = PurePosixPath(tmpdir / "input.bin") + output_text_file = PurePosixPath(tmpdir / "output.txt") + output_binary_file = PurePosixPath(tmpdir / "output.bin") + + pipeline_inputs = [ + PipelineInput(InterfaceTypes.TextFile, TextFile(input_text_file)), + PipelineInput(InterfaceTypes.BinaryFile, BinaryFile(input_binary_file)), + ] + + pipeline_outputs = [ + PipelineOutput(InterfaceTypes.TextFile, TextFile(output_text_file)), + PipelineOutput(InterfaceTypes.BinaryFile, BinaryFile(output_binary_file)), + ] + + args = [ + "--memory-io", + "--use-files", + "--input-text-file", + str(input_text_file), + "--input-binary-file", + str(input_binary_file), + "--output-text-file", + str(output_text_file), + "--output-binary-file", + str(output_binary_file), + ] + + outputs = pipeline.run(args, pipeline_outputs, pipeline_inputs) + + assert outputs[0].type == InterfaceTypes.TextFile + with open(outputs[0].data.path, "r") as fp: + assert fp.read() == "The answer is 42." + assert outputs[1].type == InterfaceTypes.BinaryFile + with open(outputs[1].data.path, "rb") as fp: + content = fp.read() + assert content[0] == 222 + assert content[1] == 173 + assert content[2] == 190 + assert content[3] == 239 + + def test_pipeline_write_read_image(): pipeline = Pipeline(test_input_dir / "median-filter-test.wasi.wasm") From 1ff80455ddf533a1f2fda3814ba5370baf98475f Mon Sep 17 00:00:00 2001 From: Matt McCormick Date: Thu, 20 Aug 2026 22:06:42 -0400 Subject: [PATCH 2/2] chore(itkwasm): bump version to 1.0b201 --- packages/core/python/itkwasm/itkwasm/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/python/itkwasm/itkwasm/__init__.py b/packages/core/python/itkwasm/itkwasm/__init__.py index 39a1b866b..deea439b5 100644 --- a/packages/core/python/itkwasm/itkwasm/__init__.py +++ b/packages/core/python/itkwasm/itkwasm/__init__.py @@ -1,6 +1,6 @@ """itkwasm: Python interface to itk-wasm WebAssembly modules.""" -__version__ = "1.0b200" +__version__ = "1.0b201" from .interface_types import InterfaceTypes from .image import Image, ImageType, ImageRegion