From 07baf557cba4b2f0641fff93d7c3b770a975b89e Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Wed, 8 Apr 2026 16:17:43 -0600 Subject: [PATCH 1/2] make declaration order stable when generating bindings Previously, the binding generator was non-deterministic in that it would generate different output for the same input across several runs due to the use of randomly-seeded `HashMap`s and `HashSet`s. Now we ensure the iteration order over those maps and sets is deterministic by first converting them to `BTreeMap`s and `BTreeSet`s, respectively. Also, the `Raises` clause we were adding to docstrings had not been updated to point to `componentize_py_types.Err`, so I fixed that as well. --- src/summary.rs | 58 ++++++++++++++++++++++++++++---------------------- 1 file changed, 32 insertions(+), 26 deletions(-) diff --git a/src/summary.rs b/src/summary.rs index 9429fff..a2f170d 100644 --- a/src/summary.rs +++ b/src/summary.rs @@ -11,7 +11,7 @@ use { indexmap::{IndexMap, IndexSet}, semver::Version, std::{ - collections::{HashMap, HashSet, hash_map::Entry}, + collections::{BTreeMap, BTreeSet, HashMap, HashSet, hash_map::Entry}, fmt::Write as _, fs::{self, File}, io::Write as _, @@ -1448,7 +1448,7 @@ impl<'a> Summary<'a> { "pass".to_owned().clone_into(&mut fields) } - let docs = docstring(world_module, docs, 1, None); + let docs = docstring(docs, 1, None); format!( " @@ -1505,7 +1505,7 @@ class {name}: .collect::>() .join(", "); - let docs = docstring(world_module, ty.docs.contents.as_deref(), 0, None); + let docs = docstring(ty.docs.contents.as_deref(), 0, None); ( Some(Code::Shared(format!( @@ -1540,7 +1540,7 @@ class {name}: .collect::>() .join("\n "); - let docs = docstring(world_module, ty.docs.contents.as_deref(), 1, None); + let docs = docstring(ty.docs.contents.as_deref(), 1, None); ( Some(Code::Shared(format!( @@ -1567,7 +1567,7 @@ class {camel}(Enum): flags }; - let docs = docstring(world_module, ty.docs.contents.as_deref(), 1, None); + let docs = docstring(ty.docs.contents.as_deref(), 1, None); ( Some(Code::Shared(format!( @@ -1582,7 +1582,7 @@ class {camel}(Flag): TypeDefKind::Resource => { let camel = camel(); - let docs = docstring(world_module, ty.docs.contents.as_deref(), 1, None); + let docs = docstring(ty.docs.contents.as_deref(), 1, None); let empty = &ResourceInfo::default(); @@ -1605,8 +1605,7 @@ class {camel}(Flag): Some(id), ); - let docs = - docstring(world_module, function.docs, 2, error.as_deref()); + let docs = docstring(function.docs, 2, error.as_deref()); if let wit_parser::FunctionKind::Constructor(_) = function.wit_kind { @@ -1710,8 +1709,7 @@ class {camel}: Some(id), ); - let docs = - docstring(world_module, function.docs, 2, error.as_deref()); + let docs = docstring(function.docs, 2, error.as_deref()); self.generate_export_code( stub_runtime_calls, @@ -1856,7 +1854,13 @@ def {snake}_future(default: Callable[[], {camel}]) -> tuple[FutureWriter[{camel} let aliases = if let (Some(code), false) = (code.as_ref(), names.is_empty()) { let aliases = iter::once(world_module_import(world_module, "peer")) - .chain(names.iter().map(|name| format!("{name} = peer.{name}"))) + .chain( + names + .iter() + .collect::>() + .into_iter() + .map(|name| format!("{name} = peer.{name}")), + ) .collect::>() .join("\n"); @@ -1990,7 +1994,7 @@ def {snake}_future(default: Callable[[], {camel}]) -> tuple[FutureWriter[{camel} match function.kind { FunctionKind::Import => { - let docs = docstring(world_module, function.docs, 1, error.as_deref()); + let docs = docstring(function.docs, 1, error.as_deref()); let code = self.generate_import_code( 0, @@ -2046,8 +2050,7 @@ def {snake}_future(default: Callable[[], {camel}]) -> tuple[FutureWriter[{camel} format!("self, {params}") }; - let function_docs = - docstring(world_module, function.docs, 2, error.as_deref()); + let function_docs = docstring(function.docs, 2, error.as_deref()); let code = self.generate_export_code( stub_runtime_calls, @@ -2094,7 +2097,7 @@ from componentize_py_async_support.futures import FutureReader, FutureWriter"; let dir = path.join("imports"); fs::create_dir(&dir)?; File::create(dir.join("__init__.py"))?; - for (id, code) in interface_imports { + for (id, code) in interface_imports.into_iter().collect::>() { let name = self.imported_interface_names.get(&id).unwrap(); let mut file = File::create(dir.join(format!("{}.py", name.to_snake_case().escape())))?; @@ -2103,12 +2106,14 @@ from componentize_py_async_support.futures import FutureReader, FutureWriter"; let imports = code .type_imports .union(&code.function_imports) + .collect::>() + .into_iter() .map(|&interface| import("..", interface)) .chain(self.need_async.then(|| async_imports.into())) .chain((!stub_runtime_calls).then(|| "import componentize_py_runtime".into())) .collect::>() .join("\n"); - let docs = docstring(world_module, code.docs, 0, None); + let docs = docstring(code.docs, 0, None); write!( file, @@ -2128,7 +2133,7 @@ from componentize_py_types import Result, Ok, Err, Some let mut protocol_imports = HashSet::new(); let mut protocols = String::new(); - for (id, code) in interface_exports { + for (id, code) in interface_exports.into_iter().collect::>() { let name = self.exported_interface_names.get(&id).unwrap(); let mut file = File::create(dir.join(format!("{}.py", name.to_snake_case().escape())))?; @@ -2136,11 +2141,13 @@ from componentize_py_types import Result, Ok, Err, Some let imports = code .type_imports .into_iter() + .collect::>() + .into_iter() .map(|interface| import("..", interface)) .chain(self.need_async.then(|| async_imports.into())) .collect::>() .join("\n"); - let docs = docstring(world_module, code.docs, 0, None); + let docs = docstring(code.docs, 0, None); write!( file, @@ -2184,6 +2191,8 @@ class {camel}(Protocol): let mut init = File::create(dir.join("__init__.py"))?; let imports = protocol_imports + .into_iter() + .collect::>() .into_iter() .map(|interface| import("..", interface)) .chain(self.need_async.then(|| async_imports.into())) @@ -2230,13 +2239,15 @@ from componentize_py_types import Result, Ok, Err, Some .copied() .collect(), ) + .collect::>() + .into_iter() .map(|&interface| import(".", interface)) .chain(self.need_async.then(|| async_imports.into())) .chain((!stub_runtime_calls).then(|| "import componentize_py_runtime".into())) .collect::>() .join("\n"); - let docs = docstring(world_module, world_exports.docs, 0, None); + let docs = docstring(world_exports.docs, 0, None); write!( file, @@ -2620,15 +2631,10 @@ fn world_module_import(name: &str, alias: &str) -> String { } } -fn docstring( - world_module: &str, - docs: Option<&str>, - indent_level: usize, - error: Option<&str>, -) -> String { +fn docstring(docs: Option<&str>, indent_level: usize, error: Option<&str>) -> String { let docs = match ( docs, - error.map(|e| format!("Raises: `{world_module}.types.Err({e})`")), + error.map(|e| format!("Raises: `componentize_py_types.Err({e})`")), ) { (Some(docs), Some(error_docs)) => Some(format!("{docs}\n\n{error_docs}")), (Some(docs), None) => Some(docs.to_owned()), From ab952eb0b8d3f8785d108890d9f7975105071b39 Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Wed, 8 Apr 2026 16:22:28 -0600 Subject: [PATCH 2/2] bump version to 0.22.1 --- Cargo.lock | 2 +- Cargo.toml | 2 +- examples/cli-p3/README.md | 4 ++-- examples/cli/README.md | 4 ++-- examples/http-p3/README.md | 4 ++-- examples/http/README.md | 4 ++-- examples/matrix-math/README.md | 4 ++-- examples/sandbox/README.md | 4 ++-- examples/tcp-p3/README.md | 4 ++-- examples/tcp/README.md | 4 ++-- pyproject.toml | 2 +- 11 files changed, 19 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 13c9229..eccc775 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -486,7 +486,7 @@ dependencies = [ [[package]] name = "componentize-py" -version = "0.22.0" +version = "0.22.1" dependencies = [ "anyhow", "assert_cmd", diff --git a/Cargo.toml b/Cargo.toml index ebdb7d1..7a0f92f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "componentize-py" -version = "0.22.0" +version = "0.22.1" edition = "2024" exclude = ["cpython"] diff --git a/examples/cli-p3/README.md b/examples/cli-p3/README.md index ff1c148..b3ee3e4 100644 --- a/examples/cli-p3/README.md +++ b/examples/cli-p3/README.md @@ -11,7 +11,7 @@ run a Python-based component targetting version `0.3.0-rc-2026-03-15` of the ## Prerequisites * `Wasmtime` 43.0.0 -* `componentize-py` 0.22.0 +* `componentize-py` 0.22.1 Below, we use [Rust](https://rustup.rs/)'s `cargo` to install `Wasmtime`. If you don't have `cargo`, you can download and install from @@ -19,7 +19,7 @@ https://github.com/bytecodealliance/wasmtime/releases/tag/v43.0.0. ``` cargo install --version 43.0.0 wasmtime-cli -pip install componentize-py==0.22.0 +pip install componentize-py==0.22.1 ``` ## Running the demo diff --git a/examples/cli/README.md b/examples/cli/README.md index 3ffc1e0..30f77d2 100644 --- a/examples/cli/README.md +++ b/examples/cli/README.md @@ -10,7 +10,7 @@ run a Python-based component targetting the [wasi-cli] `command` world. ## Prerequisites * `Wasmtime` 38.0.0 or later -* `componentize-py` 0.22.0 +* `componentize-py` 0.22.1 Below, we use [Rust](https://rustup.rs/)'s `cargo` to install `Wasmtime`. If you don't have `cargo`, you can download and install from @@ -18,7 +18,7 @@ https://github.com/bytecodealliance/wasmtime/releases/tag/v38.0.0. ``` cargo install --version 38.0.0 wasmtime-cli -pip install componentize-py==0.22.0 +pip install componentize-py==0.22.1 ``` ## Running the demo diff --git a/examples/http-p3/README.md b/examples/http-p3/README.md index dfafb2e..5d4f37d 100644 --- a/examples/http-p3/README.md +++ b/examples/http-p3/README.md @@ -11,7 +11,7 @@ run a Python-based component targetting version `0.3.0-rc-2026-03-15` of the ## Prerequisites * `Wasmtime` 43.0.0 -* `componentize-py` 0.22.0 +* `componentize-py` 0.22.1 Below, we use [Rust](https://rustup.rs/)'s `cargo` to install `Wasmtime`. If you don't have `cargo`, you can download and install from @@ -19,7 +19,7 @@ https://github.com/bytecodealliance/wasmtime/releases/tag/v43.0.0. ``` cargo install --version 43.0.0 wasmtime-cli -pip install componentize-py==0.22.0 +pip install componentize-py==0.22.1 ``` ## Running the demo diff --git a/examples/http/README.md b/examples/http/README.md index 0f7ae37..042c27d 100644 --- a/examples/http/README.md +++ b/examples/http/README.md @@ -10,7 +10,7 @@ run a Python-based component targetting the [wasi-http] `proxy` world. ## Prerequisites * `Wasmtime` 38.0.0 or later -* `componentize-py` 0.22.0 +* `componentize-py` 0.22.1 Below, we use [Rust](https://rustup.rs/)'s `cargo` to install `Wasmtime`. If you don't have `cargo`, you can download and install from @@ -18,7 +18,7 @@ https://github.com/bytecodealliance/wasmtime/releases/tag/v38.0.0. ``` cargo install --version 38.0.0 wasmtime-cli -pip install componentize-py==0.22.0 +pip install componentize-py==0.22.1 ``` ## Running the demo diff --git a/examples/matrix-math/README.md b/examples/matrix-math/README.md index a015808..7c46497 100644 --- a/examples/matrix-math/README.md +++ b/examples/matrix-math/README.md @@ -11,7 +11,7 @@ within a guest component. ## Prerequisites * `wasmtime` 38.0.0 or later -* `componentize-py` 0.22.0 +* `componentize-py` 0.22.1 * `NumPy`, built for WASI Note that we use an unofficial build of NumPy since the upstream project does @@ -23,7 +23,7 @@ https://github.com/bytecodealliance/wasmtime/releases/tag/v38.0.0. ``` cargo install --version 38.0.0 wasmtime-cli -pip install componentize-py==0.22.0 +pip install componentize-py==0.22.1 curl -OL https://github.com/dicej/wasi-wheels/releases/download/v0.0.2/numpy-wasi.tar.gz tar xf numpy-wasi.tar.gz ``` diff --git a/examples/sandbox/README.md b/examples/sandbox/README.md index d1fcbd0..ac46056 100644 --- a/examples/sandbox/README.md +++ b/examples/sandbox/README.md @@ -12,10 +12,10 @@ versions have a different API for working with components, and this example has not yet been updated to use it. * `wasmtime-py` 38.0.0 -* `componentize-py` 0.22.0 +* `componentize-py` 0.22.1 ``` -pip install componentize-py==0.22.0 wasmtime==38.0.0 +pip install componentize-py==0.22.1 wasmtime==38.0.0 ``` ## Running the demo diff --git a/examples/tcp-p3/README.md b/examples/tcp-p3/README.md index ad72799..bd7fb9b 100644 --- a/examples/tcp-p3/README.md +++ b/examples/tcp-p3/README.md @@ -12,7 +12,7 @@ run a Python-based component targetting version `0.3.0-rc-2026-03-15` of the ## Prerequisites * `Wasmtime` 43.0.0 -* `componentize-py` 0.22.0 +* `componentize-py` 0.22.1 Below, we use [Rust](https://rustup.rs/)'s `cargo` to install `Wasmtime`. If you don't have `cargo`, you can download and install from @@ -20,7 +20,7 @@ https://github.com/bytecodealliance/wasmtime/releases/tag/v43.0.0. ``` cargo install --version 43.0.0 wasmtime-cli -pip install componentize-py==0.22.0 +pip install componentize-py==0.22.1 ``` ## Running the demo diff --git a/examples/tcp/README.md b/examples/tcp/README.md index d1dad14..6eea088 100644 --- a/examples/tcp/README.md +++ b/examples/tcp/README.md @@ -11,7 +11,7 @@ making an outbound TCP request using `wasi-sockets`. ## Prerequisites * `Wasmtime` 38.0.0 or later -* `componentize-py` 0.22.0 +* `componentize-py` 0.22.1 Below, we use [Rust](https://rustup.rs/)'s `cargo` to install `Wasmtime`. If you don't have `cargo`, you can download and install from @@ -19,7 +19,7 @@ https://github.com/bytecodealliance/wasmtime/releases/tag/v38.0.0. ``` cargo install --version 38.0.0 wasmtime-cli -pip install componentize-py==0.22.0 +pip install componentize-py==0.22.1 ``` ## Running the demo diff --git a/pyproject.toml b/pyproject.toml index 8cdb5c0..c8429f6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ features = ["pyo3/extension-module"] [project] name = "componentize-py" -version = "0.22.0" +version = "0.22.1" description = "Tool to package Python applications as WebAssembly components" readme = "README.md" license = { file = "LICENSE" }