diff --git a/Cargo.lock b/Cargo.lock index ddcb33a..bb11b15 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -482,7 +482,7 @@ dependencies = [ [[package]] name = "componentize-py" -version = "0.16.0" +version = "0.17.0" dependencies = [ "anyhow", "assert_cmd", diff --git a/Cargo.toml b/Cargo.toml index adbe7b9..c2f8e02 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "componentize-py" -version = "0.16.0" +version = "0.17.0" edition = "2021" exclude = ["cpython"] diff --git a/README.md b/README.md index 1095c03..3cd1778 100644 --- a/README.md +++ b/README.md @@ -46,8 +46,8 @@ Then, use the `hello` module produced by the command above to write your app: ```shell cat >app.py < str: return "Hello, World!" EOF diff --git a/bundled/poll_loop.py b/bundled/poll_loop.py index d6d8911..c963cdb 100644 --- a/bundled/poll_loop.py +++ b/bundled/poll_loop.py @@ -11,16 +11,16 @@ import socket import subprocess -from proxy.types import Ok, Err -from proxy.imports import types, streams, poll, outgoing_handler -from proxy.imports.types import ( +from wit_world.types import Ok, Err +from wit_world.imports import types, streams, poll, outgoing_handler +from wit_world.imports.types import ( IncomingBody, OutgoingBody, OutgoingRequest, IncomingResponse, ) -from proxy.imports.streams import StreamError_Closed, InputStream -from proxy.imports.poll import Pollable +from wit_world.imports.streams import StreamError_Closed, InputStream +from wit_world.imports.poll import Pollable from typing import Optional, cast # Maximum number of bytes to read at a time diff --git a/examples/cli/README.md b/examples/cli/README.md index 0cd4ba5..9a23c2e 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` 26.0.0 or later -* `componentize-py` 0.16.0 +* `componentize-py` 0.17.0 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/v26.0.0. ``` cargo install --version 26.0.0 wasmtime-cli -pip install componentize-py==0.16.0 +pip install componentize-py==0.17.0 ``` ## Running the demo diff --git a/examples/cli/app.py b/examples/cli/app.py index 9abeb06..d94d486 100644 --- a/examples/cli/app.py +++ b/examples/cli/app.py @@ -1,6 +1,9 @@ -from command import exports - +from wit_world import exports +from wit_world.imports.environment import get_arguments +import pdb class Run(exports.Run): def run(self) -> None: + if "--pdb" in get_arguments(): + pdb.set_trace() print("Hello, world!") diff --git a/examples/http/README.md b/examples/http/README.md index 8e88371..b97dc10 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` 26.0.0 or later -* `componentize-py` 0.16.0 +* `componentize-py` 0.17.0 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/v26.0.0. ``` cargo install --version 26.0.0 wasmtime-cli -pip install componentize-py==0.16.0 +pip install componentize-py==0.17.0 ``` ## Running the demo diff --git a/examples/http/app.py b/examples/http/app.py index de6a78a..5043427 100644 --- a/examples/http/app.py +++ b/examples/http/app.py @@ -9,10 +9,10 @@ import hashlib import poll_loop -from proxy import exports -from proxy.types import Ok -from proxy.imports import types -from proxy.imports.types import ( +from wit_world import exports +from wit_world.types import Ok +from wit_world.imports import types +from wit_world.imports.types import ( Method_Get, Method_Post, Scheme, diff --git a/examples/matrix-math/README.md b/examples/matrix-math/README.md index 64872a9..869ad02 100644 --- a/examples/matrix-math/README.md +++ b/examples/matrix-math/README.md @@ -11,7 +11,7 @@ within a guest component. ## Prerequisites * `wasmtime` 26.0.0 or later -* `componentize-py` 0.16.0 +* `componentize-py` 0.17.0 * `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/v26.0.0. ``` cargo install --version 26.0.0 wasmtime-cli -pip install componentize-py==0.16.0 +pip install componentize-py==0.17.0 curl -OL https://github.com/dicej/wasi-wheels/releases/download/v0.0.1/numpy-wasi.tar.gz tar xf numpy-wasi.tar.gz ``` diff --git a/examples/matrix-math/app.py b/examples/matrix-math/app.py index b16902e..d1b09fd 100644 --- a/examples/matrix-math/app.py +++ b/examples/matrix-math/app.py @@ -3,12 +3,12 @@ import sys import numpy -import matrix_math -from matrix_math import exports -from matrix_math.types import Err +import wit_world +from wit_world import exports +from wit_world.types import Err -class MatrixMath(matrix_math.MatrixMath): +class WitWorld(wit_world.WitWorld): def multiply(self, a: list[list[float]], b: list[list[float]]) -> list[list[float]]: print(f"matrix_multiply received arguments {a} and {b}") return numpy.matmul(a, b).tolist() # type: ignore @@ -21,4 +21,4 @@ def run(self) -> None: print("usage: matrix-math ", file=sys.stderr) exit(-1) - print(MatrixMath().multiply(eval(args[0]), eval(args[1]))) + print(WitWorld().multiply(eval(args[0]), eval(args[1]))) diff --git a/examples/sandbox/README.md b/examples/sandbox/README.md index 008cb5e..f8919b8 100644 --- a/examples/sandbox/README.md +++ b/examples/sandbox/README.md @@ -8,10 +8,10 @@ sandboxed Python code snippets from within a Python app. ## Prerequisites * `wasmtime-py` 25.0.0 or later -* `componentize-py` 0.16.0 +* `componentize-py` 0.17.0 ``` -pip install componentize-py==0.16.0 wasmtime==25.0.0 +pip install componentize-py==0.17.0 wasmtime==25.0.0 ``` ## Running the demo diff --git a/examples/sandbox/guest.py b/examples/sandbox/guest.py index 43f13e6..37355d2 100644 --- a/examples/sandbox/guest.py +++ b/examples/sandbox/guest.py @@ -1,5 +1,5 @@ -import sandbox -from sandbox.types import Err +import wit_world +from wit_world.types import Err import json @@ -11,7 +11,7 @@ def handle(e: Exception) -> Err[str]: return Err(f"{type(e).__name__}: {message}") -class Sandbox(sandbox.Sandbox): +class WitWorld(wit_world.WitWorld): def eval(self, expression: str) -> str: try: return json.dumps(eval(expression)) diff --git a/examples/tcp/README.md b/examples/tcp/README.md index 52ad715..c5636f7 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` 26.0.0 or later -* `componentize-py` 0.16.0 +* `componentize-py` 0.17.0 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/v26.0.0. ``` cargo install --version 26.0.0 wasmtime-cli -pip install componentize-py==0.16.0 +pip install componentize-py==0.17.0 ``` ## Running the demo diff --git a/examples/tcp/app.py b/examples/tcp/app.py index bb47257..00aae63 100644 --- a/examples/tcp/app.py +++ b/examples/tcp/app.py @@ -2,7 +2,7 @@ import asyncio import ipaddress from ipaddress import IPv4Address, IPv6Address -from command import exports +from wit_world import exports from typing import Tuple diff --git a/pyproject.toml b/pyproject.toml index ec5aca5..bb63671 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ features = ["pyo3/extension-module"] [project] name = "componentize-py" -version = "0.16.0" +version = "0.17.0" description = "Tool to package Python applications as WebAssembly components" readme = "README.md" license = { file = "LICENSE" } diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index fffc239..1a8d966 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -658,9 +658,7 @@ pub extern "C" fn componentize_py_get_field<'a>( .nth(field) .unwrap_or(0); - unsafe { mem::transmute::(value) } - .to_object(*py) - .into_bound(*py) + u32::cast_signed(value).to_object(*py).into_bound(*py) } Type::Option => match i32::try_from(field).unwrap() { DISCRIMINANT_FIELD_INDEX => if value.is_none() { 0 } else { 1 } @@ -865,7 +863,7 @@ pub unsafe extern "C" fn componentize_py_init<'a>( slice::from_raw_parts(data, len) .iter() .map(|v| { - mem::transmute::( + i32::cast_unsigned( Bound::from_borrowed_ptr(*py, v.as_ptr()).extract().unwrap(), ) }) diff --git a/src/command.rs b/src/command.rs index 88e5524..fe75668 100644 --- a/src/command.rs +++ b/src/command.rs @@ -66,6 +66,12 @@ pub struct Common { /// name. #[arg(long, value_parser = parse_key_value)] pub export_interface_name: Vec<(String, String)>, + + /// Optional name of top-level module to use for bindings. + /// + /// If this is not specified, the module name will default to "wit_world". + #[arg(long)] + pub world_module: Option, } #[derive(clap::Subcommand, Debug)] @@ -126,12 +132,6 @@ pub struct Bindings { /// /// This will be created if it does not already exist. pub output_dir: PathBuf, - - /// Optional name of top-level module to use for bindings. - /// - /// If this is not specified, the module name will be derived from the world name. - #[arg(long)] - pub world_module: Option, } fn parse_key_value(s: &str) -> Result<(String, String), String> { @@ -157,7 +157,7 @@ fn generate_bindings(common: Common, bindings: Bindings) -> Result<()> { common.world.as_deref(), &common.features, common.all_features, - bindings.world_module.as_deref(), + common.world_module.as_deref(), &bindings.output_dir, &common .import_interface_name @@ -189,6 +189,7 @@ fn componentize(common: Common, componentize: Componentize) -> Result<()> { common.world.as_deref(), &common.features, common.all_features, + common.world_module.as_deref(), &python_path.iter().map(|s| s.as_str()).collect::>(), &componentize .module_worlds @@ -339,6 +340,7 @@ mod tests { let common = Common { wit_path: Some(wit.path().into()), world: None, + world_module: Some("bindings".into()), quiet: false, features: vec![], all_features: false, @@ -347,7 +349,6 @@ mod tests { }; let bindings = Bindings { output_dir: out_dir.path().into(), - world_module: None, }; generate_bindings(common, bindings)?; @@ -369,6 +370,7 @@ mod tests { let common = Common { wit_path: Some(wit.path().into()), world: None, + world_module: Some("bindings".into()), quiet: false, features: vec!["x".to_owned()], all_features: false, @@ -377,7 +379,6 @@ mod tests { }; let bindings = Bindings { output_dir: out_dir.path().into(), - world_module: None, }; generate_bindings(common, bindings)?; @@ -399,6 +400,7 @@ mod tests { let common = Common { wit_path: Some(wit.path().into()), world: None, + world_module: Some("bindings".into()), quiet: false, features: vec![], all_features: true, @@ -407,7 +409,6 @@ mod tests { }; let bindings = Bindings { output_dir: out_dir.path().into(), - world_module: None, }; generate_bindings(common, bindings)?; @@ -427,6 +428,7 @@ mod tests { let common = Common { wit_path: Some(wit.path().into()), world: None, + world_module: Some("bindings".into()), quiet: false, features: vec!["x".to_owned()], all_features: false, @@ -435,7 +437,6 @@ mod tests { }; let bindings = Bindings { output_dir: out_dir.path().into(), - world_module: None, }; generate_bindings(common.clone(), bindings)?; fs::write( diff --git a/src/lib.rs b/src/lib.rs index 8c1608a..9d8951c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -42,6 +42,11 @@ mod summary; mod test; mod util; +/// The default name of the Python module containing code generated from the +/// specified WIT world. This may be overriden programatically or via the CLI +/// using the `--world-module` option. +static DEFAULT_WORLD_MODULE: &str = "wit_world"; + wasmtime::component::bindgen!({ path: "wit", world: "init", @@ -193,8 +198,7 @@ pub fn generate_bindings( import_interface_names, export_interface_names, )?; - let world_name = resolve.worlds[world].name.to_snake_case().escape(); - let world_module = world_module.unwrap_or(&world_name); + let world_module = world_module.unwrap_or(DEFAULT_WORLD_MODULE); let world_dir = output_dir.join(world_module.replace('.', "/")); fs::create_dir_all(&world_dir)?; summary.generate_code( @@ -214,6 +218,7 @@ pub async fn componentize( world: Option<&str>, features: &[String], all_features: bool, + world_module: Option<&str>, python_path: &[&str], module_worlds: &[(&str, &str)], app_name: &str, @@ -436,15 +441,15 @@ pub async fn componentize( // If the caller specified a world and we haven't already generated bindings for it above, do so now. if let (Some(world), false) = (main_world, saw_main_world) { - let module = resolve.worlds[world].name.to_snake_case(); + let module = world_module.unwrap_or(DEFAULT_WORLD_MODULE); let world_dir = tempfile::tempdir()?; - let module_path = world_dir.path().join(&module); + let module_path = world_dir.path().join(module); fs::create_dir_all(&module_path)?; - summary.generate_code(&module_path, world, &module, &mut locations, false)?; + summary.generate_code(&module_path, world, module, &mut locations, false)?; world_dir_mounts.push((vec!["world".to_owned()], world_dir)); - // The helper utilities are hard-coded to assume the world module is named `proxy`. Here we replace that - // with the actual world name. + // The helper utilities are hard-coded to assume the world module is named `wit_world`. Here we replace + // that with the actual world module name. fn replace(path: &Path, pattern: &str, replacement: &str) -> Result<()> { if path.is_dir() { for entry in fs::read_dir(path)? { @@ -461,7 +466,7 @@ pub async fn componentize( Ok(()) } - replace(embedded_helper_utils.path(), "proxy", &module)?; + replace(embedded_helper_utils.path(), "wit_world", module)?; }; for (mounts, world_dir) in world_dir_mounts.iter() { diff --git a/src/python.rs b/src/python.rs index 791993b..2c49e1e 100644 --- a/src/python.rs +++ b/src/python.rs @@ -17,12 +17,13 @@ use { #[allow(clippy::too_many_arguments)] #[pyo3::pyfunction] #[pyo3(name = "componentize")] -#[pyo3(signature = (wit_path, world, features, all_features, python_path, module_worlds, app_name, output_path, stub_wasi, import_interface_names, export_interface_names))] +#[pyo3(signature = (wit_path, world, features, all_features, world_module, python_path, module_worlds, app_name, output_path, stub_wasi, import_interface_names, export_interface_names))] fn python_componentize( wit_path: Option, world: Option<&str>, features: Vec, all_features: bool, + world_module: Option<&str>, python_path: Vec, module_worlds: Vec<(PyBackedStr, PyBackedStr)>, app_name: &str, @@ -37,6 +38,7 @@ fn python_componentize( world, &features, all_features, + world_module, &python_path.iter().map(|s| s.as_ref()).collect::>(), &module_worlds .iter() diff --git a/src/summary.rs b/src/summary.rs index da6e7da..a7d9e0f 100644 --- a/src/summary.rs +++ b/src/summary.rs @@ -1945,10 +1945,7 @@ from ..types import Result, Ok, Err, Some let mut file = File::create(path.join("__init__.py"))?; let function_imports = world_imports.functions.concat(); let type_exports = world_exports.types.concat(); - let camel = self.resolve.worlds[world] - .name - .to_upper_camel_case() - .escape(); + let camel = world_module.to_upper_camel_case().escape(); let protocol = if let Some(alias_module) = world_exports.alias_module { format!("{camel} = {alias_module}.{camel}") diff --git a/src/test.rs b/src/test.rs index b8c2ab5..0a0574e 100644 --- a/src/test.rs +++ b/src/test.rs @@ -45,6 +45,7 @@ static ENGINE: Lazy = Lazy::new(|| { #[allow(clippy::type_complexity)] async fn make_component( wit: &str, + world_module: Option<&str>, guest_code: &[(&str, &str)], python_path: &[&str], module_worlds: &[(&str, &str)], @@ -64,6 +65,7 @@ async fn make_component( None, &[], false, + world_module, &python_path .iter() .copied() @@ -119,6 +121,7 @@ struct Tester { impl Tester { fn new( wit: &str, + world_module: Option<&str>, guest_code: &[(&str, &str)], python_path: &[&str], module_worlds: &[(&str, &str)], @@ -129,6 +132,7 @@ impl Tester { // would slow it down a lot). This will help exercise the stub mechanism when pre-initializing. let component = &Runtime::new()?.block_on(make_component( wit, + world_module, guest_code, python_path, module_worlds, diff --git a/src/test/echoes.rs b/src/test/echoes.rs index 6fc5386..8c92f60 100644 --- a/src/test/echoes.rs +++ b/src/test/echoes.rs @@ -313,7 +313,15 @@ class Echoes(exports.Echoes): )]; static TESTER: Lazy> = Lazy::new(|| { - Tester::::new(include_str!("wit/echoes.wit"), GUEST_CODE, &[], &[], *SEED).unwrap() + Tester::::new( + include_str!("wit/echoes.wit"), + Some("echoes_test"), + GUEST_CODE, + &[], + &[], + *SEED, + ) + .unwrap() }); #[test] diff --git a/src/test/tests.rs b/src/test/tests.rs index 691551e..daf0d09 100644 --- a/src/test/tests.rs +++ b/src/test/tests.rs @@ -135,6 +135,7 @@ impl super::Host for BarHost { static TESTER: Lazy> = Lazy::new(|| { Tester::::new( include_str!("wit/tests.wit"), + Some("tests"), GUEST_CODE, &["src/test"], &[("foo_sdk", "foo-world"), ("bar_sdk", "bar-world")], diff --git a/test-generator/src/lib.rs b/test-generator/src/lib.rs index e9bbd2c..1e2d668 100644 --- a/test-generator/src/lib.rs +++ b/test-generator/src/lib.rs @@ -859,7 +859,14 @@ class EchoesGenerated(exports.EchoesGenerated): )]; static TESTER: Lazy> = Lazy::new(|| {{ - Tester::::new(include_str!({wit_path:?}), GUEST_CODE, &[], &[], *SEED).unwrap() + Tester::::new( + include_str!({wit_path:?}), + Some("echoes_generated_test"), + GUEST_CODE, + &[], + &[], + *SEED + ).unwrap() }}); {test_functions} diff --git a/tests/bindings.rs b/tests/bindings.rs index 3260b63..723f7de 100644 --- a/tests/bindings.rs +++ b/tests/bindings.rs @@ -21,7 +21,7 @@ fn lint_cli_bindings() -> anyhow::Result<()> { generate_bindings(&path, "wasi:cli/command@0.2.0")?; - assert!(predicate::path::is_dir().eval(&path.join("command"))); + assert!(predicate::path::is_dir().eval(&path.join("wit_world"))); mypy_check(&path, ["--strict", "."]); @@ -40,7 +40,7 @@ fn lint_http_bindings() -> anyhow::Result<()> { generate_bindings(&path, "wasi:http/proxy@0.2.0")?; - assert!(predicate::path::is_dir().eval(&path.join("proxy"))); + assert!(predicate::path::is_dir().eval(&path.join("wit_world"))); mypy_check( &path, @@ -51,7 +51,7 @@ fn lint_http_bindings() -> anyhow::Result<()> { "-m", "app", "-p", - "proxy", + "wit_world", ], ); @@ -72,7 +72,7 @@ fn lint_matrix_math_bindings() -> anyhow::Result<()> { generate_bindings(&path, "matrix-math")?; - assert!(predicate::path::is_dir().eval(&path.join("matrix_math"))); + assert!(predicate::path::is_dir().eval(&path.join("wit_world"))); mypy_check( &path, @@ -84,7 +84,7 @@ fn lint_matrix_math_bindings() -> anyhow::Result<()> { "-m", "app", "-p", - "matrix_math", + "wit_world", ], ); @@ -103,9 +103,9 @@ fn lint_sandbox_bindings() -> anyhow::Result<()> { .assert() .success(); - assert!(predicate::path::is_dir().eval(&path.join("sandbox"))); + assert!(predicate::path::is_dir().eval(&path.join("wit_world"))); - mypy_check(&path, ["--strict", "-m", "guest", "-p", "sandbox"]); + mypy_check(&path, ["--strict", "-m", "guest", "-p", "wit_world"]); Ok(()) } @@ -122,7 +122,7 @@ fn lint_tcp_bindings() -> anyhow::Result<()> { generate_bindings(&path, "wasi:cli/command@0.2.0")?; - assert!(predicate::path::is_dir().eval(&path.join("command"))); + assert!(predicate::path::is_dir().eval(&path.join("wit_world"))); mypy_check(&path, ["--strict", "."]);