From a4abb83af60dd8869070b794ffd6f940b4a639be Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Fri, 11 Sep 2026 14:52:41 -0600 Subject: [PATCH] update to `spin:up@4.1.0` worlds - update WIT files and regenerate bindings - update handwritten code to target WASI 0.3.0 interfaces - update mysql bindings to use new async interface - bump componentize-py version to v0.25.1 - bump version to v5.0.0 Fixes #157 Signed-off-by: Joel Dice --- .github/workflows/build.yml | 5 +- CONTRIBUTING.md | 4 +- README.md | 4 +- .../external-lib-example/requirements.txt | 4 +- examples/external-lib-example/spin.toml | 2 +- examples/hello/requirements.txt | 4 +- examples/hello/spin.toml | 2 +- examples/matrix-math/requirements.txt | 4 +- examples/matrix-math/spin.toml | 2 +- examples/outgoing-request/requirements.txt | 4 +- examples/outgoing-request/spin.toml | 2 +- examples/redis-trigger/requirements.txt | 4 +- examples/spin-kv/requirements.txt | 4 +- examples/spin-kv/spin.toml | 2 +- examples/spin-llm/requirements.txt | 4 +- examples/spin-llm/spin.toml | 2 +- examples/spin-mysql/app.py | 4 +- examples/spin-mysql/requirements.txt | 4 +- examples/spin-mysql/spin.toml | 2 +- examples/spin-outbound-mqtt/requirements.txt | 4 +- examples/spin-outbound-mqtt/spin.toml | 2 +- examples/spin-postgres/requirements.txt | 4 +- examples/spin-postgres/spin.toml | 2 +- examples/spin-redis/requirements.txt | 4 +- examples/spin-redis/spin.toml | 2 +- examples/spin-sqlite/requirements.txt | 4 +- examples/spin-sqlite/spin.toml | 2 +- examples/spin-variables/requirements.txt | 4 +- examples/spin-variables/spin.toml | 2 +- examples/streaming/app.py | 8 +- examples/streaming/requirements.txt | 4 +- examples/streaming/spin.toml | 2 +- pyproject.toml | 2 +- regenerate_bindings.sh | 10 +- src/componentize_py_async_support/__init__.py | 41 + src/componentize_py_async_support/futures.py | 64 +- src/componentize_py_async_support/streams.py | 188 +++- src/componentize_py_runtime.py | 6 + src/componentize_py_runtime.pyi | 6 + src/componentize_py_types.py | 13 + src/spin_sdk/componentize-py.toml | 2 +- src/spin_sdk/http/__init__.py | 10 +- src/spin_sdk/mysql.py | 6 +- src/spin_sdk/wit/__init__.py | 34 +- src/spin_sdk/wit/deps/cli.wit | 256 +++++ src/spin_sdk/wit/deps/clocks.wit | 161 +++ src/spin_sdk/wit/deps/filesystem.wit | 575 +++++++++++ src/spin_sdk/wit/deps/http.wit | 509 ++++++++++ src/spin_sdk/wit/deps/random.wit | 107 ++ src/spin_sdk/wit/deps/sockets.wit | 839 +++++++++++++++ .../wit/deps/spin-mysql@3.0.0/mysql.wit | 88 ++ src/spin_sdk/wit/deps/spin@4.0.0/world.wit | 34 + src/spin_sdk/wit/exports/__init__.py | 6 +- .../wit/imports/spin_mysql_mysql_3_0_0.py | 271 +++++ .../wit/imports/wasi_cli_environment_0_3_0.py | 38 + .../wit/imports/wasi_cli_exit_0_3_0.py | 32 + .../wit/imports/wasi_cli_stderr_0_3_0.py | 28 + .../wit/imports/wasi_cli_stdin_0_3_0.py | 33 + .../wit/imports/wasi_cli_stdout_0_3_0.py | 28 + .../imports/wasi_cli_terminal_input_0_3_0.py | 38 + .../imports/wasi_cli_terminal_output_0_3_0.py | 38 + .../imports/wasi_cli_terminal_stderr_0_3_0.py | 26 + .../imports/wasi_cli_terminal_stdin_0_3_0.py | 26 + .../imports/wasi_cli_terminal_stdout_0_3_0.py | 26 + .../wit/imports/wasi_cli_types_0_3_0.py | 20 + .../wasi_clocks_monotonic_clock_0_3_0.py | 54 + .../imports/wasi_clocks_system_clock_0_3_0.py | 66 ++ .../imports/wasi_filesystem_preopens_0_3_0.py | 21 + .../imports/wasi_filesystem_types_0_3_0.py | 737 ++++++++++++++ .../wit/imports/wasi_http_client_0_3_0.py | 36 + .../wit/imports/wasi_http_types_0_3_0.py | 815 +++++++++++++++ .../wit/imports/wasi_random_insecure_0_3_0.py | 48 + .../wasi_random_insecure_seed_0_3_0.py | 42 + .../wit/imports/wasi_random_random_0_3_0.py | 53 + .../wasi_sockets_ip_name_lookup_0_3_0.py | 78 ++ .../wit/imports/wasi_sockets_types_0_3_0.py | 957 ++++++++++++++++++ src/spin_sdk/wit/world.wit | 7 +- templates/http-py/content/requirements.txt | 4 +- templates/http-py/content/spin.toml | 2 +- 79 files changed, 6489 insertions(+), 99 deletions(-) create mode 100644 src/spin_sdk/wit/deps/cli.wit create mode 100644 src/spin_sdk/wit/deps/clocks.wit create mode 100644 src/spin_sdk/wit/deps/filesystem.wit create mode 100644 src/spin_sdk/wit/deps/http.wit create mode 100644 src/spin_sdk/wit/deps/random.wit create mode 100644 src/spin_sdk/wit/deps/sockets.wit create mode 100644 src/spin_sdk/wit/deps/spin-mysql@3.0.0/mysql.wit create mode 100644 src/spin_sdk/wit/deps/spin@4.0.0/world.wit create mode 100644 src/spin_sdk/wit/imports/spin_mysql_mysql_3_0_0.py create mode 100644 src/spin_sdk/wit/imports/wasi_cli_environment_0_3_0.py create mode 100644 src/spin_sdk/wit/imports/wasi_cli_exit_0_3_0.py create mode 100644 src/spin_sdk/wit/imports/wasi_cli_stderr_0_3_0.py create mode 100644 src/spin_sdk/wit/imports/wasi_cli_stdin_0_3_0.py create mode 100644 src/spin_sdk/wit/imports/wasi_cli_stdout_0_3_0.py create mode 100644 src/spin_sdk/wit/imports/wasi_cli_terminal_input_0_3_0.py create mode 100644 src/spin_sdk/wit/imports/wasi_cli_terminal_output_0_3_0.py create mode 100644 src/spin_sdk/wit/imports/wasi_cli_terminal_stderr_0_3_0.py create mode 100644 src/spin_sdk/wit/imports/wasi_cli_terminal_stdin_0_3_0.py create mode 100644 src/spin_sdk/wit/imports/wasi_cli_terminal_stdout_0_3_0.py create mode 100644 src/spin_sdk/wit/imports/wasi_cli_types_0_3_0.py create mode 100644 src/spin_sdk/wit/imports/wasi_clocks_monotonic_clock_0_3_0.py create mode 100644 src/spin_sdk/wit/imports/wasi_clocks_system_clock_0_3_0.py create mode 100644 src/spin_sdk/wit/imports/wasi_filesystem_preopens_0_3_0.py create mode 100644 src/spin_sdk/wit/imports/wasi_filesystem_types_0_3_0.py create mode 100644 src/spin_sdk/wit/imports/wasi_http_client_0_3_0.py create mode 100644 src/spin_sdk/wit/imports/wasi_http_types_0_3_0.py create mode 100644 src/spin_sdk/wit/imports/wasi_random_insecure_0_3_0.py create mode 100644 src/spin_sdk/wit/imports/wasi_random_insecure_seed_0_3_0.py create mode 100644 src/spin_sdk/wit/imports/wasi_random_random_0_3_0.py create mode 100644 src/spin_sdk/wit/imports/wasi_sockets_ip_name_lookup_0_3_0.py create mode 100644 src/spin_sdk/wit/imports/wasi_sockets_types_0_3_0.py diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 69a5f0e..5444b33 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -16,14 +16,13 @@ jobs: - name: Install Spin uses: fermyon/actions/spin/setup@v1 with: - # TODO: Switch to Spin 4.0 when it's available - version: "canary" + version: "v4.1.0" - name: Build examples and run tests run: | python -m venv venv source venv/bin/activate - pip install componentize-py==0.23.0 http-router==4.1.2 build==1.4.2 mypy==1.13 + pip install componentize-py==0.25.1 http-router==4.1.2 build==1.4.2 mypy==1.13 python -m build VERSION=$(yq '.project.version' pyproject.toml) pip install dist/spin_sdk-${VERSION//-}-py3-none-any.whl diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index eb462f0..ad45a67 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,12 +4,12 @@ - Python - `pip` -- `componentize-py` 0.23.0 +- `componentize-py` 0.25.1 Once you have `pip` installed, you can install `componentize-py` using: ```bash -pip install componentize-py==0.23.0 +pip install componentize-py==0.25.1 ``` ### Generating the bindings diff --git a/README.md b/README.md index 6990237..db9c9bc 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ enter a virtual environment and then install the desired packages ```shell python -m venv .venv source .venv/bin/activate -pip install componentize-py==0.23.0 spin-sdk==4.0.0 mypy==1.8.0 +pip install componentize-py==0.25.1 spin-sdk==5.0.0 mypy==1.8.0 ``` ### Hello, World @@ -48,7 +48,7 @@ component = "hello" [component.hello] source = "app.wasm" [component.hello.build] -command = "componentize-py -w spin:up/http-trigger@4.0.0 componentize app -o app.wasm" +command = "componentize-py -w spin:up/http-trigger@4.1.0 componentize app -o app.wasm" EOF ``` diff --git a/examples/external-lib-example/requirements.txt b/examples/external-lib-example/requirements.txt index 5209022..75e99f3 100644 --- a/examples/external-lib-example/requirements.txt +++ b/examples/external-lib-example/requirements.txt @@ -1,3 +1,3 @@ -spin-sdk == 4.0.0 -componentize-py == 0.23.0 +spin-sdk == 5.0.0 +componentize-py == 0.25.1 http-router == 4.1.2 \ No newline at end of file diff --git a/examples/external-lib-example/spin.toml b/examples/external-lib-example/spin.toml index 2ee23d3..d84b781 100644 --- a/examples/external-lib-example/spin.toml +++ b/examples/external-lib-example/spin.toml @@ -14,4 +14,4 @@ component = "test" source = "app.wasm" allowed_outbound_hosts = [] [component.test.build] -command = "componentize-py -w spin:up/http-trigger@4.0.0 componentize app -o app.wasm" +command = "componentize-py -w spin:up/http-trigger@4.1.0 componentize app -o app.wasm" diff --git a/examples/hello/requirements.txt b/examples/hello/requirements.txt index 5f25c4e..be8038e 100644 --- a/examples/hello/requirements.txt +++ b/examples/hello/requirements.txt @@ -1,2 +1,2 @@ -spin-sdk == 4.0.0 -componentize-py == 0.23.0 \ No newline at end of file +spin-sdk == 5.0.0 +componentize-py == 0.25.1 \ No newline at end of file diff --git a/examples/hello/spin.toml b/examples/hello/spin.toml index 2ee23d3..d84b781 100644 --- a/examples/hello/spin.toml +++ b/examples/hello/spin.toml @@ -14,4 +14,4 @@ component = "test" source = "app.wasm" allowed_outbound_hosts = [] [component.test.build] -command = "componentize-py -w spin:up/http-trigger@4.0.0 componentize app -o app.wasm" +command = "componentize-py -w spin:up/http-trigger@4.1.0 componentize app -o app.wasm" diff --git a/examples/matrix-math/requirements.txt b/examples/matrix-math/requirements.txt index 5f25c4e..be8038e 100644 --- a/examples/matrix-math/requirements.txt +++ b/examples/matrix-math/requirements.txt @@ -1,2 +1,2 @@ -spin-sdk == 4.0.0 -componentize-py == 0.23.0 \ No newline at end of file +spin-sdk == 5.0.0 +componentize-py == 0.25.1 \ No newline at end of file diff --git a/examples/matrix-math/spin.toml b/examples/matrix-math/spin.toml index 2ee23d3..d84b781 100644 --- a/examples/matrix-math/spin.toml +++ b/examples/matrix-math/spin.toml @@ -14,4 +14,4 @@ component = "test" source = "app.wasm" allowed_outbound_hosts = [] [component.test.build] -command = "componentize-py -w spin:up/http-trigger@4.0.0 componentize app -o app.wasm" +command = "componentize-py -w spin:up/http-trigger@4.1.0 componentize app -o app.wasm" diff --git a/examples/outgoing-request/requirements.txt b/examples/outgoing-request/requirements.txt index 5f25c4e..be8038e 100644 --- a/examples/outgoing-request/requirements.txt +++ b/examples/outgoing-request/requirements.txt @@ -1,2 +1,2 @@ -spin-sdk == 4.0.0 -componentize-py == 0.23.0 \ No newline at end of file +spin-sdk == 5.0.0 +componentize-py == 0.25.1 \ No newline at end of file diff --git a/examples/outgoing-request/spin.toml b/examples/outgoing-request/spin.toml index 8fa5525..9540f39 100644 --- a/examples/outgoing-request/spin.toml +++ b/examples/outgoing-request/spin.toml @@ -14,4 +14,4 @@ component = "test" source = "app.wasm" allowed_outbound_hosts = ["http://*:*", "https://*:*"] [component.test.build] -command = "componentize-py -w spin:up/http-trigger@4.0.0 componentize app -o app.wasm" +command = "componentize-py -w spin:up/http-trigger@4.1.0 componentize app -o app.wasm" diff --git a/examples/redis-trigger/requirements.txt b/examples/redis-trigger/requirements.txt index 5f25c4e..be8038e 100644 --- a/examples/redis-trigger/requirements.txt +++ b/examples/redis-trigger/requirements.txt @@ -1,2 +1,2 @@ -spin-sdk == 4.0.0 -componentize-py == 0.23.0 \ No newline at end of file +spin-sdk == 5.0.0 +componentize-py == 0.25.1 \ No newline at end of file diff --git a/examples/spin-kv/requirements.txt b/examples/spin-kv/requirements.txt index 5f25c4e..be8038e 100644 --- a/examples/spin-kv/requirements.txt +++ b/examples/spin-kv/requirements.txt @@ -1,2 +1,2 @@ -spin-sdk == 4.0.0 -componentize-py == 0.23.0 \ No newline at end of file +spin-sdk == 5.0.0 +componentize-py == 0.25.1 \ No newline at end of file diff --git a/examples/spin-kv/spin.toml b/examples/spin-kv/spin.toml index 98fd8e6..cedbb1e 100644 --- a/examples/spin-kv/spin.toml +++ b/examples/spin-kv/spin.toml @@ -15,4 +15,4 @@ source = "app.wasm" allowed_outbound_hosts = [] key_value_stores = ["default"] [component.test.build] -command = "componentize-py -w spin:up/http-trigger@4.0.0 componentize app -o app.wasm" +command = "componentize-py -w spin:up/http-trigger@4.1.0 componentize app -o app.wasm" diff --git a/examples/spin-llm/requirements.txt b/examples/spin-llm/requirements.txt index 5f25c4e..be8038e 100644 --- a/examples/spin-llm/requirements.txt +++ b/examples/spin-llm/requirements.txt @@ -1,2 +1,2 @@ -spin-sdk == 4.0.0 -componentize-py == 0.23.0 \ No newline at end of file +spin-sdk == 5.0.0 +componentize-py == 0.25.1 \ No newline at end of file diff --git a/examples/spin-llm/spin.toml b/examples/spin-llm/spin.toml index 2c97b59..68be658 100644 --- a/examples/spin-llm/spin.toml +++ b/examples/spin-llm/spin.toml @@ -15,4 +15,4 @@ source = "app.wasm" allowed_outbound_hosts = [] ai_models = ["all-minilm-l6-v2"] [component.test.build] -command = "componentize-py -w spin:up/http-trigger@4.0.0 componentize app -o app.wasm" +command = "componentize-py -w spin:up/http-trigger@4.1.0 componentize app -o app.wasm" diff --git a/examples/spin-mysql/app.py b/examples/spin-mysql/app.py index 368ca46..ae5f57f 100644 --- a/examples/spin-mysql/app.py +++ b/examples/spin-mysql/app.py @@ -3,8 +3,8 @@ class HttpHandler(http.Handler): async def handle_request(self, request: Request) -> Response: - with mysql.open("mysql://root:@127.0.0.1/spin_dev") as db: - print(db.query("select * from test", [])) + with await mysql.open("mysql://root:@127.0.0.1/spin_dev") as db: + print(await db.query("select * from test", [])) return Response( 200, diff --git a/examples/spin-mysql/requirements.txt b/examples/spin-mysql/requirements.txt index 5f25c4e..be8038e 100644 --- a/examples/spin-mysql/requirements.txt +++ b/examples/spin-mysql/requirements.txt @@ -1,2 +1,2 @@ -spin-sdk == 4.0.0 -componentize-py == 0.23.0 \ No newline at end of file +spin-sdk == 5.0.0 +componentize-py == 0.25.1 \ No newline at end of file diff --git a/examples/spin-mysql/spin.toml b/examples/spin-mysql/spin.toml index baea96c..2a086f7 100644 --- a/examples/spin-mysql/spin.toml +++ b/examples/spin-mysql/spin.toml @@ -14,4 +14,4 @@ component = "test" source = "app.wasm" allowed_outbound_hosts = ["*://*:*"] [component.test.build] -command = "componentize-py -w spin:up/http-trigger@4.0.0 componentize app -o app.wasm" +command = "componentize-py -w spin:up/http-trigger@4.1.0 componentize app -o app.wasm" diff --git a/examples/spin-outbound-mqtt/requirements.txt b/examples/spin-outbound-mqtt/requirements.txt index 5f25c4e..be8038e 100644 --- a/examples/spin-outbound-mqtt/requirements.txt +++ b/examples/spin-outbound-mqtt/requirements.txt @@ -1,2 +1,2 @@ -spin-sdk == 4.0.0 -componentize-py == 0.23.0 \ No newline at end of file +spin-sdk == 5.0.0 +componentize-py == 0.25.1 \ No newline at end of file diff --git a/examples/spin-outbound-mqtt/spin.toml b/examples/spin-outbound-mqtt/spin.toml index baea96c..2a086f7 100644 --- a/examples/spin-outbound-mqtt/spin.toml +++ b/examples/spin-outbound-mqtt/spin.toml @@ -14,4 +14,4 @@ component = "test" source = "app.wasm" allowed_outbound_hosts = ["*://*:*"] [component.test.build] -command = "componentize-py -w spin:up/http-trigger@4.0.0 componentize app -o app.wasm" +command = "componentize-py -w spin:up/http-trigger@4.1.0 componentize app -o app.wasm" diff --git a/examples/spin-postgres/requirements.txt b/examples/spin-postgres/requirements.txt index 5f25c4e..be8038e 100644 --- a/examples/spin-postgres/requirements.txt +++ b/examples/spin-postgres/requirements.txt @@ -1,2 +1,2 @@ -spin-sdk == 4.0.0 -componentize-py == 0.23.0 \ No newline at end of file +spin-sdk == 5.0.0 +componentize-py == 0.25.1 \ No newline at end of file diff --git a/examples/spin-postgres/spin.toml b/examples/spin-postgres/spin.toml index baea96c..2a086f7 100644 --- a/examples/spin-postgres/spin.toml +++ b/examples/spin-postgres/spin.toml @@ -14,4 +14,4 @@ component = "test" source = "app.wasm" allowed_outbound_hosts = ["*://*:*"] [component.test.build] -command = "componentize-py -w spin:up/http-trigger@4.0.0 componentize app -o app.wasm" +command = "componentize-py -w spin:up/http-trigger@4.1.0 componentize app -o app.wasm" diff --git a/examples/spin-redis/requirements.txt b/examples/spin-redis/requirements.txt index 42dc3d6..4721223 100644 --- a/examples/spin-redis/requirements.txt +++ b/examples/spin-redis/requirements.txt @@ -1,2 +1,2 @@ -spin-sdk == 4.0.0 -componentize-py == 0.23.0 +spin-sdk == 5.0.0 +componentize-py == 0.25.1 diff --git a/examples/spin-redis/spin.toml b/examples/spin-redis/spin.toml index 1cd3a31..887027b 100644 --- a/examples/spin-redis/spin.toml +++ b/examples/spin-redis/spin.toml @@ -15,4 +15,4 @@ source = "app.wasm" allowed_outbound_hosts = ["redis://*:*"] ai_models = ["llama2-chat"] [component.test.build] -command = "componentize-py -w spin:up/http-trigger@4.0.0 componentize app -o app.wasm" +command = "componentize-py -w spin:up/http-trigger@4.1.0 componentize app -o app.wasm" diff --git a/examples/spin-sqlite/requirements.txt b/examples/spin-sqlite/requirements.txt index 5f25c4e..be8038e 100644 --- a/examples/spin-sqlite/requirements.txt +++ b/examples/spin-sqlite/requirements.txt @@ -1,2 +1,2 @@ -spin-sdk == 4.0.0 -componentize-py == 0.23.0 \ No newline at end of file +spin-sdk == 5.0.0 +componentize-py == 0.25.1 \ No newline at end of file diff --git a/examples/spin-sqlite/spin.toml b/examples/spin-sqlite/spin.toml index ec8f76c..15d4e42 100644 --- a/examples/spin-sqlite/spin.toml +++ b/examples/spin-sqlite/spin.toml @@ -15,4 +15,4 @@ source = "app.wasm" sqlite_databases = ["default"] ai_models = ["llama2-chat"] [component.test.build] -command = "componentize-py -w spin:up/http-trigger@4.0.0 componentize app -o app.wasm" +command = "componentize-py -w spin:up/http-trigger@4.1.0 componentize app -o app.wasm" diff --git a/examples/spin-variables/requirements.txt b/examples/spin-variables/requirements.txt index 5f25c4e..be8038e 100644 --- a/examples/spin-variables/requirements.txt +++ b/examples/spin-variables/requirements.txt @@ -1,2 +1,2 @@ -spin-sdk == 4.0.0 -componentize-py == 0.23.0 \ No newline at end of file +spin-sdk == 5.0.0 +componentize-py == 0.25.1 \ No newline at end of file diff --git a/examples/spin-variables/spin.toml b/examples/spin-variables/spin.toml index b1281dc..e317d01 100644 --- a/examples/spin-variables/spin.toml +++ b/examples/spin-variables/spin.toml @@ -16,4 +16,4 @@ allowed_outbound_hosts = [] [component.test.variables] message = "hello, world!" [component.test.build] -command = "componentize-py -w spin:up/http-trigger@4.0.0 componentize app -o app.wasm" +command = "componentize-py -w spin:up/http-trigger@4.1.0 componentize app -o app.wasm" diff --git a/examples/streaming/app.py b/examples/streaming/app.py index 312d6c4..72ad298 100644 --- a/examples/streaming/app.py +++ b/examples/streaming/app.py @@ -15,8 +15,8 @@ from componentize_py_async_support.futures import FutureReader from spin_sdk import wit from spin_sdk.wit import exports -from spin_sdk.wit.imports import wasi_http_client_0_3_0_rc_2026_03_15 as client -from spin_sdk.wit.imports.wasi_http_types_0_3_0_rc_2026_03_15 import ( +from spin_sdk.wit.imports import wasi_http_client_0_3_0 as client +from spin_sdk.wit.imports.wasi_http_types_0_3_0 import ( Method_Get, Method_Post, Scheme, @@ -127,8 +127,8 @@ async def sha256(url: str) -> tuple[str, str]: def trailers_future() -> FutureReader[Result[Optional[Fields], ErrorCode]]: - return wit.result_option_wasi_http_types_0_3_0_rc_2026_03_15_fields_wasi_http_types_0_3_0_rc_2026_03_15_error_code_future(lambda: Ok(None))[1] + return wit.result_option_wasi_http_types_0_3_0_fields_wasi_http_types_0_3_0_error_code_future(lambda: Ok(None))[1] def unit_future() -> FutureReader[Result[None, ErrorCode]]: - return wit.result_unit_wasi_http_types_0_3_0_rc_2026_03_15_error_code_future(lambda: Ok(None))[1] + return wit.result_unit_wasi_http_types_0_3_0_error_code_future(lambda: Ok(None))[1] diff --git a/examples/streaming/requirements.txt b/examples/streaming/requirements.txt index 5f25c4e..be8038e 100644 --- a/examples/streaming/requirements.txt +++ b/examples/streaming/requirements.txt @@ -1,2 +1,2 @@ -spin-sdk == 4.0.0 -componentize-py == 0.23.0 \ No newline at end of file +spin-sdk == 5.0.0 +componentize-py == 0.25.1 \ No newline at end of file diff --git a/examples/streaming/spin.toml b/examples/streaming/spin.toml index 47fa928..54fad88 100644 --- a/examples/streaming/spin.toml +++ b/examples/streaming/spin.toml @@ -14,4 +14,4 @@ component = "test" source = "app.wasm" allowed_outbound_hosts = ["http://*:*", "https://*:*"] [component.test.build] -command = "componentize-py -w wasi:http/service@0.3.0-rc-2026-03-15 componentize app -o app.wasm" +command = "componentize-py -w wasi:http/service@0.3.0 componentize app -o app.wasm" diff --git a/pyproject.toml b/pyproject.toml index b892506..8e54174 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "spin-sdk" -version = "4.0.0" +version = "5.0.0" description = "Python SDK for Spin" readme = "README.md" license-files = [ "LICENSE" ] diff --git a/regenerate_bindings.sh b/regenerate_bindings.sh index 7a5d7be..74d109b 100644 --- a/regenerate_bindings.sh +++ b/regenerate_bindings.sh @@ -2,18 +2,18 @@ componentize-py \ -d src/spin_sdk/wit \ - -w "spin:up/http-trigger@4.0.0" \ - -w "spin:up/redis-trigger@4.0.0" \ - -w "wasi:http/service@0.3.0-rc-2026-03-15" \ + -w "spin:up/http-trigger@4.1.0" \ + -w "spin:up/redis-trigger@4.1.0" \ + -w "wasi:http/service@0.3.0" \ -w "fermyon:spin/http-trigger@3.0.0" \ -w "fermyon:spin/redis-trigger" \ - --export-interface-name "wasi:http/handler@0.3.0-rc-2026-03-15=http-handler" \ + --export-interface-name "wasi:http/handler@0.3.0=http-handler" \ --export-interface-name "spin:redis/inbound-redis@3.0.0=redis-handler" \ --world-module spin_sdk.wit \ --full-names \ bindings \ bindings -rm -r src/spin_sdk/wit/imports src/spin_sdk/wit/exports src/componentize_py_* +rm -r src/spin_sdk/wit/__init__.py src/spin_sdk/wit/imports src/spin_sdk/wit/exports src/componentize_py_* mv bindings/spin_sdk/wit/* src/spin_sdk/wit/ mv bindings/componentize_py_* src/ # `pdoc3` needs to be able to load all modules in order to generate docs, so we diff --git a/src/componentize_py_async_support/__init__.py b/src/componentize_py_async_support/__init__.py index a7bd016..d402566 100644 --- a/src/componentize_py_async_support/__init__.py +++ b/src/componentize_py_async_support/__init__.py @@ -1,3 +1,11 @@ +"""This module contains code to integrate Component Model async features with +Python's `asyncio` framework. + +Most of the code here is used internally by code generated by `componentize-py` +for worlds which use async features. As of this writing, the only item meant to +be used directly by application code is the `spawn` function. + +""" import asyncio import componentize_py_runtime import subprocess @@ -441,6 +449,11 @@ async def _return_result(export_index: int, borrows: int, coroutine: Any) -> Non _future_state.get().pending_count -= 1 def first_poll(export_index: int, borrows: int, coroutine: Any) -> int: + """Internal function called by generated code for exported functions. + + This is not meant to be called by application code. + + """ context = Context() future_state = _FutureState(None, {}, [], 1) context.run(_set_future_state, future_state) @@ -464,6 +477,11 @@ def _poll(future_state: _FutureState) -> int: return _CallbackCode.WAIT | (waitable_set << 4) def callback(event0: int, event1: int, event2: int) -> int: + """Internal function called by generated code for exported functions. + + This is not meant to be called by application code. + + """ future_state = componentize_py_runtime.context_get() componentize_py_runtime.context_set(None) @@ -493,6 +511,11 @@ def callback(event0: int, event1: int, event2: int) -> int: return _poll(future_state) async def await_result[T](result: Result[T, tuple[int, int]]) -> T: + """Internal function called by generated code for imported functions. + + This is not meant to be called directly by application code. + + """ global _loop global _future_state @@ -522,6 +545,24 @@ async def _wrap_spawned(coroutine: Any) -> None: _future_state.get().pending_count -= 1 def spawn(coroutine: Any) -> None: + """Spawn an awaitable to be executed as part of the current Component Model + task. + + Each call to an async function exported by a component creates a new + Component Model task which is represented as a Python awaitable. By + default, that task will exit after returning a value. However, this + function may be used to do additional work concurrently, and that additional + work may extend the lifetime of the task arbitrarily after it has returned a + value. + + For example, implementing `wasi:http/service@0.3.x` involves returning a + `response` resource, but sending the response body must happen after + returning since it involves writing to a stream which the caller will only + have received as part of the `response` resource. Therefore, the only way + to write the response body is by using this function. See the `http-p3` + example in the `componentize-py` Git repository for what that looks like. + + """ global _future_state _future_state.get().pending_count += 1 diff --git a/src/componentize_py_async_support/futures.py b/src/componentize_py_async_support/futures.py index a50c16e..10c58e4 100644 --- a/src/componentize_py_async_support/futures.py +++ b/src/componentize_py_async_support/futures.py @@ -9,12 +9,40 @@ T = TypeVar('T') class FutureReader(Generic[T]): + """Represents the readable end of a Component Model `future`. + + Each object of this type should be closed promptly using either context + management (e.g. a `with` statement) or by calling `read` in order to notify + the owner of the writable end of the `future` that the readable end has been + closed. If the object becomes unreachable without being closed, it will be + closed via finalization. + + """ def __init__(self, type_: int, handle: int): + """Constructor for internal use by generated code. + + Application code should not call this directly. Instead, + `componentize-py` will generate a constructor function for each unique + `future` type used by the target world. Each such function will return + a (`FutureReader`, `FutureWriter`) pair and use this constructor behind + the scenes. + + """ self.type_ = type_ self.handle: int | None = handle self.finalizer = weakref.finalize(self, componentize_py_runtime.future_drop_readable, type_, handle) async def read(self) -> T: + """Asynchronously read the value sent to this `future`. + + The awaitable returned by this function will resolve when a value has + been delivered to the `future`. + + Calling this function consumes the target object; any attempt to call + `read` more than once will raise an `AssertionError`. + + """ + self.finalizer.detach() handle = self.handle self.handle = None @@ -42,23 +70,51 @@ def __exit__(self, return None -async def write(type_: int, handle: int, value: Any) -> None: +async def _write(type_: int, handle: int, value: Any) -> None: await componentize_py_async_support.await_result( componentize_py_runtime.future_write(type_, handle, value) ) componentize_py_runtime.future_drop_writable(type_, handle) -def write_default(type_: int, handle: int, default: Callable[[], Any]) -> None: - componentize_py_async_support.spawn(write(type_, handle, default())) +def _write_default(type_: int, handle: int, default: Callable[[], Any]) -> None: + componentize_py_async_support.spawn(_write(type_, handle, default())) class FutureWriter(Generic[T]): + """Represents the writable end of a Component Model `future`. + + Each object of this type should be closed promptly using either context + management (e.g. a `with` statement) or by calling `write` in order to send + a value to the readable end of the `future. If the object becomes + unreachable without being closed, it will be closed via finalization after + sending the default value which was specified during construction. + + """ def __init__(self, type_: int, handle: int, default: Callable[[], T]): + """Constructor for internal use by generated code. + + Application code should not call this directly. Instead, + `componentize-py` will generate a constructor function for each unique + `future` type used by the target world. Each such function will return + a (`FutureReader`, `FutureWriter`) pair and use this constructor behind + the scenes. + + """ self.type_ = type_ self.handle: int | None = handle self.default = default - self.finalizer = weakref.finalize(self, write_default, type_, handle, default) + self.finalizer = weakref.finalize(self, _write_default, type_, handle, default) async def write(self, value: T) -> bool: + """Asynchronously write a value to the `future`. + + The awaitable returned by this function will resolve once either the + value has been delivered to the readable end of the `future` or the + readable end has been closed. + + Calling this function consumes the target object; any attempt to call + `write` more than once will raise an `AssertionError`. + + """ self.finalizer.detach() handle = self.handle self.handle = None diff --git a/src/componentize_py_async_support/streams.py b/src/componentize_py_async_support/streams.py index 7650d62..0b0b95a 100644 --- a/src/componentize_py_async_support/streams.py +++ b/src/componentize_py_async_support/streams.py @@ -7,27 +7,60 @@ from componentize_py_async_support import _ReturnCode class ByteStreamReader: + """Represents the readable end of a Component Model `stream`. + + Each object of this type should be closed promptly using context management + (e.g. a `with` statement) in order to notify the owner of the writable end + of the `stream` that the readable end has been closed. If the object + becomes unreachable without being closed, it will be closed via + finalization. + + """ def __init__(self, type_: int, handle: int): + """Constructor for internal use by generated code. + + Application code should not call this directly. Instead, + `componentize-py` will generate `byte_stream` function if the target + world uses the `stream` type. That function will return a + (`ByteStreamReader`, `ByteStreamWriter`) pair and use this constructor + behind the scenes. + + """ self.writer_dropped = False self.type_ = type_ self.handle: int | None = handle self.finalizer = weakref.finalize(self, componentize_py_runtime.stream_drop_readable, type_, handle) async def read(self, max_count: int) -> bytes: + """Asynchronously read up to `max_count` bytes sent to this `stream`. + + The awaitable returned by this function will resolve when either at + least one byte has been delivered to the `stream` or the writable end + has been closed (in which case the `writer_dropped` field will be set to + `True`). + + Only one `read` operation is allowed at a time for a given object. Any + attempt to start a second read while the first is still in progress will + raise an `AssertionError`. + + """ if self.writer_dropped: return bytes() - code, values = await self._read(max_count) + handle = self.handle + self.handle = None + code, values = await self._read(max_count, handle) + self.handle = handle if code == _ReturnCode.DROPPED: self.writer_dropped = True return values - async def _read(self, max_count: int) -> tuple[int, bytes]: - if self.handle is not None: + async def _read(self, max_count: int, handle: int | None) -> tuple[int, bytes]: + if handle is not None: return cast(tuple[int, bytes], await componentize_py_async_support.await_result( - componentize_py_runtime.stream_read(self.type_, self.handle, max_count) + componentize_py_runtime.stream_read(self.type_, handle, max_count) )) else: raise AssertionError @@ -47,32 +80,77 @@ def __exit__(self, return None class ByteStreamWriter: + """Represents the writable end of a Component Model `stream`. + + Each object of this type should be closed promptly using context management + (e.g. a `with` statement) in order to notify the owner of the readable end + of the `stream` that the writable end has been closed. If the object + becomes unreachable without being closed, it will be closed via + finalization. + + """ def __init__(self, type_: int, handle: int): + """Constructor for internal use by generated code. + + Application code should not call this directly. Instead, + `componentize-py` will generate `byte_stream` function if the target + world uses the `stream` type. That function will return a + (`ByteStreamReader`, `ByteStreamWriter`) pair and use this constructor + behind the scenes. + + """ self.reader_dropped = False self.type_ = type_ self.handle: int | None = handle self.finalizer = weakref.finalize(self, componentize_py_runtime.stream_drop_writable, type_, handle) async def write(self, source: bytes) -> int: + """Asynchronously write (some of) the specified bytes to the `stream`. + + The awaitable returned by this function will resolve when either at + least one byte has been delivered to the `stream` or the readable end + has been closed (in which case the `reader_dropped` field will be set to + `True`). + + The return value is the total number of bytes delivered (which may be + less than `len(source)`). See also `write_all`, which attempts to write + the entire buffer before returning. + + Only one `write` operation is allowed at a time for a given object. Any + attempt to start a second write while the first is still in progress + will raise an `AssertionError`. + + """ if self.reader_dropped: return 0 - code, count = await self._write(source) + handle = self.handle + self.handle = None + code, count = await self._write(source, handle) + self.handle = handle if code == _ReturnCode.DROPPED: self.reader_dropped = True return count - async def _write(self, source: bytes) -> tuple[int, int]: - if self.handle is not None: + async def _write(self, source: bytes, handle: int | None) -> tuple[int, int]: + if handle is not None: return await componentize_py_async_support.await_result( - componentize_py_runtime.stream_write(self.type_, self.handle, source) + componentize_py_runtime.stream_write(self.type_, handle, source) ) else: raise AssertionError async def write_all(self, source: bytes) -> int: + """Asynchronously write the specified bytes to the `stream`. + + This calls `write` in a loop until either the entire buffer has been + delivered or the readable end of the `stream` has been closed. The + return value is the total number of bytes delivered (which may be less + than `len(source)`). + + """ total = 0 while len(source) > 0 and not self.reader_dropped: @@ -99,27 +177,60 @@ def __exit__(self, T = TypeVar('T') class StreamReader(Generic[T]): + """Represents the readable end of a Component Model `stream`. + + Each object of this type should be closed promptly using context management + (e.g. a `with` statement) in order to notify the owner of the writable end + of the `stream` that the readable end has been closed. If the object + becomes unreachable without being closed, it will be closed via + finalization. + + """ def __init__(self, type_: int, handle: int): + """Constructor for internal use by generated code. + + Application code should not call this directly. Instead, + `componentize-py` will generate a constructor function for each unique + `stream` type used by the target world. Each such function will return + a (`StreamReader`, `StreamWriter`) pair and use this constructor behind + the scenes. + + """ self.writer_dropped = False self.type_ = type_ self.handle: int | None = handle self.finalizer = weakref.finalize(self, componentize_py_runtime.stream_drop_readable, type_, handle) async def read(self, max_count: int) -> list[T]: + """Asynchronously read up to `max_count` items sent to this `stream`. + + The awaitable returned by this function will resolve when either at + least one item has been delivered to the `stream` or the writable end + has been closed (in which case the `writer_dropped` field will be set to + `True`). + + Only one `read` operation is allowed at a time for a given object. Any + attempt to start a second read while the first is still in progress will + raise an `AssertionError`. + + """ if self.writer_dropped: return [] - code, values = await self._read(max_count) + handle = self.handle + self.handle = None + code, values = await self._read(max_count, handle) + self.handle = handle if code == _ReturnCode.DROPPED: self.writer_dropped = True return values - async def _read(self, max_count: int) -> tuple[int, list[T]]: - if self.handle is not None: + async def _read(self, max_count: int, handle: int | None) -> tuple[int, list[T]]: + if handle is not None: return cast(tuple[int, list[T]], await componentize_py_async_support.await_result( - componentize_py_runtime.stream_read(self.type_, self.handle, max_count) + componentize_py_runtime.stream_read(self.type_, handle, max_count) )) else: raise AssertionError @@ -139,32 +250,77 @@ def __exit__(self, return None class StreamWriter(Generic[T]): + """Represents the writable end of a Component Model `stream`. + + Each object of this type should be closed promptly using context management + (e.g. a `with` statement) in order to notify the owner of the readable end + of the `stream` that the writable end has been closed. If the object + becomes unreachable without being closed, it will be closed via + finalization. + + """ def __init__(self, type_: int, handle: int): + """Constructor for internal use by generated code. + + Application code should not call this directly. Instead, + `componentize-py` will generate a constructor function for each unique + `stream` type used by the target world. Each such function will return + a (`StreamReader`, `StreamWriter`) pair and use this constructor behind + the scenes. + + """ self.reader_dropped = False self.type_ = type_ self.handle: int | None = handle self.finalizer = weakref.finalize(self, componentize_py_runtime.stream_drop_writable, type_, handle) async def write(self, source: list[T]) -> int: + """Asynchronously write (some of) the specified itmes to the `stream`. + + The awaitable returned by this function will resolve when either at + least one item has been delivered to the `stream` or the readable end + has been closed (in which case the `reader_dropped` field will be set to + `True`). + + The return value is the total number of items delivered (which may be + less than `len(source)`). See also `write_all`, which attempts to write + the entire buffer before returning. + + Only one `write` operation is allowed at a time for a given object. Any + attempt to start a second write while the first is still in progress will + raise an `AssertionError`. + + """ if self.reader_dropped: return 0 - code, count = await self._write(source) + handle = self.handle + self.handle = None + code, count = await self._write(source, handle) + self.handle = handle if code == _ReturnCode.DROPPED: self.reader_dropped = True return count - async def _write(self, source: list[T]) -> tuple[int, int]: - if self.handle is not None: + async def _write(self, source: list[T], handle: int | None) -> tuple[int, int]: + if handle is not None: return await componentize_py_async_support.await_result( - componentize_py_runtime.stream_write(self.type_, self.handle, source) + componentize_py_runtime.stream_write(self.type_, handle, source) ) else: raise AssertionError async def write_all(self, source: list[T]) -> int: + """Asynchronously write the specified items to the `stream`. + + This calls `write` in a loop until either the entire buffer has been + delivered or the readable end of the `stream` has been closed. The + return value is the total number of items delivered (which may be less + than `len(source)`). + + """ total = 0 while len(source) > 0 and not self.reader_dropped: diff --git a/src/componentize_py_runtime.py b/src/componentize_py_runtime.py index 50efc38..a8ff6fa 100644 --- a/src/componentize_py_runtime.py +++ b/src/componentize_py_runtime.py @@ -1,3 +1,9 @@ +"""Internal functions used by generated code to interact with the +`componentize-py` runtime. + +These are not meant to be called directly by application code. + +""" from typing import Any from componentize_py_types import Result diff --git a/src/componentize_py_runtime.pyi b/src/componentize_py_runtime.pyi index ff09cf8..ed2f42b 100644 --- a/src/componentize_py_runtime.pyi +++ b/src/componentize_py_runtime.pyi @@ -1,3 +1,9 @@ +"""Internal functions used by generated code to interact with the +`componentize-py` runtime. + +These are not meant to be called directly by application code. + +""" from typing import Any from componentize_py_types import Result diff --git a/src/componentize_py_types.py b/src/componentize_py_types.py index 9867d14..4269f15 100644 --- a/src/componentize_py_types.py +++ b/src/componentize_py_types.py @@ -4,16 +4,29 @@ S = TypeVar('S') @dataclass class Some(Generic[S]): + """Represents the "present" (i.e. non-absent) case of an optional value. + + This is used to disambiguate values of a nested ComponentModel `option` type + (e.g. `option>` or similar). Non-nested `option` values are + represented using `typing.Optional`, i.e. nullable types. + + """ value: S T = TypeVar('T') @dataclass class Ok(Generic[T]): + """Represents the success case of a Component Model `result` value.""" value: T E = TypeVar('E') @dataclass(frozen=True) class Err(Generic[E], Exception): + """Represents the failure case of a Component Model `result` value.""" value: E Result = Union[Ok[T], Err[E]] +"""Represents a Component Model `result` value, i.e. a variant type representing +either success payload or failure payload. + +""" diff --git a/src/spin_sdk/componentize-py.toml b/src/spin_sdk/componentize-py.toml index e0034fa..1f72adf 100644 --- a/src/spin_sdk/componentize-py.toml +++ b/src/spin_sdk/componentize-py.toml @@ -3,5 +3,5 @@ bindings = "wit" full_names = true [export_interface_names] -"wasi:http/handler@0.3.0-rc-2026-03-15" = "http-handler" +"wasi:http/handler@0.3.0" = "http-handler" "spin:redis/inbound-redis@3.0.0" = "redis-handler" diff --git a/src/spin_sdk/http/__init__.py b/src/spin_sdk/http/__init__.py index ad1084a..944b38d 100644 --- a/src/spin_sdk/http/__init__.py +++ b/src/spin_sdk/http/__init__.py @@ -6,8 +6,8 @@ from componentize_py_async_support.streams import ByteStreamWriter from componentize_py_async_support.futures import FutureReader from spin_sdk import wit -from spin_sdk.wit.imports import wasi_http_client_0_3_0_rc_2026_03_15 as client -from spin_sdk.wit.imports.wasi_http_types_0_3_0_rc_2026_03_15 import ( +from spin_sdk.wit.imports import wasi_http_client_0_3_0 as client +from spin_sdk.wit.imports.wasi_http_types_0_3_0 import ( Method, Method_Get, Method_Head, Method_Post, Method_Put, Method_Delete, Method_Connect, Method_Options, Method_Trace, Method_Patch, Method_Other, Fields, Scheme, Scheme_Http, Scheme_Https, Scheme_Other, ErrorCode, Request as WasiRequest, Response as WasiResponse @@ -111,7 +111,7 @@ async def handle(self, request: WasiRequest) -> WasiResponse: return response except ImportError: - # `spin_sdk.wit.exports` won't exist if the use is targeting `spin-imports`, + # `spin_sdk.wit.exports` won't exist if the user is targeting `spin-imports`, # so just skip this part pass @@ -234,7 +234,7 @@ async def _copy(bytes: bytes | None, tx: ByteStreamWriter) -> None: await tx.write_all(bytes) def _trailers_future() -> FutureReader[Result[Optional[Fields], ErrorCode]]: - return wit.result_option_wasi_http_types_0_3_0_rc_2026_03_15_fields_wasi_http_types_0_3_0_rc_2026_03_15_error_code_future(lambda: Ok(None))[1] + return wit.result_option_wasi_http_types_0_3_0_fields_wasi_http_types_0_3_0_error_code_future(lambda: Ok(None))[1] def _unit_future() -> FutureReader[Result[None, ErrorCode]]: - return wit.result_unit_wasi_http_types_0_3_0_rc_2026_03_15_error_code_future(lambda: Ok(None))[1] + return wit.result_unit_wasi_http_types_0_3_0_error_code_future(lambda: Ok(None))[1] diff --git a/src/spin_sdk/mysql.py b/src/spin_sdk/mysql.py index e053e0e..e62ec2b 100644 --- a/src/spin_sdk/mysql.py +++ b/src/spin_sdk/mysql.py @@ -1,8 +1,8 @@ """Module for interacting with a MySQL database""" -from spin_sdk.wit.imports.fermyon_spin_mysql_2_0_0 import Connection +from spin_sdk.wit.imports.spin_mysql_mysql_3_0_0 import Connection -def open(connection_string: str) -> Connection: +async def open(connection_string: str) -> Connection: """ Open a connection with a MySQL database. @@ -12,4 +12,4 @@ def open(connection_string: str) -> Connection: A `componentize_py_types.Err(Error_Other(str))` when some other error occurs. """ - return Connection.open(connection_string) + return await Connection.open(connection_string) diff --git a/src/spin_sdk/wit/__init__.py b/src/spin_sdk/wit/__init__.py index c0b046e..a6c64a1 100644 --- a/src/spin_sdk/wit/__init__.py +++ b/src/spin_sdk/wit/__init__.py @@ -1,5 +1,3 @@ -""" Module with the bindings generated from the wit by componentize-py """ - # This file is automatically generated by componentize-py # It is not intended for manual editing. from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self, Callable @@ -11,12 +9,17 @@ from componentize_py_types import Result, Ok, Err, Some from .imports import spin_key_value_key_value_3_0_0 +from .imports import spin_mysql_mysql_3_0_0 from .imports import spin_postgres_postgres_4_2_0 from .imports import spin_sqlite_sqlite_3_1_0 from .imports import wasi_filesystem_types_0_3_0_rc_2026_03_15 from .imports import wasi_sockets_types_0_3_0_rc_2026_03_15 from .imports import wasi_cli_types_0_3_0_rc_2026_03_15 from .imports import wasi_http_types_0_3_0_rc_2026_03_15 +from .imports import wasi_filesystem_types_0_3_0 +from .imports import wasi_sockets_types_0_3_0 +from .imports import wasi_cli_types_0_3_0 +from .imports import wasi_http_types_0_3_0 import componentize_py_async_support from componentize_py_async_support.streams import StreamReader, StreamWriter, ByteStreamReader, ByteStreamWriter from componentize_py_async_support.futures import FutureReader, FutureWriter @@ -30,12 +33,24 @@ def result_option_wasi_http_types_0_3_0_rc_2026_03_15_fields_wasi_http_types_0_3 def result_unit_wasi_http_types_0_3_0_rc_2026_03_15_error_code_future(default: Callable[[], Result[None, wasi_http_types_0_3_0_rc_2026_03_15.ErrorCode]]) -> tuple[FutureWriter[Result[None, wasi_http_types_0_3_0_rc_2026_03_15.ErrorCode]], FutureReader[Result[None, wasi_http_types_0_3_0_rc_2026_03_15.ErrorCode]]]: raise NotImplementedError +def result_option_wasi_http_types_0_3_0_fields_wasi_http_types_0_3_0_error_code_future(default: Callable[[], Result[Optional[wasi_http_types_0_3_0.Fields], wasi_http_types_0_3_0.ErrorCode]]) -> tuple[FutureWriter[Result[Optional[wasi_http_types_0_3_0.Fields], wasi_http_types_0_3_0.ErrorCode]], FutureReader[Result[Optional[wasi_http_types_0_3_0.Fields], wasi_http_types_0_3_0.ErrorCode]]]: + raise NotImplementedError + +def result_unit_wasi_http_types_0_3_0_error_code_future(default: Callable[[], Result[None, wasi_http_types_0_3_0.ErrorCode]]) -> tuple[FutureWriter[Result[None, wasi_http_types_0_3_0.ErrorCode]], FutureReader[Result[None, wasi_http_types_0_3_0.ErrorCode]]]: + raise NotImplementedError + def string_stream() -> tuple[StreamWriter[str], StreamReader[str]]: raise NotImplementedError def result_unit_spin_key_value_key_value_3_0_0_error_future(default: Callable[[], Result[None, spin_key_value_key_value_3_0_0.Error]]) -> tuple[FutureWriter[Result[None, spin_key_value_key_value_3_0_0.Error]], FutureReader[Result[None, spin_key_value_key_value_3_0_0.Error]]]: raise NotImplementedError +def list_spin_mysql_mysql_3_0_0_db_value_stream() -> tuple[StreamWriter[List[spin_mysql_mysql_3_0_0.DbValue]], StreamReader[List[spin_mysql_mysql_3_0_0.DbValue]]]: + raise NotImplementedError + +def result_unit_spin_mysql_mysql_3_0_0_error_future(default: Callable[[], Result[None, spin_mysql_mysql_3_0_0.Error]]) -> tuple[FutureWriter[Result[None, spin_mysql_mysql_3_0_0.Error]], FutureReader[Result[None, spin_mysql_mysql_3_0_0.Error]]]: + raise NotImplementedError + def list_spin_postgres_postgres_4_2_0_db_value_stream() -> tuple[StreamWriter[List[spin_postgres_postgres_4_2_0.DbValue]], StreamReader[List[spin_postgres_postgres_4_2_0.DbValue]]]: raise NotImplementedError @@ -63,6 +78,21 @@ def wasi_sockets_types_0_3_0_rc_2026_03_15_tcp_socket_stream() -> tuple[StreamWr def result_unit_wasi_sockets_types_0_3_0_rc_2026_03_15_error_code_future(default: Callable[[], Result[None, wasi_sockets_types_0_3_0_rc_2026_03_15.ErrorCode]]) -> tuple[FutureWriter[Result[None, wasi_sockets_types_0_3_0_rc_2026_03_15.ErrorCode]], FutureReader[Result[None, wasi_sockets_types_0_3_0_rc_2026_03_15.ErrorCode]]]: raise NotImplementedError +def result_unit_wasi_cli_types_0_3_0_error_code_future(default: Callable[[], Result[None, wasi_cli_types_0_3_0.ErrorCode]]) -> tuple[FutureWriter[Result[None, wasi_cli_types_0_3_0.ErrorCode]], FutureReader[Result[None, wasi_cli_types_0_3_0.ErrorCode]]]: + raise NotImplementedError + +def result_unit_wasi_filesystem_types_0_3_0_error_code_future(default: Callable[[], Result[None, wasi_filesystem_types_0_3_0.ErrorCode]]) -> tuple[FutureWriter[Result[None, wasi_filesystem_types_0_3_0.ErrorCode]], FutureReader[Result[None, wasi_filesystem_types_0_3_0.ErrorCode]]]: + raise NotImplementedError + +def wasi_filesystem_types_0_3_0_directory_entry_stream() -> tuple[StreamWriter[wasi_filesystem_types_0_3_0.DirectoryEntry], StreamReader[wasi_filesystem_types_0_3_0.DirectoryEntry]]: + raise NotImplementedError + +def wasi_sockets_types_0_3_0_tcp_socket_stream() -> tuple[StreamWriter[Any], StreamReader[Any]]: + raise NotImplementedError + +def result_unit_wasi_sockets_types_0_3_0_error_code_future(default: Callable[[], Result[None, wasi_sockets_types_0_3_0.ErrorCode]]) -> tuple[FutureWriter[Result[None, wasi_sockets_types_0_3_0.ErrorCode]], FutureReader[Result[None, wasi_sockets_types_0_3_0.ErrorCode]]]: + raise NotImplementedError + class SpinSdkWit(Protocol): pass diff --git a/src/spin_sdk/wit/deps/cli.wit b/src/spin_sdk/wit/deps/cli.wit new file mode 100644 index 0000000..7aae56c --- /dev/null +++ b/src/spin_sdk/wit/deps/cli.wit @@ -0,0 +1,256 @@ +package wasi:cli@0.3.0; + +@since(version = 0.3.0) +interface environment { + /// Get the POSIX-style environment variables. + /// + /// Each environment variable is provided as a pair of string variable names + /// and string value. + /// + /// Morally, these are a value import, but until value imports are available + /// in the component model, this import function should return the same + /// values each time it is called. + @since(version = 0.3.0) + get-environment: func() -> list>; + + /// Get the POSIX-style arguments to the program. + @since(version = 0.3.0) + get-arguments: func() -> list; + + /// Return a path that programs should use as their initial current working + /// directory, interpreting `.` as shorthand for this. + @since(version = 0.3.0) + get-initial-cwd: func() -> option; +} + +@since(version = 0.3.0) +interface exit { + /// Exit the current instance and any linked instances. + @since(version = 0.3.0) + exit: func(status: result); + + /// Exit the current instance and any linked instances, reporting the + /// specified status code to the host. + /// + /// The meaning of the code depends on the context, with 0 usually meaning + /// "success", and other values indicating various types of failure. + /// + /// This function does not return; the effect is analogous to a trap, but + /// without the connotation that something bad has happened. + @since(version = 0.3.0) + exit-with-code: func(status-code: u8); +} + +@since(version = 0.3.0) +interface run { + /// Run the program. + @since(version = 0.3.0) + run: async func() -> result; +} + +@since(version = 0.3.0) +interface types { + @since(version = 0.3.0) + enum error-code { + /// Input/output error + io, + /// Invalid or incomplete multibyte or wide character + illegal-byte-sequence, + /// Broken pipe + pipe, + } +} + +@since(version = 0.3.0) +interface stdin { + use types.{error-code}; + + /// Return a stream for reading from stdin. + /// + /// This function returns a stream which provides data read from stdin, + /// and a future to signal read results. + /// + /// If the stream's readable end is dropped the future will resolve to success. + /// + /// If the stream's writable end is dropped the future will either resolve to + /// success if stdin was closed by the writer or to an error-code if reading + /// failed for some other reason. + /// + /// Multiple streams may be active at the same time. The behavior of concurrent + /// reads is implementation-specific. + @since(version = 0.3.0) + read-via-stream: func() -> tuple, future>>; +} + +@since(version = 0.3.0) +interface stdout { + use types.{error-code}; + + /// Write the given stream to stdout. + /// + /// If the stream's writable end is dropped this function will either return + /// success once the entire contents of the stream have been written or an + /// error-code representing a failure. + /// + /// Otherwise if there is an error the readable end of the stream will be + /// dropped and this function will return an error-code. + @since(version = 0.3.0) + write-via-stream: func(data: stream) -> future>; +} + +@since(version = 0.3.0) +interface stderr { + use types.{error-code}; + + /// Write the given stream to stderr. + /// + /// If the stream's writable end is dropped this function will either return + /// success once the entire contents of the stream have been written or an + /// error-code representing a failure. + /// + /// Otherwise if there is an error the readable end of the stream will be + /// dropped and this function will return an error-code. + @since(version = 0.3.0) + write-via-stream: func(data: stream) -> future>; +} + +/// Terminal input. +/// +/// In the future, this may include functions for disabling echoing, +/// disabling input buffering so that keyboard events are sent through +/// immediately, querying supported features, and so on. +@since(version = 0.3.0) +interface terminal-input { + /// The input side of a terminal. + @since(version = 0.3.0) + resource terminal-input; +} + +/// Terminal output. +/// +/// In the future, this may include functions for querying the terminal +/// size, being notified of terminal size changes, querying supported +/// features, and so on. +@since(version = 0.3.0) +interface terminal-output { + /// The output side of a terminal. + @since(version = 0.3.0) + resource terminal-output; +} + +/// An interface providing an optional `terminal-input` for stdin as a +/// link-time authority. +@since(version = 0.3.0) +interface terminal-stdin { + @since(version = 0.3.0) + use terminal-input.{terminal-input}; + + /// If stdin is connected to a terminal, return a `terminal-input` handle + /// allowing further interaction with it. + @since(version = 0.3.0) + get-terminal-stdin: func() -> option; +} + +/// An interface providing an optional `terminal-output` for stdout as a +/// link-time authority. +@since(version = 0.3.0) +interface terminal-stdout { + @since(version = 0.3.0) + use terminal-output.{terminal-output}; + + /// If stdout is connected to a terminal, return a `terminal-output` handle + /// allowing further interaction with it. + @since(version = 0.3.0) + get-terminal-stdout: func() -> option; +} + +/// An interface providing an optional `terminal-output` for stderr as a +/// link-time authority. +@since(version = 0.3.0) +interface terminal-stderr { + @since(version = 0.3.0) + use terminal-output.{terminal-output}; + + /// If stderr is connected to a terminal, return a `terminal-output` handle + /// allowing further interaction with it. + @since(version = 0.3.0) + get-terminal-stderr: func() -> option; +} + +@since(version = 0.3.0) +world imports { + @since(version = 0.3.0) + import environment; + @since(version = 0.3.0) + import exit; + @since(version = 0.3.0) + import types; + @since(version = 0.3.0) + import stdin; + @since(version = 0.3.0) + import stdout; + @since(version = 0.3.0) + import stderr; + @since(version = 0.3.0) + import terminal-input; + @since(version = 0.3.0) + import terminal-output; + @since(version = 0.3.0) + import terminal-stdin; + @since(version = 0.3.0) + import terminal-stdout; + @since(version = 0.3.0) + import terminal-stderr; + import wasi:clocks/types@0.3.0; + import wasi:clocks/monotonic-clock@0.3.0; + import wasi:clocks/system-clock@0.3.0; + @unstable(feature = clocks-timezone) + import wasi:clocks/timezone@0.3.0; + import wasi:filesystem/types@0.3.0; + import wasi:filesystem/preopens@0.3.0; + import wasi:sockets/types@0.3.0; + import wasi:sockets/ip-name-lookup@0.3.0; + import wasi:random/random@0.3.0; + import wasi:random/insecure@0.3.0; + import wasi:random/insecure-seed@0.3.0; +} +@since(version = 0.3.0) +world command { + @since(version = 0.3.0) + import environment; + @since(version = 0.3.0) + import exit; + @since(version = 0.3.0) + import types; + @since(version = 0.3.0) + import stdin; + @since(version = 0.3.0) + import stdout; + @since(version = 0.3.0) + import stderr; + @since(version = 0.3.0) + import terminal-input; + @since(version = 0.3.0) + import terminal-output; + @since(version = 0.3.0) + import terminal-stdin; + @since(version = 0.3.0) + import terminal-stdout; + @since(version = 0.3.0) + import terminal-stderr; + import wasi:clocks/types@0.3.0; + import wasi:clocks/monotonic-clock@0.3.0; + import wasi:clocks/system-clock@0.3.0; + @unstable(feature = clocks-timezone) + import wasi:clocks/timezone@0.3.0; + import wasi:filesystem/types@0.3.0; + import wasi:filesystem/preopens@0.3.0; + import wasi:sockets/types@0.3.0; + import wasi:sockets/ip-name-lookup@0.3.0; + import wasi:random/random@0.3.0; + import wasi:random/insecure@0.3.0; + import wasi:random/insecure-seed@0.3.0; + + @since(version = 0.3.0) + export run; +} diff --git a/src/spin_sdk/wit/deps/clocks.wit b/src/spin_sdk/wit/deps/clocks.wit new file mode 100644 index 0000000..d8b8cfe --- /dev/null +++ b/src/spin_sdk/wit/deps/clocks.wit @@ -0,0 +1,161 @@ +package wasi:clocks@0.3.0; + +/// This interface common types used throughout wasi:clocks. +@since(version = 0.3.0) +interface types { + /// A duration of time, in nanoseconds. + @since(version = 0.3.0) + type duration = u64; +} + +/// WASI Monotonic Clock is a clock API intended to let users measure elapsed +/// time. +/// +/// It is intended to be portable at least between Unix-family platforms and +/// Windows. +/// +/// A monotonic clock is a clock which has an unspecified initial value, and +/// successive reads of the clock will produce non-decreasing values. +@since(version = 0.3.0) +interface monotonic-clock { + use types.{duration}; + + /// A mark on a monotonic clock is a number of nanoseconds since an + /// unspecified initial value, and can only be compared to instances from + /// the same monotonic-clock. + @since(version = 0.3.0) + type mark = u64; + + /// Read the current value of the clock. + /// + /// The clock is monotonic, therefore calling this function repeatedly will + /// produce a sequence of non-decreasing values. + /// + /// For completeness, this function traps if it's not possible to represent + /// the value of the clock in a `mark`. Consequently, implementations + /// should ensure that the starting time is low enough to avoid the + /// possibility of overflow in practice. + @since(version = 0.3.0) + now: func() -> mark; + + /// Query the resolution of the clock. Returns the duration of time + /// corresponding to a clock tick. + @since(version = 0.3.0) + get-resolution: func() -> duration; + + /// Wait until the specified mark has occurred. + @since(version = 0.3.0) + wait-until: async func(when: mark); + + /// Wait for the specified duration to elapse. + @since(version = 0.3.0) + wait-for: async func(how-long: duration); +} + +/// WASI System Clock is a clock API intended to let users query the current +/// time. The clock is not necessarily monotonic as it may be reset. +/// +/// It is intended to be portable at least between Unix-family platforms and +/// Windows. +/// +/// External references may be reset, so this clock is not necessarily +/// monotonic, making it unsuitable for measuring elapsed time. +/// +/// It is intended for reporting the current date and time for humans. +@since(version = 0.3.0) +interface system-clock { + use types.{duration}; + + /// An "instant", or "exact time", is a point in time without regard to any + /// time zone: just the time since a particular external reference point, + /// often called an "epoch". + /// + /// Here, the epoch is 1970-01-01T00:00:00Z, also known as + /// [POSIX's Seconds Since the Epoch], also known as [Unix Time]. + /// + /// Note that even if the seconds field is negative, incrementing + /// nanoseconds always represents moving forwards in time. + /// For example, `{ -1 seconds, 999999999 nanoseconds }` represents the + /// instant one nanosecond before the epoch. + /// For more on various different ways to represent time, see + /// https://tc39.es/proposal-temporal/docs/timezone.html + /// + /// [POSIX's Seconds Since the Epoch]: https://pubs.opengroup.org/onlinepubs/9699919799/xrat/V4_xbd_chap04.html#tag_21_04_16 + /// [Unix Time]: https://en.wikipedia.org/wiki/Unix_time + @since(version = 0.3.0) + record instant { + seconds: s64, + nanoseconds: u32, + } + + /// Read the current value of the clock. + /// + /// This clock is not monotonic, therefore calling this function repeatedly + /// will not necessarily produce a sequence of non-decreasing values. + /// + /// The nanoseconds field of the output is always less than 1000000000. + @since(version = 0.3.0) + now: func() -> instant; + + /// Query the resolution of the clock. Returns the smallest duration of time + /// that the implementation permits distinguishing. + @since(version = 0.3.0) + get-resolution: func() -> duration; +} + +@unstable(feature = clocks-timezone) +interface timezone { + @unstable(feature = clocks-timezone) + use system-clock.{instant}; + + /// Return the IANA identifier of the currently configured timezone. This + /// should be an identifier from the IANA Time Zone Database. + /// + /// For displaying to a user, the identifier should be converted into a + /// localized name by means of an internationalization API. + /// + /// If the implementation does not expose an actual timezone, or is unable + /// to provide mappings from times to deltas between the configured timezone + /// and UTC, or determining the current timezone fails, or the timezone does + /// not have an IANA identifier, this returns nothing. + @unstable(feature = clocks-timezone) + iana-id: func() -> option; + + /// The number of nanoseconds difference between UTC time and the local + /// time of the currently configured timezone, at the exact time of + /// `instant`. + /// + /// The magnitude of the returned value will always be less than + /// 86,400,000,000,000 which is the number of nanoseconds in a day + /// (24*60*60*1e9). + /// + /// If the implementation does not expose an actual timezone, or is unable + /// to provide mappings from times to deltas between the configured timezone + /// and UTC, or determining the current timezone fails, this returns + /// nothing. + @unstable(feature = clocks-timezone) + utc-offset: func(when: instant) -> option; + + /// Returns a string that is suitable to assist humans in debugging whether + /// any timezone is available, and if so, which. This may be the same string + /// as `iana-id`, or a formatted representation of the UTC offset such as + /// `-04:00`, or something else. + /// + /// WARNING: The returned string should not be consumed mechanically! It may + /// change across platforms, hosts, or other implementation details. Parsing + /// this string is a major platform-compatibility hazard. + @unstable(feature = clocks-timezone) + to-debug-string: func() -> string; +} + +@since(version = 0.3.0) +world imports { + @since(version = 0.3.0) + import types; + @since(version = 0.3.0) + import monotonic-clock; + @since(version = 0.3.0) + import system-clock; + @unstable(feature = clocks-timezone) + import timezone; +} diff --git a/src/spin_sdk/wit/deps/filesystem.wit b/src/spin_sdk/wit/deps/filesystem.wit new file mode 100644 index 0000000..e4a778f --- /dev/null +++ b/src/spin_sdk/wit/deps/filesystem.wit @@ -0,0 +1,575 @@ +package wasi:filesystem@0.3.0; + +/// WASI filesystem is a filesystem API primarily intended to let users run WASI +/// programs that access their files on their existing filesystems, without +/// significant overhead. +/// +/// Paths are passed as interface-type `string`s, meaning they must consist of +/// a sequence of Unicode Scalar Values (USVs). Some filesystems may contain +/// paths which are not accessible by this API. +/// +/// The directory separator in WASI is always the forward-slash (`/`). +/// +/// All paths in WASI are relative paths, and are interpreted relative to a +/// `descriptor` referring to a base directory. If a `path` argument to any WASI +/// function starts with `/`, or if any step of resolving a `path`, including +/// `..` and symbolic link steps, reaches a directory outside of the base +/// directory, or reaches a symlink to an absolute or rooted path in the +/// underlying filesystem, the function fails with `error-code::not-permitted`. +/// +/// For more information about WASI path resolution and sandboxing, see +/// [WASI filesystem path resolution]. +/// +/// Though this package presents a portable interface modelled on POSIX, it +/// prioritizes compatibility over portability: allowing users to access their +/// files on their machine is more important than exposing a single semantics +/// across all platforms. Notably, depending on the underlying operating system +/// and file system: +/// * Paths may be case-folded or not. +/// * Deleting (unlinking) a file may fail if there are other file descriptors +/// open. +/// * Durability and atomicity of changes to underlying files when there are +/// concurrent writers. +/// +/// Users that need well-defined, portable semantics should use a key-value +/// store or a database instead. +/// +/// [WASI filesystem path resolution]: https://github.com/WebAssembly/wasi-filesystem/blob/main/path-resolution.md +@since(version = 0.3.0) +interface types { + @since(version = 0.3.0) + use wasi:clocks/system-clock@0.3.0.{instant}; + + /// File size or length of a region within a file. + @since(version = 0.3.0) + type filesize = u64; + + /// The type of a filesystem object referenced by a descriptor. + /// + /// Note: This was called `filetype` in earlier versions of WASI. + @since(version = 0.3.0) + variant descriptor-type { + /// The descriptor refers to a block device inode. + block-device, + /// The descriptor refers to a character device inode. + character-device, + /// The descriptor refers to a directory inode. + directory, + /// The descriptor refers to a named pipe. + fifo, + /// The file refers to a symbolic link inode. + symbolic-link, + /// The descriptor refers to a regular file inode. + regular-file, + /// The descriptor refers to a socket. + socket, + /// The type of the descriptor or file is different from any of the + /// other types specified. + other(option), + } + + /// Descriptor flags. + /// + /// Note: This was called `fdflags` in earlier versions of WASI. + @since(version = 0.3.0) + flags descriptor-flags { + /// Read mode: Data can be read. + read, + /// Write mode: Data can be written to. + write, + /// Request that writes be performed according to synchronized I/O file + /// integrity completion. The data stored in the file and the file's + /// metadata are synchronized. This is similar to `O_SYNC` in POSIX. + /// + /// The precise semantics of this operation have not yet been defined for + /// WASI. At this time, it should be interpreted as a request, and not a + /// requirement. + file-integrity-sync, + /// Request that writes be performed according to synchronized I/O data + /// integrity completion. Only the data stored in the file is + /// synchronized. This is similar to `O_DSYNC` in POSIX. + /// + /// The precise semantics of this operation have not yet been defined for + /// WASI. At this time, it should be interpreted as a request, and not a + /// requirement. + data-integrity-sync, + /// Requests that reads be performed at the same level of integrity + /// requested for writes. This is similar to `O_RSYNC` in POSIX. + /// + /// The precise semantics of this operation have not yet been defined for + /// WASI. At this time, it should be interpreted as a request, and not a + /// requirement. + requested-write-sync, + /// Mutating directories mode: Directory contents may be mutated. + /// + /// When this flag is unset on a descriptor, operations using the + /// descriptor which would create, rename, delete, modify the data or + /// metadata of filesystem objects, or obtain another handle which + /// would permit any of those, shall fail with `error-code::read-only` if + /// they would otherwise succeed. + /// + /// This may only be set on directories. + mutate-directory, + } + + /// Flags determining the method of how paths are resolved. + @since(version = 0.3.0) + flags path-flags { + /// As long as the resolved path corresponds to a symbolic link, it is + /// expanded. + symlink-follow, + } + + /// Open flags used by `open-at`. + @since(version = 0.3.0) + flags open-flags { + /// Create file if it does not exist, similar to `O_CREAT` in POSIX. + create, + /// Fail if not a directory, similar to `O_DIRECTORY` in POSIX. + directory, + /// Fail if file already exists, similar to `O_EXCL` in POSIX. + exclusive, + /// Truncate file to size 0, similar to `O_TRUNC` in POSIX. + truncate, + } + + /// Number of hard links to an inode. + @since(version = 0.3.0) + type link-count = u64; + + /// File attributes. + /// + /// Note: This was called `filestat` in earlier versions of WASI. + @since(version = 0.3.0) + record descriptor-stat { + /// File type. + %type: descriptor-type, + /// Number of hard links to the file. + link-count: link-count, + /// For regular files, the file size in bytes. For symbolic links, the + /// length in bytes of the pathname contained in the symbolic link. + size: filesize, + /// Last data access timestamp. + /// + /// If the `option` is none, the platform doesn't maintain an access + /// timestamp for this file. + data-access-timestamp: option, + /// Last data modification timestamp. + /// + /// If the `option` is none, the platform doesn't maintain a + /// modification timestamp for this file. + data-modification-timestamp: option, + /// Last file status-change timestamp. + /// + /// If the `option` is none, the platform doesn't maintain a + /// status-change timestamp for this file. + status-change-timestamp: option, + } + + /// When setting a timestamp, this gives the value to set it to. + @since(version = 0.3.0) + variant new-timestamp { + /// Leave the timestamp set to its previous value. + no-change, + /// Set the timestamp to the current time of the system clock associated + /// with the filesystem. + now, + /// Set the timestamp to the given value. + timestamp(instant), + } + + /// A directory entry. + @since(version = 0.3.0) + record directory-entry { + /// The type of the file referred to by this directory entry. + %type: descriptor-type, + /// The name of the object. + name: string, + } + + /// Error codes returned by functions, similar to `errno` in POSIX. + /// Not all of these error codes are returned by the functions provided by this + /// API; some are used in higher-level library layers, and others are provided + /// merely for alignment with POSIX. + @since(version = 0.3.0) + variant error-code { + /// Permission denied, similar to `EACCES` in POSIX. + access, + /// Connection already in progress, similar to `EALREADY` in POSIX. + already, + /// Bad descriptor, similar to `EBADF` in POSIX. + bad-descriptor, + /// Device or resource busy, similar to `EBUSY` in POSIX. + busy, + /// Resource deadlock would occur, similar to `EDEADLK` in POSIX. + deadlock, + /// Storage quota exceeded, similar to `EDQUOT` in POSIX. + quota, + /// File exists, similar to `EEXIST` in POSIX. + exist, + /// File too large, similar to `EFBIG` in POSIX. + file-too-large, + /// Illegal byte sequence, similar to `EILSEQ` in POSIX. + illegal-byte-sequence, + /// Operation in progress, similar to `EINPROGRESS` in POSIX. + in-progress, + /// Interrupted function, similar to `EINTR` in POSIX. + interrupted, + /// Invalid argument, similar to `EINVAL` in POSIX. + invalid, + /// I/O error, similar to `EIO` in POSIX. + io, + /// Is a directory, similar to `EISDIR` in POSIX. + is-directory, + /// Too many levels of symbolic links, similar to `ELOOP` in POSIX. + loop, + /// Too many links, similar to `EMLINK` in POSIX. + too-many-links, + /// Message too large, similar to `EMSGSIZE` in POSIX. + message-size, + /// Filename too long, similar to `ENAMETOOLONG` in POSIX. + name-too-long, + /// No such device, similar to `ENODEV` in POSIX. + no-device, + /// No such file or directory, similar to `ENOENT` in POSIX. + no-entry, + /// No locks available, similar to `ENOLCK` in POSIX. + no-lock, + /// Not enough space, similar to `ENOMEM` in POSIX. + insufficient-memory, + /// No space left on device, similar to `ENOSPC` in POSIX. + insufficient-space, + /// Not a directory or a symbolic link to a directory, similar to `ENOTDIR` in POSIX. + not-directory, + /// Directory not empty, similar to `ENOTEMPTY` in POSIX. + not-empty, + /// State not recoverable, similar to `ENOTRECOVERABLE` in POSIX. + not-recoverable, + /// Not supported, similar to `ENOTSUP` and `ENOSYS` in POSIX. + unsupported, + /// Inappropriate I/O control operation, similar to `ENOTTY` in POSIX. + no-tty, + /// No such device or address, similar to `ENXIO` in POSIX. + no-such-device, + /// Value too large to be stored in data type, similar to `EOVERFLOW` in POSIX. + overflow, + /// Operation not permitted, similar to `EPERM` in POSIX. + not-permitted, + /// Broken pipe, similar to `EPIPE` in POSIX. + pipe, + /// Read-only file system, similar to `EROFS` in POSIX. + read-only, + /// Invalid seek, similar to `ESPIPE` in POSIX. + invalid-seek, + /// Text file busy, similar to `ETXTBSY` in POSIX. + text-file-busy, + /// Cross-device link, similar to `EXDEV` in POSIX. + cross-device, + /// A catch-all for errors not captured by the existing variants. + /// Implementations can use this to extend the error type without + /// breaking existing code. + other(option), + } + + /// File or memory access pattern advisory information. + @since(version = 0.3.0) + enum advice { + /// The application has no advice to give on its behavior with respect + /// to the specified data. + normal, + /// The application expects to access the specified data sequentially + /// from lower offsets to higher offsets. + sequential, + /// The application expects to access the specified data in a random + /// order. + random, + /// The application expects to access the specified data in the near + /// future. + will-need, + /// The application expects that it will not access the specified data + /// in the near future. + dont-need, + /// The application expects to access the specified data once and then + /// not reuse it thereafter. + no-reuse, + } + + /// A 128-bit hash value, split into parts because wasm doesn't have a + /// 128-bit integer type. + @since(version = 0.3.0) + record metadata-hash-value { + /// 64 bits of a 128-bit hash value. + lower: u64, + /// Another 64 bits of a 128-bit hash value. + upper: u64, + } + + /// A descriptor is a reference to a filesystem object, which may be a file, + /// directory, named pipe, special file, or other object on which filesystem + /// calls may be made. + @since(version = 0.3.0) + resource descriptor { + /// Return a stream for reading from a file. + /// + /// Multiple read, write, and append streams may be active on the same open + /// file and they do not interfere with each other. + /// + /// This function returns a `stream` which provides the data received from the + /// file, and a `future` providing additional error information in case an + /// error is encountered. + /// + /// If no error is encountered, `stream.read` on the `stream` will return + /// `read-status::closed` with no `error-context` and the future resolves to + /// the value `ok`. If an error is encountered, `stream.read` on the + /// `stream` returns `read-status::closed` with an `error-context` and the future + /// resolves to `err` with an `error-code`. + /// + /// Note: This is similar to `pread` in POSIX. + @since(version = 0.3.0) + read-via-stream: func(offset: filesize) -> tuple, future>>; + /// Return a stream for writing to a file, if available. + /// + /// May fail with an error-code describing why the file cannot be written. + /// + /// It is valid to write past the end of a file; the file is extended to the + /// extent of the write, with bytes between the previous end and the start of + /// the write set to zero. + /// + /// This function returns once either full contents of the stream are + /// written or an error is encountered. + /// + /// Note: This is similar to `pwrite` in POSIX. + @since(version = 0.3.0) + write-via-stream: func(data: stream, offset: filesize) -> future>; + /// Return a stream for appending to a file, if available. + /// + /// May fail with an error-code describing why the file cannot be appended. + /// + /// This function returns once either full contents of the stream are + /// written or an error is encountered. + /// + /// Note: This is similar to `write` with `O_APPEND` in POSIX. + @since(version = 0.3.0) + append-via-stream: func(data: stream) -> future>; + /// Provide file advisory information on a descriptor. + /// + /// This is similar to `posix_fadvise` in POSIX. + @since(version = 0.3.0) + advise: async func(offset: filesize, length: filesize, advice: advice) -> result<_, error-code>; + /// Synchronize the data of a file to disk. + /// + /// This function succeeds with no effect if the file descriptor is not + /// opened for writing. + /// + /// Note: This is similar to `fdatasync` in POSIX. + @since(version = 0.3.0) + sync-data: async func() -> result<_, error-code>; + /// Get flags associated with a descriptor. + /// + /// Note: This returns similar flags to `fcntl(fd, F_GETFL)` in POSIX. + /// + /// Note: This returns the value that was the `fs_flags` value returned + /// from `fdstat_get` in earlier versions of WASI. + @since(version = 0.3.0) + get-flags: async func() -> result; + /// Get the dynamic type of a descriptor. + /// + /// Note: This returns the same value as the `type` field of the `fd-stat` + /// returned by `stat`, `stat-at` and similar. + /// + /// Note: This returns similar flags to the `st_mode & S_IFMT` value provided + /// by `fstat` in POSIX. + /// + /// Note: This returns the value that was the `fs_filetype` value returned + /// from `fdstat_get` in earlier versions of WASI. + @since(version = 0.3.0) + get-type: async func() -> result; + /// Adjust the size of an open file. If this increases the file's size, the + /// extra bytes are filled with zeros. + /// + /// Note: This was called `fd_filestat_set_size` in earlier versions of WASI. + @since(version = 0.3.0) + set-size: async func(size: filesize) -> result<_, error-code>; + /// Adjust the timestamps of an open file or directory. + /// + /// Note: This is similar to `futimens` in POSIX. + /// + /// Note: This was called `fd_filestat_set_times` in earlier versions of WASI. + @since(version = 0.3.0) + set-times: async func(data-access-timestamp: new-timestamp, data-modification-timestamp: new-timestamp) -> result<_, error-code>; + /// Read directory entries from a directory. + /// + /// On filesystems where directories contain entries referring to themselves + /// and their parents, often named `.` and `..` respectively, these entries + /// are omitted. + /// + /// This always returns a new stream which starts at the beginning of the + /// directory. Multiple streams may be active on the same directory, and they + /// do not interfere with each other. + /// + /// This function returns a future, which will resolve to an error code if + /// reading full contents of the directory fails. + @since(version = 0.3.0) + read-directory: func() -> tuple, future>>; + /// Synchronize the data and metadata of a file to disk. + /// + /// This function succeeds with no effect if the file descriptor is not + /// opened for writing. + /// + /// Note: This is similar to `fsync` in POSIX. + @since(version = 0.3.0) + sync: async func() -> result<_, error-code>; + /// Create a directory. + /// + /// Note: This is similar to `mkdirat` in POSIX. + @since(version = 0.3.0) + create-directory-at: async func(path: string) -> result<_, error-code>; + /// Return the attributes of an open file or directory. + /// + /// Note: This is similar to `fstat` in POSIX, except that it does not return + /// device and inode information. For testing whether two descriptors refer to + /// the same underlying filesystem object, use `is-same-object`. To obtain + /// additional data that can be used do determine whether a file has been + /// modified, use `metadata-hash`. + /// + /// Note: This was called `fd_filestat_get` in earlier versions of WASI. + @since(version = 0.3.0) + stat: async func() -> result; + /// Return the attributes of a file or directory. + /// + /// Note: This is similar to `fstatat` in POSIX, except that it does not + /// return device and inode information. See the `stat` description for a + /// discussion of alternatives. + /// + /// Note: This was called `path_filestat_get` in earlier versions of WASI. + @since(version = 0.3.0) + stat-at: async func(path-flags: path-flags, path: string) -> result; + /// Adjust the timestamps of a file or directory. + /// + /// Note: This is similar to `utimensat` in POSIX. + /// + /// Note: This was called `path_filestat_set_times` in earlier versions of + /// WASI. + @since(version = 0.3.0) + set-times-at: async func(path-flags: path-flags, path: string, data-access-timestamp: new-timestamp, data-modification-timestamp: new-timestamp) -> result<_, error-code>; + /// Create a hard link. + /// + /// Fails with `error-code::no-entry` if the old path does not exist, + /// with `error-code::exist` if the new path already exists, and + /// `error-code::not-permitted` if the old path is not a file. + /// + /// Note: This is similar to `linkat` in POSIX. + @since(version = 0.3.0) + link-at: async func(old-path-flags: path-flags, old-path: string, new-descriptor: borrow, new-path: string) -> result<_, error-code>; + /// Open a file or directory. + /// + /// If `flags` contains `descriptor-flags::mutate-directory`, and the base + /// descriptor doesn't have `descriptor-flags::mutate-directory` set, + /// `open-at` fails with `error-code::read-only`. + /// + /// If `flags` contains `write` or `mutate-directory`, or `open-flags` + /// contains `truncate` or `create`, and the base descriptor doesn't have + /// `descriptor-flags::mutate-directory` set, `open-at` fails with + /// `error-code::read-only`. + /// + /// Note: This is similar to `openat` in POSIX. + @since(version = 0.3.0) + open-at: async func(path-flags: path-flags, path: string, open-flags: open-flags, %flags: descriptor-flags) -> result; + /// Read the contents of a symbolic link. + /// + /// If the contents contain an absolute or rooted path in the underlying + /// filesystem, this function fails with `error-code::not-permitted`. + /// + /// Note: This is similar to `readlinkat` in POSIX. + @since(version = 0.3.0) + readlink-at: async func(path: string) -> result; + /// Remove a directory. + /// + /// Return `error-code::not-empty` if the directory is not empty. + /// + /// Note: This is similar to `unlinkat(fd, path, AT_REMOVEDIR)` in POSIX. + @since(version = 0.3.0) + remove-directory-at: async func(path: string) -> result<_, error-code>; + /// Rename a filesystem object. + /// + /// Note: This is similar to `renameat` in POSIX. + @since(version = 0.3.0) + rename-at: async func(old-path: string, new-descriptor: borrow, new-path: string) -> result<_, error-code>; + /// Create a symbolic link (also known as a "symlink"). + /// + /// If `old-path` starts with `/`, the function fails with + /// `error-code::not-permitted`. + /// + /// Note: This is similar to `symlinkat` in POSIX. + @since(version = 0.3.0) + symlink-at: async func(old-path: string, new-path: string) -> result<_, error-code>; + /// Unlink a filesystem object that is not a directory. + /// + /// This is similar to `unlinkat(fd, path, 0)` in POSIX. + /// + /// Error returns are as specified by POSIX. + /// + /// If the filesystem object is a directory, `error-code::access` or + /// `error-code::is-directory` may be returned instead of the + /// POSIX-specified `error-code::not-permitted`. + @since(version = 0.3.0) + unlink-file-at: async func(path: string) -> result<_, error-code>; + /// Test whether two descriptors refer to the same filesystem object. + /// + /// In POSIX, this corresponds to testing whether the two descriptors have the + /// same device (`st_dev`) and inode (`st_ino` or `d_ino`) numbers. + /// wasi-filesystem does not expose device and inode numbers, so this function + /// may be used instead. + @since(version = 0.3.0) + is-same-object: async func(other: borrow) -> bool; + /// Return a hash of the metadata associated with a filesystem object referred + /// to by a descriptor. + /// + /// This returns a hash of the last-modification timestamp and file size, and + /// may also include the inode number, device number, birth timestamp, and + /// other metadata fields that may change when the file is modified or + /// replaced. It may also include a secret value chosen by the + /// implementation and not otherwise exposed. + /// + /// Implementations are encouraged to provide the following properties: + /// + /// - If the file is not modified or replaced, the computed hash value should + /// usually not change. + /// - If the object is modified or replaced, the computed hash value should + /// usually change. + /// - The inputs to the hash should not be easily computable from the + /// computed hash. + /// + /// However, none of these is required. + @since(version = 0.3.0) + metadata-hash: async func() -> result; + /// Return a hash of the metadata associated with a filesystem object referred + /// to by a directory descriptor and a relative path. + /// + /// This performs the same hash computation as `metadata-hash`. + @since(version = 0.3.0) + metadata-hash-at: async func(path-flags: path-flags, path: string) -> result; + } +} + +@since(version = 0.3.0) +interface preopens { + @since(version = 0.3.0) + use types.{descriptor}; + + /// Return the set of preopened directories, and their paths. + @since(version = 0.3.0) + get-directories: func() -> list>; +} + +@since(version = 0.3.0) +world imports { + @since(version = 0.3.0) + import wasi:clocks/types@0.3.0; + @since(version = 0.3.0) + import wasi:clocks/system-clock@0.3.0; + @since(version = 0.3.0) + import types; + @since(version = 0.3.0) + import preopens; +} diff --git a/src/spin_sdk/wit/deps/http.wit b/src/spin_sdk/wit/deps/http.wit new file mode 100644 index 0000000..08458f7 --- /dev/null +++ b/src/spin_sdk/wit/deps/http.wit @@ -0,0 +1,509 @@ +package wasi:http@0.3.0; + +/// This interface defines all of the types and methods for implementing HTTP +/// Requests and Responses, as well as their headers, trailers, and bodies. +@since(version = 0.3.0) +interface types { + use wasi:clocks/types@0.3.0.{duration}; + + /// This type corresponds to HTTP standard Methods. + @since(version = 0.3.0) + variant method { + get, + head, + post, + put, + delete, + connect, + options, + trace, + patch, + other(string), + } + + /// This type corresponds to HTTP standard Related Schemes. + @since(version = 0.3.0) + variant scheme { + HTTP, + HTTPS, + other(string), + } + + /// Defines the case payload type for `DNS-error` above: + @since(version = 0.3.0) + record DNS-error-payload { + rcode: option, + info-code: option, + } + + /// Defines the case payload type for `TLS-alert-received` above: + @since(version = 0.3.0) + record TLS-alert-received-payload { + alert-id: option, + alert-message: option, + } + + /// Defines the case payload type for `HTTP-response-{header,trailer}-size` above: + @since(version = 0.3.0) + record field-size-payload { + field-name: option, + field-size: option, + } + + /// These cases are inspired by the IANA HTTP Proxy Error Types: + /// + @since(version = 0.3.0) + variant error-code { + DNS-timeout, + DNS-error(DNS-error-payload), + destination-not-found, + destination-unavailable, + destination-IP-prohibited, + destination-IP-unroutable, + connection-refused, + connection-terminated, + connection-timeout, + connection-read-timeout, + connection-write-timeout, + connection-limit-reached, + TLS-protocol-error, + TLS-certificate-error, + TLS-alert-received(TLS-alert-received-payload), + HTTP-request-denied, + HTTP-request-length-required, + HTTP-request-body-size(option), + HTTP-request-method-invalid, + HTTP-request-URI-invalid, + HTTP-request-URI-too-long, + HTTP-request-header-section-size(option), + HTTP-request-header-size(option), + HTTP-request-trailer-section-size(option), + HTTP-request-trailer-size(field-size-payload), + HTTP-response-incomplete, + HTTP-response-header-section-size(option), + HTTP-response-header-size(field-size-payload), + HTTP-response-body-size(option), + HTTP-response-trailer-section-size(option), + HTTP-response-trailer-size(field-size-payload), + HTTP-response-transfer-coding(option), + HTTP-response-content-coding(option), + HTTP-response-timeout, + HTTP-upgrade-failed, + HTTP-protocol-error, + loop-detected, + configuration-error, + /// This is a catch-all error for anything that doesn't fit cleanly into a + /// more specific case. It also includes an optional string for an + /// unstructured description of the error. Users should not depend on the + /// string for diagnosing errors, as it's not required to be consistent + /// between implementations. + internal-error(option), + } + + /// This type enumerates the different kinds of errors that may occur when + /// setting or appending to a `fields` resource. + @since(version = 0.3.0) + variant header-error { + /// This error indicates that a `field-name` or `field-value` was + /// syntactically invalid when used with an operation that sets headers in a + /// `fields`. + invalid-syntax, + /// This error indicates that a forbidden `field-name` was used when trying + /// to set a header in a `fields`. + forbidden, + /// This error indicates that the operation on the `fields` was not + /// permitted because the fields are immutable. + immutable, + /// This error indicates that the operation would exceed an + /// implementation-defined limit on field sizes. This may apply to + /// an individual `field-value`, a single `field-name` plus all its + /// values, or the total aggregate size of all fields. + size-exceeded, + /// This is a catch-all error for anything that doesn't fit cleanly into a + /// more specific case. Implementations can use this to extend the error + /// type without breaking existing code. It also includes an optional + /// string for an unstructured description of the error. Users should not + /// depend on the string for diagnosing errors, as it's not required to be + /// consistent between implementations. + other(option), + } + + /// This type enumerates the different kinds of errors that may occur when + /// setting fields of a `request-options` resource. + @since(version = 0.3.0) + variant request-options-error { + /// Indicates the specified field is not supported by this implementation. + not-supported, + /// Indicates that the operation on the `request-options` was not permitted + /// because it is immutable. + immutable, + /// This is a catch-all error for anything that doesn't fit cleanly into a + /// more specific case. Implementations can use this to extend the error + /// type without breaking existing code. It also includes an optional + /// string for an unstructured description of the error. Users should not + /// depend on the string for diagnosing errors, as it's not required to be + /// consistent between implementations. + other(option), + } + + /// Field names are always strings. + /// + /// Field names should always be treated as case insensitive by the `fields` + /// resource for the purposes of equality checking. + @since(version = 0.3.0) + type field-name = string; + + /// Field values should always be ASCII strings. However, in + /// reality, HTTP implementations often have to interpret malformed values, + /// so they are provided as a list of bytes. + @since(version = 0.3.0) + type field-value = list; + + /// This following block defines the `fields` resource which corresponds to + /// HTTP standard Fields. Fields are a common representation used for both + /// Headers and Trailers. + /// + /// A `fields` may be mutable or immutable. A `fields` created using the + /// constructor, `from-list`, or `clone` will be mutable, but a `fields` + /// resource given by other means (including, but not limited to, + /// `request.headers`) might be be immutable. In an immutable fields, the + /// `set`, `append`, and `delete` operations will fail with + /// `header-error.immutable`. + /// + /// A `fields` resource should store `field-name`s and `field-value`s in their + /// original casing used to construct or mutate the `fields` resource. The `fields` + /// resource should use that original casing when serializing the fields for + /// transport or when returning them from a method. + /// + /// Implementations may impose limits on individual field values and on total + /// aggregate field section size. Operations that would exceed these limits + /// fail with `header-error.size-exceeded` + @since(version = 0.3.0) + resource fields { + /// Construct an empty HTTP Fields. + /// + /// The resulting `fields` is mutable. + constructor(); + /// Construct an HTTP Fields. + /// + /// The resulting `fields` is mutable. + /// + /// The list represents each name-value pair in the Fields. Names + /// which have multiple values are represented by multiple entries in this + /// list with the same name. + /// + /// The tuple is a pair of the field name, represented as a string, and + /// Value, represented as a list of bytes. In a valid Fields, all names + /// and values are valid UTF-8 strings. However, values are not always + /// well-formed, so they are represented as a raw list of bytes. + /// + /// An error result will be returned if any header or value was + /// syntactically invalid, if a header was forbidden, or if the + /// entries would exceed an implementation size limit. + from-list: static func(entries: list>) -> result; + /// Get all of the values corresponding to a name. If the name is not present + /// in this `fields`, an empty list is returned. However, if the name is + /// present but empty, this is represented by a list with one or more + /// empty field-values present. + get: func(name: field-name) -> list; + /// Returns `true` when the name is present in this `fields`. If the name is + /// syntactically invalid, `false` is returned. + has: func(name: field-name) -> bool; + /// Set all of the values for a name. Clears any existing values for that + /// name, if they have been set. + /// + /// Fails with `header-error.immutable` if the `fields` are immutable. + /// + /// Fails with `header-error.size-exceeded` if the name or values would + /// exceed an implementation-defined size limit. + set: func(name: field-name, value: list) -> result<_, header-error>; + /// Delete all values for a name. Does nothing if no values for the name + /// exist. + /// + /// Fails with `header-error.immutable` if the `fields` are immutable. + delete: func(name: field-name) -> result<_, header-error>; + /// Delete all values for a name. Does nothing if no values for the name + /// exist. + /// + /// Returns all values previously corresponding to the name, if any. + /// + /// Fails with `header-error.immutable` if the `fields` are immutable. + get-and-delete: func(name: field-name) -> result, header-error>; + /// Append a value for a name. Does not change or delete any existing + /// values for that name. + /// + /// Fails with `header-error.immutable` if the `fields` are immutable. + /// + /// Fails with `header-error.size-exceeded` if the value would exceed + /// an implementation-defined size limit. + append: func(name: field-name, value: field-value) -> result<_, header-error>; + /// Retrieve the full set of names and values in the Fields. Like the + /// constructor, the list represents each name-value pair. + /// + /// The outer list represents each name-value pair in the Fields. Names + /// which have multiple values are represented by multiple entries in this + /// list with the same name. + /// + /// The names and values are always returned in the original casing and in + /// the order in which they will be serialized for transport. + copy-all: func() -> list>; + /// Make a deep copy of the Fields. Equivalent in behavior to calling the + /// `fields` constructor on the return value of `copy-all`. The resulting + /// `fields` is mutable. + clone: func() -> fields; + } + + /// Headers is an alias for Fields. + @since(version = 0.3.0) + type headers = fields; + + /// Trailers is an alias for Fields. + @since(version = 0.3.0) + type trailers = fields; + + /// Represents an HTTP Request. + @since(version = 0.3.0) + resource request { + /// Construct a new `request` with a default `method` of `GET`, and + /// `none` values for `path-with-query`, `scheme`, and `authority`. + /// + /// `headers` is the HTTP Headers for the Request. + /// + /// `contents` is the optional body content stream with `none` + /// representing a zero-length content stream. + /// Once it is closed, `trailers` future must resolve to a result. + /// If `trailers` resolves to an error, underlying connection + /// will be closed immediately. + /// + /// `options` is optional `request-options` resource to be used + /// if the request is sent over a network connection. + /// + /// It is possible to construct, or manipulate with the accessor functions + /// below, a `request` with an invalid combination of `scheme` + /// and `authority`, or `headers` which are not permitted to be sent. + /// It is the obligation of the `handler.handle` implementation + /// to reject invalid constructions of `request`. + /// + /// The returned future resolves to result of transmission of this request. + new: static func(headers: headers, contents: option>, trailers: future, error-code>>, options: option) -> tuple>>; + /// Get the Method for the Request. + get-method: func() -> method; + /// Set the Method for the Request. Fails if the string present in a + /// `method.other` argument is not a syntactically valid method. + set-method: func(method: method) -> result; + /// Get the combination of the HTTP Path and Query for the Request. When + /// `none`, this represents an empty Path and empty Query. + get-path-with-query: func() -> option; + /// Set the combination of the HTTP Path and Query for the Request. When + /// `none`, this represents an empty Path and empty Query. Fails is the + /// string given is not a syntactically valid path and query uri component. + set-path-with-query: func(path-with-query: option) -> result; + /// Get the HTTP Related Scheme for the Request. When `none`, the + /// implementation may choose an appropriate default scheme. + get-scheme: func() -> option; + /// Set the HTTP Related Scheme for the Request. When `none`, the + /// implementation may choose an appropriate default scheme. Fails if the + /// string given is not a syntactically valid uri scheme. + set-scheme: func(scheme: option) -> result; + /// Get the authority of the Request's target URI. A value of `none` may be used + /// with Related Schemes which do not require an authority. The HTTP and + /// HTTPS schemes always require an authority. + get-authority: func() -> option; + /// Set the authority of the Request's target URI. A value of `none` may be used + /// with Related Schemes which do not require an authority. The HTTP and + /// HTTPS schemes always require an authority. Fails if the string given is + /// not a syntactically valid URI authority. + set-authority: func(authority: option) -> result; + /// Get the `request-options` to be associated with this request + /// + /// The returned `request-options` resource is immutable: `set-*` operations + /// will fail if invoked. + /// + /// This `request-options` resource is a child: it must be dropped before + /// the parent `request` is dropped, or its ownership is transferred to + /// another component by e.g. `handler.handle`. + get-options: func() -> option; + /// Get the headers associated with the Request. + /// + /// The returned `headers` resource is immutable: `set`, `append`, and + /// `delete` operations will fail with `header-error.immutable`. + get-headers: func() -> headers; + /// Get body of the Request. + /// + /// Stream returned by this method represents the contents of the body. + /// Once the stream is reported as closed, callers should await the returned + /// future to determine whether the body was received successfully. + /// The future will only resolve after the stream is reported as closed. + /// + /// This function takes a `res` future as a parameter, which can be used to + /// communicate an error in handling of the request. + /// + /// Note that function will move the `request`, but references to headers or + /// request options acquired from it previously will remain valid. + consume-body: static func(this: request, res: future>) -> tuple, future, error-code>>>; + } + + /// Parameters for making an HTTP Request. Each of these parameters is + /// currently an optional timeout applicable to the transport layer of the + /// HTTP protocol. + /// + /// These timeouts are separate from any the user may use to bound an + /// asynchronous call. + @since(version = 0.3.0) + resource request-options { + /// Construct a default `request-options` value. + constructor(); + /// The timeout for the initial connect to the HTTP Server. + get-connect-timeout: func() -> option; + /// Set the timeout for the initial connect to the HTTP Server. An error + /// return value indicates that this timeout is not supported or that this + /// handle is immutable. + set-connect-timeout: func(duration: option) -> result<_, request-options-error>; + /// The timeout for receiving the first byte of the Response body. + get-first-byte-timeout: func() -> option; + /// Set the timeout for receiving the first byte of the Response body. An + /// error return value indicates that this timeout is not supported or that + /// this handle is immutable. + set-first-byte-timeout: func(duration: option) -> result<_, request-options-error>; + /// The timeout for receiving subsequent chunks of bytes in the Response + /// body stream. + get-between-bytes-timeout: func() -> option; + /// Set the timeout for receiving subsequent chunks of bytes in the Response + /// body stream. An error return value indicates that this timeout is not + /// supported or that this handle is immutable. + set-between-bytes-timeout: func(duration: option) -> result<_, request-options-error>; + /// Make a deep copy of the `request-options`. + /// The resulting `request-options` is mutable. + clone: func() -> request-options; + } + + /// This type corresponds to the HTTP standard Status Code. + @since(version = 0.3.0) + type status-code = u16; + + /// Represents an HTTP Response. + @since(version = 0.3.0) + resource response { + /// Construct a new `response`, with a default `status-code` of `200`. + /// If a different `status-code` is needed, it must be set via the + /// `set-status-code` method. + /// + /// `headers` is the HTTP Headers for the Response. + /// + /// `contents` is the optional body content stream with `none` + /// representing a zero-length content stream. + /// Once it is closed, `trailers` future must resolve to a result. + /// If `trailers` resolves to an error, underlying connection + /// will be closed immediately. + /// + /// The returned future resolves to result of transmission of this response. + new: static func(headers: headers, contents: option>, trailers: future, error-code>>) -> tuple>>; + /// Get the HTTP Status Code for the Response. + get-status-code: func() -> status-code; + /// Set the HTTP Status Code for the Response. Fails if the status-code + /// given is not a valid http status code. + set-status-code: func(status-code: status-code) -> result; + /// Get the headers associated with the Response. + /// + /// The returned `headers` resource is immutable: `set`, `append`, and + /// `delete` operations will fail with `header-error.immutable`. + get-headers: func() -> headers; + /// Get body of the Response. + /// + /// Stream returned by this method represents the contents of the body. + /// Once the stream is reported as closed, callers should await the returned + /// future to determine whether the body was received successfully. + /// The future will only resolve after the stream is reported as closed. + /// + /// This function takes a `res` future as a parameter, which can be used to + /// communicate an error in handling of the response. + /// + /// Note that function will move the `response`, but references to headers + /// acquired from it previously will remain valid. + consume-body: static func(this: response, res: future>) -> tuple, future, error-code>>>; + } +} + +/// This interface defines a handler of HTTP Requests. +/// +/// In a `wasi:http/service` this interface is exported to respond to an +/// incoming HTTP Request with a Response. +/// +/// In `wasi:http/middleware` this interface is both exported and imported as +/// the "downstream" and "upstream" directions of the middleware chain. +@since(version = 0.3.0) +interface handler { + use types.{request, response, error-code}; + + /// This function may be called with either an incoming request read from the + /// network or a request synthesized or forwarded by another component. + handle: async func(request: request) -> result; +} + +/// This interface defines an HTTP client for sending "outgoing" requests. +/// +/// Most components are expected to import this interface to provide the +/// capability to send HTTP requests to arbitrary destinations on a network. +/// +/// The type signature of `client.send` is the same as `handler.handle`. This +/// duplication is currently necessary because some Component Model tooling +/// (including WIT itself) is unable to represent a component importing two +/// instances of the same interface. A `client.send` import may be linked +/// directly to a `handler.handle` export to bypass the network. +@since(version = 0.3.0) +interface client { + use types.{request, response, error-code}; + + /// This function may be used to either send an outgoing request over the + /// network or to forward it to another component. + send: async func(request: request) -> result; +} + +/// The `wasi:http/service` world captures a broad category of HTTP services +/// including web applications, API servers, and proxies. It may be `include`d +/// in more specific worlds such as `wasi:http/middleware`. +@since(version = 0.3.0) +world service { + import wasi:cli/types@0.3.0; + import wasi:cli/stdout@0.3.0; + import wasi:cli/stderr@0.3.0; + import wasi:cli/stdin@0.3.0; + import wasi:clocks/types@0.3.0; + import types; + import client; + import wasi:clocks/monotonic-clock@0.3.0; + import wasi:clocks/system-clock@0.3.0; + @unstable(feature = clocks-timezone) + import wasi:clocks/timezone@0.3.0; + import wasi:random/random@0.3.0; + import wasi:random/insecure@0.3.0; + import wasi:random/insecure-seed@0.3.0; + + export handler; +} +/// The `wasi:http/middleware` world captures HTTP services that forward HTTP +/// Requests to another handler. +/// +/// Components may implement this world to allow them to participate in handler +/// "chains" where a `request` flows through handlers on its way to some terminal +/// `service` and corresponding `response` flows in the opposite direction. +@since(version = 0.3.0) +world middleware { + import wasi:clocks/types@0.3.0; + import types; + import handler; + import wasi:cli/types@0.3.0; + import wasi:cli/stdout@0.3.0; + import wasi:cli/stderr@0.3.0; + import wasi:cli/stdin@0.3.0; + import client; + import wasi:clocks/monotonic-clock@0.3.0; + import wasi:clocks/system-clock@0.3.0; + @unstable(feature = clocks-timezone) + import wasi:clocks/timezone@0.3.0; + import wasi:random/random@0.3.0; + import wasi:random/insecure@0.3.0; + import wasi:random/insecure-seed@0.3.0; + + export handler; +} diff --git a/src/spin_sdk/wit/deps/random.wit b/src/spin_sdk/wit/deps/random.wit new file mode 100644 index 0000000..0b9a55f --- /dev/null +++ b/src/spin_sdk/wit/deps/random.wit @@ -0,0 +1,107 @@ +package wasi:random@0.3.0; + +/// The insecure-seed interface for seeding hash-map DoS resistance. +/// +/// It is intended to be portable at least between Unix-family platforms and +/// Windows. +@since(version = 0.3.0) +interface insecure-seed { + /// Return a 128-bit value that may contain a pseudo-random value. + /// + /// The returned value is not required to be computed from a CSPRNG, and may + /// even be entirely deterministic. Host implementations are encouraged to + /// provide pseudo-random values to any program exposed to + /// attacker-controlled content, to enable DoS protection built into many + /// languages' hash-map implementations. + /// + /// This function is intended to only be called once, by a source language + /// to initialize Denial Of Service (DoS) protection in its hash-map + /// implementation. + /// + /// # Expected future evolution + /// + /// This will likely be changed to a value import, to prevent it from being + /// called multiple times and potentially used for purposes other than DoS + /// protection. + @since(version = 0.3.0) + get-insecure-seed: func() -> tuple; +} + +/// The insecure interface for insecure pseudo-random numbers. +/// +/// It is intended to be portable at least between Unix-family platforms and +/// Windows. +@since(version = 0.3.0) +interface insecure { + /// Return up to `max-len` insecure pseudo-random bytes. + /// + /// This function is not cryptographically secure. Do not use it for + /// anything related to security. + /// + /// There are no requirements on the values of the returned bytes, however + /// implementations are encouraged to return evenly distributed values with + /// a long period. + /// + /// Implementations MAY return fewer bytes than requested (a short read). + /// Callers that require exactly `max-len` bytes MUST call this function in + /// a loop until the desired number of bytes has been accumulated. + /// Implementations MUST return at least 1 byte when `max-len` is greater + /// than zero. When `max-len` is zero, implementations MUST return an empty + /// list without trapping. + @since(version = 0.3.0) + get-insecure-random-bytes: func(max-len: u64) -> list; + + /// Return an insecure pseudo-random `u64` value. + /// + /// This function returns the same type of pseudo-random data as + /// `get-insecure-random-bytes`, represented as a `u64`. + @since(version = 0.3.0) + get-insecure-random-u64: func() -> u64; +} + +/// WASI Random is a random data API. +/// +/// It is intended to be portable at least between Unix-family platforms and +/// Windows. +@since(version = 0.3.0) +interface random { + /// Return up to `max-len` cryptographically-secure random or pseudo-random + /// bytes. + /// + /// This function must produce data at least as cryptographically secure and + /// fast as an adequately seeded cryptographically-secure pseudo-random + /// number generator (CSPRNG). It must not block, from the perspective of + /// the calling program, under any circumstances, including on the first + /// request and on requests for numbers of bytes. The returned data must + /// always be unpredictable. + /// + /// Implementations MAY return fewer bytes than requested (a short read). + /// Callers that require exactly `max-len` bytes MUST call this function in + /// a loop until the desired number of bytes has been accumulated. + /// Implementations MUST return at least 1 byte when `max-len` is greater + /// than zero. When `max-len` is zero, implementations MUST return an empty + /// list without trapping. + /// + /// This function must always return fresh data. Deterministic environments + /// must omit this function, rather than implementing it with deterministic + /// data. + @since(version = 0.3.0) + get-random-bytes: func(max-len: u64) -> list; + + /// Return a cryptographically-secure random or pseudo-random `u64` value. + /// + /// This function returns the same type of data as `get-random-bytes`, + /// represented as a `u64`. + @since(version = 0.3.0) + get-random-u64: func() -> u64; +} + +@since(version = 0.3.0) +world imports { + @since(version = 0.3.0) + import random; + @since(version = 0.3.0) + import insecure; + @since(version = 0.3.0) + import insecure-seed; +} diff --git a/src/spin_sdk/wit/deps/sockets.wit b/src/spin_sdk/wit/deps/sockets.wit new file mode 100644 index 0000000..0dd27a4 --- /dev/null +++ b/src/spin_sdk/wit/deps/sockets.wit @@ -0,0 +1,839 @@ +package wasi:sockets@0.3.0; + +@since(version = 0.3.0) +interface types { + @since(version = 0.3.0) + use wasi:clocks/types@0.3.0.{duration}; + + /// Error codes. + /// + /// In theory, every API can return any error code. + /// In practice, API's typically only return the errors documented per API + /// combined with a couple of errors that are always possible: + /// - `other` + /// - `access-denied` + /// - `not-supported` + /// - `out-of-memory` + /// + /// See each individual API for what the POSIX equivalents are. They sometimes differ per API. + @since(version = 0.3.0) + variant error-code { + /// Access denied. + /// + /// POSIX equivalent: EACCES, EPERM + access-denied, + /// The operation is not supported. + /// + /// POSIX equivalent: EOPNOTSUPP, ENOPROTOOPT, EPFNOSUPPORT, EPROTONOSUPPORT, ESOCKTNOSUPPORT + not-supported, + /// One of the arguments is invalid. + /// + /// POSIX equivalent: EINVAL, EDESTADDRREQ, EAFNOSUPPORT + invalid-argument, + /// Not enough memory to complete the operation. + /// + /// POSIX equivalent: ENOMEM, ENOBUFS + out-of-memory, + /// The operation timed out before it could finish completely. + /// + /// POSIX equivalent: ETIMEDOUT + timeout, + /// The operation is not valid in the socket's current state. + invalid-state, + /// The local address is not available. + /// + /// POSIX equivalent: EADDRNOTAVAIL + address-not-bindable, + /// A bind operation failed because the provided address is already in + /// use or because there are no ephemeral ports available. + /// + /// POSIX equivalent: EADDRINUSE + address-in-use, + /// The remote address is not reachable. + /// + /// POSIX equivalent: EHOSTUNREACH, EHOSTDOWN, ENETDOWN, ENETUNREACH, ENONET + remote-unreachable, + /// The connection was forcefully rejected. + /// + /// POSIX equivalent: ECONNREFUSED + connection-refused, + /// A write failed because the connection was broken. + /// + /// POSIX equivalent: EPIPE + connection-broken, + /// The connection was reset. + /// + /// POSIX equivalent: ECONNRESET + connection-reset, + /// The connection was aborted. + /// + /// POSIX equivalent: ECONNABORTED + connection-aborted, + /// The size of a datagram sent to a UDP socket exceeded the maximum + /// supported size. + /// + /// POSIX equivalent: EMSGSIZE + datagram-too-large, + /// A catch-all for errors not captured by the existing variants. + /// Implementations can use this to extend the error type without + /// breaking existing code. + other(option), + } + + @since(version = 0.3.0) + enum ip-address-family { + /// Similar to `AF_INET` in POSIX. + ipv4, + /// Similar to `AF_INET6` in POSIX. + ipv6, + } + + @since(version = 0.3.0) + type ipv4-address = tuple; + + @since(version = 0.3.0) + type ipv6-address = tuple; + + @since(version = 0.3.0) + variant ip-address { + ipv4(ipv4-address), + ipv6(ipv6-address), + } + + @since(version = 0.3.0) + record ipv4-socket-address { + /// sin_port + port: u16, + /// sin_addr + address: ipv4-address, + } + + @since(version = 0.3.0) + record ipv6-socket-address { + /// sin6_port + port: u16, + /// sin6_flowinfo + flow-info: u32, + /// sin6_addr + address: ipv6-address, + /// sin6_scope_id + scope-id: u32, + } + + @since(version = 0.3.0) + variant ip-socket-address { + ipv4(ipv4-socket-address), + ipv6(ipv6-socket-address), + } + + /// A TCP socket resource. + /// + /// The socket can be in one of the following states: + /// - `unbound` + /// - `bound` (See note below) + /// - `listening` + /// - `connecting` + /// - `connected` + /// - `closed` + /// See + /// for more information. + /// + /// Note: Except where explicitly mentioned, whenever this documentation uses + /// the term "bound" without backticks it actually means: in the `bound` state *or higher*. + /// (i.e. `bound`, `listening`, `connecting` or `connected`) + /// + /// WASI uses shared ownership semantics: the `tcp-socket` handle and all + /// derived `stream` and `future` values reference a single underlying OS + /// socket: + /// - Send/receive streams remain functional after the original `tcp-socket` + /// handle is dropped. + /// - The stream returned by `listen` behaves similarly. + /// - Client sockets returned by `tcp-socket::listen` are independent and do + /// not keep the listening socket alive. + /// + /// The OS socket is closed only after the last handle is dropped. This + /// model has observable effects; for example, it affects when the local + /// port binding is released. + /// + /// In addition to the general error codes documented on the + /// `types::error-code` type, TCP socket methods may always return + /// `error(invalid-state)` when in the `closed` state. + @since(version = 0.3.0) + resource tcp-socket { + /// Create a new TCP socket. + /// + /// Similar to `socket(AF_INET or AF_INET6, SOCK_STREAM, IPPROTO_TCP)` + /// in POSIX. On IPv6 sockets, IPV6_V6ONLY is enabled by default and + /// can't be configured otherwise. + /// + /// Unlike POSIX, WASI sockets have no notion of a socket-level + /// `O_NONBLOCK` flag. Instead they fully rely on the Component Model's + /// async support. + /// + /// # Typical errors + /// - `not-supported`: The `address-family` is not supported. (EAFNOSUPPORT) + /// + /// # References + /// - + /// - + /// - + /// - + @since(version = 0.3.0) + create: static func(address-family: ip-address-family) -> result; + /// Bind the socket to the provided IP address and port. + /// + /// If the IP address is zero (`0.0.0.0` in IPv4, `::` in IPv6), it is + /// left to the implementation to decide which network interface(s) to + /// bind to. If the TCP/UDP port is zero, the socket will be bound to a + /// random free port. + /// + /// Bind can be attempted multiple times on the same socket, even with + /// different arguments on each iteration. But never concurrently and + /// only as long as the previous bind failed. Once a bind succeeds, the + /// binding can't be changed anymore. + /// + /// # Typical errors + /// - `invalid-argument`: The `local-address` has the wrong address family. (EAFNOSUPPORT, EFAULT on Windows) + /// - `invalid-argument`: `local-address` is not a unicast address. (EINVAL) + /// - `invalid-argument`: `local-address` is an IPv4-mapped IPv6 address. (EINVAL) + /// - `invalid-state`: The socket is already bound. (EINVAL) + /// - `address-in-use`: No ephemeral ports available. (EADDRINUSE, ENOBUFS on Windows) + /// - `address-in-use`: Address is already in use. (EADDRINUSE) + /// - `address-not-bindable`: `local-address` is not an address that can be bound to. (EADDRNOTAVAIL) + /// + /// # Implementors note + /// The bind operation shouldn't be affected by the TIME_WAIT state of a + /// recently closed socket on the same local address. In practice this + /// means that the SO_REUSEADDR socket option should be set implicitly + /// on all platforms, except on Windows where this is the default + /// behavior and SO_REUSEADDR performs something different. + /// + /// # References + /// - + /// - + /// - + /// - + @since(version = 0.3.0) + bind: func(local-address: ip-socket-address) -> result<_, error-code>; + /// Connect to a remote endpoint. + /// + /// On success, the socket is transitioned into the `connected` state + /// and the `remote-address` of the socket is updated. + /// The `local-address` may be updated as well, based on the best network + /// path to `remote-address`. If the socket was not already explicitly + /// bound, this function will implicitly bind the socket to a random + /// free port. + /// + /// After a failed connection attempt, the socket will be in the `closed` + /// state and the only valid action left is to `drop` the socket. A single + /// socket can not be used to connect more than once. + /// + /// # Typical errors + /// - `invalid-argument`: The `remote-address` has the wrong address family. (EAFNOSUPPORT) + /// - `invalid-argument`: `remote-address` is not a unicast address. (EINVAL, ENETUNREACH on Linux, EAFNOSUPPORT on MacOS) + /// - `invalid-argument`: `remote-address` is an IPv4-mapped IPv6 address. (EINVAL, EADDRNOTAVAIL on Illumos) + /// - `invalid-argument`: The IP address in `remote-address` is set to INADDR_ANY (`0.0.0.0` / `::`). (EADDRNOTAVAIL on Windows) + /// - `invalid-argument`: The port in `remote-address` is set to 0. (EADDRNOTAVAIL on Windows) + /// - `invalid-state`: The socket is already in the `connecting` state. (EALREADY) + /// - `invalid-state`: The socket is already in the `connected` state. (EISCONN) + /// - `invalid-state`: The socket is already in the `listening` state. (EOPNOTSUPP, EINVAL on Windows) + /// - `timeout`: Connection timed out. (ETIMEDOUT) + /// - `connection-refused`: The connection was forcefully rejected. (ECONNREFUSED) + /// - `connection-reset`: The connection was reset. (ECONNRESET) + /// - `connection-aborted`: The connection was aborted. (ECONNABORTED) + /// - `remote-unreachable`: The remote address is not reachable. (EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) + /// - `address-in-use`: Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE, EADDRNOTAVAIL on Linux, EAGAIN on BSD) + /// + /// # References + /// - + /// - + /// - + /// - + @since(version = 0.3.0) + connect: async func(remote-address: ip-socket-address) -> result<_, error-code>; + /// Start listening and return a stream of new inbound connections. + /// + /// Transitions the socket into the `listening` state. This can be called + /// at most once per socket. + /// + /// If the socket is not already explicitly bound, this function will + /// implicitly bind the socket to a random free port. + /// + /// Normally, the returned sockets are bound, in the `connected` state + /// and immediately ready for I/O. Though, depending on exact timing and + /// circumstances, a newly accepted connection may already be `closed` + /// by the time the server attempts to perform its first I/O on it. This + /// is true regardless of whether the WASI implementation uses + /// "synthesized" sockets or not (see Implementors Notes below). + /// + /// The following properties are inherited from the listener socket: + /// - `address-family` + /// - `keep-alive-enabled` + /// - `keep-alive-idle-time` + /// - `keep-alive-interval` + /// - `keep-alive-count` + /// - `hop-limit` + /// - `receive-buffer-size` + /// - `send-buffer-size` + /// + /// # Typical errors + /// - `invalid-state`: The socket is already in the `connected` state. (EISCONN, EINVAL on BSD) + /// - `invalid-state`: The socket is already in the `listening` state. + /// - `address-in-use`: Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE) + /// + /// # Implementors note + /// This method returns a single perpetual stream that should only close + /// on fatal errors (if any). Yet, the POSIX' `accept` function may also + /// return transient errors (e.g. ECONNABORTED). The exact details differ + /// per operation system. For example, the Linux manual mentions: + /// + /// > Linux accept() passes already-pending network errors on the new + /// > socket as an error code from accept(). This behavior differs from + /// > other BSD socket implementations. For reliable operation the + /// > application should detect the network errors defined for the + /// > protocol after accept() and treat them like EAGAIN by retrying. + /// > In the case of TCP/IP, these are ENETDOWN, EPROTO, ENOPROTOOPT, + /// > EHOSTDOWN, ENONET, EHOSTUNREACH, EOPNOTSUPP, and ENETUNREACH. + /// Source: https://man7.org/linux/man-pages/man2/accept.2.html + /// + /// WASI implementations have two options to handle this: + /// - Optionally log it and then skip over non-fatal errors returned by + /// `accept`. Guest code never gets to see these failures. Or: + /// - Synthesize a `tcp-socket` resource that exposes the error when + /// attempting to send or receive on it. Guest code then sees these + /// failures as regular I/O errors. + /// + /// In either case, the stream returned by this `listen` method remains + /// operational. + /// + /// WASI requires `listen` to perform an implicit bind if the socket + /// has not already been bound. Not all platforms (notably Windows) + /// exhibit this behavior out of the box. On platforms that require it, + /// the WASI implementation can emulate this behavior by performing + /// the bind itself if the guest hasn't already done so. + /// + /// # References + /// - + /// - + /// - + /// - + /// - + /// - + /// - + /// - + @since(version = 0.3.0) + listen: func() -> result, error-code>; + /// Transmit data to peer. + /// + /// The caller should close the stream when it has no more data to send + /// to the peer. Under normal circumstances this will cause a FIN packet + /// to be sent out. Closing the stream is equivalent to calling + /// `shutdown(SHUT_WR)` in POSIX. + /// + /// This function may be called at most once and returns once the full + /// contents of the stream are transmitted or an error is encountered. + /// + /// # Typical errors + /// - `invalid-state`: The socket is not in the `connected` state. (ENOTCONN) + /// - `invalid-state`: `send` has already been called on this socket. + /// - `connection-broken`: The connection is not writable anymore. (EPIPE, ECONNABORTED on Windows) + /// - `connection-reset`: The connection was reset. (ECONNRESET) + /// - `remote-unreachable`: The remote address is not reachable. (EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) + /// + /// # References + /// - + /// - + /// - + /// - + @since(version = 0.3.0) + send: func(data: stream) -> future>; + /// Read data from peer. + /// + /// Returns a `stream` of data sent by the peer. The implementation + /// drops the stream once no more data is available. At that point, the + /// returned `future` resolves to: + /// - `ok` after a graceful shutdown from the peer (i.e. a FIN packet), or + /// - `err` if the socket was closed abnormally. + /// + /// `receive` may be called only once per socket. Subsequent calls return + /// a closed stream and a future resolved to `err(invalid-state)`. + /// + /// If the caller is not expecting to receive any more data from the peer, + /// they should drop the stream. Any data still in the receive queue + /// will be discarded. This is equivalent to calling `shutdown(SHUT_RD)` + /// in POSIX. + /// + /// # Typical errors + /// - `invalid-state`: The socket is not in the `connected` state. (ENOTCONN) + /// - `invalid-state`: `receive` has already been called on this socket. + /// - `connection-reset`: The connection was reset. (ECONNRESET) + /// - `remote-unreachable`: The remote address is not reachable. (EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) + /// + /// # References + /// - + /// - + /// - + /// - + @since(version = 0.3.0) + receive: func() -> tuple, future>>; + /// Get the bound local address. + /// + /// POSIX mentions: + /// > If the socket has not been bound to a local name, the value + /// > stored in the object pointed to by `address` is unspecified. + /// + /// WASI is stricter and requires `get-local-address` to return + /// `invalid-state` when the socket hasn't been bound yet. + /// + /// # Typical errors + /// - `invalid-state`: The socket is not bound to any local address. + /// + /// # References + /// - + /// - + /// - + /// - + @since(version = 0.3.0) + get-local-address: func() -> result; + /// Get the remote address. + /// + /// # Typical errors + /// - `invalid-state`: The socket is not connected to a remote address. (ENOTCONN) + /// + /// # References + /// - + /// - + /// - + /// - + @since(version = 0.3.0) + get-remote-address: func() -> result; + /// Whether the socket is in the `listening` state. + /// + /// Equivalent to the SO_ACCEPTCONN socket option. + @since(version = 0.3.0) + get-is-listening: func() -> bool; + /// Whether this is a IPv4 or IPv6 socket. + /// + /// This is the value passed to the constructor. + /// + /// Equivalent to the SO_DOMAIN socket option. + @since(version = 0.3.0) + get-address-family: func() -> ip-address-family; + /// Hints the desired listen queue size. Implementations are free to + /// ignore this. + /// + /// If the provided value is 0, an `invalid-argument` error is returned. + /// Any other value will never cause an error, but it might be silently + /// clamped and/or rounded. + /// + /// # Typical errors + /// - `not-supported`: (set) The platform does not support changing the backlog size after the initial listen. + /// - `invalid-argument`: (set) The provided value was 0. + /// - `invalid-state`: (set) The socket is in the `connecting` or `connected` state. + @since(version = 0.3.0) + set-listen-backlog-size: func(value: u64) -> result<_, error-code>; + /// Enables or disables keepalive. + /// + /// The keepalive behavior can be adjusted using: + /// - `keep-alive-idle-time` + /// - `keep-alive-interval` + /// - `keep-alive-count` + /// These properties can be configured while `keep-alive-enabled` is + /// false, but only come into effect when `keep-alive-enabled` is true. + /// + /// Equivalent to the SO_KEEPALIVE socket option. + @since(version = 0.3.0) + get-keep-alive-enabled: func() -> result; + @since(version = 0.3.0) + set-keep-alive-enabled: func(value: bool) -> result<_, error-code>; + /// Amount of time the connection has to be idle before TCP starts + /// sending keepalive packets. + /// + /// If the provided value is 0, an `invalid-argument` error is returned. + /// All other values are accepted without error, but may be + /// clamped or rounded. As a result, the value read back from + /// this setting may differ from the value that was set. + /// + /// Equivalent to the TCP_KEEPIDLE socket option. (TCP_KEEPALIVE on MacOS) + /// + /// # Typical errors + /// - `invalid-argument`: (set) The provided value was 0. + @since(version = 0.3.0) + get-keep-alive-idle-time: func() -> result; + @since(version = 0.3.0) + set-keep-alive-idle-time: func(value: duration) -> result<_, error-code>; + /// The time between keepalive packets. + /// + /// If the provided value is 0, an `invalid-argument` error is returned. + /// All other values are accepted without error, but may be + /// clamped or rounded. As a result, the value read back from + /// this setting may differ from the value that was set. + /// + /// Equivalent to the TCP_KEEPINTVL socket option. + /// + /// # Typical errors + /// - `invalid-argument`: (set) The provided value was 0. + @since(version = 0.3.0) + get-keep-alive-interval: func() -> result; + @since(version = 0.3.0) + set-keep-alive-interval: func(value: duration) -> result<_, error-code>; + /// The maximum amount of keepalive packets TCP should send before + /// aborting the connection. + /// + /// If the provided value is 0, an `invalid-argument` error is returned. + /// All other values are accepted without error, but may be + /// clamped or rounded. As a result, the value read back from + /// this setting may differ from the value that was set. + /// + /// Equivalent to the TCP_KEEPCNT socket option. + /// + /// # Typical errors + /// - `invalid-argument`: (set) The provided value was 0. + @since(version = 0.3.0) + get-keep-alive-count: func() -> result; + @since(version = 0.3.0) + set-keep-alive-count: func(value: u32) -> result<_, error-code>; + /// Equivalent to the IP_TTL & IPV6_UNICAST_HOPS socket options. + /// + /// If the provided value is 0, an `invalid-argument` error is returned. + /// + /// # Typical errors + /// - `invalid-argument`: (set) The TTL value must be 1 or higher. + @since(version = 0.3.0) + get-hop-limit: func() -> result; + @since(version = 0.3.0) + set-hop-limit: func(value: u8) -> result<_, error-code>; + /// Kernel buffer space reserved for sending/receiving on this socket. + /// Implementations usually treat this as a cap the buffer can grow to, + /// rather than allocating the full amount immediately. + /// + /// If the provided value is 0, an `invalid-argument` error is returned. + /// All other values are accepted without error, but may be + /// clamped or rounded. As a result, the value read back from + /// this setting may differ from the value that was set. + /// + /// This is only a performance hint. The implementation may ignore it or + /// tweak it based on real traffic patterns. + /// Linux and macOS appear to behave differently depending on whether a + /// buffer size was explicitly set. When set, they tend to honor it; when + /// not set, they dynamically adjust the buffer size as the connection + /// progresses. This is especially noticeable when comparing the values + /// from before and after connection establishment. + /// + /// Equivalent to the SO_RCVBUF and SO_SNDBUF socket options. + /// + /// # Typical errors + /// - `invalid-argument`: (set) The provided value was 0. + @since(version = 0.3.0) + get-receive-buffer-size: func() -> result; + @since(version = 0.3.0) + set-receive-buffer-size: func(value: u64) -> result<_, error-code>; + @since(version = 0.3.0) + get-send-buffer-size: func() -> result; + @since(version = 0.3.0) + set-send-buffer-size: func(value: u64) -> result<_, error-code>; + } + + /// A UDP socket handle. + @since(version = 0.3.0) + resource udp-socket { + /// Create a new UDP socket. + /// + /// Similar to `socket(AF_INET or AF_INET6, SOCK_DGRAM, IPPROTO_UDP)` + /// in POSIX. On IPv6 sockets, IPV6_V6ONLY is enabled by default and + /// can't be configured otherwise. + /// + /// Unlike POSIX, WASI sockets have no notion of a socket-level + /// `O_NONBLOCK` flag. Instead they fully rely on the Component Model's + /// async support. + /// + /// # References: + /// - + /// - + /// - + /// - + @since(version = 0.3.0) + create: static func(address-family: ip-address-family) -> result; + /// Bind the socket to the provided IP address and port. + /// + /// If the IP address is zero (`0.0.0.0` in IPv4, `::` in IPv6), it is + /// left to the implementation to decide which network interface(s) to + /// bind to. If the port is zero, the socket will be bound to a random + /// free port. + /// + /// # Typical errors + /// - `invalid-argument`: The `local-address` has the wrong address family. (EAFNOSUPPORT, EFAULT on Windows) + /// - `invalid-state`: The socket is already bound. (EINVAL) + /// - `address-in-use`: No ephemeral ports available. (EADDRINUSE, ENOBUFS on Windows) + /// - `address-in-use`: Address is already in use. (EADDRINUSE) + /// - `address-not-bindable`: `local-address` is not an address that can be bound to. (EADDRNOTAVAIL) + /// + /// # References + /// - + /// - + /// - + /// - + @since(version = 0.3.0) + bind: func(local-address: ip-socket-address) -> result<_, error-code>; + /// Associate this socket with a specific peer address. + /// + /// On success, the `remote-address` of the socket is updated. + /// The `local-address` may be updated as well, based on the best network + /// path to `remote-address`. If the socket was not already explicitly + /// bound, this function will implicitly bind the socket to a random + /// free port. + /// + /// When a UDP socket is "connected", the `send` and `receive` methods + /// are limited to communicating with that peer only: + /// - `send` can only be used to send to this destination. + /// - `receive` will only return datagrams sent from the provided `remote-address`. + /// + /// The name "connect" was kept to align with the existing POSIX + /// terminology. Other than that, this function only changes the local + /// socket configuration and does not generate any network traffic. + /// The peer is not aware of this "connection". + /// + /// This method may be called multiple times on the same socket to change + /// its association, but only the most recent one will be effective. + /// + /// # Typical errors + /// - `invalid-argument`: The `remote-address` has the wrong address family. (EAFNOSUPPORT) + /// - `invalid-argument`: The IP address in `remote-address` is set to INADDR_ANY (`0.0.0.0` / `::`). (EDESTADDRREQ, EADDRNOTAVAIL) + /// - `invalid-argument`: The port in `remote-address` is set to 0. (EDESTADDRREQ, EADDRNOTAVAIL) + /// - `address-in-use`: Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE, EADDRNOTAVAIL on Linux, EAGAIN on BSD) + /// + /// # Implementors note + /// If the socket is already connected, some platforms (e.g. Linux) + /// require a disconnect before connecting to a different peer address. + /// + /// # References + /// - + /// - + /// - + /// - + @since(version = 0.3.0) + connect: func(remote-address: ip-socket-address) -> result<_, error-code>; + /// Dissociate this socket from its peer address. + /// + /// After calling this method, `send` & `receive` are free to communicate + /// with any remote address again. + /// + /// The POSIX equivalent of this is calling `connect` with an `AF_UNSPEC` address. + /// + /// # Typical errors + /// - `invalid-state`: The socket is not connected. + /// + /// # References + /// - + /// - + /// - + /// - + @since(version = 0.3.0) + disconnect: func() -> result<_, error-code>; + /// Send a message on the socket to a particular peer. + /// + /// If the socket is connected, the peer address may be left empty. In + /// that case this is equivalent to `send` in POSIX. Otherwise it is + /// equivalent to `sendto`. + /// + /// Additionally, if the socket is connected, a `remote-address` argument + /// _may_ be provided but then it must be identical to the address + /// passed to `connect`. + /// + /// If the socket has not been explicitly bound, it will be + /// implicitly bound to a random free port. + /// + /// Implementations may trap if the `data` length exceeds 64 KiB. + /// + /// # Typical errors + /// - `invalid-argument`: The `remote-address` has the wrong address family. (EAFNOSUPPORT) + /// - `invalid-argument`: The IP address in `remote-address` is set to INADDR_ANY (`0.0.0.0` / `::`). (EDESTADDRREQ, EADDRNOTAVAIL) + /// - `invalid-argument`: The port in `remote-address` is set to 0. (EDESTADDRREQ, EADDRNOTAVAIL) + /// - `invalid-argument`: The socket is in "connected" mode and `remote-address` is `some` value that does not match the address passed to `connect`. (EISCONN) + /// - `invalid-argument`: The socket is not "connected" and no value for `remote-address` was provided. (EDESTADDRREQ) + /// - `remote-unreachable`: The remote address is not reachable. (ECONNRESET, ENETRESET on Windows, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) + /// - `connection-refused`: The connection was refused. (ECONNREFUSED) + /// - `datagram-too-large`: The datagram is too large. (EMSGSIZE) + /// - `address-in-use`: Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE) + /// + /// # Implementors note + /// WASI requires `send` to perform an implicit bind if the socket + /// has not been bound. Not all platforms (notably Windows) exhibit + /// this behavior natively. On such platforms, the WASI implementation + /// should emulate it by performing the bind if the guest has not + /// already done so. + /// + /// # References + /// - + /// - + /// - + /// - + /// - + /// - + /// - + /// - + @since(version = 0.3.0) + send: async func(data: list, remote-address: option) -> result<_, error-code>; + /// Receive a message on the socket. + /// + /// On success, the return value contains a tuple of the received data + /// and the address of the sender. Theoretical maximum length of the + /// data is 64 KiB. Though in practice, it will typically be less than + /// 1500 bytes. + /// + /// If the socket is connected, the sender address is guaranteed to + /// match the remote address passed to `connect`. + /// + /// # Typical errors + /// - `invalid-state`: The socket has not been bound yet. + /// - `remote-unreachable`: The remote address is not reachable. (ECONNRESET, ENETRESET on Windows, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) + /// - `connection-refused`: The connection was refused. (ECONNREFUSED) + /// + /// # References + /// - + /// - + /// - + /// - + /// - + /// - + /// - + @since(version = 0.3.0) + receive: async func() -> result, ip-socket-address>, error-code>; + /// Get the current bound address. + /// + /// POSIX mentions: + /// > If the socket has not been bound to a local name, the value + /// > stored in the object pointed to by `address` is unspecified. + /// + /// WASI is stricter and requires `get-local-address` to return + /// `invalid-state` when the socket hasn't been bound yet. + /// + /// # Typical errors + /// - `invalid-state`: The socket is not bound to any local address. + /// + /// # References + /// - + /// - + /// - + /// - + @since(version = 0.3.0) + get-local-address: func() -> result; + /// Get the address the socket is currently "connected" to. + /// + /// # Typical errors + /// - `invalid-state`: The socket is not "connected" to a specific remote address. (ENOTCONN) + /// + /// # References + /// - + /// - + /// - + /// - + @since(version = 0.3.0) + get-remote-address: func() -> result; + /// Whether this is a IPv4 or IPv6 socket. + /// + /// This is the value passed to the constructor. + /// + /// Equivalent to the SO_DOMAIN socket option. + @since(version = 0.3.0) + get-address-family: func() -> ip-address-family; + /// Equivalent to the IP_TTL & IPV6_UNICAST_HOPS socket options. + /// + /// If the provided value is 0, an `invalid-argument` error is returned. + /// + /// # Typical errors + /// - `invalid-argument`: (set) The TTL value must be 1 or higher. + @since(version = 0.3.0) + get-unicast-hop-limit: func() -> result; + @since(version = 0.3.0) + set-unicast-hop-limit: func(value: u8) -> result<_, error-code>; + /// Kernel buffer space reserved for sending/receiving on this socket. + /// Implementations usually treat this as a cap the buffer can grow to, + /// rather than allocating the full amount immediately. + /// + /// If the provided value is 0, an `invalid-argument` error is returned. + /// All other values are accepted without error, but may be + /// clamped or rounded. As a result, the value read back from + /// this setting may differ from the value that was set. + /// + /// Equivalent to the SO_RCVBUF and SO_SNDBUF socket options. + /// + /// # Typical errors + /// - `invalid-argument`: (set) The provided value was 0. + @since(version = 0.3.0) + get-receive-buffer-size: func() -> result; + @since(version = 0.3.0) + set-receive-buffer-size: func(value: u64) -> result<_, error-code>; + @since(version = 0.3.0) + get-send-buffer-size: func() -> result; + @since(version = 0.3.0) + set-send-buffer-size: func(value: u64) -> result<_, error-code>; + } +} + +@since(version = 0.3.0) +interface ip-name-lookup { + @since(version = 0.3.0) + use types.{ip-address}; + + /// Lookup error codes. + @since(version = 0.3.0) + variant error-code { + /// Access denied. + /// + /// POSIX equivalent: EACCES, EPERM + access-denied, + /// `name` is a syntactically invalid domain name or IP address. + /// + /// POSIX equivalent: EINVAL + invalid-argument, + /// Name does not exist or has no suitable associated IP addresses. + /// + /// POSIX equivalent: EAI_NONAME, EAI_NODATA, EAI_ADDRFAMILY + name-unresolvable, + /// A temporary failure in name resolution occurred. + /// + /// POSIX equivalent: EAI_AGAIN + temporary-resolver-failure, + /// A permanent failure in name resolution occurred. + /// + /// POSIX equivalent: EAI_FAIL + permanent-resolver-failure, + /// A catch-all for errors not captured by the existing variants. + /// Implementations can use this to extend the error type without + /// breaking existing code. + other(option), + } + + /// Resolve an internet host name to a list of IP addresses. + /// + /// Unicode domain names are automatically converted to ASCII using IDNA + /// encoding. If the input is an IP address string, the address is parsed + /// and returned as-is without making any external requests. + /// + /// See the wasi-socket proposal README.md for a comparison with getaddrinfo. + /// + /// The results are returned in connection order preference. + /// + /// This function never succeeds with 0 results. It either fails or succeeds + /// with at least one address. Additionally, this function never returns + /// IPv4-mapped IPv6 addresses. + /// + /// # References: + /// - + /// - + /// - + /// - + @since(version = 0.3.0) + resolve-addresses: async func(name: string) -> result, error-code>; +} + +@since(version = 0.3.0) +world imports { + @since(version = 0.3.0) + import wasi:clocks/types@0.3.0; + @since(version = 0.3.0) + import types; + @since(version = 0.3.0) + import ip-name-lookup; +} diff --git a/src/spin_sdk/wit/deps/spin-mysql@3.0.0/mysql.wit b/src/spin_sdk/wit/deps/spin-mysql@3.0.0/mysql.wit new file mode 100644 index 0000000..ca1fd58 --- /dev/null +++ b/src/spin_sdk/wit/deps/spin-mysql@3.0.0/mysql.wit @@ -0,0 +1,88 @@ +package spin:mysql@3.0.0; + +interface mysql { + /// Errors related to interacting with a database. + variant error { + connection-failed(string), + bad-parameter(string), + query-failed(string), + value-conversion-failed(string), + other(string) + } + + /// Data types for a database column + enum db-data-type { + boolean, + int8, + int16, + int32, + int64, + uint8, + uint16, + uint32, + uint64, + floating32, + floating64, + str, + binary, + other, + } + + /// Database values + variant db-value { + boolean(bool), + int8(s8), + int16(s16), + int32(s32), + int64(s64), + uint8(u8), + uint16(u16), + uint32(u32), + uint64(u64), + floating32(f32), + floating64(f64), + str(string), + binary(list), + db-null, + unsupported, + } + + /// Values used in parameterized queries + variant parameter-value { + boolean(bool), + int8(s8), + int16(s16), + int32(s32), + int64(s64), + uint8(u8), + uint16(u16), + uint32(u32), + uint64(u64), + floating32(f32), + floating64(f64), + str(string), + binary(list), + db-null, + } + + /// A database column + record column { + name: string, + data-type: db-data-type, + } + + /// A database row + type row = list; + + /// A connection to a MySQL database. + resource connection { + /// Open a connection to the MySQL instance at `address`. + open: static async func(address: string) -> result; + + /// query the database: select + query: async func(statement: string, params: list) -> result, stream, future>>, error>; + + /// execute command to the database: insert, update, delete + execute: async func(statement: string, params: list) -> result<_, error>; + } +} diff --git a/src/spin_sdk/wit/deps/spin@4.0.0/world.wit b/src/spin_sdk/wit/deps/spin@4.0.0/world.wit new file mode 100644 index 0000000..d2e70b6 --- /dev/null +++ b/src/spin_sdk/wit/deps/spin@4.0.0/world.wit @@ -0,0 +1,34 @@ +package spin:up@4.0.0; + +/// The full world of a guest targeting an http-trigger +world http-trigger { + include platform; + export wasi:http/handler@0.3.0-rc-2026-03-15; +} + +/// The full world of a guest targeting a redis-trigger +world redis-trigger { + include platform; + export spin:redis/inbound-redis@3.0.0; +} + +/// The imports needed for a guest to run on a Spin host +world platform { + include wasi:cli/imports@0.2.6; + include wasi:cli/imports@0.3.0-rc-2026-03-15; + import wasi:http/outgoing-handler@0.2.6; + import wasi:http/client@0.3.0-rc-2026-03-15; + @unstable(feature = wasi-otel) + include wasi:otel/imports@0.2.0-rc.2; + include fermyon:spin/platform@2.0.0; + include wasi:keyvalue/imports@0.2.0-draft2; + import spin:key-value/key-value@3.0.0; + import spin:mqtt/mqtt@3.0.0; + import spin:mysql/mysql@3.0.0; + import spin:postgres/postgres@3.0.0; + import spin:postgres/postgres@4.2.0; + import spin:redis/redis@3.0.0; + import spin:sqlite/sqlite@3.1.0; + import spin:variables/variables@3.0.0; + import wasi:config/store@0.2.0-draft-2024-09-27; +} diff --git a/src/spin_sdk/wit/exports/__init__.py b/src/spin_sdk/wit/exports/__init__.py index 77b2af1..b650252 100644 --- a/src/spin_sdk/wit/exports/__init__.py +++ b/src/spin_sdk/wit/exports/__init__.py @@ -9,7 +9,7 @@ from componentize_py_types import Result, Ok, Err, Some from ..imports import wasi_http_types_0_2_0 -from ..imports import wasi_http_types_0_3_0_rc_2026_03_15 +from ..imports import wasi_http_types_0_3_0 import componentize_py_async_support from componentize_py_async_support.streams import StreamReader, StreamWriter, ByteStreamReader, ByteStreamWriter from componentize_py_async_support.futures import FutureReader, FutureWriter @@ -60,12 +60,12 @@ async def handle_message(self, message: bytes) -> None: class HttpHandler(Protocol): @abstractmethod - async def handle(self, request: wasi_http_types_0_3_0_rc_2026_03_15.Request) -> wasi_http_types_0_3_0_rc_2026_03_15.Response: + async def handle(self, request: wasi_http_types_0_3_0.Request) -> wasi_http_types_0_3_0.Response: """ This function may be called with either an incoming request read from the network or a request synthesized or forwarded by another component. - Raises: `componentize_py_types.Err(spin_sdk.wit.imports.wasi_http_types_0_3_0_rc_2026_03_15.ErrorCode)` + Raises: `componentize_py_types.Err(spin_sdk.wit.imports.wasi_http_types_0_3_0.ErrorCode)` """ raise NotImplementedError diff --git a/src/spin_sdk/wit/imports/spin_mysql_mysql_3_0_0.py b/src/spin_sdk/wit/imports/spin_mysql_mysql_3_0_0.py new file mode 100644 index 0000000..7ec8f58 --- /dev/null +++ b/src/spin_sdk/wit/imports/spin_mysql_mysql_3_0_0.py @@ -0,0 +1,271 @@ +# This file is automatically generated by componentize-py +# It is not intended for manual editing. +from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self, Callable +from types import TracebackType +from enum import Flag, Enum, auto +from dataclasses import dataclass +from abc import abstractmethod +import weakref + +from componentize_py_types import Result, Ok, Err, Some +import componentize_py_async_support +from componentize_py_async_support.streams import StreamReader, StreamWriter, ByteStreamReader, ByteStreamWriter +from componentize_py_async_support.futures import FutureReader, FutureWriter + + +@dataclass +class Error_ConnectionFailed: + value: str + + +@dataclass +class Error_BadParameter: + value: str + + +@dataclass +class Error_QueryFailed: + value: str + + +@dataclass +class Error_ValueConversionFailed: + value: str + + +@dataclass +class Error_Other: + value: str + + +Error = Union[Error_ConnectionFailed, Error_BadParameter, Error_QueryFailed, Error_ValueConversionFailed, Error_Other] +""" +Errors related to interacting with a database. +""" + + +class DbDataType(Enum): + """ + Data types for a database column + """ + BOOLEAN = 0 + INT8 = 1 + INT16 = 2 + INT32 = 3 + INT64 = 4 + UINT8 = 5 + UINT16 = 6 + UINT32 = 7 + UINT64 = 8 + FLOATING32 = 9 + FLOATING64 = 10 + STR = 11 + BINARY = 12 + OTHER = 13 + + +@dataclass +class DbValue_Boolean: + value: bool + + +@dataclass +class DbValue_Int8: + value: int + + +@dataclass +class DbValue_Int16: + value: int + + +@dataclass +class DbValue_Int32: + value: int + + +@dataclass +class DbValue_Int64: + value: int + + +@dataclass +class DbValue_Uint8: + value: int + + +@dataclass +class DbValue_Uint16: + value: int + + +@dataclass +class DbValue_Uint32: + value: int + + +@dataclass +class DbValue_Uint64: + value: int + + +@dataclass +class DbValue_Floating32: + value: float + + +@dataclass +class DbValue_Floating64: + value: float + + +@dataclass +class DbValue_Str: + value: str + + +@dataclass +class DbValue_Binary: + value: bytes + + +@dataclass +class DbValue_DbNull: + pass + + +@dataclass +class DbValue_Unsupported: + pass + + +DbValue = Union[DbValue_Boolean, DbValue_Int8, DbValue_Int16, DbValue_Int32, DbValue_Int64, DbValue_Uint8, DbValue_Uint16, DbValue_Uint32, DbValue_Uint64, DbValue_Floating32, DbValue_Floating64, DbValue_Str, DbValue_Binary, DbValue_DbNull, DbValue_Unsupported] +""" +Database values +""" + + + +@dataclass +class ParameterValue_Boolean: + value: bool + + +@dataclass +class ParameterValue_Int8: + value: int + + +@dataclass +class ParameterValue_Int16: + value: int + + +@dataclass +class ParameterValue_Int32: + value: int + + +@dataclass +class ParameterValue_Int64: + value: int + + +@dataclass +class ParameterValue_Uint8: + value: int + + +@dataclass +class ParameterValue_Uint16: + value: int + + +@dataclass +class ParameterValue_Uint32: + value: int + + +@dataclass +class ParameterValue_Uint64: + value: int + + +@dataclass +class ParameterValue_Floating32: + value: float + + +@dataclass +class ParameterValue_Floating64: + value: float + + +@dataclass +class ParameterValue_Str: + value: str + + +@dataclass +class ParameterValue_Binary: + value: bytes + + +@dataclass +class ParameterValue_DbNull: + pass + + +ParameterValue = Union[ParameterValue_Boolean, ParameterValue_Int8, ParameterValue_Int16, ParameterValue_Int32, ParameterValue_Int64, ParameterValue_Uint8, ParameterValue_Uint16, ParameterValue_Uint32, ParameterValue_Uint64, ParameterValue_Floating32, ParameterValue_Floating64, ParameterValue_Str, ParameterValue_Binary, ParameterValue_DbNull] +""" +Values used in parameterized queries +""" + + +@dataclass +class Column: + """ + A database column + """ + name: str + data_type: DbDataType + +class Connection: + """ + A connection to a MySQL database. + """ + + @classmethod + async def open(cls, address: str) -> Self: + """ + Open a connection to the MySQL instance at `address`. + + Raises: `componentize_py_types.Err(spin_sdk.wit.imports.spin_mysql_mysql_3_0_0.Error)` + """ + raise NotImplementedError + async def query(self, statement: str, params: List[ParameterValue]) -> Tuple[List[Column], StreamReader[List[DbValue]], FutureReader[Result[None, Error]]]: + """ + query the database: select + + Raises: `componentize_py_types.Err(spin_sdk.wit.imports.spin_mysql_mysql_3_0_0.Error)` + """ + raise NotImplementedError + async def execute(self, statement: str, params: List[ParameterValue]) -> None: + """ + execute command to the database: insert, update, delete + + Raises: `componentize_py_types.Err(spin_sdk.wit.imports.spin_mysql_mysql_3_0_0.Error)` + """ + raise NotImplementedError + def __enter__(self) -> Self: + """Returns self""" + return self + + def __exit__(self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None) -> bool | None: + """ + Release this resource. + """ + raise NotImplementedError + + + diff --git a/src/spin_sdk/wit/imports/wasi_cli_environment_0_3_0.py b/src/spin_sdk/wit/imports/wasi_cli_environment_0_3_0.py new file mode 100644 index 0000000..82d6840 --- /dev/null +++ b/src/spin_sdk/wit/imports/wasi_cli_environment_0_3_0.py @@ -0,0 +1,38 @@ +# This file is automatically generated by componentize-py +# It is not intended for manual editing. +from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self, Callable +from types import TracebackType +from enum import Flag, Enum, auto +from dataclasses import dataclass +from abc import abstractmethod +import weakref + +from componentize_py_types import Result, Ok, Err, Some +import componentize_py_async_support +from componentize_py_async_support.streams import StreamReader, StreamWriter, ByteStreamReader, ByteStreamWriter +from componentize_py_async_support.futures import FutureReader, FutureWriter + + +def get_environment() -> List[Tuple[str, str]]: + """ + Get the POSIX-style environment variables. + + Each environment variable is provided as a pair of string variable names + and string value. + + Morally, these are a value import, but until value imports are available + in the component model, this import function should return the same + values each time it is called. + """ + raise NotImplementedError +def get_arguments() -> List[str]: + """ + Get the POSIX-style arguments to the program. + """ + raise NotImplementedError +def get_initial_cwd() -> Optional[str]: + """ + Return a path that programs should use as their initial current working + directory, interpreting `.` as shorthand for this. + """ + raise NotImplementedError diff --git a/src/spin_sdk/wit/imports/wasi_cli_exit_0_3_0.py b/src/spin_sdk/wit/imports/wasi_cli_exit_0_3_0.py new file mode 100644 index 0000000..37be6f7 --- /dev/null +++ b/src/spin_sdk/wit/imports/wasi_cli_exit_0_3_0.py @@ -0,0 +1,32 @@ +# This file is automatically generated by componentize-py +# It is not intended for manual editing. +from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self, Callable +from types import TracebackType +from enum import Flag, Enum, auto +from dataclasses import dataclass +from abc import abstractmethod +import weakref + +from componentize_py_types import Result, Ok, Err, Some +import componentize_py_async_support +from componentize_py_async_support.streams import StreamReader, StreamWriter, ByteStreamReader, ByteStreamWriter +from componentize_py_async_support.futures import FutureReader, FutureWriter + + +def exit(status: Result[None, None]) -> None: + """ + Exit the current instance and any linked instances. + """ + raise NotImplementedError +def exit_with_code(status_code: int) -> None: + """ + Exit the current instance and any linked instances, reporting the + specified status code to the host. + + The meaning of the code depends on the context, with 0 usually meaning + "success", and other values indicating various types of failure. + + This function does not return; the effect is analogous to a trap, but + without the connotation that something bad has happened. + """ + raise NotImplementedError diff --git a/src/spin_sdk/wit/imports/wasi_cli_stderr_0_3_0.py b/src/spin_sdk/wit/imports/wasi_cli_stderr_0_3_0.py new file mode 100644 index 0000000..636b740 --- /dev/null +++ b/src/spin_sdk/wit/imports/wasi_cli_stderr_0_3_0.py @@ -0,0 +1,28 @@ +# This file is automatically generated by componentize-py +# It is not intended for manual editing. +from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self, Callable +from types import TracebackType +from enum import Flag, Enum, auto +from dataclasses import dataclass +from abc import abstractmethod +import weakref + +from componentize_py_types import Result, Ok, Err, Some +from ..imports import wasi_cli_types_0_3_0 +import componentize_py_async_support +from componentize_py_async_support.streams import StreamReader, StreamWriter, ByteStreamReader, ByteStreamWriter +from componentize_py_async_support.futures import FutureReader, FutureWriter + + +def write_via_stream(data: ByteStreamReader) -> FutureReader[Result[None, wasi_cli_types_0_3_0.ErrorCode]]: + """ + Write the given stream to stderr. + + If the stream's writable end is dropped this function will either return + success once the entire contents of the stream have been written or an + error-code representing a failure. + + Otherwise if there is an error the readable end of the stream will be + dropped and this function will return an error-code. + """ + raise NotImplementedError diff --git a/src/spin_sdk/wit/imports/wasi_cli_stdin_0_3_0.py b/src/spin_sdk/wit/imports/wasi_cli_stdin_0_3_0.py new file mode 100644 index 0000000..55f4f0a --- /dev/null +++ b/src/spin_sdk/wit/imports/wasi_cli_stdin_0_3_0.py @@ -0,0 +1,33 @@ +# This file is automatically generated by componentize-py +# It is not intended for manual editing. +from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self, Callable +from types import TracebackType +from enum import Flag, Enum, auto +from dataclasses import dataclass +from abc import abstractmethod +import weakref + +from componentize_py_types import Result, Ok, Err, Some +from ..imports import wasi_cli_types_0_3_0 +import componentize_py_async_support +from componentize_py_async_support.streams import StreamReader, StreamWriter, ByteStreamReader, ByteStreamWriter +from componentize_py_async_support.futures import FutureReader, FutureWriter + + +def read_via_stream() -> Tuple[ByteStreamReader, FutureReader[Result[None, wasi_cli_types_0_3_0.ErrorCode]]]: + """ + Return a stream for reading from stdin. + + This function returns a stream which provides data read from stdin, + and a future to signal read results. + + If the stream's readable end is dropped the future will resolve to success. + + If the stream's writable end is dropped the future will either resolve to + success if stdin was closed by the writer or to an error-code if reading + failed for some other reason. + + Multiple streams may be active at the same time. The behavior of concurrent + reads is implementation-specific. + """ + raise NotImplementedError diff --git a/src/spin_sdk/wit/imports/wasi_cli_stdout_0_3_0.py b/src/spin_sdk/wit/imports/wasi_cli_stdout_0_3_0.py new file mode 100644 index 0000000..cb3c703 --- /dev/null +++ b/src/spin_sdk/wit/imports/wasi_cli_stdout_0_3_0.py @@ -0,0 +1,28 @@ +# This file is automatically generated by componentize-py +# It is not intended for manual editing. +from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self, Callable +from types import TracebackType +from enum import Flag, Enum, auto +from dataclasses import dataclass +from abc import abstractmethod +import weakref + +from componentize_py_types import Result, Ok, Err, Some +from ..imports import wasi_cli_types_0_3_0 +import componentize_py_async_support +from componentize_py_async_support.streams import StreamReader, StreamWriter, ByteStreamReader, ByteStreamWriter +from componentize_py_async_support.futures import FutureReader, FutureWriter + + +def write_via_stream(data: ByteStreamReader) -> FutureReader[Result[None, wasi_cli_types_0_3_0.ErrorCode]]: + """ + Write the given stream to stdout. + + If the stream's writable end is dropped this function will either return + success once the entire contents of the stream have been written or an + error-code representing a failure. + + Otherwise if there is an error the readable end of the stream will be + dropped and this function will return an error-code. + """ + raise NotImplementedError diff --git a/src/spin_sdk/wit/imports/wasi_cli_terminal_input_0_3_0.py b/src/spin_sdk/wit/imports/wasi_cli_terminal_input_0_3_0.py new file mode 100644 index 0000000..4889243 --- /dev/null +++ b/src/spin_sdk/wit/imports/wasi_cli_terminal_input_0_3_0.py @@ -0,0 +1,38 @@ +# This file is automatically generated by componentize-py +# It is not intended for manual editing. +""" +Terminal input. + +In the future, this may include functions for disabling echoing, +disabling input buffering so that keyboard events are sent through +immediately, querying supported features, and so on. +""" +from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self, Callable +from types import TracebackType +from enum import Flag, Enum, auto +from dataclasses import dataclass +from abc import abstractmethod +import weakref + +from componentize_py_types import Result, Ok, Err, Some +import componentize_py_async_support +from componentize_py_async_support.streams import StreamReader, StreamWriter, ByteStreamReader, ByteStreamWriter +from componentize_py_async_support.futures import FutureReader, FutureWriter + +class TerminalInput: + """ + The input side of a terminal. + """ + + def __enter__(self) -> Self: + """Returns self""" + return self + + def __exit__(self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None) -> bool | None: + """ + Release this resource. + """ + raise NotImplementedError + + + diff --git a/src/spin_sdk/wit/imports/wasi_cli_terminal_output_0_3_0.py b/src/spin_sdk/wit/imports/wasi_cli_terminal_output_0_3_0.py new file mode 100644 index 0000000..e978892 --- /dev/null +++ b/src/spin_sdk/wit/imports/wasi_cli_terminal_output_0_3_0.py @@ -0,0 +1,38 @@ +# This file is automatically generated by componentize-py +# It is not intended for manual editing. +""" +Terminal output. + +In the future, this may include functions for querying the terminal +size, being notified of terminal size changes, querying supported +features, and so on. +""" +from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self, Callable +from types import TracebackType +from enum import Flag, Enum, auto +from dataclasses import dataclass +from abc import abstractmethod +import weakref + +from componentize_py_types import Result, Ok, Err, Some +import componentize_py_async_support +from componentize_py_async_support.streams import StreamReader, StreamWriter, ByteStreamReader, ByteStreamWriter +from componentize_py_async_support.futures import FutureReader, FutureWriter + +class TerminalOutput: + """ + The output side of a terminal. + """ + + def __enter__(self) -> Self: + """Returns self""" + return self + + def __exit__(self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None) -> bool | None: + """ + Release this resource. + """ + raise NotImplementedError + + + diff --git a/src/spin_sdk/wit/imports/wasi_cli_terminal_stderr_0_3_0.py b/src/spin_sdk/wit/imports/wasi_cli_terminal_stderr_0_3_0.py new file mode 100644 index 0000000..40da4e3 --- /dev/null +++ b/src/spin_sdk/wit/imports/wasi_cli_terminal_stderr_0_3_0.py @@ -0,0 +1,26 @@ +# This file is automatically generated by componentize-py +# It is not intended for manual editing. +""" +An interface providing an optional `terminal-output` for stderr as a +link-time authority. +""" +from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self, Callable +from types import TracebackType +from enum import Flag, Enum, auto +from dataclasses import dataclass +from abc import abstractmethod +import weakref + +from componentize_py_types import Result, Ok, Err, Some +from ..imports import wasi_cli_terminal_output_0_3_0 +import componentize_py_async_support +from componentize_py_async_support.streams import StreamReader, StreamWriter, ByteStreamReader, ByteStreamWriter +from componentize_py_async_support.futures import FutureReader, FutureWriter + + +def get_terminal_stderr() -> Optional[wasi_cli_terminal_output_0_3_0.TerminalOutput]: + """ + If stderr is connected to a terminal, return a `terminal-output` handle + allowing further interaction with it. + """ + raise NotImplementedError diff --git a/src/spin_sdk/wit/imports/wasi_cli_terminal_stdin_0_3_0.py b/src/spin_sdk/wit/imports/wasi_cli_terminal_stdin_0_3_0.py new file mode 100644 index 0000000..b840b09 --- /dev/null +++ b/src/spin_sdk/wit/imports/wasi_cli_terminal_stdin_0_3_0.py @@ -0,0 +1,26 @@ +# This file is automatically generated by componentize-py +# It is not intended for manual editing. +""" +An interface providing an optional `terminal-input` for stdin as a +link-time authority. +""" +from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self, Callable +from types import TracebackType +from enum import Flag, Enum, auto +from dataclasses import dataclass +from abc import abstractmethod +import weakref + +from componentize_py_types import Result, Ok, Err, Some +from ..imports import wasi_cli_terminal_input_0_3_0 +import componentize_py_async_support +from componentize_py_async_support.streams import StreamReader, StreamWriter, ByteStreamReader, ByteStreamWriter +from componentize_py_async_support.futures import FutureReader, FutureWriter + + +def get_terminal_stdin() -> Optional[wasi_cli_terminal_input_0_3_0.TerminalInput]: + """ + If stdin is connected to a terminal, return a `terminal-input` handle + allowing further interaction with it. + """ + raise NotImplementedError diff --git a/src/spin_sdk/wit/imports/wasi_cli_terminal_stdout_0_3_0.py b/src/spin_sdk/wit/imports/wasi_cli_terminal_stdout_0_3_0.py new file mode 100644 index 0000000..7f7d36f --- /dev/null +++ b/src/spin_sdk/wit/imports/wasi_cli_terminal_stdout_0_3_0.py @@ -0,0 +1,26 @@ +# This file is automatically generated by componentize-py +# It is not intended for manual editing. +""" +An interface providing an optional `terminal-output` for stdout as a +link-time authority. +""" +from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self, Callable +from types import TracebackType +from enum import Flag, Enum, auto +from dataclasses import dataclass +from abc import abstractmethod +import weakref + +from componentize_py_types import Result, Ok, Err, Some +from ..imports import wasi_cli_terminal_output_0_3_0 +import componentize_py_async_support +from componentize_py_async_support.streams import StreamReader, StreamWriter, ByteStreamReader, ByteStreamWriter +from componentize_py_async_support.futures import FutureReader, FutureWriter + + +def get_terminal_stdout() -> Optional[wasi_cli_terminal_output_0_3_0.TerminalOutput]: + """ + If stdout is connected to a terminal, return a `terminal-output` handle + allowing further interaction with it. + """ + raise NotImplementedError diff --git a/src/spin_sdk/wit/imports/wasi_cli_types_0_3_0.py b/src/spin_sdk/wit/imports/wasi_cli_types_0_3_0.py new file mode 100644 index 0000000..d52007a --- /dev/null +++ b/src/spin_sdk/wit/imports/wasi_cli_types_0_3_0.py @@ -0,0 +1,20 @@ +# This file is automatically generated by componentize-py +# It is not intended for manual editing. +from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self, Callable +from types import TracebackType +from enum import Flag, Enum, auto +from dataclasses import dataclass +from abc import abstractmethod +import weakref + +from componentize_py_types import Result, Ok, Err, Some +import componentize_py_async_support +from componentize_py_async_support.streams import StreamReader, StreamWriter, ByteStreamReader, ByteStreamWriter +from componentize_py_async_support.futures import FutureReader, FutureWriter + +class ErrorCode(Enum): + IO = 0 + ILLEGAL_BYTE_SEQUENCE = 1 + PIPE = 2 + + diff --git a/src/spin_sdk/wit/imports/wasi_clocks_monotonic_clock_0_3_0.py b/src/spin_sdk/wit/imports/wasi_clocks_monotonic_clock_0_3_0.py new file mode 100644 index 0000000..2d340dc --- /dev/null +++ b/src/spin_sdk/wit/imports/wasi_clocks_monotonic_clock_0_3_0.py @@ -0,0 +1,54 @@ +# This file is automatically generated by componentize-py +# It is not intended for manual editing. +""" +WASI Monotonic Clock is a clock API intended to let users measure elapsed +time. + +It is intended to be portable at least between Unix-family platforms and +Windows. + +A monotonic clock is a clock which has an unspecified initial value, and +successive reads of the clock will produce non-decreasing values. +""" +from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self, Callable +from types import TracebackType +from enum import Flag, Enum, auto +from dataclasses import dataclass +from abc import abstractmethod +import weakref + +from componentize_py_types import Result, Ok, Err, Some +import componentize_py_async_support +from componentize_py_async_support.streams import StreamReader, StreamWriter, ByteStreamReader, ByteStreamWriter +from componentize_py_async_support.futures import FutureReader, FutureWriter + + +def now() -> int: + """ + Read the current value of the clock. + + The clock is monotonic, therefore calling this function repeatedly will + produce a sequence of non-decreasing values. + + For completeness, this function traps if it's not possible to represent + the value of the clock in a `mark`. Consequently, implementations + should ensure that the starting time is low enough to avoid the + possibility of overflow in practice. + """ + raise NotImplementedError +def get_resolution() -> int: + """ + Query the resolution of the clock. Returns the duration of time + corresponding to a clock tick. + """ + raise NotImplementedError +async def wait_until(when: int) -> None: + """ + Wait until the specified mark has occurred. + """ + raise NotImplementedError +async def wait_for(how_long: int) -> None: + """ + Wait for the specified duration to elapse. + """ + raise NotImplementedError diff --git a/src/spin_sdk/wit/imports/wasi_clocks_system_clock_0_3_0.py b/src/spin_sdk/wit/imports/wasi_clocks_system_clock_0_3_0.py new file mode 100644 index 0000000..2d98e0e --- /dev/null +++ b/src/spin_sdk/wit/imports/wasi_clocks_system_clock_0_3_0.py @@ -0,0 +1,66 @@ +# This file is automatically generated by componentize-py +# It is not intended for manual editing. +""" +WASI System Clock is a clock API intended to let users query the current +time. The clock is not necessarily monotonic as it may be reset. + +It is intended to be portable at least between Unix-family platforms and +Windows. + +External references may be reset, so this clock is not necessarily +monotonic, making it unsuitable for measuring elapsed time. + +It is intended for reporting the current date and time for humans. +""" +from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self, Callable +from types import TracebackType +from enum import Flag, Enum, auto +from dataclasses import dataclass +from abc import abstractmethod +import weakref + +from componentize_py_types import Result, Ok, Err, Some +import componentize_py_async_support +from componentize_py_async_support.streams import StreamReader, StreamWriter, ByteStreamReader, ByteStreamWriter +from componentize_py_async_support.futures import FutureReader, FutureWriter + +@dataclass +class Instant: + """ + An "instant", or "exact time", is a point in time without regard to any + time zone: just the time since a particular external reference point, + often called an "epoch". + + Here, the epoch is 1970-01-01T00:00:00Z, also known as + [POSIX's Seconds Since the Epoch], also known as [Unix Time]. + + Note that even if the seconds field is negative, incrementing + nanoseconds always represents moving forwards in time. + For example, `{ -1 seconds, 999999999 nanoseconds }` represents the + instant one nanosecond before the epoch. + For more on various different ways to represent time, see + https://tc39.es/proposal-temporal/docs/timezone.html + + [POSIX's Seconds Since the Epoch]: https://pubs.opengroup.org/onlinepubs/9699919799/xrat/V4_xbd_chap04.html#tag_21_04_16 + [Unix Time]: https://en.wikipedia.org/wiki/Unix_time + """ + seconds: int + nanoseconds: int + + +def now() -> Instant: + """ + Read the current value of the clock. + + This clock is not monotonic, therefore calling this function repeatedly + will not necessarily produce a sequence of non-decreasing values. + + The nanoseconds field of the output is always less than 1000000000. + """ + raise NotImplementedError +def get_resolution() -> int: + """ + Query the resolution of the clock. Returns the smallest duration of time + that the implementation permits distinguishing. + """ + raise NotImplementedError diff --git a/src/spin_sdk/wit/imports/wasi_filesystem_preopens_0_3_0.py b/src/spin_sdk/wit/imports/wasi_filesystem_preopens_0_3_0.py new file mode 100644 index 0000000..b89358c --- /dev/null +++ b/src/spin_sdk/wit/imports/wasi_filesystem_preopens_0_3_0.py @@ -0,0 +1,21 @@ +# This file is automatically generated by componentize-py +# It is not intended for manual editing. +from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self, Callable +from types import TracebackType +from enum import Flag, Enum, auto +from dataclasses import dataclass +from abc import abstractmethod +import weakref + +from componentize_py_types import Result, Ok, Err, Some +from ..imports import wasi_filesystem_types_0_3_0 +import componentize_py_async_support +from componentize_py_async_support.streams import StreamReader, StreamWriter, ByteStreamReader, ByteStreamWriter +from componentize_py_async_support.futures import FutureReader, FutureWriter + + +def get_directories() -> List[Tuple[wasi_filesystem_types_0_3_0.Descriptor, str]]: + """ + Return the set of preopened directories, and their paths. + """ + raise NotImplementedError diff --git a/src/spin_sdk/wit/imports/wasi_filesystem_types_0_3_0.py b/src/spin_sdk/wit/imports/wasi_filesystem_types_0_3_0.py new file mode 100644 index 0000000..1bb5b82 --- /dev/null +++ b/src/spin_sdk/wit/imports/wasi_filesystem_types_0_3_0.py @@ -0,0 +1,737 @@ +# This file is automatically generated by componentize-py +# It is not intended for manual editing. +""" +WASI filesystem is a filesystem API primarily intended to let users run WASI +programs that access their files on their existing filesystems, without +significant overhead. + +Paths are passed as interface-type `string`s, meaning they must consist of +a sequence of Unicode Scalar Values (USVs). Some filesystems may contain +paths which are not accessible by this API. + +The directory separator in WASI is always the forward-slash (`/`). + +All paths in WASI are relative paths, and are interpreted relative to a +`descriptor` referring to a base directory. If a `path` argument to any WASI +function starts with `/`, or if any step of resolving a `path`, including +`..` and symbolic link steps, reaches a directory outside of the base +directory, or reaches a symlink to an absolute or rooted path in the +underlying filesystem, the function fails with `error-code::not-permitted`. + +For more information about WASI path resolution and sandboxing, see +[WASI filesystem path resolution]. + +Though this package presents a portable interface modelled on POSIX, it +prioritizes compatibility over portability: allowing users to access their +files on their machine is more important than exposing a single semantics +across all platforms. Notably, depending on the underlying operating system +and file system: + * Paths may be case-folded or not. + * Deleting (unlinking) a file may fail if there are other file descriptors + open. + * Durability and atomicity of changes to underlying files when there are + concurrent writers. + +Users that need well-defined, portable semantics should use a key-value +store or a database instead. + +[WASI filesystem path resolution]: https://github.com/WebAssembly/wasi-filesystem/blob/main/path-resolution.md +""" +from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self, Callable +from types import TracebackType +from enum import Flag, Enum, auto +from dataclasses import dataclass +from abc import abstractmethod +import weakref + +from componentize_py_types import Result, Ok, Err, Some +from ..imports import wasi_clocks_system_clock_0_3_0 +import componentize_py_async_support +from componentize_py_async_support.streams import StreamReader, StreamWriter, ByteStreamReader, ByteStreamWriter +from componentize_py_async_support.futures import FutureReader, FutureWriter + + +@dataclass +class DescriptorType_BlockDevice: + pass + + +@dataclass +class DescriptorType_CharacterDevice: + pass + + +@dataclass +class DescriptorType_Directory: + pass + + +@dataclass +class DescriptorType_Fifo: + pass + + +@dataclass +class DescriptorType_SymbolicLink: + pass + + +@dataclass +class DescriptorType_RegularFile: + pass + + +@dataclass +class DescriptorType_Socket: + pass + + +@dataclass +class DescriptorType_Other: + value: Optional[str] + + +DescriptorType = Union[DescriptorType_BlockDevice, DescriptorType_CharacterDevice, DescriptorType_Directory, DescriptorType_Fifo, DescriptorType_SymbolicLink, DescriptorType_RegularFile, DescriptorType_Socket, DescriptorType_Other] +""" +The type of a filesystem object referenced by a descriptor. + +Note: This was called `filetype` in earlier versions of WASI. +""" + + +class DescriptorFlags(Flag): + """ + Descriptor flags. + + Note: This was called `fdflags` in earlier versions of WASI. + """ + READ = auto() + WRITE = auto() + FILE_INTEGRITY_SYNC = auto() + DATA_INTEGRITY_SYNC = auto() + REQUESTED_WRITE_SYNC = auto() + MUTATE_DIRECTORY = auto() + +class PathFlags(Flag): + """ + Flags determining the method of how paths are resolved. + """ + SYMLINK_FOLLOW = auto() + +class OpenFlags(Flag): + """ + Open flags used by `open-at`. + """ + CREATE = auto() + DIRECTORY = auto() + EXCLUSIVE = auto() + TRUNCATE = auto() + +@dataclass +class DescriptorStat: + """ + File attributes. + + Note: This was called `filestat` in earlier versions of WASI. + """ + type: DescriptorType + link_count: int + size: int + data_access_timestamp: Optional[wasi_clocks_system_clock_0_3_0.Instant] + data_modification_timestamp: Optional[wasi_clocks_system_clock_0_3_0.Instant] + status_change_timestamp: Optional[wasi_clocks_system_clock_0_3_0.Instant] + + +@dataclass +class NewTimestamp_NoChange: + pass + + +@dataclass +class NewTimestamp_Now: + pass + + +@dataclass +class NewTimestamp_Timestamp: + value: wasi_clocks_system_clock_0_3_0.Instant + + +NewTimestamp = Union[NewTimestamp_NoChange, NewTimestamp_Now, NewTimestamp_Timestamp] +""" +When setting a timestamp, this gives the value to set it to. +""" + + +@dataclass +class DirectoryEntry: + """ + A directory entry. + """ + type: DescriptorType + name: str + + +@dataclass +class ErrorCode_Access: + pass + + +@dataclass +class ErrorCode_Already: + pass + + +@dataclass +class ErrorCode_BadDescriptor: + pass + + +@dataclass +class ErrorCode_Busy: + pass + + +@dataclass +class ErrorCode_Deadlock: + pass + + +@dataclass +class ErrorCode_Quota: + pass + + +@dataclass +class ErrorCode_Exist: + pass + + +@dataclass +class ErrorCode_FileTooLarge: + pass + + +@dataclass +class ErrorCode_IllegalByteSequence: + pass + + +@dataclass +class ErrorCode_InProgress: + pass + + +@dataclass +class ErrorCode_Interrupted: + pass + + +@dataclass +class ErrorCode_Invalid: + pass + + +@dataclass +class ErrorCode_Io: + pass + + +@dataclass +class ErrorCode_IsDirectory: + pass + + +@dataclass +class ErrorCode_Loop: + pass + + +@dataclass +class ErrorCode_TooManyLinks: + pass + + +@dataclass +class ErrorCode_MessageSize: + pass + + +@dataclass +class ErrorCode_NameTooLong: + pass + + +@dataclass +class ErrorCode_NoDevice: + pass + + +@dataclass +class ErrorCode_NoEntry: + pass + + +@dataclass +class ErrorCode_NoLock: + pass + + +@dataclass +class ErrorCode_InsufficientMemory: + pass + + +@dataclass +class ErrorCode_InsufficientSpace: + pass + + +@dataclass +class ErrorCode_NotDirectory: + pass + + +@dataclass +class ErrorCode_NotEmpty: + pass + + +@dataclass +class ErrorCode_NotRecoverable: + pass + + +@dataclass +class ErrorCode_Unsupported: + pass + + +@dataclass +class ErrorCode_NoTty: + pass + + +@dataclass +class ErrorCode_NoSuchDevice: + pass + + +@dataclass +class ErrorCode_Overflow: + pass + + +@dataclass +class ErrorCode_NotPermitted: + pass + + +@dataclass +class ErrorCode_Pipe: + pass + + +@dataclass +class ErrorCode_ReadOnly: + pass + + +@dataclass +class ErrorCode_InvalidSeek: + pass + + +@dataclass +class ErrorCode_TextFileBusy: + pass + + +@dataclass +class ErrorCode_CrossDevice: + pass + + +@dataclass +class ErrorCode_Other: + value: Optional[str] + + +ErrorCode = Union[ErrorCode_Access, ErrorCode_Already, ErrorCode_BadDescriptor, ErrorCode_Busy, ErrorCode_Deadlock, ErrorCode_Quota, ErrorCode_Exist, ErrorCode_FileTooLarge, ErrorCode_IllegalByteSequence, ErrorCode_InProgress, ErrorCode_Interrupted, ErrorCode_Invalid, ErrorCode_Io, ErrorCode_IsDirectory, ErrorCode_Loop, ErrorCode_TooManyLinks, ErrorCode_MessageSize, ErrorCode_NameTooLong, ErrorCode_NoDevice, ErrorCode_NoEntry, ErrorCode_NoLock, ErrorCode_InsufficientMemory, ErrorCode_InsufficientSpace, ErrorCode_NotDirectory, ErrorCode_NotEmpty, ErrorCode_NotRecoverable, ErrorCode_Unsupported, ErrorCode_NoTty, ErrorCode_NoSuchDevice, ErrorCode_Overflow, ErrorCode_NotPermitted, ErrorCode_Pipe, ErrorCode_ReadOnly, ErrorCode_InvalidSeek, ErrorCode_TextFileBusy, ErrorCode_CrossDevice, ErrorCode_Other] +""" +Error codes returned by functions, similar to `errno` in POSIX. +Not all of these error codes are returned by the functions provided by this +API; some are used in higher-level library layers, and others are provided +merely for alignment with POSIX. +""" + + +class Advice(Enum): + """ + File or memory access pattern advisory information. + """ + NORMAL = 0 + SEQUENTIAL = 1 + RANDOM = 2 + WILL_NEED = 3 + DONT_NEED = 4 + NO_REUSE = 5 + +@dataclass +class MetadataHashValue: + """ + A 128-bit hash value, split into parts because wasm doesn't have a + 128-bit integer type. + """ + lower: int + upper: int + +class Descriptor: + """ + A descriptor is a reference to a filesystem object, which may be a file, + directory, named pipe, special file, or other object on which filesystem + calls may be made. + """ + + def read_via_stream(self, offset: int) -> Tuple[ByteStreamReader, FutureReader[Result[None, ErrorCode]]]: + """ + Return a stream for reading from a file. + + Multiple read, write, and append streams may be active on the same open + file and they do not interfere with each other. + + This function returns a `stream` which provides the data received from the + file, and a `future` providing additional error information in case an + error is encountered. + + If no error is encountered, `stream.read` on the `stream` will return + `read-status::closed` with no `error-context` and the future resolves to + the value `ok`. If an error is encountered, `stream.read` on the + `stream` returns `read-status::closed` with an `error-context` and the future + resolves to `err` with an `error-code`. + + Note: This is similar to `pread` in POSIX. + """ + raise NotImplementedError + def write_via_stream(self, data: ByteStreamReader, offset: int) -> FutureReader[Result[None, ErrorCode]]: + """ + Return a stream for writing to a file, if available. + + May fail with an error-code describing why the file cannot be written. + + It is valid to write past the end of a file; the file is extended to the + extent of the write, with bytes between the previous end and the start of + the write set to zero. + + This function returns once either full contents of the stream are + written or an error is encountered. + + Note: This is similar to `pwrite` in POSIX. + """ + raise NotImplementedError + def append_via_stream(self, data: ByteStreamReader) -> FutureReader[Result[None, ErrorCode]]: + """ + Return a stream for appending to a file, if available. + + May fail with an error-code describing why the file cannot be appended. + + This function returns once either full contents of the stream are + written or an error is encountered. + + Note: This is similar to `write` with `O_APPEND` in POSIX. + """ + raise NotImplementedError + async def advise(self, offset: int, length: int, advice: Advice) -> None: + """ + Provide file advisory information on a descriptor. + + This is similar to `posix_fadvise` in POSIX. + + Raises: `componentize_py_types.Err(spin_sdk.wit.imports.wasi_filesystem_types_0_3_0.ErrorCode)` + """ + raise NotImplementedError + async def sync_data(self) -> None: + """ + Synchronize the data of a file to disk. + + This function succeeds with no effect if the file descriptor is not + opened for writing. + + Note: This is similar to `fdatasync` in POSIX. + + Raises: `componentize_py_types.Err(spin_sdk.wit.imports.wasi_filesystem_types_0_3_0.ErrorCode)` + """ + raise NotImplementedError + async def get_flags(self) -> DescriptorFlags: + """ + Get flags associated with a descriptor. + + Note: This returns similar flags to `fcntl(fd, F_GETFL)` in POSIX. + + Note: This returns the value that was the `fs_flags` value returned + from `fdstat_get` in earlier versions of WASI. + + Raises: `componentize_py_types.Err(spin_sdk.wit.imports.wasi_filesystem_types_0_3_0.ErrorCode)` + """ + raise NotImplementedError + async def get_type(self) -> DescriptorType: + """ + Get the dynamic type of a descriptor. + + Note: This returns the same value as the `type` field of the `fd-stat` + returned by `stat`, `stat-at` and similar. + + Note: This returns similar flags to the `st_mode & S_IFMT` value provided + by `fstat` in POSIX. + + Note: This returns the value that was the `fs_filetype` value returned + from `fdstat_get` in earlier versions of WASI. + + Raises: `componentize_py_types.Err(spin_sdk.wit.imports.wasi_filesystem_types_0_3_0.ErrorCode)` + """ + raise NotImplementedError + async def set_size(self, size: int) -> None: + """ + Adjust the size of an open file. If this increases the file's size, the + extra bytes are filled with zeros. + + Note: This was called `fd_filestat_set_size` in earlier versions of WASI. + + Raises: `componentize_py_types.Err(spin_sdk.wit.imports.wasi_filesystem_types_0_3_0.ErrorCode)` + """ + raise NotImplementedError + async def set_times(self, data_access_timestamp: NewTimestamp, data_modification_timestamp: NewTimestamp) -> None: + """ + Adjust the timestamps of an open file or directory. + + Note: This is similar to `futimens` in POSIX. + + Note: This was called `fd_filestat_set_times` in earlier versions of WASI. + + Raises: `componentize_py_types.Err(spin_sdk.wit.imports.wasi_filesystem_types_0_3_0.ErrorCode)` + """ + raise NotImplementedError + def read_directory(self) -> Tuple[StreamReader[DirectoryEntry], FutureReader[Result[None, ErrorCode]]]: + """ + Read directory entries from a directory. + + On filesystems where directories contain entries referring to themselves + and their parents, often named `.` and `..` respectively, these entries + are omitted. + + This always returns a new stream which starts at the beginning of the + directory. Multiple streams may be active on the same directory, and they + do not interfere with each other. + + This function returns a future, which will resolve to an error code if + reading full contents of the directory fails. + """ + raise NotImplementedError + async def sync(self) -> None: + """ + Synchronize the data and metadata of a file to disk. + + This function succeeds with no effect if the file descriptor is not + opened for writing. + + Note: This is similar to `fsync` in POSIX. + + Raises: `componentize_py_types.Err(spin_sdk.wit.imports.wasi_filesystem_types_0_3_0.ErrorCode)` + """ + raise NotImplementedError + async def create_directory_at(self, path: str) -> None: + """ + Create a directory. + + Note: This is similar to `mkdirat` in POSIX. + + Raises: `componentize_py_types.Err(spin_sdk.wit.imports.wasi_filesystem_types_0_3_0.ErrorCode)` + """ + raise NotImplementedError + async def stat(self) -> DescriptorStat: + """ + Return the attributes of an open file or directory. + + Note: This is similar to `fstat` in POSIX, except that it does not return + device and inode information. For testing whether two descriptors refer to + the same underlying filesystem object, use `is-same-object`. To obtain + additional data that can be used do determine whether a file has been + modified, use `metadata-hash`. + + Note: This was called `fd_filestat_get` in earlier versions of WASI. + + Raises: `componentize_py_types.Err(spin_sdk.wit.imports.wasi_filesystem_types_0_3_0.ErrorCode)` + """ + raise NotImplementedError + async def stat_at(self, path_flags: PathFlags, path: str) -> DescriptorStat: + """ + Return the attributes of a file or directory. + + Note: This is similar to `fstatat` in POSIX, except that it does not + return device and inode information. See the `stat` description for a + discussion of alternatives. + + Note: This was called `path_filestat_get` in earlier versions of WASI. + + Raises: `componentize_py_types.Err(spin_sdk.wit.imports.wasi_filesystem_types_0_3_0.ErrorCode)` + """ + raise NotImplementedError + async def set_times_at(self, path_flags: PathFlags, path: str, data_access_timestamp: NewTimestamp, data_modification_timestamp: NewTimestamp) -> None: + """ + Adjust the timestamps of a file or directory. + + Note: This is similar to `utimensat` in POSIX. + + Note: This was called `path_filestat_set_times` in earlier versions of + WASI. + + Raises: `componentize_py_types.Err(spin_sdk.wit.imports.wasi_filesystem_types_0_3_0.ErrorCode)` + """ + raise NotImplementedError + async def link_at(self, old_path_flags: PathFlags, old_path: str, new_descriptor: Self, new_path: str) -> None: + """ + Create a hard link. + + Fails with `error-code::no-entry` if the old path does not exist, + with `error-code::exist` if the new path already exists, and + `error-code::not-permitted` if the old path is not a file. + + Note: This is similar to `linkat` in POSIX. + + Raises: `componentize_py_types.Err(spin_sdk.wit.imports.wasi_filesystem_types_0_3_0.ErrorCode)` + """ + raise NotImplementedError + async def open_at(self, path_flags: PathFlags, path: str, open_flags: OpenFlags, flags: DescriptorFlags) -> Self: + """ + Open a file or directory. + + If `flags` contains `descriptor-flags::mutate-directory`, and the base + descriptor doesn't have `descriptor-flags::mutate-directory` set, + `open-at` fails with `error-code::read-only`. + + If `flags` contains `write` or `mutate-directory`, or `open-flags` + contains `truncate` or `create`, and the base descriptor doesn't have + `descriptor-flags::mutate-directory` set, `open-at` fails with + `error-code::read-only`. + + Note: This is similar to `openat` in POSIX. + + Raises: `componentize_py_types.Err(spin_sdk.wit.imports.wasi_filesystem_types_0_3_0.ErrorCode)` + """ + raise NotImplementedError + async def readlink_at(self, path: str) -> str: + """ + Read the contents of a symbolic link. + + If the contents contain an absolute or rooted path in the underlying + filesystem, this function fails with `error-code::not-permitted`. + + Note: This is similar to `readlinkat` in POSIX. + + Raises: `componentize_py_types.Err(spin_sdk.wit.imports.wasi_filesystem_types_0_3_0.ErrorCode)` + """ + raise NotImplementedError + async def remove_directory_at(self, path: str) -> None: + """ + Remove a directory. + + Return `error-code::not-empty` if the directory is not empty. + + Note: This is similar to `unlinkat(fd, path, AT_REMOVEDIR)` in POSIX. + + Raises: `componentize_py_types.Err(spin_sdk.wit.imports.wasi_filesystem_types_0_3_0.ErrorCode)` + """ + raise NotImplementedError + async def rename_at(self, old_path: str, new_descriptor: Self, new_path: str) -> None: + """ + Rename a filesystem object. + + Note: This is similar to `renameat` in POSIX. + + Raises: `componentize_py_types.Err(spin_sdk.wit.imports.wasi_filesystem_types_0_3_0.ErrorCode)` + """ + raise NotImplementedError + async def symlink_at(self, old_path: str, new_path: str) -> None: + """ + Create a symbolic link (also known as a "symlink"). + + If `old-path` starts with `/`, the function fails with + `error-code::not-permitted`. + + Note: This is similar to `symlinkat` in POSIX. + + Raises: `componentize_py_types.Err(spin_sdk.wit.imports.wasi_filesystem_types_0_3_0.ErrorCode)` + """ + raise NotImplementedError + async def unlink_file_at(self, path: str) -> None: + """ + Unlink a filesystem object that is not a directory. + + This is similar to `unlinkat(fd, path, 0)` in POSIX. + + Error returns are as specified by POSIX. + + If the filesystem object is a directory, `error-code::access` or + `error-code::is-directory` may be returned instead of the + POSIX-specified `error-code::not-permitted`. + + Raises: `componentize_py_types.Err(spin_sdk.wit.imports.wasi_filesystem_types_0_3_0.ErrorCode)` + """ + raise NotImplementedError + async def is_same_object(self, other: Self) -> bool: + """ + Test whether two descriptors refer to the same filesystem object. + + In POSIX, this corresponds to testing whether the two descriptors have the + same device (`st_dev`) and inode (`st_ino` or `d_ino`) numbers. + wasi-filesystem does not expose device and inode numbers, so this function + may be used instead. + """ + raise NotImplementedError + async def metadata_hash(self) -> MetadataHashValue: + """ + Return a hash of the metadata associated with a filesystem object referred + to by a descriptor. + + This returns a hash of the last-modification timestamp and file size, and + may also include the inode number, device number, birth timestamp, and + other metadata fields that may change when the file is modified or + replaced. It may also include a secret value chosen by the + implementation and not otherwise exposed. + + Implementations are encouraged to provide the following properties: + + - If the file is not modified or replaced, the computed hash value should + usually not change. + - If the object is modified or replaced, the computed hash value should + usually change. + - The inputs to the hash should not be easily computable from the + computed hash. + + However, none of these is required. + + Raises: `componentize_py_types.Err(spin_sdk.wit.imports.wasi_filesystem_types_0_3_0.ErrorCode)` + """ + raise NotImplementedError + async def metadata_hash_at(self, path_flags: PathFlags, path: str) -> MetadataHashValue: + """ + Return a hash of the metadata associated with a filesystem object referred + to by a directory descriptor and a relative path. + + This performs the same hash computation as `metadata-hash`. + + Raises: `componentize_py_types.Err(spin_sdk.wit.imports.wasi_filesystem_types_0_3_0.ErrorCode)` + """ + raise NotImplementedError + def __enter__(self) -> Self: + """Returns self""" + return self + + def __exit__(self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None) -> bool | None: + """ + Release this resource. + """ + raise NotImplementedError + + + diff --git a/src/spin_sdk/wit/imports/wasi_http_client_0_3_0.py b/src/spin_sdk/wit/imports/wasi_http_client_0_3_0.py new file mode 100644 index 0000000..1366a05 --- /dev/null +++ b/src/spin_sdk/wit/imports/wasi_http_client_0_3_0.py @@ -0,0 +1,36 @@ +# This file is automatically generated by componentize-py +# It is not intended for manual editing. +""" +This interface defines an HTTP client for sending "outgoing" requests. + +Most components are expected to import this interface to provide the +capability to send HTTP requests to arbitrary destinations on a network. + +The type signature of `client.send` is the same as `handler.handle`. This +duplication is currently necessary because some Component Model tooling +(including WIT itself) is unable to represent a component importing two +instances of the same interface. A `client.send` import may be linked +directly to a `handler.handle` export to bypass the network. +""" +from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self, Callable +from types import TracebackType +from enum import Flag, Enum, auto +from dataclasses import dataclass +from abc import abstractmethod +import weakref + +from componentize_py_types import Result, Ok, Err, Some +from ..imports import wasi_http_types_0_3_0 +import componentize_py_async_support +from componentize_py_async_support.streams import StreamReader, StreamWriter, ByteStreamReader, ByteStreamWriter +from componentize_py_async_support.futures import FutureReader, FutureWriter + + +async def send(request: wasi_http_types_0_3_0.Request) -> wasi_http_types_0_3_0.Response: + """ + This function may be used to either send an outgoing request over the + network or to forward it to another component. + + Raises: `componentize_py_types.Err(spin_sdk.wit.imports.wasi_http_types_0_3_0.ErrorCode)` + """ + raise NotImplementedError diff --git a/src/spin_sdk/wit/imports/wasi_http_types_0_3_0.py b/src/spin_sdk/wit/imports/wasi_http_types_0_3_0.py new file mode 100644 index 0000000..0581068 --- /dev/null +++ b/src/spin_sdk/wit/imports/wasi_http_types_0_3_0.py @@ -0,0 +1,815 @@ +# This file is automatically generated by componentize-py +# It is not intended for manual editing. +""" +This interface defines all of the types and methods for implementing HTTP +Requests and Responses, as well as their headers, trailers, and bodies. +""" +from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self, Callable +from types import TracebackType +from enum import Flag, Enum, auto +from dataclasses import dataclass +from abc import abstractmethod +import weakref + +from componentize_py_types import Result, Ok, Err, Some +import componentize_py_async_support +from componentize_py_async_support.streams import StreamReader, StreamWriter, ByteStreamReader, ByteStreamWriter +from componentize_py_async_support.futures import FutureReader, FutureWriter + + +@dataclass +class Method_Get: + pass + + +@dataclass +class Method_Head: + pass + + +@dataclass +class Method_Post: + pass + + +@dataclass +class Method_Put: + pass + + +@dataclass +class Method_Delete: + pass + + +@dataclass +class Method_Connect: + pass + + +@dataclass +class Method_Options: + pass + + +@dataclass +class Method_Trace: + pass + + +@dataclass +class Method_Patch: + pass + + +@dataclass +class Method_Other: + value: str + + +Method = Union[Method_Get, Method_Head, Method_Post, Method_Put, Method_Delete, Method_Connect, Method_Options, Method_Trace, Method_Patch, Method_Other] +""" +This type corresponds to HTTP standard Methods. +""" + + + +@dataclass +class Scheme_Http: + pass + + +@dataclass +class Scheme_Https: + pass + + +@dataclass +class Scheme_Other: + value: str + + +Scheme = Union[Scheme_Http, Scheme_Https, Scheme_Other] +""" +This type corresponds to HTTP standard Related Schemes. +""" + + +@dataclass +class DnsErrorPayload: + """ + Defines the case payload type for `DNS-error` above: + """ + rcode: Optional[str] + info_code: Optional[int] + +@dataclass +class TlsAlertReceivedPayload: + """ + Defines the case payload type for `TLS-alert-received` above: + """ + alert_id: Optional[int] + alert_message: Optional[str] + +@dataclass +class FieldSizePayload: + """ + Defines the case payload type for `HTTP-response-{header,trailer}-size` above: + """ + field_name: Optional[str] + field_size: Optional[int] + + +@dataclass +class ErrorCode_DnsTimeout: + pass + + +@dataclass +class ErrorCode_DnsError: + value: DnsErrorPayload + + +@dataclass +class ErrorCode_DestinationNotFound: + pass + + +@dataclass +class ErrorCode_DestinationUnavailable: + pass + + +@dataclass +class ErrorCode_DestinationIpProhibited: + pass + + +@dataclass +class ErrorCode_DestinationIpUnroutable: + pass + + +@dataclass +class ErrorCode_ConnectionRefused: + pass + + +@dataclass +class ErrorCode_ConnectionTerminated: + pass + + +@dataclass +class ErrorCode_ConnectionTimeout: + pass + + +@dataclass +class ErrorCode_ConnectionReadTimeout: + pass + + +@dataclass +class ErrorCode_ConnectionWriteTimeout: + pass + + +@dataclass +class ErrorCode_ConnectionLimitReached: + pass + + +@dataclass +class ErrorCode_TlsProtocolError: + pass + + +@dataclass +class ErrorCode_TlsCertificateError: + pass + + +@dataclass +class ErrorCode_TlsAlertReceived: + value: TlsAlertReceivedPayload + + +@dataclass +class ErrorCode_HttpRequestDenied: + pass + + +@dataclass +class ErrorCode_HttpRequestLengthRequired: + pass + + +@dataclass +class ErrorCode_HttpRequestBodySize: + value: Optional[int] + + +@dataclass +class ErrorCode_HttpRequestMethodInvalid: + pass + + +@dataclass +class ErrorCode_HttpRequestUriInvalid: + pass + + +@dataclass +class ErrorCode_HttpRequestUriTooLong: + pass + + +@dataclass +class ErrorCode_HttpRequestHeaderSectionSize: + value: Optional[int] + + +@dataclass +class ErrorCode_HttpRequestHeaderSize: + value: Optional[FieldSizePayload] + + +@dataclass +class ErrorCode_HttpRequestTrailerSectionSize: + value: Optional[int] + + +@dataclass +class ErrorCode_HttpRequestTrailerSize: + value: FieldSizePayload + + +@dataclass +class ErrorCode_HttpResponseIncomplete: + pass + + +@dataclass +class ErrorCode_HttpResponseHeaderSectionSize: + value: Optional[int] + + +@dataclass +class ErrorCode_HttpResponseHeaderSize: + value: FieldSizePayload + + +@dataclass +class ErrorCode_HttpResponseBodySize: + value: Optional[int] + + +@dataclass +class ErrorCode_HttpResponseTrailerSectionSize: + value: Optional[int] + + +@dataclass +class ErrorCode_HttpResponseTrailerSize: + value: FieldSizePayload + + +@dataclass +class ErrorCode_HttpResponseTransferCoding: + value: Optional[str] + + +@dataclass +class ErrorCode_HttpResponseContentCoding: + value: Optional[str] + + +@dataclass +class ErrorCode_HttpResponseTimeout: + pass + + +@dataclass +class ErrorCode_HttpUpgradeFailed: + pass + + +@dataclass +class ErrorCode_HttpProtocolError: + pass + + +@dataclass +class ErrorCode_LoopDetected: + pass + + +@dataclass +class ErrorCode_ConfigurationError: + pass + + +@dataclass +class ErrorCode_InternalError: + value: Optional[str] + + +ErrorCode = Union[ErrorCode_DnsTimeout, ErrorCode_DnsError, ErrorCode_DestinationNotFound, ErrorCode_DestinationUnavailable, ErrorCode_DestinationIpProhibited, ErrorCode_DestinationIpUnroutable, ErrorCode_ConnectionRefused, ErrorCode_ConnectionTerminated, ErrorCode_ConnectionTimeout, ErrorCode_ConnectionReadTimeout, ErrorCode_ConnectionWriteTimeout, ErrorCode_ConnectionLimitReached, ErrorCode_TlsProtocolError, ErrorCode_TlsCertificateError, ErrorCode_TlsAlertReceived, ErrorCode_HttpRequestDenied, ErrorCode_HttpRequestLengthRequired, ErrorCode_HttpRequestBodySize, ErrorCode_HttpRequestMethodInvalid, ErrorCode_HttpRequestUriInvalid, ErrorCode_HttpRequestUriTooLong, ErrorCode_HttpRequestHeaderSectionSize, ErrorCode_HttpRequestHeaderSize, ErrorCode_HttpRequestTrailerSectionSize, ErrorCode_HttpRequestTrailerSize, ErrorCode_HttpResponseIncomplete, ErrorCode_HttpResponseHeaderSectionSize, ErrorCode_HttpResponseHeaderSize, ErrorCode_HttpResponseBodySize, ErrorCode_HttpResponseTrailerSectionSize, ErrorCode_HttpResponseTrailerSize, ErrorCode_HttpResponseTransferCoding, ErrorCode_HttpResponseContentCoding, ErrorCode_HttpResponseTimeout, ErrorCode_HttpUpgradeFailed, ErrorCode_HttpProtocolError, ErrorCode_LoopDetected, ErrorCode_ConfigurationError, ErrorCode_InternalError] +""" +These cases are inspired by the IANA HTTP Proxy Error Types: + +""" + + + +@dataclass +class HeaderError_InvalidSyntax: + pass + + +@dataclass +class HeaderError_Forbidden: + pass + + +@dataclass +class HeaderError_Immutable: + pass + + +@dataclass +class HeaderError_SizeExceeded: + pass + + +@dataclass +class HeaderError_Other: + value: Optional[str] + + +HeaderError = Union[HeaderError_InvalidSyntax, HeaderError_Forbidden, HeaderError_Immutable, HeaderError_SizeExceeded, HeaderError_Other] +""" +This type enumerates the different kinds of errors that may occur when +setting or appending to a `fields` resource. +""" + + + +@dataclass +class RequestOptionsError_NotSupported: + pass + + +@dataclass +class RequestOptionsError_Immutable: + pass + + +@dataclass +class RequestOptionsError_Other: + value: Optional[str] + + +RequestOptionsError = Union[RequestOptionsError_NotSupported, RequestOptionsError_Immutable, RequestOptionsError_Other] +""" +This type enumerates the different kinds of errors that may occur when +setting fields of a `request-options` resource. +""" + + +class Fields: + """ + This following block defines the `fields` resource which corresponds to + HTTP standard Fields. Fields are a common representation used for both + Headers and Trailers. + + A `fields` may be mutable or immutable. A `fields` created using the + constructor, `from-list`, or `clone` will be mutable, but a `fields` + resource given by other means (including, but not limited to, + `request.headers`) might be be immutable. In an immutable fields, the + `set`, `append`, and `delete` operations will fail with + `header-error.immutable`. + + A `fields` resource should store `field-name`s and `field-value`s in their + original casing used to construct or mutate the `fields` resource. The `fields` + resource should use that original casing when serializing the fields for + transport or when returning them from a method. + + Implementations may impose limits on individual field values and on total + aggregate field section size. Operations that would exceed these limits + fail with `header-error.size-exceeded` + """ + + def __init__(self) -> None: + """ + Construct an empty HTTP Fields. + + The resulting `fields` is mutable. + """ + raise NotImplementedError + + @classmethod + def from_list(cls, entries: List[Tuple[str, bytes]]) -> Self: + """ + Construct an HTTP Fields. + + The resulting `fields` is mutable. + + The list represents each name-value pair in the Fields. Names + which have multiple values are represented by multiple entries in this + list with the same name. + + The tuple is a pair of the field name, represented as a string, and + Value, represented as a list of bytes. In a valid Fields, all names + and values are valid UTF-8 strings. However, values are not always + well-formed, so they are represented as a raw list of bytes. + + An error result will be returned if any header or value was + syntactically invalid, if a header was forbidden, or if the + entries would exceed an implementation size limit. + + Raises: `componentize_py_types.Err(spin_sdk.wit.imports.wasi_http_types_0_3_0.HeaderError)` + """ + raise NotImplementedError + def get(self, name: str) -> List[bytes]: + """ + Get all of the values corresponding to a name. If the name is not present + in this `fields`, an empty list is returned. However, if the name is + present but empty, this is represented by a list with one or more + empty field-values present. + """ + raise NotImplementedError + def has(self, name: str) -> bool: + """ + Returns `true` when the name is present in this `fields`. If the name is + syntactically invalid, `false` is returned. + """ + raise NotImplementedError + def set(self, name: str, value: List[bytes]) -> None: + """ + Set all of the values for a name. Clears any existing values for that + name, if they have been set. + + Fails with `header-error.immutable` if the `fields` are immutable. + + Fails with `header-error.size-exceeded` if the name or values would + exceed an implementation-defined size limit. + + Raises: `componentize_py_types.Err(spin_sdk.wit.imports.wasi_http_types_0_3_0.HeaderError)` + """ + raise NotImplementedError + def delete(self, name: str) -> None: + """ + Delete all values for a name. Does nothing if no values for the name + exist. + + Fails with `header-error.immutable` if the `fields` are immutable. + + Raises: `componentize_py_types.Err(spin_sdk.wit.imports.wasi_http_types_0_3_0.HeaderError)` + """ + raise NotImplementedError + def get_and_delete(self, name: str) -> List[bytes]: + """ + Delete all values for a name. Does nothing if no values for the name + exist. + + Returns all values previously corresponding to the name, if any. + + Fails with `header-error.immutable` if the `fields` are immutable. + + Raises: `componentize_py_types.Err(spin_sdk.wit.imports.wasi_http_types_0_3_0.HeaderError)` + """ + raise NotImplementedError + def append(self, name: str, value: bytes) -> None: + """ + Append a value for a name. Does not change or delete any existing + values for that name. + + Fails with `header-error.immutable` if the `fields` are immutable. + + Fails with `header-error.size-exceeded` if the value would exceed + an implementation-defined size limit. + + Raises: `componentize_py_types.Err(spin_sdk.wit.imports.wasi_http_types_0_3_0.HeaderError)` + """ + raise NotImplementedError + def copy_all(self) -> List[Tuple[str, bytes]]: + """ + Retrieve the full set of names and values in the Fields. Like the + constructor, the list represents each name-value pair. + + The outer list represents each name-value pair in the Fields. Names + which have multiple values are represented by multiple entries in this + list with the same name. + + The names and values are always returned in the original casing and in + the order in which they will be serialized for transport. + """ + raise NotImplementedError + def clone(self) -> Self: + """ + Make a deep copy of the Fields. Equivalent in behavior to calling the + `fields` constructor on the return value of `copy-all`. The resulting + `fields` is mutable. + """ + raise NotImplementedError + def __enter__(self) -> Self: + """Returns self""" + return self + + def __exit__(self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None) -> bool | None: + """ + Release this resource. + """ + raise NotImplementedError + + +class RequestOptions: + """ + Parameters for making an HTTP Request. Each of these parameters is + currently an optional timeout applicable to the transport layer of the + HTTP protocol. + + These timeouts are separate from any the user may use to bound an + asynchronous call. + """ + + def __init__(self) -> None: + """ + Construct a default `request-options` value. + """ + raise NotImplementedError + + def get_connect_timeout(self) -> Optional[int]: + """ + The timeout for the initial connect to the HTTP Server. + """ + raise NotImplementedError + def set_connect_timeout(self, duration: Optional[int]) -> None: + """ + Set the timeout for the initial connect to the HTTP Server. An error + return value indicates that this timeout is not supported or that this + handle is immutable. + + Raises: `componentize_py_types.Err(spin_sdk.wit.imports.wasi_http_types_0_3_0.RequestOptionsError)` + """ + raise NotImplementedError + def get_first_byte_timeout(self) -> Optional[int]: + """ + The timeout for receiving the first byte of the Response body. + """ + raise NotImplementedError + def set_first_byte_timeout(self, duration: Optional[int]) -> None: + """ + Set the timeout for receiving the first byte of the Response body. An + error return value indicates that this timeout is not supported or that + this handle is immutable. + + Raises: `componentize_py_types.Err(spin_sdk.wit.imports.wasi_http_types_0_3_0.RequestOptionsError)` + """ + raise NotImplementedError + def get_between_bytes_timeout(self) -> Optional[int]: + """ + The timeout for receiving subsequent chunks of bytes in the Response + body stream. + """ + raise NotImplementedError + def set_between_bytes_timeout(self, duration: Optional[int]) -> None: + """ + Set the timeout for receiving subsequent chunks of bytes in the Response + body stream. An error return value indicates that this timeout is not + supported or that this handle is immutable. + + Raises: `componentize_py_types.Err(spin_sdk.wit.imports.wasi_http_types_0_3_0.RequestOptionsError)` + """ + raise NotImplementedError + def clone(self) -> Self: + """ + Make a deep copy of the `request-options`. + The resulting `request-options` is mutable. + """ + raise NotImplementedError + def __enter__(self) -> Self: + """Returns self""" + return self + + def __exit__(self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None) -> bool | None: + """ + Release this resource. + """ + raise NotImplementedError + + +class Request: + """ + Represents an HTTP Request. + """ + + @classmethod + def new(cls, headers: Fields, contents: Optional[ByteStreamReader], trailers: FutureReader[Result[Optional[Fields], ErrorCode]], options: Optional[RequestOptions]) -> Tuple[Self, FutureReader[Result[None, ErrorCode]]]: + """ + Construct a new `request` with a default `method` of `GET`, and + `none` values for `path-with-query`, `scheme`, and `authority`. + + `headers` is the HTTP Headers for the Request. + + `contents` is the optional body content stream with `none` + representing a zero-length content stream. + Once it is closed, `trailers` future must resolve to a result. + If `trailers` resolves to an error, underlying connection + will be closed immediately. + + `options` is optional `request-options` resource to be used + if the request is sent over a network connection. + + It is possible to construct, or manipulate with the accessor functions + below, a `request` with an invalid combination of `scheme` + and `authority`, or `headers` which are not permitted to be sent. + It is the obligation of the `handler.handle` implementation + to reject invalid constructions of `request`. + + The returned future resolves to result of transmission of this request. + """ + raise NotImplementedError + def get_method(self) -> Method: + """ + Get the Method for the Request. + """ + raise NotImplementedError + def set_method(self, method: Method) -> None: + """ + Set the Method for the Request. Fails if the string present in a + `method.other` argument is not a syntactically valid method. + + Raises: `componentize_py_types.Err(None)` + """ + raise NotImplementedError + def get_path_with_query(self) -> Optional[str]: + """ + Get the combination of the HTTP Path and Query for the Request. When + `none`, this represents an empty Path and empty Query. + """ + raise NotImplementedError + def set_path_with_query(self, path_with_query: Optional[str]) -> None: + """ + Set the combination of the HTTP Path and Query for the Request. When + `none`, this represents an empty Path and empty Query. Fails is the + string given is not a syntactically valid path and query uri component. + + Raises: `componentize_py_types.Err(None)` + """ + raise NotImplementedError + def get_scheme(self) -> Optional[Scheme]: + """ + Get the HTTP Related Scheme for the Request. When `none`, the + implementation may choose an appropriate default scheme. + """ + raise NotImplementedError + def set_scheme(self, scheme: Optional[Scheme]) -> None: + """ + Set the HTTP Related Scheme for the Request. When `none`, the + implementation may choose an appropriate default scheme. Fails if the + string given is not a syntactically valid uri scheme. + + Raises: `componentize_py_types.Err(None)` + """ + raise NotImplementedError + def get_authority(self) -> Optional[str]: + """ + Get the authority of the Request's target URI. A value of `none` may be used + with Related Schemes which do not require an authority. The HTTP and + HTTPS schemes always require an authority. + """ + raise NotImplementedError + def set_authority(self, authority: Optional[str]) -> None: + """ + Set the authority of the Request's target URI. A value of `none` may be used + with Related Schemes which do not require an authority. The HTTP and + HTTPS schemes always require an authority. Fails if the string given is + not a syntactically valid URI authority. + + Raises: `componentize_py_types.Err(None)` + """ + raise NotImplementedError + def get_options(self) -> Optional[RequestOptions]: + """ + Get the `request-options` to be associated with this request + + The returned `request-options` resource is immutable: `set-*` operations + will fail if invoked. + + This `request-options` resource is a child: it must be dropped before + the parent `request` is dropped, or its ownership is transferred to + another component by e.g. `handler.handle`. + """ + raise NotImplementedError + def get_headers(self) -> Fields: + """ + Get the headers associated with the Request. + + The returned `headers` resource is immutable: `set`, `append`, and + `delete` operations will fail with `header-error.immutable`. + """ + raise NotImplementedError + @classmethod + def consume_body(cls, this: Self, res: FutureReader[Result[None, ErrorCode]]) -> Tuple[ByteStreamReader, FutureReader[Result[Optional[Fields], ErrorCode]]]: + """ + Get body of the Request. + + Stream returned by this method represents the contents of the body. + Once the stream is reported as closed, callers should await the returned + future to determine whether the body was received successfully. + The future will only resolve after the stream is reported as closed. + + This function takes a `res` future as a parameter, which can be used to + communicate an error in handling of the request. + + Note that function will move the `request`, but references to headers or + request options acquired from it previously will remain valid. + """ + raise NotImplementedError + def __enter__(self) -> Self: + """Returns self""" + return self + + def __exit__(self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None) -> bool | None: + """ + Release this resource. + """ + raise NotImplementedError + + +class Response: + """ + Represents an HTTP Response. + """ + + @classmethod + def new(cls, headers: Fields, contents: Optional[ByteStreamReader], trailers: FutureReader[Result[Optional[Fields], ErrorCode]]) -> Tuple[Self, FutureReader[Result[None, ErrorCode]]]: + """ + Construct a new `response`, with a default `status-code` of `200`. + If a different `status-code` is needed, it must be set via the + `set-status-code` method. + + `headers` is the HTTP Headers for the Response. + + `contents` is the optional body content stream with `none` + representing a zero-length content stream. + Once it is closed, `trailers` future must resolve to a result. + If `trailers` resolves to an error, underlying connection + will be closed immediately. + + The returned future resolves to result of transmission of this response. + """ + raise NotImplementedError + def get_status_code(self) -> int: + """ + Get the HTTP Status Code for the Response. + """ + raise NotImplementedError + def set_status_code(self, status_code: int) -> None: + """ + Set the HTTP Status Code for the Response. Fails if the status-code + given is not a valid http status code. + + Raises: `componentize_py_types.Err(None)` + """ + raise NotImplementedError + def get_headers(self) -> Fields: + """ + Get the headers associated with the Response. + + The returned `headers` resource is immutable: `set`, `append`, and + `delete` operations will fail with `header-error.immutable`. + """ + raise NotImplementedError + @classmethod + def consume_body(cls, this: Self, res: FutureReader[Result[None, ErrorCode]]) -> Tuple[ByteStreamReader, FutureReader[Result[Optional[Fields], ErrorCode]]]: + """ + Get body of the Response. + + Stream returned by this method represents the contents of the body. + Once the stream is reported as closed, callers should await the returned + future to determine whether the body was received successfully. + The future will only resolve after the stream is reported as closed. + + This function takes a `res` future as a parameter, which can be used to + communicate an error in handling of the response. + + Note that function will move the `response`, but references to headers + acquired from it previously will remain valid. + """ + raise NotImplementedError + def __enter__(self) -> Self: + """Returns self""" + return self + + def __exit__(self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None) -> bool | None: + """ + Release this resource. + """ + raise NotImplementedError + + + diff --git a/src/spin_sdk/wit/imports/wasi_random_insecure_0_3_0.py b/src/spin_sdk/wit/imports/wasi_random_insecure_0_3_0.py new file mode 100644 index 0000000..d4bb13d --- /dev/null +++ b/src/spin_sdk/wit/imports/wasi_random_insecure_0_3_0.py @@ -0,0 +1,48 @@ +# This file is automatically generated by componentize-py +# It is not intended for manual editing. +""" +The insecure interface for insecure pseudo-random numbers. + +It is intended to be portable at least between Unix-family platforms and +Windows. +""" +from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self, Callable +from types import TracebackType +from enum import Flag, Enum, auto +from dataclasses import dataclass +from abc import abstractmethod +import weakref + +from componentize_py_types import Result, Ok, Err, Some +import componentize_py_async_support +from componentize_py_async_support.streams import StreamReader, StreamWriter, ByteStreamReader, ByteStreamWriter +from componentize_py_async_support.futures import FutureReader, FutureWriter + + +def get_insecure_random_bytes(max_len: int) -> bytes: + """ + Return up to `max-len` insecure pseudo-random bytes. + + This function is not cryptographically secure. Do not use it for + anything related to security. + + There are no requirements on the values of the returned bytes, however + implementations are encouraged to return evenly distributed values with + a long period. + + Implementations MAY return fewer bytes than requested (a short read). + Callers that require exactly `max-len` bytes MUST call this function in + a loop until the desired number of bytes has been accumulated. + Implementations MUST return at least 1 byte when `max-len` is greater + than zero. When `max-len` is zero, implementations MUST return an empty + list without trapping. + """ + raise NotImplementedError +def get_insecure_random_u64() -> int: + """ + Return an insecure pseudo-random `u64` value. + + This function returns the same type of pseudo-random data as + `get-insecure-random-bytes`, represented as a `u64`. + """ + raise NotImplementedError diff --git a/src/spin_sdk/wit/imports/wasi_random_insecure_seed_0_3_0.py b/src/spin_sdk/wit/imports/wasi_random_insecure_seed_0_3_0.py new file mode 100644 index 0000000..b816e54 --- /dev/null +++ b/src/spin_sdk/wit/imports/wasi_random_insecure_seed_0_3_0.py @@ -0,0 +1,42 @@ +# This file is automatically generated by componentize-py +# It is not intended for manual editing. +""" +The insecure-seed interface for seeding hash-map DoS resistance. + +It is intended to be portable at least between Unix-family platforms and +Windows. +""" +from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self, Callable +from types import TracebackType +from enum import Flag, Enum, auto +from dataclasses import dataclass +from abc import abstractmethod +import weakref + +from componentize_py_types import Result, Ok, Err, Some +import componentize_py_async_support +from componentize_py_async_support.streams import StreamReader, StreamWriter, ByteStreamReader, ByteStreamWriter +from componentize_py_async_support.futures import FutureReader, FutureWriter + + +def get_insecure_seed() -> Tuple[int, int]: + """ + Return a 128-bit value that may contain a pseudo-random value. + + The returned value is not required to be computed from a CSPRNG, and may + even be entirely deterministic. Host implementations are encouraged to + provide pseudo-random values to any program exposed to + attacker-controlled content, to enable DoS protection built into many + languages' hash-map implementations. + + This function is intended to only be called once, by a source language + to initialize Denial Of Service (DoS) protection in its hash-map + implementation. + + # Expected future evolution + + This will likely be changed to a value import, to prevent it from being + called multiple times and potentially used for purposes other than DoS + protection. + """ + raise NotImplementedError diff --git a/src/spin_sdk/wit/imports/wasi_random_random_0_3_0.py b/src/spin_sdk/wit/imports/wasi_random_random_0_3_0.py new file mode 100644 index 0000000..1e7bf81 --- /dev/null +++ b/src/spin_sdk/wit/imports/wasi_random_random_0_3_0.py @@ -0,0 +1,53 @@ +# This file is automatically generated by componentize-py +# It is not intended for manual editing. +""" +WASI Random is a random data API. + +It is intended to be portable at least between Unix-family platforms and +Windows. +""" +from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self, Callable +from types import TracebackType +from enum import Flag, Enum, auto +from dataclasses import dataclass +from abc import abstractmethod +import weakref + +from componentize_py_types import Result, Ok, Err, Some +import componentize_py_async_support +from componentize_py_async_support.streams import StreamReader, StreamWriter, ByteStreamReader, ByteStreamWriter +from componentize_py_async_support.futures import FutureReader, FutureWriter + + +def get_random_bytes(max_len: int) -> bytes: + """ + Return up to `max-len` cryptographically-secure random or pseudo-random + bytes. + + This function must produce data at least as cryptographically secure and + fast as an adequately seeded cryptographically-secure pseudo-random + number generator (CSPRNG). It must not block, from the perspective of + the calling program, under any circumstances, including on the first + request and on requests for numbers of bytes. The returned data must + always be unpredictable. + + Implementations MAY return fewer bytes than requested (a short read). + Callers that require exactly `max-len` bytes MUST call this function in + a loop until the desired number of bytes has been accumulated. + Implementations MUST return at least 1 byte when `max-len` is greater + than zero. When `max-len` is zero, implementations MUST return an empty + list without trapping. + + This function must always return fresh data. Deterministic environments + must omit this function, rather than implementing it with deterministic + data. + """ + raise NotImplementedError +def get_random_u64() -> int: + """ + Return a cryptographically-secure random or pseudo-random `u64` value. + + This function returns the same type of data as `get-random-bytes`, + represented as a `u64`. + """ + raise NotImplementedError diff --git a/src/spin_sdk/wit/imports/wasi_sockets_ip_name_lookup_0_3_0.py b/src/spin_sdk/wit/imports/wasi_sockets_ip_name_lookup_0_3_0.py new file mode 100644 index 0000000..dc114f8 --- /dev/null +++ b/src/spin_sdk/wit/imports/wasi_sockets_ip_name_lookup_0_3_0.py @@ -0,0 +1,78 @@ +# This file is automatically generated by componentize-py +# It is not intended for manual editing. +from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self, Callable +from types import TracebackType +from enum import Flag, Enum, auto +from dataclasses import dataclass +from abc import abstractmethod +import weakref + +from componentize_py_types import Result, Ok, Err, Some +from ..imports import wasi_sockets_types_0_3_0 +import componentize_py_async_support +from componentize_py_async_support.streams import StreamReader, StreamWriter, ByteStreamReader, ByteStreamWriter +from componentize_py_async_support.futures import FutureReader, FutureWriter + + +@dataclass +class ErrorCode_AccessDenied: + pass + + +@dataclass +class ErrorCode_InvalidArgument: + pass + + +@dataclass +class ErrorCode_NameUnresolvable: + pass + + +@dataclass +class ErrorCode_TemporaryResolverFailure: + pass + + +@dataclass +class ErrorCode_PermanentResolverFailure: + pass + + +@dataclass +class ErrorCode_Other: + value: Optional[str] + + +ErrorCode = Union[ErrorCode_AccessDenied, ErrorCode_InvalidArgument, ErrorCode_NameUnresolvable, ErrorCode_TemporaryResolverFailure, ErrorCode_PermanentResolverFailure, ErrorCode_Other] +""" +Lookup error codes. +""" + + + +async def resolve_addresses(name: str) -> List[wasi_sockets_types_0_3_0.IpAddress]: + """ + Resolve an internet host name to a list of IP addresses. + + Unicode domain names are automatically converted to ASCII using IDNA + encoding. If the input is an IP address string, the address is parsed + and returned as-is without making any external requests. + + See the wasi-socket proposal README.md for a comparison with getaddrinfo. + + The results are returned in connection order preference. + + This function never succeeds with 0 results. It either fails or succeeds + with at least one address. Additionally, this function never returns + IPv4-mapped IPv6 addresses. + + # References: + - + - + - + - + + Raises: `componentize_py_types.Err(spin_sdk.wit.imports.wasi_sockets_ip_name_lookup_0_3_0.ErrorCode)` + """ + raise NotImplementedError diff --git a/src/spin_sdk/wit/imports/wasi_sockets_types_0_3_0.py b/src/spin_sdk/wit/imports/wasi_sockets_types_0_3_0.py new file mode 100644 index 0000000..66058b7 --- /dev/null +++ b/src/spin_sdk/wit/imports/wasi_sockets_types_0_3_0.py @@ -0,0 +1,957 @@ +# This file is automatically generated by componentize-py +# It is not intended for manual editing. +from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self, Callable +from types import TracebackType +from enum import Flag, Enum, auto +from dataclasses import dataclass +from abc import abstractmethod +import weakref + +from componentize_py_types import Result, Ok, Err, Some +import componentize_py_async_support +from componentize_py_async_support.streams import StreamReader, StreamWriter, ByteStreamReader, ByteStreamWriter +from componentize_py_async_support.futures import FutureReader, FutureWriter + + +@dataclass +class ErrorCode_AccessDenied: + pass + + +@dataclass +class ErrorCode_NotSupported: + pass + + +@dataclass +class ErrorCode_InvalidArgument: + pass + + +@dataclass +class ErrorCode_OutOfMemory: + pass + + +@dataclass +class ErrorCode_Timeout: + pass + + +@dataclass +class ErrorCode_InvalidState: + pass + + +@dataclass +class ErrorCode_AddressNotBindable: + pass + + +@dataclass +class ErrorCode_AddressInUse: + pass + + +@dataclass +class ErrorCode_RemoteUnreachable: + pass + + +@dataclass +class ErrorCode_ConnectionRefused: + pass + + +@dataclass +class ErrorCode_ConnectionBroken: + pass + + +@dataclass +class ErrorCode_ConnectionReset: + pass + + +@dataclass +class ErrorCode_ConnectionAborted: + pass + + +@dataclass +class ErrorCode_DatagramTooLarge: + pass + + +@dataclass +class ErrorCode_Other: + value: Optional[str] + + +ErrorCode = Union[ErrorCode_AccessDenied, ErrorCode_NotSupported, ErrorCode_InvalidArgument, ErrorCode_OutOfMemory, ErrorCode_Timeout, ErrorCode_InvalidState, ErrorCode_AddressNotBindable, ErrorCode_AddressInUse, ErrorCode_RemoteUnreachable, ErrorCode_ConnectionRefused, ErrorCode_ConnectionBroken, ErrorCode_ConnectionReset, ErrorCode_ConnectionAborted, ErrorCode_DatagramTooLarge, ErrorCode_Other] +""" +Error codes. + +In theory, every API can return any error code. +In practice, API's typically only return the errors documented per API +combined with a couple of errors that are always possible: +- `other` +- `access-denied` +- `not-supported` +- `out-of-memory` + +See each individual API for what the POSIX equivalents are. They sometimes differ per API. +""" + + +class IpAddressFamily(Enum): + IPV4 = 0 + IPV6 = 1 + + +@dataclass +class IpAddress_Ipv4: + value: Tuple[int, int, int, int] + + +@dataclass +class IpAddress_Ipv6: + value: Tuple[int, int, int, int, int, int, int, int] + + +IpAddress = Union[IpAddress_Ipv4, IpAddress_Ipv6] + + +@dataclass +class Ipv4SocketAddress: + port: int + address: Tuple[int, int, int, int] + +@dataclass +class Ipv6SocketAddress: + port: int + flow_info: int + address: Tuple[int, int, int, int, int, int, int, int] + scope_id: int + + +@dataclass +class IpSocketAddress_Ipv4: + value: Ipv4SocketAddress + + +@dataclass +class IpSocketAddress_Ipv6: + value: Ipv6SocketAddress + + +IpSocketAddress = Union[IpSocketAddress_Ipv4, IpSocketAddress_Ipv6] + + +class TcpSocket: + """ + A TCP socket resource. + + The socket can be in one of the following states: + - `unbound` + - `bound` (See note below) + - `listening` + - `connecting` + - `connected` + - `closed` + See + for more information. + + Note: Except where explicitly mentioned, whenever this documentation uses + the term "bound" without backticks it actually means: in the `bound` state *or higher*. + (i.e. `bound`, `listening`, `connecting` or `connected`) + + WASI uses shared ownership semantics: the `tcp-socket` handle and all + derived `stream` and `future` values reference a single underlying OS + socket: + - Send/receive streams remain functional after the original `tcp-socket` + handle is dropped. + - The stream returned by `listen` behaves similarly. + - Client sockets returned by `tcp-socket::listen` are independent and do + not keep the listening socket alive. + + The OS socket is closed only after the last handle is dropped. This + model has observable effects; for example, it affects when the local + port binding is released. + + In addition to the general error codes documented on the + `types::error-code` type, TCP socket methods may always return + `error(invalid-state)` when in the `closed` state. + """ + + @classmethod + def create(cls, address_family: IpAddressFamily) -> Self: + """ + Create a new TCP socket. + + Similar to `socket(AF_INET or AF_INET6, SOCK_STREAM, IPPROTO_TCP)` + in POSIX. On IPv6 sockets, IPV6_V6ONLY is enabled by default and + can't be configured otherwise. + + Unlike POSIX, WASI sockets have no notion of a socket-level + `O_NONBLOCK` flag. Instead they fully rely on the Component Model's + async support. + + # Typical errors + - `not-supported`: The `address-family` is not supported. (EAFNOSUPPORT) + + # References + - + - + - + - + + Raises: `componentize_py_types.Err(spin_sdk.wit.imports.wasi_sockets_types_0_3_0.ErrorCode)` + """ + raise NotImplementedError + def bind(self, local_address: IpSocketAddress) -> None: + """ + Bind the socket to the provided IP address and port. + + If the IP address is zero (`0.0.0.0` in IPv4, `::` in IPv6), it is + left to the implementation to decide which network interface(s) to + bind to. If the TCP/UDP port is zero, the socket will be bound to a + random free port. + + Bind can be attempted multiple times on the same socket, even with + different arguments on each iteration. But never concurrently and + only as long as the previous bind failed. Once a bind succeeds, the + binding can't be changed anymore. + + # Typical errors + - `invalid-argument`: The `local-address` has the wrong address family. (EAFNOSUPPORT, EFAULT on Windows) + - `invalid-argument`: `local-address` is not a unicast address. (EINVAL) + - `invalid-argument`: `local-address` is an IPv4-mapped IPv6 address. (EINVAL) + - `invalid-state`: The socket is already bound. (EINVAL) + - `address-in-use`: No ephemeral ports available. (EADDRINUSE, ENOBUFS on Windows) + - `address-in-use`: Address is already in use. (EADDRINUSE) + - `address-not-bindable`: `local-address` is not an address that can be bound to. (EADDRNOTAVAIL) + + # Implementors note + The bind operation shouldn't be affected by the TIME_WAIT state of a + recently closed socket on the same local address. In practice this + means that the SO_REUSEADDR socket option should be set implicitly + on all platforms, except on Windows where this is the default + behavior and SO_REUSEADDR performs something different. + + # References + - + - + - + - + + Raises: `componentize_py_types.Err(spin_sdk.wit.imports.wasi_sockets_types_0_3_0.ErrorCode)` + """ + raise NotImplementedError + async def connect(self, remote_address: IpSocketAddress) -> None: + """ + Connect to a remote endpoint. + + On success, the socket is transitioned into the `connected` state + and the `remote-address` of the socket is updated. + The `local-address` may be updated as well, based on the best network + path to `remote-address`. If the socket was not already explicitly + bound, this function will implicitly bind the socket to a random + free port. + + After a failed connection attempt, the socket will be in the `closed` + state and the only valid action left is to `drop` the socket. A single + socket can not be used to connect more than once. + + # Typical errors + - `invalid-argument`: The `remote-address` has the wrong address family. (EAFNOSUPPORT) + - `invalid-argument`: `remote-address` is not a unicast address. (EINVAL, ENETUNREACH on Linux, EAFNOSUPPORT on MacOS) + - `invalid-argument`: `remote-address` is an IPv4-mapped IPv6 address. (EINVAL, EADDRNOTAVAIL on Illumos) + - `invalid-argument`: The IP address in `remote-address` is set to INADDR_ANY (`0.0.0.0` / `::`). (EADDRNOTAVAIL on Windows) + - `invalid-argument`: The port in `remote-address` is set to 0. (EADDRNOTAVAIL on Windows) + - `invalid-state`: The socket is already in the `connecting` state. (EALREADY) + - `invalid-state`: The socket is already in the `connected` state. (EISCONN) + - `invalid-state`: The socket is already in the `listening` state. (EOPNOTSUPP, EINVAL on Windows) + - `timeout`: Connection timed out. (ETIMEDOUT) + - `connection-refused`: The connection was forcefully rejected. (ECONNREFUSED) + - `connection-reset`: The connection was reset. (ECONNRESET) + - `connection-aborted`: The connection was aborted. (ECONNABORTED) + - `remote-unreachable`: The remote address is not reachable. (EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) + - `address-in-use`: Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE, EADDRNOTAVAIL on Linux, EAGAIN on BSD) + + # References + - + - + - + - + + Raises: `componentize_py_types.Err(spin_sdk.wit.imports.wasi_sockets_types_0_3_0.ErrorCode)` + """ + raise NotImplementedError + def listen(self) -> StreamReader[Self]: + """ + Start listening and return a stream of new inbound connections. + + Transitions the socket into the `listening` state. This can be called + at most once per socket. + + If the socket is not already explicitly bound, this function will + implicitly bind the socket to a random free port. + + Normally, the returned sockets are bound, in the `connected` state + and immediately ready for I/O. Though, depending on exact timing and + circumstances, a newly accepted connection may already be `closed` + by the time the server attempts to perform its first I/O on it. This + is true regardless of whether the WASI implementation uses + "synthesized" sockets or not (see Implementors Notes below). + + The following properties are inherited from the listener socket: + - `address-family` + - `keep-alive-enabled` + - `keep-alive-idle-time` + - `keep-alive-interval` + - `keep-alive-count` + - `hop-limit` + - `receive-buffer-size` + - `send-buffer-size` + + # Typical errors + - `invalid-state`: The socket is already in the `connected` state. (EISCONN, EINVAL on BSD) + - `invalid-state`: The socket is already in the `listening` state. + - `address-in-use`: Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE) + + # Implementors note + This method returns a single perpetual stream that should only close + on fatal errors (if any). Yet, the POSIX' `accept` function may also + return transient errors (e.g. ECONNABORTED). The exact details differ + per operation system. For example, the Linux manual mentions: + + > Linux accept() passes already-pending network errors on the new + > socket as an error code from accept(). This behavior differs from + > other BSD socket implementations. For reliable operation the + > application should detect the network errors defined for the + > protocol after accept() and treat them like EAGAIN by retrying. + > In the case of TCP/IP, these are ENETDOWN, EPROTO, ENOPROTOOPT, + > EHOSTDOWN, ENONET, EHOSTUNREACH, EOPNOTSUPP, and ENETUNREACH. + Source: https://man7.org/linux/man-pages/man2/accept.2.html + + WASI implementations have two options to handle this: + - Optionally log it and then skip over non-fatal errors returned by + `accept`. Guest code never gets to see these failures. Or: + - Synthesize a `tcp-socket` resource that exposes the error when + attempting to send or receive on it. Guest code then sees these + failures as regular I/O errors. + + In either case, the stream returned by this `listen` method remains + operational. + + WASI requires `listen` to perform an implicit bind if the socket + has not already been bound. Not all platforms (notably Windows) + exhibit this behavior out of the box. On platforms that require it, + the WASI implementation can emulate this behavior by performing + the bind itself if the guest hasn't already done so. + + # References + - + - + - + - + - + - + - + - + + Raises: `componentize_py_types.Err(spin_sdk.wit.imports.wasi_sockets_types_0_3_0.ErrorCode)` + """ + raise NotImplementedError + def send(self, data: ByteStreamReader) -> FutureReader[Result[None, ErrorCode]]: + """ + Transmit data to peer. + + The caller should close the stream when it has no more data to send + to the peer. Under normal circumstances this will cause a FIN packet + to be sent out. Closing the stream is equivalent to calling + `shutdown(SHUT_WR)` in POSIX. + + This function may be called at most once and returns once the full + contents of the stream are transmitted or an error is encountered. + + # Typical errors + - `invalid-state`: The socket is not in the `connected` state. (ENOTCONN) + - `invalid-state`: `send` has already been called on this socket. + - `connection-broken`: The connection is not writable anymore. (EPIPE, ECONNABORTED on Windows) + - `connection-reset`: The connection was reset. (ECONNRESET) + - `remote-unreachable`: The remote address is not reachable. (EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) + + # References + - + - + - + - + """ + raise NotImplementedError + def receive(self) -> Tuple[ByteStreamReader, FutureReader[Result[None, ErrorCode]]]: + """ + Read data from peer. + + Returns a `stream` of data sent by the peer. The implementation + drops the stream once no more data is available. At that point, the + returned `future` resolves to: + - `ok` after a graceful shutdown from the peer (i.e. a FIN packet), or + - `err` if the socket was closed abnormally. + + `receive` may be called only once per socket. Subsequent calls return + a closed stream and a future resolved to `err(invalid-state)`. + + If the caller is not expecting to receive any more data from the peer, + they should drop the stream. Any data still in the receive queue + will be discarded. This is equivalent to calling `shutdown(SHUT_RD)` + in POSIX. + + # Typical errors + - `invalid-state`: The socket is not in the `connected` state. (ENOTCONN) + - `invalid-state`: `receive` has already been called on this socket. + - `connection-reset`: The connection was reset. (ECONNRESET) + - `remote-unreachable`: The remote address is not reachable. (EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) + + # References + - + - + - + - + """ + raise NotImplementedError + def get_local_address(self) -> IpSocketAddress: + """ + Get the bound local address. + + POSIX mentions: + > If the socket has not been bound to a local name, the value + > stored in the object pointed to by `address` is unspecified. + + WASI is stricter and requires `get-local-address` to return + `invalid-state` when the socket hasn't been bound yet. + + # Typical errors + - `invalid-state`: The socket is not bound to any local address. + + # References + - + - + - + - + + Raises: `componentize_py_types.Err(spin_sdk.wit.imports.wasi_sockets_types_0_3_0.ErrorCode)` + """ + raise NotImplementedError + def get_remote_address(self) -> IpSocketAddress: + """ + Get the remote address. + + # Typical errors + - `invalid-state`: The socket is not connected to a remote address. (ENOTCONN) + + # References + - + - + - + - + + Raises: `componentize_py_types.Err(spin_sdk.wit.imports.wasi_sockets_types_0_3_0.ErrorCode)` + """ + raise NotImplementedError + def get_is_listening(self) -> bool: + """ + Whether the socket is in the `listening` state. + + Equivalent to the SO_ACCEPTCONN socket option. + """ + raise NotImplementedError + def get_address_family(self) -> IpAddressFamily: + """ + Whether this is a IPv4 or IPv6 socket. + + This is the value passed to the constructor. + + Equivalent to the SO_DOMAIN socket option. + """ + raise NotImplementedError + def set_listen_backlog_size(self, value: int) -> None: + """ + Hints the desired listen queue size. Implementations are free to + ignore this. + + If the provided value is 0, an `invalid-argument` error is returned. + Any other value will never cause an error, but it might be silently + clamped and/or rounded. + + # Typical errors + - `not-supported`: (set) The platform does not support changing the backlog size after the initial listen. + - `invalid-argument`: (set) The provided value was 0. + - `invalid-state`: (set) The socket is in the `connecting` or `connected` state. + + Raises: `componentize_py_types.Err(spin_sdk.wit.imports.wasi_sockets_types_0_3_0.ErrorCode)` + """ + raise NotImplementedError + def get_keep_alive_enabled(self) -> bool: + """ + Enables or disables keepalive. + + The keepalive behavior can be adjusted using: + - `keep-alive-idle-time` + - `keep-alive-interval` + - `keep-alive-count` + These properties can be configured while `keep-alive-enabled` is + false, but only come into effect when `keep-alive-enabled` is true. + + Equivalent to the SO_KEEPALIVE socket option. + + Raises: `componentize_py_types.Err(spin_sdk.wit.imports.wasi_sockets_types_0_3_0.ErrorCode)` + """ + raise NotImplementedError + def set_keep_alive_enabled(self, value: bool) -> None: + """ + Raises: `componentize_py_types.Err(spin_sdk.wit.imports.wasi_sockets_types_0_3_0.ErrorCode)` + """ + raise NotImplementedError + def get_keep_alive_idle_time(self) -> int: + """ + Amount of time the connection has to be idle before TCP starts + sending keepalive packets. + + If the provided value is 0, an `invalid-argument` error is returned. + All other values are accepted without error, but may be + clamped or rounded. As a result, the value read back from + this setting may differ from the value that was set. + + Equivalent to the TCP_KEEPIDLE socket option. (TCP_KEEPALIVE on MacOS) + + # Typical errors + - `invalid-argument`: (set) The provided value was 0. + + Raises: `componentize_py_types.Err(spin_sdk.wit.imports.wasi_sockets_types_0_3_0.ErrorCode)` + """ + raise NotImplementedError + def set_keep_alive_idle_time(self, value: int) -> None: + """ + Raises: `componentize_py_types.Err(spin_sdk.wit.imports.wasi_sockets_types_0_3_0.ErrorCode)` + """ + raise NotImplementedError + def get_keep_alive_interval(self) -> int: + """ + The time between keepalive packets. + + If the provided value is 0, an `invalid-argument` error is returned. + All other values are accepted without error, but may be + clamped or rounded. As a result, the value read back from + this setting may differ from the value that was set. + + Equivalent to the TCP_KEEPINTVL socket option. + + # Typical errors + - `invalid-argument`: (set) The provided value was 0. + + Raises: `componentize_py_types.Err(spin_sdk.wit.imports.wasi_sockets_types_0_3_0.ErrorCode)` + """ + raise NotImplementedError + def set_keep_alive_interval(self, value: int) -> None: + """ + Raises: `componentize_py_types.Err(spin_sdk.wit.imports.wasi_sockets_types_0_3_0.ErrorCode)` + """ + raise NotImplementedError + def get_keep_alive_count(self) -> int: + """ + The maximum amount of keepalive packets TCP should send before + aborting the connection. + + If the provided value is 0, an `invalid-argument` error is returned. + All other values are accepted without error, but may be + clamped or rounded. As a result, the value read back from + this setting may differ from the value that was set. + + Equivalent to the TCP_KEEPCNT socket option. + + # Typical errors + - `invalid-argument`: (set) The provided value was 0. + + Raises: `componentize_py_types.Err(spin_sdk.wit.imports.wasi_sockets_types_0_3_0.ErrorCode)` + """ + raise NotImplementedError + def set_keep_alive_count(self, value: int) -> None: + """ + Raises: `componentize_py_types.Err(spin_sdk.wit.imports.wasi_sockets_types_0_3_0.ErrorCode)` + """ + raise NotImplementedError + def get_hop_limit(self) -> int: + """ + Equivalent to the IP_TTL & IPV6_UNICAST_HOPS socket options. + + If the provided value is 0, an `invalid-argument` error is returned. + + # Typical errors + - `invalid-argument`: (set) The TTL value must be 1 or higher. + + Raises: `componentize_py_types.Err(spin_sdk.wit.imports.wasi_sockets_types_0_3_0.ErrorCode)` + """ + raise NotImplementedError + def set_hop_limit(self, value: int) -> None: + """ + Raises: `componentize_py_types.Err(spin_sdk.wit.imports.wasi_sockets_types_0_3_0.ErrorCode)` + """ + raise NotImplementedError + def get_receive_buffer_size(self) -> int: + """ + Kernel buffer space reserved for sending/receiving on this socket. + Implementations usually treat this as a cap the buffer can grow to, + rather than allocating the full amount immediately. + + If the provided value is 0, an `invalid-argument` error is returned. + All other values are accepted without error, but may be + clamped or rounded. As a result, the value read back from + this setting may differ from the value that was set. + + This is only a performance hint. The implementation may ignore it or + tweak it based on real traffic patterns. + Linux and macOS appear to behave differently depending on whether a + buffer size was explicitly set. When set, they tend to honor it; when + not set, they dynamically adjust the buffer size as the connection + progresses. This is especially noticeable when comparing the values + from before and after connection establishment. + + Equivalent to the SO_RCVBUF and SO_SNDBUF socket options. + + # Typical errors + - `invalid-argument`: (set) The provided value was 0. + + Raises: `componentize_py_types.Err(spin_sdk.wit.imports.wasi_sockets_types_0_3_0.ErrorCode)` + """ + raise NotImplementedError + def set_receive_buffer_size(self, value: int) -> None: + """ + Raises: `componentize_py_types.Err(spin_sdk.wit.imports.wasi_sockets_types_0_3_0.ErrorCode)` + """ + raise NotImplementedError + def get_send_buffer_size(self) -> int: + """ + Raises: `componentize_py_types.Err(spin_sdk.wit.imports.wasi_sockets_types_0_3_0.ErrorCode)` + """ + raise NotImplementedError + def set_send_buffer_size(self, value: int) -> None: + """ + Raises: `componentize_py_types.Err(spin_sdk.wit.imports.wasi_sockets_types_0_3_0.ErrorCode)` + """ + raise NotImplementedError + def __enter__(self) -> Self: + """Returns self""" + return self + + def __exit__(self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None) -> bool | None: + """ + Release this resource. + """ + raise NotImplementedError + + +class UdpSocket: + """ + A UDP socket handle. + """ + + @classmethod + def create(cls, address_family: IpAddressFamily) -> Self: + """ + Create a new UDP socket. + + Similar to `socket(AF_INET or AF_INET6, SOCK_DGRAM, IPPROTO_UDP)` + in POSIX. On IPv6 sockets, IPV6_V6ONLY is enabled by default and + can't be configured otherwise. + + Unlike POSIX, WASI sockets have no notion of a socket-level + `O_NONBLOCK` flag. Instead they fully rely on the Component Model's + async support. + + # References: + - + - + - + - + + Raises: `componentize_py_types.Err(spin_sdk.wit.imports.wasi_sockets_types_0_3_0.ErrorCode)` + """ + raise NotImplementedError + def bind(self, local_address: IpSocketAddress) -> None: + """ + Bind the socket to the provided IP address and port. + + If the IP address is zero (`0.0.0.0` in IPv4, `::` in IPv6), it is + left to the implementation to decide which network interface(s) to + bind to. If the port is zero, the socket will be bound to a random + free port. + + # Typical errors + - `invalid-argument`: The `local-address` has the wrong address family. (EAFNOSUPPORT, EFAULT on Windows) + - `invalid-state`: The socket is already bound. (EINVAL) + - `address-in-use`: No ephemeral ports available. (EADDRINUSE, ENOBUFS on Windows) + - `address-in-use`: Address is already in use. (EADDRINUSE) + - `address-not-bindable`: `local-address` is not an address that can be bound to. (EADDRNOTAVAIL) + + # References + - + - + - + - + + Raises: `componentize_py_types.Err(spin_sdk.wit.imports.wasi_sockets_types_0_3_0.ErrorCode)` + """ + raise NotImplementedError + def connect(self, remote_address: IpSocketAddress) -> None: + """ + Associate this socket with a specific peer address. + + On success, the `remote-address` of the socket is updated. + The `local-address` may be updated as well, based on the best network + path to `remote-address`. If the socket was not already explicitly + bound, this function will implicitly bind the socket to a random + free port. + + When a UDP socket is "connected", the `send` and `receive` methods + are limited to communicating with that peer only: + - `send` can only be used to send to this destination. + - `receive` will only return datagrams sent from the provided `remote-address`. + + The name "connect" was kept to align with the existing POSIX + terminology. Other than that, this function only changes the local + socket configuration and does not generate any network traffic. + The peer is not aware of this "connection". + + This method may be called multiple times on the same socket to change + its association, but only the most recent one will be effective. + + # Typical errors + - `invalid-argument`: The `remote-address` has the wrong address family. (EAFNOSUPPORT) + - `invalid-argument`: The IP address in `remote-address` is set to INADDR_ANY (`0.0.0.0` / `::`). (EDESTADDRREQ, EADDRNOTAVAIL) + - `invalid-argument`: The port in `remote-address` is set to 0. (EDESTADDRREQ, EADDRNOTAVAIL) + - `address-in-use`: Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE, EADDRNOTAVAIL on Linux, EAGAIN on BSD) + + # Implementors note + If the socket is already connected, some platforms (e.g. Linux) + require a disconnect before connecting to a different peer address. + + # References + - + - + - + - + + Raises: `componentize_py_types.Err(spin_sdk.wit.imports.wasi_sockets_types_0_3_0.ErrorCode)` + """ + raise NotImplementedError + def disconnect(self) -> None: + """ + Dissociate this socket from its peer address. + + After calling this method, `send` & `receive` are free to communicate + with any remote address again. + + The POSIX equivalent of this is calling `connect` with an `AF_UNSPEC` address. + + # Typical errors + - `invalid-state`: The socket is not connected. + + # References + - + - + - + - + + Raises: `componentize_py_types.Err(spin_sdk.wit.imports.wasi_sockets_types_0_3_0.ErrorCode)` + """ + raise NotImplementedError + async def send(self, data: bytes, remote_address: Optional[IpSocketAddress]) -> None: + """ + Send a message on the socket to a particular peer. + + If the socket is connected, the peer address may be left empty. In + that case this is equivalent to `send` in POSIX. Otherwise it is + equivalent to `sendto`. + + Additionally, if the socket is connected, a `remote-address` argument + _may_ be provided but then it must be identical to the address + passed to `connect`. + + If the socket has not been explicitly bound, it will be + implicitly bound to a random free port. + + Implementations may trap if the `data` length exceeds 64 KiB. + + # Typical errors + - `invalid-argument`: The `remote-address` has the wrong address family. (EAFNOSUPPORT) + - `invalid-argument`: The IP address in `remote-address` is set to INADDR_ANY (`0.0.0.0` / `::`). (EDESTADDRREQ, EADDRNOTAVAIL) + - `invalid-argument`: The port in `remote-address` is set to 0. (EDESTADDRREQ, EADDRNOTAVAIL) + - `invalid-argument`: The socket is in "connected" mode and `remote-address` is `some` value that does not match the address passed to `connect`. (EISCONN) + - `invalid-argument`: The socket is not "connected" and no value for `remote-address` was provided. (EDESTADDRREQ) + - `remote-unreachable`: The remote address is not reachable. (ECONNRESET, ENETRESET on Windows, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) + - `connection-refused`: The connection was refused. (ECONNREFUSED) + - `datagram-too-large`: The datagram is too large. (EMSGSIZE) + - `address-in-use`: Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE) + + # Implementors note + WASI requires `send` to perform an implicit bind if the socket + has not been bound. Not all platforms (notably Windows) exhibit + this behavior natively. On such platforms, the WASI implementation + should emulate it by performing the bind if the guest has not + already done so. + + # References + - + - + - + - + - + - + - + - + + Raises: `componentize_py_types.Err(spin_sdk.wit.imports.wasi_sockets_types_0_3_0.ErrorCode)` + """ + raise NotImplementedError + async def receive(self) -> Tuple[bytes, IpSocketAddress]: + """ + Receive a message on the socket. + + On success, the return value contains a tuple of the received data + and the address of the sender. Theoretical maximum length of the + data is 64 KiB. Though in practice, it will typically be less than + 1500 bytes. + + If the socket is connected, the sender address is guaranteed to + match the remote address passed to `connect`. + + # Typical errors + - `invalid-state`: The socket has not been bound yet. + - `remote-unreachable`: The remote address is not reachable. (ECONNRESET, ENETRESET on Windows, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) + - `connection-refused`: The connection was refused. (ECONNREFUSED) + + # References + - + - + - + - + - + - + - + + Raises: `componentize_py_types.Err(spin_sdk.wit.imports.wasi_sockets_types_0_3_0.ErrorCode)` + """ + raise NotImplementedError + def get_local_address(self) -> IpSocketAddress: + """ + Get the current bound address. + + POSIX mentions: + > If the socket has not been bound to a local name, the value + > stored in the object pointed to by `address` is unspecified. + + WASI is stricter and requires `get-local-address` to return + `invalid-state` when the socket hasn't been bound yet. + + # Typical errors + - `invalid-state`: The socket is not bound to any local address. + + # References + - + - + - + - + + Raises: `componentize_py_types.Err(spin_sdk.wit.imports.wasi_sockets_types_0_3_0.ErrorCode)` + """ + raise NotImplementedError + def get_remote_address(self) -> IpSocketAddress: + """ + Get the address the socket is currently "connected" to. + + # Typical errors + - `invalid-state`: The socket is not "connected" to a specific remote address. (ENOTCONN) + + # References + - + - + - + - + + Raises: `componentize_py_types.Err(spin_sdk.wit.imports.wasi_sockets_types_0_3_0.ErrorCode)` + """ + raise NotImplementedError + def get_address_family(self) -> IpAddressFamily: + """ + Whether this is a IPv4 or IPv6 socket. + + This is the value passed to the constructor. + + Equivalent to the SO_DOMAIN socket option. + """ + raise NotImplementedError + def get_unicast_hop_limit(self) -> int: + """ + Equivalent to the IP_TTL & IPV6_UNICAST_HOPS socket options. + + If the provided value is 0, an `invalid-argument` error is returned. + + # Typical errors + - `invalid-argument`: (set) The TTL value must be 1 or higher. + + Raises: `componentize_py_types.Err(spin_sdk.wit.imports.wasi_sockets_types_0_3_0.ErrorCode)` + """ + raise NotImplementedError + def set_unicast_hop_limit(self, value: int) -> None: + """ + Raises: `componentize_py_types.Err(spin_sdk.wit.imports.wasi_sockets_types_0_3_0.ErrorCode)` + """ + raise NotImplementedError + def get_receive_buffer_size(self) -> int: + """ + Kernel buffer space reserved for sending/receiving on this socket. + Implementations usually treat this as a cap the buffer can grow to, + rather than allocating the full amount immediately. + + If the provided value is 0, an `invalid-argument` error is returned. + All other values are accepted without error, but may be + clamped or rounded. As a result, the value read back from + this setting may differ from the value that was set. + + Equivalent to the SO_RCVBUF and SO_SNDBUF socket options. + + # Typical errors + - `invalid-argument`: (set) The provided value was 0. + + Raises: `componentize_py_types.Err(spin_sdk.wit.imports.wasi_sockets_types_0_3_0.ErrorCode)` + """ + raise NotImplementedError + def set_receive_buffer_size(self, value: int) -> None: + """ + Raises: `componentize_py_types.Err(spin_sdk.wit.imports.wasi_sockets_types_0_3_0.ErrorCode)` + """ + raise NotImplementedError + def get_send_buffer_size(self) -> int: + """ + Raises: `componentize_py_types.Err(spin_sdk.wit.imports.wasi_sockets_types_0_3_0.ErrorCode)` + """ + raise NotImplementedError + def set_send_buffer_size(self, value: int) -> None: + """ + Raises: `componentize_py_types.Err(spin_sdk.wit.imports.wasi_sockets_types_0_3_0.ErrorCode)` + """ + raise NotImplementedError + def __enter__(self) -> Self: + """Returns self""" + return self + + def __exit__(self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None) -> bool | None: + """ + Release this resource. + """ + raise NotImplementedError + + + diff --git a/src/spin_sdk/wit/world.wit b/src/spin_sdk/wit/world.wit index deb4b0c..4d22c31 100644 --- a/src/spin_sdk/wit/world.wit +++ b/src/spin_sdk/wit/world.wit @@ -1,9 +1,9 @@ -package spin:up@4.0.0; +package spin:up@4.1.0; /// The full world of a guest targeting an http-trigger world http-trigger { include platform; - export wasi:http/handler@0.3.0-rc-2026-03-15; + export wasi:http/handler@0.3.0; } /// The full world of a guest targeting a redis-trigger @@ -16,14 +16,17 @@ world redis-trigger { world platform { include wasi:cli/imports@0.2.6; include wasi:cli/imports@0.3.0-rc-2026-03-15; + include wasi:cli/imports@0.3.0; import wasi:http/outgoing-handler@0.2.6; import wasi:http/client@0.3.0-rc-2026-03-15; + import wasi:http/client@0.3.0; @unstable(feature = wasi-otel) include wasi:otel/imports@0.2.0-rc.2; include fermyon:spin/platform@2.0.0; include wasi:keyvalue/imports@0.2.0-draft2; import spin:key-value/key-value@3.0.0; import spin:mqtt/mqtt@3.0.0; + import spin:mysql/mysql@3.0.0; import spin:postgres/postgres@3.0.0; import spin:postgres/postgres@4.2.0; import spin:redis/redis@3.0.0; diff --git a/templates/http-py/content/requirements.txt b/templates/http-py/content/requirements.txt index 42dc3d6..4721223 100644 --- a/templates/http-py/content/requirements.txt +++ b/templates/http-py/content/requirements.txt @@ -1,2 +1,2 @@ -spin-sdk == 4.0.0 -componentize-py == 0.23.0 +spin-sdk == 5.0.0 +componentize-py == 0.25.1 diff --git a/templates/http-py/content/spin.toml b/templates/http-py/content/spin.toml index 67bffc3..ac6bd96 100644 --- a/templates/http-py/content/spin.toml +++ b/templates/http-py/content/spin.toml @@ -13,5 +13,5 @@ component = "{{project-name | kebab_case}}" [component.{{project-name | kebab_case}}] source = "app.wasm" [component.{{project-name | kebab_case}}.build] -command = "componentize-py -w spin:up/http-trigger@4.0.0 componentize app -o app.wasm" +command = "componentize-py -w spin:up/http-trigger@4.1.0 componentize app -o app.wasm" watch = ["*.py", "requirements.txt"]