├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── MAINTAINERS.md ├── MANIFEST.in ├── README.md ├── docs ├── http │ ├── index.html │ └── poll_loop.html ├── index.html ├── key_value.html ├── llm.html ├── mysql.html ├── postgres.html ├── redis.html ├── sqlite.html ├── v1 │ ├── index.html │ ├── spin_config.html │ ├── spin_http.html │ ├── spin_key_value.html │ ├── spin_llm.html │ ├── spin_redis.html │ └── spin_sqlite.html ├── v2 │ ├── http │ │ ├── index.html │ │ └── poll_loop.html │ ├── index.html │ ├── key_value.html │ ├── llm.html │ ├── mysql.html │ ├── postgres.html │ ├── redis.html │ ├── sqlite.html │ ├── variables.html │ └── wit │ │ ├── exports │ │ ├── inbound_redis.html │ │ ├── incoming_handler.html │ │ └── index.html │ │ ├── imports │ │ ├── error.html │ │ ├── index.html │ │ ├── key_value.html │ │ ├── llm.html │ │ ├── monotonic_clock.html │ │ ├── mysql.html │ │ ├── outgoing_handler.html │ │ ├── poll.html │ │ ├── postgres.html │ │ ├── rdbms_types.html │ │ ├── redis.html │ │ ├── redis_types.html │ │ ├── sqlite.html │ │ ├── streams.html │ │ ├── types.html │ │ └── variables.html │ │ ├── index.html │ │ └── types.html ├── v3 │ ├── http │ │ ├── index.html │ │ └── poll_loop.html │ ├── index.html │ ├── key_value.html │ ├── llm.html │ ├── mqtt.html │ ├── mysql.html │ ├── postgres.html │ ├── redis.html │ ├── sqlite.html │ ├── variables.html │ └── wit │ │ ├── exports │ │ ├── inbound_redis.html │ │ ├── incoming_handler.html │ │ └── index.html │ │ ├── imports │ │ ├── atomics.html │ │ ├── batch.html │ │ ├── error.html │ │ ├── index.html │ │ ├── key_value.html │ │ ├── llm.html │ │ ├── monotonic_clock.html │ │ ├── mqtt.html │ │ ├── mysql.html │ │ ├── outgoing_handler.html │ │ ├── poll.html │ │ ├── postgres.html │ │ ├── rdbms_types.html │ │ ├── redis.html │ │ ├── redis_types.html │ │ ├── spin_postgres_postgres.html │ │ ├── sqlite.html │ │ ├── streams.html │ │ ├── types.html │ │ ├── variables.html │ │ ├── wasi_config_store.html │ │ └── wasi_keyvalue_store.html │ │ ├── index.html │ │ └── types.html ├── variables.html └── wit │ ├── exports │ ├── inbound_redis.html │ ├── incoming_handler.html │ └── index.html │ ├── imports │ ├── error.html │ ├── index.html │ ├── key_value.html │ ├── llm.html │ ├── monotonic_clock.html │ ├── mysql.html │ ├── outgoing_handler.html │ ├── poll.html │ ├── postgres.html │ ├── rdbms_types.html │ ├── redis.html │ ├── redis_types.html │ ├── sqlite.html │ ├── streams.html │ ├── types.html │ └── variables.html │ ├── index.html │ └── types.html ├── examples ├── external-lib-example │ ├── README.md │ ├── app.py │ ├── requirements.txt │ └── spin.toml ├── hello │ ├── README.md │ ├── app.py │ ├── requirements.txt │ └── spin.toml ├── matrix-math │ ├── README.md │ ├── app.py │ ├── requirements.txt │ └── spin.toml ├── outgoing-request │ ├── README.md │ ├── app.py │ ├── requirements.txt │ └── spin.toml ├── redis-trigger │ ├── README.md │ ├── app.py │ ├── requirements.txt │ └── spin.toml ├── spin-kv │ ├── README.md │ ├── app.py │ ├── requirements.txt │ └── spin.toml ├── spin-llm │ ├── README.md │ ├── app.py │ ├── requirements.txt │ └── spin.toml ├── spin-mysql │ ├── README.md │ ├── app.py │ ├── requirements.txt │ └── spin.toml ├── spin-outbound-mqtt │ ├── README.md │ ├── app.py │ ├── requirements.txt │ └── spin.toml ├── spin-postgres │ ├── README.md │ ├── app.py │ ├── requirements.txt │ └── spin.toml ├── spin-redis │ ├── README.md │ ├── app.py │ ├── requirements.txt │ └── spin.toml ├── spin-sqlite │ ├── README.md │ ├── app.py │ ├── requirements.txt │ └── spin.toml ├── spin-variables │ ├── README.md │ ├── app.py │ ├── requirements.txt │ └── spin.toml └── streaming │ ├── README.md │ ├── app.py │ ├── requirements.txt │ └── spin.toml ├── pyproject.toml ├── scripts └── generate_docs.py ├── src └── spin_sdk │ ├── __init__.py │ ├── componentize-py.toml │ ├── http │ ├── __init__.py │ └── poll_loop.py │ ├── key_value.py │ ├── llm.py │ ├── mqtt.py │ ├── mysql.py │ ├── postgres.py │ ├── py.typed │ ├── redis.py │ ├── sqlite.py │ ├── variables.py │ └── wit │ ├── __init__.py │ ├── deps │ ├── cli │ │ ├── command.wit │ │ ├── environment.wit │ │ ├── exit.wit │ │ ├── imports.wit │ │ ├── run.wit │ │ ├── stdio.wit │ │ └── terminal.wit │ ├── clocks │ │ ├── monotonic-clock.wit │ │ ├── wall-clock.wit │ │ └── world.wit │ ├── filesystem │ │ ├── preopens.wit │ │ ├── types.wit │ │ └── world.wit │ ├── http │ │ ├── handler.wit │ │ ├── proxy.wit │ │ └── types.wit │ ├── io │ │ ├── error.wit │ │ ├── poll.wit │ │ ├── streams.wit │ │ └── world.wit │ ├── keyvalue-2024-10-17 │ │ ├── atomic.wit │ │ ├── batch.wit │ │ ├── store.wit │ │ ├── watch.wit │ │ └── world.wit │ ├── random │ │ ├── insecure-seed.wit │ │ ├── insecure.wit │ │ ├── random.wit │ │ └── world.wit │ ├── sockets │ │ ├── instance-network.wit │ │ ├── ip-name-lookup.wit │ │ ├── network.wit │ │ ├── tcp-create-socket.wit │ │ ├── tcp.wit │ │ ├── udp-create-socket.wit │ │ ├── udp.wit │ │ └── world.wit │ ├── spin-postgres@3.0.0 │ │ └── postgres.wit │ ├── spin@3.0.0 │ │ └── world.wit │ ├── spin@unversioned │ │ ├── inbound-redis.wit │ │ └── redis-types.wit │ └── wasi-runtime-config-2024-09-27 │ │ ├── store.wit │ │ └── world.wit │ ├── exports │ ├── __init__.py │ ├── inbound_redis.py │ └── incoming_handler.py │ ├── imports │ ├── __init__.py │ ├── atomics.py │ ├── batch.py │ ├── error.py │ ├── key_value.py │ ├── llm.py │ ├── monotonic_clock.py │ ├── mqtt.py │ ├── mysql.py │ ├── outgoing_handler.py │ ├── poll.py │ ├── postgres.py │ ├── rdbms_types.py │ ├── redis.py │ ├── redis_types.py │ ├── spin_postgres_postgres.py │ ├── sqlite.py │ ├── streams.py │ ├── types.py │ ├── variables.py │ ├── wasi_config_store.py │ └── wasi_keyvalue_store.py │ ├── key-value.wit │ ├── llm.wit │ ├── mqtt.wit │ ├── mysql.wit │ ├── postgres.wit │ ├── rdbms-types.wit │ ├── redis.wit │ ├── spin.wit │ ├── sqlite.wit │ ├── types.py │ └── variables.wit └── templates └── http-py ├── content ├── .gitignore ├── README.md ├── app.py ├── requirements.txt └── spin.toml └── metadata ├── snippets └── component.txt └── spin-template.toml /.gitignore: -------------------------------------------------------------------------------- 1 | bindings 2 | *.wasm 3 | __pycache__ 4 | .spin 5 | src/spin_sdk.egg-info 6 | dist 7 | venv/ -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Building 2 | 3 | ### Prerequisites 4 | 5 | - Python 6 | - `pip` 7 | - `componentize-py` 0.16.0 8 | 9 | Once you have `pip` installed, you can install `componentize-py` using: 10 | 11 | ```bash 12 | pip install componentize-py==0.16.0 13 | ``` 14 | 15 | ### Generating the bindings 16 | 17 | The bindings are generated from 18 | [src/spin_sdk/wit/spin.wit](./src/spin_sdk/wit/spin.wit). 19 | 20 | ```bash 21 | componentize-py \ 22 | -d src/spin_sdk/wit \ 23 | -w spin-all \ 24 | --import-interface-name fermyon:spin/postgres@2.0.0=postgres \ 25 | bindings \ 26 | bindings \ 27 | --world-module spin_sdk.wit 28 | rm -r src/spin_sdk/wit/imports src/spin_sdk/wit/exports 29 | mv bindings/spin_sdk/wit/* src/spin_sdk/wit/ 30 | rm -r bindings 31 | ``` 32 | 33 | ### Updating docs 34 | 35 | Any time you regenerate the bindings or edit files by hand, you'll want to 36 | regenerate the HTML docs to match. First, install `pdoc` using `pip install 37 | pdoc3`. Then, update the docs using: 38 | 39 | ```bash 40 | ./scripts/generate_docs.py 41 | ``` 42 | 43 | ### Building the distribution 44 | 45 | First, make sure you have an up-to-date version of the `build` package installed: 46 | 47 | ```bash 48 | pip install --upgrade build 49 | ``` 50 | 51 | Then, build the distribution: 52 | 53 | ```bash 54 | rm -rf dist 55 | python -m build 56 | ``` 57 | 58 | ### Publishing the distribution 59 | 60 | First, make sure you have an up-to-date version of the `twine` package installed: 61 | 62 | ```bash 63 | pip install --upgrade twine 64 | ``` 65 | 66 | Then, publish the distribution: 67 | 68 | ```bash 69 | twine upload dist/* 70 | ``` 71 | 72 | This first time you run that, it will ask for a username and password. Enter 73 | `__token__` for the username and specify a valid PyPI token as the password. 74 | Contact Joel Dice for a token if you don't have one. 75 | -------------------------------------------------------------------------------- /MAINTAINERS.md: -------------------------------------------------------------------------------- 1 | # MAINTAINERS 2 | 3 | ## Current Maintainers 4 | 5 | _Listed in alphabetical order by first name_ 6 | 7 | | Name | GitHub Username | 8 | | --- | --- | 9 | | Joel Dice | dicej | 10 | | Karthik Ganeshram | karthik2804 | 11 | 12 | ## Emeritus Maintainers 13 | 14 | None 15 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include src/spin_sdk *.wit *.toml py.typed 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Spin Python SDK 2 | 3 | This is an SDK for creating [Spin](https://github.com/spinframework/spin) apps using Python. 4 | 5 | Note that this SDK supersedes an earlier, experimental version, which may be 6 | found in the [old-sdk](https://github.com/spinframework/spin-python-sdk/tree/old-sdk) 7 | branch. 8 | 9 | ## [API Documentation](https://spinframework.github.io/spin-python-sdk/v3/index.html) 10 | 11 | ## Example 12 | 13 | ### Prerequisites 14 | 15 | - [Python 3.10 or later and pip](https://www.python.org/downloads/) 16 | - [componentize-py](https://pypi.org/project/componentize-py/) 17 | - [spin-sdk](https://pypi.org/project/spin-sdk/) 18 | - [Spin](https://github.com/spinframework/spin) 2.2 or later. 19 | - [MyPy](https://pypi.org/project/mypy/) -- This is optional, but useful for during development. 20 | 21 | Once you have Python and pip installed, you can use the latter to create and 22 | enter a virtual environment and then install the desired packages 23 | 24 | ```shell 25 | python -m venv .venv 26 | source .venv/bin/activate 27 | pip install componentize-py==0.16.0 spin-sdk==3.3.1 mypy==1.8.0 28 | ``` 29 | 30 | ### Hello, World 31 | 32 | A minimal app requires two files: a `spin.toml` and a Python script, which we'll 33 | name `app.py`: 34 | 35 | ```shell 36 | cat >spin.toml <"] 43 | 44 | [[trigger.http]] 45 | route = "/..." 46 | component = "hello" 47 | 48 | [component.hello] 49 | source = "app.wasm" 50 | [component.hello.build] 51 | command = "componentize-py -w spin-http componentize app -o app.wasm" 52 | EOF 53 | ``` 54 | 55 | ```shell 56 | cat >app.py < Response: 62 | return Response( 63 | 200, 64 | {"content-type": "text/plain"}, 65 | bytes("Hello from Python!", "utf-8") 66 | ) 67 | EOF 68 | ``` 69 | 70 | Once you've created those files, you can check, build, and run your app: 71 | 72 | ```.py 73 | python -m mypy app.py 74 | spin build -u 75 | ``` 76 | 77 | Finally, you can test your app using e.g. `curl` in another terminal: 78 | 79 | ```shell 80 | curl -i http://127.0.0.1:3000 81 | ``` 82 | 83 | If all goes well, you should see something like: 84 | 85 | ``` 86 | HTTP/1.1 200 OK 87 | content-type: text/plain 88 | content-length: 18 89 | date: Thu, 11 Apr 2024 17:42:31 GMT 90 | 91 | Hello from Python! 92 | ``` 93 | 94 | Please file an issue if you have any trouble. 95 | 96 | See the [examples directory](https://github.com/spinframework/spin-python-sdk/tree/main/examples) in the repository for more examples. 97 | -------------------------------------------------------------------------------- /docs/v2/wit/exports/inbound_redis.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | spin_sdk.wit.exports.inbound_redis API documentation 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 |
21 |
22 |

Module spin_sdk.wit.exports.inbound_redis

23 |
24 |
25 |
26 | 27 | Expand source code 28 | 29 |
from typing import TypeVar, Generic, Union, Optional, Union, Protocol, Tuple, List, Any, Self
30 | from enum import Flag, Enum, auto
31 | from dataclasses import dataclass
32 | from abc import abstractmethod
33 | import weakref
34 | 
35 | from ..types import Result, Ok, Err, Some
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | 60 |
61 | 64 | 65 | -------------------------------------------------------------------------------- /docs/v3/variables.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | spin_sdk.variables API documentation 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 20 | 21 | 22 |
23 |
24 |
25 |

Module spin_sdk.variables

26 |
27 |
28 |

Module for interacting with Spin Variables

29 |
30 |
31 |
32 |
33 |
34 |
35 |

Functions

36 |
37 |
38 | def get(key: str) 39 |
40 |
41 |

Gets the value of the given key

42 |
43 |
44 |
45 |
46 |
47 |
48 | 65 |
66 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /docs/v3/wit/exports/inbound_redis.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | spin_sdk.wit.exports.inbound_redis API documentation 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 20 | 21 | 22 |
23 |
24 |
25 |

Module spin_sdk.wit.exports.inbound_redis

26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | 50 |
51 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /docs/v3/wit/exports/incoming_handler.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | spin_sdk.wit.exports.incoming_handler API documentation 8 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 21 | 22 | 23 |
24 |
25 |
26 |

Module spin_sdk.wit.exports.incoming_handler

27 |
28 |
29 |

This interface defines a handler of incoming HTTP Requests. It should 30 | be exported by components which can respond to HTTP Requests.

31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | 53 |
54 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /docs/wit/exports/inbound_redis.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | spin_sdk.wit.exports.inbound_redis API documentation 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 |
21 |
22 |

Module spin_sdk.wit.exports.inbound_redis

23 |
24 |
25 |
26 | 27 | Expand source code 28 | 29 |
from typing import TypeVar, Generic, Union, Optional, Union, Protocol, Tuple, List, Any, Self
30 | from enum import Flag, Enum, auto
31 | from dataclasses import dataclass
32 | from abc import abstractmethod
33 | import weakref
34 | 
35 | from ..types import Result, Ok, Err, Some
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | 60 |
61 | 64 | 65 | -------------------------------------------------------------------------------- /examples/external-lib-example/README.md: -------------------------------------------------------------------------------- 1 | # Example: External Library 2 | 3 | This is an example showcasing the use of an external library [`http-router`](https://pypi.org/project/http-router/) within a guest component. 4 | 5 | ## Preparing the Environment 6 | 7 | Run the following commands to setup a virtual environment with Python. 8 | 9 | ```bash 10 | python3 -m venv venv 11 | source venv/bin/activate 12 | ``` 13 | 14 | Install the required packages specified in the `requirements.txt` using the command: 15 | 16 | ```bash 17 | pip3 install -r requirements.txt 18 | ``` 19 | 20 | The above install all the required packages which includes the `http-router` pacakge. 21 | 22 | ## Building and Running the Examples 23 | 24 | ```bash 25 | spin build --up 26 | ``` 27 | 28 | ## Testing the App 29 | 30 | ```bash 31 | $ curl localhost:3000 32 | Hello World 33 | 34 | $ curl "localhost:3000/queryparams?foo=bar" 35 | bar 36 | 37 | curl -XPOST -d "TEST" localhost:3000/post 38 | TEST 39 | ``` -------------------------------------------------------------------------------- /examples/external-lib-example/app.py: -------------------------------------------------------------------------------- 1 | from spin_sdk import http 2 | from spin_sdk.http import Request, Response 3 | import re 4 | 5 | # Import the Router from the upstream project http_router 6 | # See docs: https://github.com/klen/http-router 7 | from http_router import Router, exceptions 8 | 9 | # This is the built-in URL parser. 10 | from urllib.parse import ParseResult, urlparse, parse_qs 11 | 12 | # You want the router declared outside of the handler function for scope. 13 | # the trim_last_slash means that `/foo/` and `/foo` will be treated as the 14 | # same. 15 | router = Router(trim_last_slash=True) 16 | 17 | @router.route("/") 18 | def handle_index(uri: ParseResult, request: Request) -> Response: 19 | # Basic response for a basic request 20 | return Response(200, {"content-type": "text/plain"}, b"Hello World") 21 | 22 | 23 | # Try this one with `curl -XPOST -d "TEST" localhost:3000/post` 24 | @router.route("/post", methods=["POST"]) 25 | def handle_post(uri: ParseResult, request: Request) -> Response: 26 | # The post body is in `request.body` 27 | # Handle it however is appropriate. In this case, 28 | # we'll just echo it back 29 | return Response(200, {"content-type": "text/plain"}, request.body) 30 | 31 | 32 | # Call this with curl localhost:3000/queryparams?foo=bar 33 | @router.route("/queryparams") 34 | def handle_queryparams(uri: ParseResult, request: Request) -> Response: 35 | # This is how to get query params out of URI 36 | params = parse_qs(uri.query) 37 | foo = params["foo"][0] 38 | # Echo back the value 39 | return Response(200, {"content-type": "text/plain"}, bytes(foo, "utf-8")) 40 | 41 | 42 | class IncomingHandler(http.IncomingHandler): 43 | def handle_request(self, request: Request) -> Response: 44 | # I need to parse the URI because the Request object in Spin 45 | # is in the form /path/to/thing?param1=val1&p2=v2#anchor 46 | # and we need just /path/to/thing 47 | uri = urlparse(request.uri) 48 | try: 49 | handler = router(uri.path, request.method) 50 | return handler.target(uri, request) 51 | except exceptions.NotFoundError: 52 | return Response(404, {}, None) 53 | 54 | -------------------------------------------------------------------------------- /examples/external-lib-example/requirements.txt: -------------------------------------------------------------------------------- 1 | spin-sdk == 3.3.1 2 | componentize-py == 0.16.0 3 | http-router == 4.1.2 -------------------------------------------------------------------------------- /examples/external-lib-example/spin.toml: -------------------------------------------------------------------------------- 1 | spin_manifest_version = 2 2 | 3 | [application] 4 | name = "test" 5 | version = "0.1.0" 6 | authors = ["Fermyon Engineering "] 7 | description = "" 8 | 9 | [[trigger.http]] 10 | route = "/..." 11 | component = "test" 12 | 13 | [component.test] 14 | source = "app.wasm" 15 | allowed_outbound_hosts = [] 16 | [component.test.build] 17 | command = "componentize-py -w spin-http componentize app -o app.wasm" 18 | -------------------------------------------------------------------------------- /examples/hello/README.md: -------------------------------------------------------------------------------- 1 | # Example: Hello world 2 | 3 | This is an example showcasing a guest component. 4 | 5 | ## Preparing the Environment 6 | 7 | Run the following commands to setup a virtual environment with Python. 8 | 9 | ```bash 10 | python3 -m venv venv 11 | source venv/bin/activate 12 | ``` 13 | 14 | Install the required packages specified in the `requirements.txt` using the command: 15 | 16 | ```bash 17 | pip3 install -r requirements.txt 18 | ``` 19 | 20 | ## Building and Running the Examples 21 | 22 | ```bash 23 | spin build --up 24 | ``` 25 | 26 | ## Testing the App 27 | 28 | ```bash 29 | $ curl localhost:3000 30 | Hello from Python! 31 | ``` -------------------------------------------------------------------------------- /examples/hello/app.py: -------------------------------------------------------------------------------- 1 | from spin_sdk import http 2 | from spin_sdk.http import Request, Response 3 | 4 | class IncomingHandler(http.IncomingHandler): 5 | def handle_request(self, request: Request) -> Response: 6 | return Response( 7 | 200, 8 | {"content-type": "text/plain"}, 9 | bytes("Hello from Python!", "utf-8") 10 | ) 11 | -------------------------------------------------------------------------------- /examples/hello/requirements.txt: -------------------------------------------------------------------------------- 1 | spin-sdk == 3.3.1 2 | componentize-py == 0.16.0 -------------------------------------------------------------------------------- /examples/hello/spin.toml: -------------------------------------------------------------------------------- 1 | spin_manifest_version = 2 2 | 3 | [application] 4 | name = "test" 5 | version = "0.1.0" 6 | authors = ["Fermyon Engineering "] 7 | description = "" 8 | 9 | [[trigger.http]] 10 | route = "/..." 11 | component = "test" 12 | 13 | [component.test] 14 | source = "app.wasm" 15 | allowed_outbound_hosts = [] 16 | [component.test.build] 17 | command = "componentize-py -w spin-http componentize app -o app.wasm" 18 | -------------------------------------------------------------------------------- /examples/matrix-math/README.md: -------------------------------------------------------------------------------- 1 | # Example: `matrix-math` 2 | 3 | This is an example of how to use [componentize-py] to build an HTTP app that 4 | does matrix multiplication using [NumPy]. This demonstrates using a non-trivial 5 | Python package containing native extensions within a guest component. 6 | 7 | [componentize-py]: https://github.com/bytecodealliance/componentize-py 8 | [NumPy]: https://numpy.org 9 | 10 | ## Prerequisites 11 | 12 | * A clone of the Git repo in which you found this example 13 | * `spin` 2.2 or later 14 | * `componentize-py` 0.16.0 15 | * `NumPy`, built for WASI 16 | 17 | Note that we use an unofficial build of NumPy since the upstream project does 18 | not yet publish WASI builds. 19 | 20 | ``` 21 | curl -OL https://github.com/dicej/wasi-wheels/releases/download/v0.0.1/numpy-wasi.tar.gz 22 | tar xf numpy-wasi.tar.gz 23 | ``` 24 | 25 | ## Running the example 26 | 27 | ``` 28 | spin build -u 29 | ``` 30 | 31 | Then, in another terminal, send a request to the app: 32 | 33 | ``` 34 | curl -i -H 'content-type: application/json' -d '[[[1,2],[4,5],[6,7]], [[1,2,3],[4,5,6]]]' \ 35 | http://127.0.0.1:3000/multiply 36 | ``` 37 | 38 | The output of that command should be something like: 39 | 40 | ``` 41 | HTTP/1.1 200 OK 42 | content-type: application/json 43 | transfer-encoding: chunked 44 | date: Fri, 19 Jan 2024 22:24:50 GMT 45 | 46 | [[9, 12, 15], [24, 33, 42], [34, 47, 60]] 47 | ``` 48 | 49 | If you run into any problems, please file an issue! 50 | -------------------------------------------------------------------------------- /examples/matrix-math/app.py: -------------------------------------------------------------------------------- 1 | import numpy 2 | import json 3 | from spin_sdk import http 4 | from spin_sdk.http import Request, Response 5 | 6 | class IncomingHandler(http.IncomingHandler): 7 | def handle_request(self, request: Request) -> Response: 8 | if request.method == "POST" \ 9 | and request.uri == "/multiply" \ 10 | and request.headers["content-type"] == "application/json" \ 11 | and request.body is not None: 12 | [a, b] = json.loads(request.body) 13 | return Response( 14 | 200, 15 | {"content-type": "application/json"}, 16 | bytes(json.dumps(numpy.matmul(a, b).tolist()), "utf-8") 17 | ) 18 | else: 19 | return Response( 20 | 400, 21 | {"content-type": "text/plain"}, 22 | bytes("Bad Request", "utf-8") 23 | ) 24 | -------------------------------------------------------------------------------- /examples/matrix-math/requirements.txt: -------------------------------------------------------------------------------- 1 | spin-sdk == 3.3.1 2 | componentize-py == 0.16.0 -------------------------------------------------------------------------------- /examples/matrix-math/spin.toml: -------------------------------------------------------------------------------- 1 | spin_manifest_version = 2 2 | 3 | [application] 4 | name = "test" 5 | version = "0.1.0" 6 | authors = ["Fermyon Engineering "] 7 | description = "" 8 | 9 | [[trigger.http]] 10 | route = "/..." 11 | component = "test" 12 | 13 | [component.test] 14 | source = "app.wasm" 15 | allowed_outbound_hosts = [] 16 | [component.test.build] 17 | command = "componentize-py -w spin-http componentize -p . -p ../../src app -o app.wasm" 18 | -------------------------------------------------------------------------------- /examples/outgoing-request/README.md: -------------------------------------------------------------------------------- 1 | # Example: Outgoing Request 2 | 3 | This is an example showcasing the use of outbound requests within a guest component. 4 | 5 | ## Preparing the Environment 6 | 7 | Run the following commands to setup a virtual environment with Python. 8 | 9 | ```bash 10 | python3 -m venv venv 11 | source venv/bin/activate 12 | ``` 13 | 14 | Install the required packages specified in the `requirements.txt` using the command: 15 | 16 | ```bash 17 | pip3 install -r requirements.txt 18 | ``` 19 | 20 | ## Building and Running the Examples 21 | 22 | ```bash 23 | spin build --up 24 | ``` 25 | 26 | ## Testing the App 27 | 28 | ```bash 29 | $ curl -H "url: https://example.com" localhost:3000 30 | 31 | 32 | 33 | Example Domain 34 | ... 35 | ``` -------------------------------------------------------------------------------- /examples/outgoing-request/app.py: -------------------------------------------------------------------------------- 1 | from spin_sdk import http 2 | from spin_sdk.http import Request, Response, send 3 | 4 | class IncomingHandler(http.IncomingHandler): 5 | def handle_request(self, request: Request) -> Response: 6 | try: 7 | url = request.headers["url"] 8 | except KeyError: 9 | return Response( 10 | 400, 11 | {"content-type": "text/plain"}, 12 | bytes("Please specify `url` header", "utf-8") 13 | ) 14 | 15 | return send(Request("GET", url, {}, None)) 16 | -------------------------------------------------------------------------------- /examples/outgoing-request/requirements.txt: -------------------------------------------------------------------------------- 1 | spin-sdk == 3.3.1 2 | componentize-py == 0.16.0 -------------------------------------------------------------------------------- /examples/outgoing-request/spin.toml: -------------------------------------------------------------------------------- 1 | spin_manifest_version = 2 2 | 3 | [application] 4 | name = "test" 5 | version = "0.1.0" 6 | authors = ["Fermyon Engineering "] 7 | description = "" 8 | 9 | [[trigger.http]] 10 | route = "/..." 11 | component = "test" 12 | 13 | [component.test] 14 | source = "app.wasm" 15 | allowed_outbound_hosts = ["http://*:*", "https://*:*"] 16 | [component.test.build] 17 | command = "componentize-py -w spin-http componentize app -o app.wasm" 18 | -------------------------------------------------------------------------------- /examples/redis-trigger/README.md: -------------------------------------------------------------------------------- 1 | # Example: External Library 2 | 3 | This is an example showcasing the use of an external library [`http-router`](https://pypi.org/project/http-router/) within a guest component. 4 | 5 | ## Preparing the Environment 6 | 7 | Run the following commands to setup a virtual environment with Python. 8 | 9 | ```bash 10 | python3 -m venv venv 11 | source venv/bin/activate 12 | ``` 13 | 14 | This app also needs a [redis server](https://redis.io/) running on the port `6379`. Installation information can be found [here](https://redis.io/docs/install/). Once installed the server can be started using: 15 | 16 | ```bash 17 | redis-server 18 | ``` 19 | 20 | Install the required packages specified in the `requirements.txt` using the command: 21 | 22 | ```bash 23 | pip3 install -r requirements.txt 24 | ``` 25 | 26 | ## Building and Running the Examples 27 | 28 | ```bash 29 | spin build --up 30 | ``` 31 | 32 | ## Testing the App 33 | 34 | Send a message on a redis channel named `messages`: 35 | 36 | ```bash 37 | $ redis-cli 38 | 127.0.0.1:6379> publish messages "hello world" 39 | ``` 40 | 41 | The app should output: 42 | 43 | ```bash 44 | hello world 45 | ``` 46 | -------------------------------------------------------------------------------- /examples/redis-trigger/app.py: -------------------------------------------------------------------------------- 1 | from spin_sdk.wit import exports 2 | 3 | class InboundRedis(exports.InboundRedis): 4 | def handle_message(self, message: bytes): 5 | print(message) -------------------------------------------------------------------------------- /examples/redis-trigger/requirements.txt: -------------------------------------------------------------------------------- 1 | spin-sdk == 3.3.1 2 | componentize-py == 0.16.0 -------------------------------------------------------------------------------- /examples/redis-trigger/spin.toml: -------------------------------------------------------------------------------- 1 | spin_manifest_version = 2 2 | 3 | [application] 4 | authors = ["Fermyon Engineering "] 5 | description = "A redis application." 6 | name = "spin-redis" 7 | version = "0.1.0" 8 | 9 | [application.trigger.redis] 10 | address = "redis://localhost:6379" 11 | 12 | [[trigger.redis]] 13 | channel = "messages" 14 | component = "echo-message" 15 | 16 | [component.echo-message] 17 | source = "app.wasm" 18 | [component.echo-message.build] 19 | command = "componentize-py -w spin-redis componentize app -o app.wasm" 20 | -------------------------------------------------------------------------------- /examples/spin-kv/README.md: -------------------------------------------------------------------------------- 1 | # Example: Spin Key-Value 2 | 3 | This is an example showcasing the using Spin key-value with a guest component. 4 | 5 | ## Preparing the Environment 6 | 7 | Run the following commands to setup a virtual environment with Python. 8 | 9 | ```bash 10 | python3 -m venv venv 11 | source venv/bin/activate 12 | ``` 13 | 14 | Install the required packages specified in the `requirements.txt` using the command: 15 | 16 | ```bash 17 | pip3 install -r requirements.txt 18 | ``` 19 | 20 | ## Building and Running the Examples 21 | 22 | ```bash 23 | spin build --up 24 | ``` 25 | 26 | ## Testing the App 27 | 28 | ```bash 29 | $ curl localhost:3000 30 | Hello from Python! 31 | ``` -------------------------------------------------------------------------------- /examples/spin-kv/app.py: -------------------------------------------------------------------------------- 1 | from spin_sdk import http, key_value 2 | from spin_sdk.http import Request, Response 3 | 4 | class IncomingHandler(http.IncomingHandler): 5 | def handle_request(self, request: Request) -> Response: 6 | with key_value.open_default() as a: 7 | a.set("test", bytes("hello world!", "utf-8")) 8 | print(a.get_keys()) 9 | print(a.exists("test")) 10 | print(a.get("test")) 11 | a.delete("test") 12 | print(a.get_keys()) 13 | 14 | return Response( 15 | 200, 16 | {"content-type": "text/plain"}, 17 | bytes("Hello from Python!", "utf-8") 18 | ) 19 | -------------------------------------------------------------------------------- /examples/spin-kv/requirements.txt: -------------------------------------------------------------------------------- 1 | spin-sdk == 3.3.1 2 | componentize-py == 0.16.0 -------------------------------------------------------------------------------- /examples/spin-kv/spin.toml: -------------------------------------------------------------------------------- 1 | spin_manifest_version = 2 2 | 3 | [application] 4 | name = "test" 5 | version = "0.1.0" 6 | authors = ["Fermyon Engineering "] 7 | description = "" 8 | 9 | [[trigger.http]] 10 | route = "/..." 11 | component = "test" 12 | 13 | [component.test] 14 | source = "app.wasm" 15 | allowed_outbound_hosts = [] 16 | key_value_stores = ["default"] 17 | [component.test.build] 18 | command = "componentize-py -w spin-http componentize app -o app.wasm" 19 | -------------------------------------------------------------------------------- /examples/spin-llm/README.md: -------------------------------------------------------------------------------- 1 | # Example: Spin LLM 2 | 3 | This is an example showcasing the use of Spin LLM within a guest component. 4 | 5 | ## Preparing the Environment 6 | 7 | Run the following commands to setup a virtual environment with Python. 8 | 9 | ```bash 10 | python3 -m venv venv 11 | source venv/bin/activate 12 | ``` 13 | 14 | Install the required packages specified in the `requirements.txt` using the command: 15 | 16 | ```bash 17 | pip3 install -r requirements.txt 18 | ``` 19 | 20 | ## Building and Running the Examples 21 | 22 | ```bash 23 | spin build --up 24 | ``` 25 | 26 | ## Testing the App 27 | 28 | ```bash 29 | $ curl localhost:3000 30 | Hello from Python! 31 | ``` -------------------------------------------------------------------------------- /examples/spin-llm/app.py: -------------------------------------------------------------------------------- 1 | from spin_sdk import http, llm 2 | from spin_sdk.http import Request, Response 3 | 4 | class IncomingHandler(http.IncomingHandler): 5 | def handle_request(self, request: Request) -> Response: 6 | res = llm.infer("llama2-chat", "tell me a joke") 7 | print(res.text) 8 | print(res.usage) 9 | res = llm.infer_with_options("llama2-chat", "what is the theory of relativity", llm.InferencingParams(temperature=0.5)) 10 | print(res.text) 11 | print(res.usage) 12 | return Response( 13 | 200, 14 | {"content-type": "text/plain"}, 15 | bytes("Hello from Python!", "utf-8") 16 | ) 17 | -------------------------------------------------------------------------------- /examples/spin-llm/requirements.txt: -------------------------------------------------------------------------------- 1 | spin-sdk == 3.3.1 2 | componentize-py == 0.16.0 -------------------------------------------------------------------------------- /examples/spin-llm/spin.toml: -------------------------------------------------------------------------------- 1 | spin_manifest_version = 2 2 | 3 | [application] 4 | name = "test" 5 | version = "0.1.0" 6 | authors = ["Fermyon Engineering "] 7 | description = "" 8 | 9 | [[trigger.http]] 10 | route = "/..." 11 | component = "test" 12 | 13 | [component.test] 14 | source = "app.wasm" 15 | allowed_outbound_hosts = [] 16 | ai_models = ["llama2-chat"] 17 | [component.test.build] 18 | command = "componentize-py -w spin-http componentize -o app.wasm" 19 | -------------------------------------------------------------------------------- /examples/spin-mysql/README.md: -------------------------------------------------------------------------------- 1 | # Example: Spin Mysql 2 | 3 | This is an example showcasing the use of Spin MySQL bindings within a guest component. 4 | 5 | ## Preparing the Environment 6 | 7 | Run the following commands to setup a virtual environment with Python. 8 | 9 | ```bash 10 | python3 -m venv venv 11 | source venv/bin/activate 12 | ``` 13 | 14 | Install the required packages specified in the `requirements.txt` using the command: 15 | 16 | ```bash 17 | pip3 install -r requirements.txt 18 | ``` 19 | 20 | For this example, a MySQL database named `spin_dev` must be accessible at `mysql://root:@127.0.0.1/` should exist. 21 | 22 | ## Building and Running the Examples 23 | 24 | ```bash 25 | spin build --up 26 | ``` 27 | 28 | ## Testing the App 29 | 30 | ```bash 31 | $ curl localhost:3000 32 | Hello from Python! 33 | ``` -------------------------------------------------------------------------------- /examples/spin-mysql/app.py: -------------------------------------------------------------------------------- 1 | from spin_sdk import http, mysql 2 | from spin_sdk.http import Request, Response 3 | 4 | class IncomingHandler(http.IncomingHandler): 5 | def handle_request(self, request: Request) -> Response: 6 | with mysql.open("mysql://root:@127.0.0.1/spin_dev") as db: 7 | print(db.query("select * from test", [])) 8 | 9 | return Response( 10 | 200, 11 | {"content-type": "text/plain"}, 12 | bytes("Hello from Python!", "utf-8") 13 | ) 14 | -------------------------------------------------------------------------------- /examples/spin-mysql/requirements.txt: -------------------------------------------------------------------------------- 1 | spin-sdk == 3.3.1 2 | componentize-py == 0.16.0 -------------------------------------------------------------------------------- /examples/spin-mysql/spin.toml: -------------------------------------------------------------------------------- 1 | spin_manifest_version = 2 2 | 3 | [application] 4 | name = "test" 5 | version = "0.1.0" 6 | authors = ["Fermyon Engineering "] 7 | description = "" 8 | 9 | [[trigger.http]] 10 | route = "/..." 11 | component = "test" 12 | 13 | [component.test] 14 | source = "app.wasm" 15 | allowed_outbound_hosts = ["*://*:*"] 16 | [component.test.build] 17 | command = "componentize-py -w spin-http componentize app -o app.wasm" 18 | -------------------------------------------------------------------------------- /examples/spin-outbound-mqtt/README.md: -------------------------------------------------------------------------------- 1 | # Example: Spin Postgres 2 | 3 | This is an example showcasing the use of Spin outbound MQTT bindings within a guest component. 4 | 5 | ## Preparing the Environment 6 | 7 | Run the following commands to setup a virtual environment with Python. 8 | 9 | ```bash 10 | python3 -m venv venv 11 | source venv/bin/activate 12 | ``` 13 | 14 | Install the required packages specified in the `requirements.txt` using the command: 15 | 16 | ```bash 17 | pip3 install -r requirements.txt 18 | ``` 19 | 20 | For this example, a MQTT broker must be accessible at `127.0.0.1`. An username `user` and password `password` should exist. 21 | 22 | ## Building and Running the Examples 23 | 24 | ```bash 25 | spin build --up 26 | ``` 27 | 28 | ## Testing the App 29 | 30 | ```bash 31 | $ curl localhost:3000 32 | Sent outbound mqtt message! 33 | ``` -------------------------------------------------------------------------------- /examples/spin-outbound-mqtt/app.py: -------------------------------------------------------------------------------- 1 | from spin_sdk import http, mqtt 2 | from spin_sdk.mqtt import Qos 3 | from spin_sdk.http import Request, Response 4 | 5 | class IncomingHandler(http.IncomingHandler): 6 | def handle_request(self, request: Request) -> Response: 7 | with mqtt.open("mqtt://localhost:1883?client_id=client001", "user", "password", 30) as conn: 8 | conn.publish("telemetry", bytes("Eureka!", "utf-8"), Qos.AT_LEAST_ONCE) 9 | 10 | return Response( 11 | 200, 12 | {"content-type": "text/plain"}, 13 | bytes("Sent outbound mqtt message!", "utf-8") 14 | ) 15 | -------------------------------------------------------------------------------- /examples/spin-outbound-mqtt/requirements.txt: -------------------------------------------------------------------------------- 1 | spin-sdk == 3.3.1 2 | componentize-py == 0.16.0 -------------------------------------------------------------------------------- /examples/spin-outbound-mqtt/spin.toml: -------------------------------------------------------------------------------- 1 | spin_manifest_version = 2 2 | 3 | [application] 4 | name = "test" 5 | version = "0.1.0" 6 | authors = ["Fermyon Engineering "] 7 | description = "" 8 | 9 | [[trigger.http]] 10 | route = "/..." 11 | component = "test" 12 | 13 | [component.test] 14 | source = "app.wasm" 15 | allowed_outbound_hosts = ["*://*:*"] 16 | [component.test.build] 17 | command = "componentize-py -d ../../src/spin_sdk/wit -w spin-http componentize app -o app.wasm" 18 | -------------------------------------------------------------------------------- /examples/spin-postgres/README.md: -------------------------------------------------------------------------------- 1 | # Example: Spin Postgres 2 | 3 | This is an example showcasing the use of Spin PostgreSQL bindings within a guest component. 4 | 5 | ## Preparing the Environment 6 | 7 | Run the following commands to setup a virtual environment with Python. 8 | 9 | ```bash 10 | python3 -m venv venv 11 | source venv/bin/activate 12 | ``` 13 | 14 | Install the required packages specified in the `requirements.txt` using the command: 15 | 16 | ```bash 17 | pip3 install -r requirements.txt 18 | ``` 19 | 20 | For this example, a PostgreSQL database named `spin_dev` must be accessible at `127.0.0.1` with a user `postgres` should exist. 21 | 22 | ## Building and Running the Examples 23 | 24 | ```bash 25 | spin build --up 26 | ``` 27 | 28 | ## Testing the App 29 | 30 | ```bash 31 | $ curl localhost:3000 32 | Hello from Python! 33 | ``` 34 | -------------------------------------------------------------------------------- /examples/spin-postgres/app.py: -------------------------------------------------------------------------------- 1 | from spin_sdk import http, postgres 2 | from spin_sdk.http import Request, Response 3 | 4 | class IncomingHandler(http.IncomingHandler): 5 | def handle_request(self, request: Request) -> Response: 6 | with postgres.open("user=postgres dbname=spin_dev host=127.0.0.1") as db: 7 | print(db.query("select * from test", [])) 8 | 9 | return Response( 10 | 200, 11 | {"content-type": "text/plain"}, 12 | bytes("Hello from Python!", "utf-8") 13 | ) 14 | -------------------------------------------------------------------------------- /examples/spin-postgres/requirements.txt: -------------------------------------------------------------------------------- 1 | spin-sdk == 3.3.1 2 | componentize-py == 0.16.0 -------------------------------------------------------------------------------- /examples/spin-postgres/spin.toml: -------------------------------------------------------------------------------- 1 | spin_manifest_version = 2 2 | 3 | [application] 4 | name = "test" 5 | version = "0.1.0" 6 | authors = ["Fermyon Engineering "] 7 | description = "" 8 | 9 | [[trigger.http]] 10 | route = "/..." 11 | component = "test" 12 | 13 | [component.test] 14 | source = "app.wasm" 15 | allowed_outbound_hosts = ["*://*:*"] 16 | [component.test.build] 17 | command = "componentize-py -w spin-http componentize app -o app.wasm" 18 | -------------------------------------------------------------------------------- /examples/spin-redis/README.md: -------------------------------------------------------------------------------- 1 | # Example: Spin Redis 2 | 3 | This is an example showcasing the use of Spin Redis bindings within a guest component. 4 | 5 | ## Preparing the Environment 6 | 7 | Run the following commands to setup a virtual environment with Python. 8 | 9 | ```bash 10 | python3 -m venv venv 11 | source venv/bin/activate 12 | ``` 13 | 14 | Install the required packages specified in the `requirements.txt` using the command: 15 | 16 | ```bash 17 | pip3 install -r requirements.txt 18 | ``` 19 | 20 | For this example, a redis database should be accessable at `localhost:6379`. 21 | 22 | ## Building and Running the Examples 23 | 24 | ```bash 25 | spin build --up 26 | ``` 27 | 28 | ## Testing the App 29 | 30 | ```bash 31 | $ curl localhost:3000 32 | Hello from Python! 33 | ``` -------------------------------------------------------------------------------- /examples/spin-redis/app.py: -------------------------------------------------------------------------------- 1 | from spin_sdk import http, redis 2 | from spin_sdk.http import Request, Response 3 | 4 | class IncomingHandler(http.IncomingHandler): 5 | def handle_request(self, request: Request) -> Response: 6 | with redis.open("redis://localhost:6379") as db: 7 | print(db.get("test")) 8 | 9 | return Response( 10 | 200, 11 | {"content-type": "text/plain"}, 12 | bytes("Hello from Python!", "utf-8") 13 | ) 14 | -------------------------------------------------------------------------------- /examples/spin-redis/requirements.txt: -------------------------------------------------------------------------------- 1 | spin-sdk == 3.3.1 2 | componentize-py == 0.16.0 3 | -------------------------------------------------------------------------------- /examples/spin-redis/spin.toml: -------------------------------------------------------------------------------- 1 | spin_manifest_version = 2 2 | 3 | [application] 4 | name = "test" 5 | version = "0.1.0" 6 | authors = ["Fermyon Engineering "] 7 | description = "" 8 | 9 | [[trigger.http]] 10 | route = "/..." 11 | component = "test" 12 | 13 | [component.test] 14 | source = "app.wasm" 15 | allowed_outbound_hosts = ["redis://*:*"] 16 | ai_models = ["llama2-chat"] 17 | [component.test.build] 18 | command = "componentize-py -w spin-http componentize app -o app.wasm" 19 | -------------------------------------------------------------------------------- /examples/spin-sqlite/README.md: -------------------------------------------------------------------------------- 1 | # Example: Spin Sqlite 2 | 3 | This is an example showcasing the use of Spin SQLite within a guest component. 4 | 5 | ## Preparing the Environment 6 | 7 | Run the following commands to setup a virtual environment with Python. 8 | 9 | ```bash 10 | python3 -m venv venv 11 | source venv/bin/activate 12 | ``` 13 | 14 | Install the required packages specified in the `requirements.txt` using the command: 15 | 16 | ```bash 17 | pip3 install -r requirements.txt 18 | ``` 19 | 20 | ## Building and Running the Examples 21 | 22 | ```bash 23 | spin build --up 24 | ``` 25 | 26 | ## Testing the App 27 | 28 | ```bash 29 | $ curl localhost:3000 30 | [] 31 | ``` -------------------------------------------------------------------------------- /examples/spin-sqlite/app.py: -------------------------------------------------------------------------------- 1 | from spin_sdk import http, sqlite 2 | from spin_sdk.http import Request, Response 3 | from spin_sdk.sqlite import Value_Integer 4 | 5 | class IncomingHandler(http.IncomingHandler): 6 | def handle_request(self, request: Request) -> Response: 7 | with sqlite.open_default() as db: 8 | result = db.execute("SELECT * FROM todos WHERE id > (?);", [Value_Integer(1)]) 9 | rows = result.rows 10 | 11 | return Response( 12 | 200, 13 | {"content-type": "text/plain"}, 14 | bytes(str(rows), "utf-8") 15 | ) 16 | -------------------------------------------------------------------------------- /examples/spin-sqlite/requirements.txt: -------------------------------------------------------------------------------- 1 | spin-sdk == 3.3.1 2 | componentize-py == 0.16.0 -------------------------------------------------------------------------------- /examples/spin-sqlite/spin.toml: -------------------------------------------------------------------------------- 1 | spin_manifest_version = 2 2 | 3 | [application] 4 | name = "test" 5 | version = "0.1.0" 6 | authors = ["Fermyon Engineering "] 7 | description = "" 8 | 9 | [[trigger.http]] 10 | route = "/..." 11 | component = "test" 12 | 13 | [component.test] 14 | source = "app.wasm" 15 | sqlite_databases = ["default"] 16 | ai_models = ["llama2-chat"] 17 | [component.test.build] 18 | command = "componentize-py -w spin-http componentize app -o app.wasm" 19 | -------------------------------------------------------------------------------- /examples/spin-variables/README.md: -------------------------------------------------------------------------------- 1 | # Example: Spin Variables 2 | 3 | This is an example showcasing the use of Spin variables within a guest component. 4 | 5 | ## Preparing the Environment 6 | 7 | Run the following commands to setup a virtual environment with Python. 8 | 9 | ```bash 10 | python3 -m venv venv 11 | source venv/bin/activate 12 | ``` 13 | 14 | Install the required packages specified in the `requirements.txt` using the command: 15 | 16 | ```bash 17 | pip3 install -r requirements.txt 18 | ``` 19 | 20 | ## Building and Running the Examples 21 | 22 | ```bash 23 | spin build --up 24 | ``` 25 | 26 | ## Testing the App 27 | 28 | ```bash 29 | $ curl localhost:3000 30 | Hello from Python! 31 | ``` -------------------------------------------------------------------------------- /examples/spin-variables/app.py: -------------------------------------------------------------------------------- 1 | from spin_sdk import http, variables 2 | from spin_sdk.http import Request, Response 3 | 4 | class IncomingHandler(http.IncomingHandler): 5 | def handle_request(self, request: Request) -> Response: 6 | print(variables.get("message")) 7 | return Response( 8 | 200, 9 | {"content-type": "text/plain"}, 10 | bytes("Hello from Python!", "utf-8") 11 | ) 12 | -------------------------------------------------------------------------------- /examples/spin-variables/requirements.txt: -------------------------------------------------------------------------------- 1 | spin-sdk == 3.3.1 2 | componentize-py == 0.16.0 -------------------------------------------------------------------------------- /examples/spin-variables/spin.toml: -------------------------------------------------------------------------------- 1 | spin_manifest_version = 2 2 | 3 | [application] 4 | name = "test" 5 | version = "0.1.0" 6 | authors = ["Fermyon Engineering "] 7 | description = "" 8 | 9 | [[trigger.http]] 10 | route = "/..." 11 | component = "test" 12 | 13 | [component.test] 14 | source = "app.wasm" 15 | allowed_outbound_hosts = [] 16 | [component.test.variables] 17 | message = "hello, world!" 18 | [component.test.build] 19 | command = "componentize-py -w spin-http componentize app -o app.wasm" 20 | -------------------------------------------------------------------------------- /examples/streaming/README.md: -------------------------------------------------------------------------------- 1 | # Example: Streaming 2 | 3 | This is an example showcasing the use of HTTP request and response body streaming within a guest component. 4 | 5 | ## Preparing the Environment 6 | 7 | Run the following commands to setup a virtual environment with Python. 8 | 9 | ```bash 10 | python3 -m venv venv 11 | source venv/bin/activate 12 | ``` 13 | 14 | Install the required packages specified in the `requirements.txt` using the command: 15 | 16 | ```bash 17 | pip3 install -r requirements.txt 18 | ``` 19 | 20 | ## Building and Running the Examples 21 | 22 | ```bash 23 | spin build --up 24 | ``` 25 | 26 | ## Testing the App 27 | 28 | ```bash 29 | $ curl -d "hello" localhost:3000/echo 30 | hello% 31 | ``` -------------------------------------------------------------------------------- /examples/streaming/app.py: -------------------------------------------------------------------------------- 1 | """Demo of a serverless app using `wasi-http` to handle inbound HTTP requests. 2 | 3 | This demonstrates how to use WASI's asynchronous capabilities to manage multiple 4 | concurrent requests and streaming bodies. It uses a custom `asyncio` event loop 5 | to thread I/O through coroutines. 6 | """ 7 | 8 | import asyncio 9 | import hashlib 10 | 11 | from spin_sdk.wit import exports 12 | from spin_sdk.wit.types import Ok 13 | from spin_sdk.wit.imports import types 14 | from spin_sdk.wit.imports.types import ( 15 | Method_Get, Method_Post, Scheme, Scheme_Http, Scheme_Https, Scheme_Other, IncomingRequest, ResponseOutparam, 16 | OutgoingResponse, Fields, OutgoingBody, OutgoingRequest 17 | ) 18 | from spin_sdk.http import poll_loop 19 | from spin_sdk.http.poll_loop import Stream, Sink, PollLoop 20 | from typing import Tuple 21 | from urllib import parse 22 | 23 | class IncomingHandler(exports.IncomingHandler): 24 | """Implements the `export`ed portion of the `wasi-http` `proxy` world.""" 25 | 26 | def handle(self, request: IncomingRequest, response_out: ResponseOutparam): 27 | """Handle the specified `request`, sending the response to `response_out`. 28 | 29 | """ 30 | # Dispatch the request using `asyncio`, backed by a custom event loop 31 | # based on WASI's `poll_oneoff` function. 32 | loop = PollLoop() 33 | asyncio.set_event_loop(loop) 34 | loop.run_until_complete(handle_async(request, response_out)) 35 | 36 | async def handle_async(request: IncomingRequest, response_out: ResponseOutparam): 37 | """Handle the specified `request`, sending the response to `response_out`.""" 38 | 39 | method = request.method() 40 | path = request.path_with_query() 41 | headers = request.headers().entries() 42 | 43 | if isinstance(method, Method_Get) and path == "/hash-all": 44 | # Collect one or more "url" headers, download their contents 45 | # concurrently, compute their SHA-256 hashes incrementally (i.e. without 46 | # buffering the response bodies), and stream the results back to the 47 | # client as they become available. 48 | 49 | urls = map(lambda pair: str(pair[1], "utf-8"), filter(lambda pair: pair[0] == "url", headers)) 50 | 51 | response = OutgoingResponse(Fields.from_list([("content-type", b"text/plain")])) 52 | 53 | response_body = response.body() 54 | 55 | ResponseOutparam.set(response_out, Ok(response)) 56 | 57 | sink = Sink(response_body) 58 | for result in asyncio.as_completed(map(sha256, urls)): 59 | url, sha = await result 60 | await sink.send(bytes(f"{url}: {sha}\n", "utf-8")) 61 | 62 | sink.close() 63 | 64 | elif isinstance(method, Method_Post) and path == "/echo": 65 | # Echo the request body back to the client without buffering. 66 | 67 | response = OutgoingResponse(Fields.from_list(list(filter(lambda pair: pair[0] == "content-type", headers)))) 68 | 69 | response_body = response.body() 70 | 71 | ResponseOutparam.set(response_out, Ok(response)) 72 | 73 | stream = Stream(request.consume()) 74 | sink = Sink(response_body) 75 | while True: 76 | chunk = await stream.next() 77 | if chunk is None: 78 | break 79 | else: 80 | await sink.send(chunk) 81 | 82 | sink.close() 83 | else: 84 | response = OutgoingResponse(Fields.from_list([])) 85 | response.set_status_code(400) 86 | body = response.body() 87 | ResponseOutparam.set(response_out, Ok(response)) 88 | OutgoingBody.finish(body, None) 89 | 90 | async def sha256(url: str) -> Tuple[str, str]: 91 | """Download the contents of the specified URL, computing the SHA-256 92 | incrementally as the response body arrives. 93 | 94 | This returns a tuple of the original URL and either the hex-encoded hash or 95 | an error message. 96 | """ 97 | 98 | url_parsed = parse.urlparse(url) 99 | 100 | match url_parsed.scheme: 101 | case "http": 102 | scheme: Scheme = Scheme_Http() 103 | case "https": 104 | scheme = Scheme_Https() 105 | case _: 106 | scheme = Scheme_Other(url_parsed.scheme) 107 | 108 | request = OutgoingRequest(Fields.from_list([])) 109 | request.set_scheme(scheme) 110 | request.set_authority(url_parsed.netloc) 111 | request.set_path_with_query(url_parsed.path) 112 | 113 | response = await poll_loop.send(request) 114 | status = response.status() 115 | if status < 200 or status > 299: 116 | return url, f"unexpected status: {status}" 117 | 118 | stream = Stream(response.consume()) 119 | hasher = hashlib.sha256() 120 | while True: 121 | chunk = await stream.next() 122 | if chunk is None: 123 | return url, hasher.hexdigest() 124 | else: 125 | hasher.update(chunk) 126 | -------------------------------------------------------------------------------- /examples/streaming/requirements.txt: -------------------------------------------------------------------------------- 1 | spin-sdk == 3.3.1 2 | componentize-py == 0.16.0 -------------------------------------------------------------------------------- /examples/streaming/spin.toml: -------------------------------------------------------------------------------- 1 | spin_manifest_version = 2 2 | 3 | [application] 4 | name = "test" 5 | version = "0.1.0" 6 | authors = ["Fermyon Engineering "] 7 | description = "" 8 | 9 | [[trigger.http]] 10 | route = "/..." 11 | component = "test" 12 | 13 | [component.test] 14 | source = "app.wasm" 15 | allowed_outbound_hosts = ["http://*:*", "https://*:*"] 16 | [component.test.build] 17 | command = "componentize-py -w spin-http componentize app -o app.wasm" 18 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools>=61.0"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name = "spin-sdk" 7 | version = "3.3.1" 8 | description = "Experimental SDK for Spin and Componentize-Py" 9 | readme = "README.md" 10 | license = { file = "LICENSE" } 11 | authors = [ { name = "Fermyon Engineering", email = "engineering@fermyon.com" } ] 12 | maintainers = [ { name = "Fermyon Engineering", email = "engineering@fermyon.com" } ] 13 | keywords = [ "webassembly", "wasm", "component", "spin" ] 14 | 15 | [project.urls] 16 | repository = "https://github.com/fermyon/spin-python-sdk" 17 | documentation = "https://fermyon.github.io/spin-python-sdk" 18 | 19 | [tool.setuptools.packages.find] 20 | where = ["src"] 21 | -------------------------------------------------------------------------------- /scripts/generate_docs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import shutil 5 | 6 | # Get the current script's directory 7 | script_dir = os.path.dirname(os.path.abspath(__file__)) 8 | 9 | wit_module = os.path.join('src', 'spin_sdk', 'wit', '__init__.py') 10 | expected_doc_comment = '""" Module with the bindings generated from the wit by componentize-py """\n\n' 11 | 12 | with open(wit_module, 'r') as init_file: 13 | content = init_file.read() 14 | 15 | if expected_doc_comment not in content: 16 | with open(wit_module, 'w') as init_file: 17 | init_file.write(expected_doc_comment + content) 18 | 19 | # Change to the root directory of the project 20 | os.chdir(os.path.join(script_dir, '..')) 21 | 22 | # Remove the 'docs/v3' directory 23 | shutil.rmtree(os.path.join('docs', 'v3'), ignore_errors=True) 24 | 25 | # Change directory to 'src' and generate HTML documentation using pdoc 26 | os.chdir('src') 27 | os.system('pdoc --html spin_sdk') 28 | 29 | # Move the generated documentation to the 'docs' directory 30 | shutil.move('html/spin_sdk', os.path.join('..', 'docs', 'v3')) 31 | 32 | # Remove the 'src/html' directory 33 | os.rmdir('html') 34 | 35 | os.chdir('..') 36 | -------------------------------------------------------------------------------- /src/spin_sdk/__init__.py: -------------------------------------------------------------------------------- 1 | """Spin Python SDK 2 | 3 | This is an SDK for creating [Spin](https://github.com/fermyon/spin) apps using Python. 4 | 5 | Note that this SDK supercedes an earlier, experimental version, the documentation for which may be found [here](https://fermyon.github.io/spin-python-sdk/v1/index.html). 6 | """ 7 | -------------------------------------------------------------------------------- /src/spin_sdk/componentize-py.toml: -------------------------------------------------------------------------------- 1 | wit_directory = "wit" 2 | bindings = "wit" 3 | 4 | [import_interface_names] 5 | "fermyon:spin/postgres@2.0.0" = "postgres" 6 | -------------------------------------------------------------------------------- /src/spin_sdk/key_value.py: -------------------------------------------------------------------------------- 1 | """Module for accessing Spin key-value stores""" 2 | 3 | from spin_sdk.wit.imports.key_value import Store 4 | 5 | def open(name: str) -> Store: 6 | """ 7 | Open the store with the specified name. 8 | 9 | If `name` is "default", the default store is opened. Otherwise, `name` must 10 | refer to a store defined and configured in a runtime configuration file 11 | supplied with the application. 12 | 13 | A `spin_sdk.wit.types.Err(spin_sdk.wit.imports.key_value.Error_NoSuchStore)` will be raised if the `name` is not recognized. 14 | 15 | A `spin_sdk.wit.types.Err(spin_sdk.wit.imports.key_value.Error_AccessDenied)` will be raised if the requesting component does not have 16 | access to the specified store. 17 | 18 | A `spin_sdk.wit.types.Err(spin_sdk.wit.imports.key_value.Error_StoreTableFull)` will be raised if too many stores have been opened simultaneously. 19 | Closing one or more previously opened stores might address this using the `__exit__` method. 20 | 21 | A `spin_sdk.wit.types.Err(spin_sdk.wit.imports.key_value.Error_Other(str))` will be raised if some implementation specific error has occured (e.g I/O) 22 | """ 23 | return Store.open(name) 24 | 25 | def open_default() -> Store: 26 | """ 27 | Open the default store. 28 | 29 | A `spin_sdk.wit.types.Err(spin_sdk.wit.imports.key_value.Error_AccessDenied)` 30 | will be raised if the requesting component does not have access to the 31 | default store. 32 | 33 | A `spin_sdk.wit.types.Err(spin_sdk.wit.imports.key_value.Error_StoreTableFull)` will be raised if too many stores have been opened simultaneously. 34 | Closing one or more previously opened stores might address this using the `__exit__` method. 35 | 36 | A `spin_sdk.wit.types.Err(spin_sdk.wit.imports.key_value.Error_Other(str))` will be raised if some implementation specific error has occured (e.g I/O) 37 | """ 38 | return Store.open("default") 39 | -------------------------------------------------------------------------------- /src/spin_sdk/llm.py: -------------------------------------------------------------------------------- 1 | """Module for working with the Spin large language model API""" 2 | 3 | from dataclasses import dataclass 4 | from typing import Optional, Sequence 5 | from spin_sdk.wit.imports import llm as spin_llm 6 | 7 | @dataclass 8 | class InferencingParams: 9 | max_tokens: int = 100 10 | repeat_penalty: float = 1.1 11 | repeat_penalty_last_n_token_count: int = 64 12 | temperature: float = 0.8 13 | top_k: int = 40 14 | top_p: float = 0.9 15 | 16 | 17 | def generate_embeddings(model: str, text: Sequence[str]) -> spin_llm.EmbeddingsResult: 18 | """ 19 | A `spin_sdk.wit.types.Err(spin_sdk.wit.imports.llm.Error_ModelNotSupported)` will be raised if the component does not have access to the specified model. 20 | 21 | A `spin_sdk.wit.types.Err(spin_sdk.wit.imports.llm.Error_RuntimeError(str))` will be raised if there are any runtime errors. 22 | 23 | A `spin_sdk.wit.types.Err(spin_sdk.wit.imports.llm.Error_InvalidInput(str))` will be raised if an invalid input is provided. 24 | """ 25 | return spin_llm.generate_embeddings(model, text) 26 | 27 | def infer_with_options(model: str, prompt: str, options: Optional[InferencingParams]) -> spin_llm.InferencingResult: 28 | """ 29 | A `spin_sdk.wit.types.Err(spin_sdk.wit.imports.llm.Error_ModelNotSupported)` will be raised if the component does not have access to the specified model. 30 | 31 | A `spin_sdk.wit.types.Err(spin_sdk.wit.imports.llm.Error_RuntimeError(str))` will be raised if there are any runtime errors. 32 | 33 | A `spin_sdk.wit.types.Err(spin_sdk.wit.imports.llm.Error_InvalidInput(str))` will be raised if an invalid input is provided. 34 | """ 35 | options = options or InferencingParams 36 | return spin_llm.infer(model, prompt, options) 37 | 38 | def infer(model: str, prompt: str) -> spin_llm.InferencingResult: 39 | """ 40 | A `spin_sdk.wit.types.Err(spin_sdk.wit.imports.llm.Error_ModelNotSupported)` will be raised if the component does not have access to the specified model. 41 | 42 | A `spin_sdk.wit.types.Err(spin_sdk.wit.imports.llm.Error_RuntimeError(str))` will be raised if there are any runtime errors. 43 | 44 | A `spin_sdk.wit.types.Err(spin_sdk.wit.imports.llm.Error_InvalidInput(str))` will be raised if an invalid input is provided. 45 | """ 46 | options = InferencingParams 47 | return spin_llm.infer(model, prompt, options) 48 | 49 | -------------------------------------------------------------------------------- /src/spin_sdk/mqtt.py: -------------------------------------------------------------------------------- 1 | """Module for utilizing Spin Outbound MQTT""" 2 | 3 | from enum import Enum 4 | from spin_sdk.wit.imports.mqtt import Connection, Qos as Qos 5 | 6 | def open(address: str, username: str, password: str, keep_alive_interval_in_secs: int) -> Connection: 7 | """ 8 | Open a connection to the Mqtt instance at `address`. 9 | 10 | A `spin_sdk.wit.types.Err(spin_sdk.wit.imports.mqtt.Error_InvalidAddress)` will be raised if the connection string is invalid. 11 | 12 | A `spin_sdk.wit.types.Err(spin_sdk.wit.imports.mqtt.Error_TooManyConnections)` will be raised if there are too many open connections. Closing one or more previously opened connection using the `__exit__` method might help. 13 | 14 | A `spin_sdk.wit.types.Err(spin_sdk.wit.imports.mqtt.Error_ConnectionFailed)` will be raised if the connection failed. 15 | 16 | A `spin_sdk.wit.types.Err(spin_sdk.wit.imports.mqtt.Error_Other(str))` when some other error occurs. 17 | """ 18 | return Connection.open(address, username, password, keep_alive_interval_in_secs) 19 | -------------------------------------------------------------------------------- /src/spin_sdk/mysql.py: -------------------------------------------------------------------------------- 1 | """Module for interacting with a MySQL database""" 2 | 3 | from spin_sdk.wit.imports.mysql import Connection 4 | 5 | def open(connection_string: str) -> Connection: 6 | """ 7 | Open a connection with a MySQL database. 8 | 9 | The connection_string is the MySQL URL connection string. 10 | 11 | A `spin_sdk.wit.types.Err(spin_sdk.wit.imports.rdbms_types.Error_ConnectionFailed(str))` when a connection fails. 12 | 13 | A `spin_sdk.wit.types.Err(spin_sdk.wit.imports.rdbms_types.Error_Other(str))` when some other error occurs. 14 | """ 15 | return Connection.open(connection_string) 16 | -------------------------------------------------------------------------------- /src/spin_sdk/postgres.py: -------------------------------------------------------------------------------- 1 | """Module for interacting with a Postgres database""" 2 | 3 | from spin_sdk.wit.imports.postgres import Connection 4 | 5 | def open(connection_string: str) -> Connection: 6 | """ 7 | Open a connection with a Postgres database. 8 | 9 | The connection_string is the Postgres URL connection string. 10 | 11 | A `spin_sdk.wit.types.Err(spin_sdk.wit.imports.rdbms_types.Error_ConnectionFailed(str))` when a connection fails. 12 | 13 | A `spin_sdk.wit.types.Err(spin_sdk.wit.imports.rdbms_types.Error_Other(str))` when some other error occurs. 14 | """ 15 | return Connection.open(connection_string) 16 | -------------------------------------------------------------------------------- /src/spin_sdk/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spinframework/spin-python-sdk/e93a76aabf01ba0be190f72308a468bf5342492d/src/spin_sdk/py.typed -------------------------------------------------------------------------------- /src/spin_sdk/redis.py: -------------------------------------------------------------------------------- 1 | """Module for interacting with a Redis database""" 2 | 3 | from spin_sdk.wit.imports.redis import Connection 4 | 5 | def open(connection_string: str) -> Connection: 6 | """ 7 | Open a connection with a Redis database. 8 | 9 | The connection_string is the Redis URL to connect to. 10 | 11 | A `spin_sdk.wit.types.Err(spin_sdk.wit.imports.redis.Error_InvalidAddress)` will be raised if the connection string is invalid. 12 | 13 | A `spin_sdk.wit.types.Err(spin_sdk.wit.imports.redis.Error_TooManyConnections)` will be raised if there are too many open connections. Closing one or more previously opened connection using the `__exit__` method might help. 14 | 15 | A `spin_sdk.wit.types.Err(spin_sdk.wit.imports.redis.Error_Other(str))` when some other error occurs. 16 | """ 17 | return Connection.open(connection_string) 18 | -------------------------------------------------------------------------------- /src/spin_sdk/sqlite.py: -------------------------------------------------------------------------------- 1 | """Module for interacting with an SQLite database""" 2 | 3 | from typing import List 4 | from spin_sdk.wit.imports.sqlite import Connection, Value_Integer, Value_Real, Value_Text, Value_Blob 5 | 6 | def open(name: str) -> Connection: 7 | """Open a connection to a named database instance. 8 | 9 | If `database` is "default", the default instance is opened. 10 | 11 | A `spin_sdk.wit.types.Err(spin_sdk.wit.imports.sqlite.Error_AccessDenied)` will be raised when the component does not have access to the specified database. 12 | 13 | A `spin_sdk.wit.types.Err(spin_sdk.wit.imports.sqlite.Error_NoSuchDatabase)` will be raised when the host does not recognize the database name requested. 14 | 15 | A `spin_sdk.wit.types.Err(spin_sdk.wit.imports.sqlite.Error_InvalidConnection)` will be raised when the provided connection string is not valid. 16 | 17 | A `spin_sdk.wit.types.Err(spin_sdk.wit.imports.sqlite.Error_Io(str))` will be raised when implementation-specific error occured (e.g. I/O) 18 | """ 19 | return Connection.open(name) 20 | 21 | def open_default() -> Connection: 22 | """Open the default store. 23 | 24 | A `spin_sdk.wit.types.Err(spin_sdk.wit.imports.sqlite.Error_AccessDenied)` will be raised when the component does not have access to the default database. 25 | 26 | A `spin_sdk.wit.types.Err(spin_sdk.wit.imports.sqlite.Error_Io(str))` will be raised when implementation-specific error occured (e.g. I/O) 27 | """ 28 | return Connection.open("default") 29 | -------------------------------------------------------------------------------- /src/spin_sdk/variables.py: -------------------------------------------------------------------------------- 1 | """Module for interacting with Spin Variables""" 2 | 3 | from spin_sdk.wit.imports import variables 4 | 5 | def get(key: str): 6 | """ 7 | Gets the value of the given key 8 | """ 9 | return variables.get(key) 10 | -------------------------------------------------------------------------------- /src/spin_sdk/wit/__init__.py: -------------------------------------------------------------------------------- 1 | """ Module with the bindings generated from the wit by componentize-py """ 2 | 3 | from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self 4 | from types import TracebackType 5 | from enum import Flag, Enum, auto 6 | from dataclasses import dataclass 7 | from abc import abstractmethod 8 | import weakref 9 | 10 | from .types import Result, Ok, Err, Some 11 | 12 | 13 | 14 | class SpinAll(Protocol): 15 | pass 16 | -------------------------------------------------------------------------------- /src/spin_sdk/wit/deps/cli/command.wit: -------------------------------------------------------------------------------- 1 | package wasi:cli@0.2.0; 2 | 3 | world command { 4 | include imports; 5 | 6 | export run; 7 | } 8 | -------------------------------------------------------------------------------- /src/spin_sdk/wit/deps/cli/environment.wit: -------------------------------------------------------------------------------- 1 | interface environment { 2 | /// Get the POSIX-style environment variables. 3 | /// 4 | /// Each environment variable is provided as a pair of string variable names 5 | /// and string value. 6 | /// 7 | /// Morally, these are a value import, but until value imports are available 8 | /// in the component model, this import function should return the same 9 | /// values each time it is called. 10 | get-environment: func() -> list>; 11 | 12 | /// Get the POSIX-style arguments to the program. 13 | get-arguments: func() -> list; 14 | 15 | /// Return a path that programs should use as their initial current working 16 | /// directory, interpreting `.` as shorthand for this. 17 | initial-cwd: func() -> option; 18 | } 19 | -------------------------------------------------------------------------------- /src/spin_sdk/wit/deps/cli/exit.wit: -------------------------------------------------------------------------------- 1 | interface exit { 2 | /// Exit the current instance and any linked instances. 3 | exit: func(status: result); 4 | } 5 | -------------------------------------------------------------------------------- /src/spin_sdk/wit/deps/cli/imports.wit: -------------------------------------------------------------------------------- 1 | package wasi:cli@0.2.0; 2 | 3 | world imports { 4 | include wasi:clocks/imports@0.2.0; 5 | include wasi:filesystem/imports@0.2.0; 6 | include wasi:sockets/imports@0.2.0; 7 | include wasi:random/imports@0.2.0; 8 | include wasi:io/imports@0.2.0; 9 | 10 | import environment; 11 | import exit; 12 | import stdin; 13 | import stdout; 14 | import stderr; 15 | import terminal-input; 16 | import terminal-output; 17 | import terminal-stdin; 18 | import terminal-stdout; 19 | import terminal-stderr; 20 | } 21 | -------------------------------------------------------------------------------- /src/spin_sdk/wit/deps/cli/run.wit: -------------------------------------------------------------------------------- 1 | interface run { 2 | /// Run the program. 3 | run: func() -> result; 4 | } 5 | -------------------------------------------------------------------------------- /src/spin_sdk/wit/deps/cli/stdio.wit: -------------------------------------------------------------------------------- 1 | interface stdin { 2 | use wasi:io/streams@0.2.0.{input-stream}; 3 | 4 | get-stdin: func() -> input-stream; 5 | } 6 | 7 | interface stdout { 8 | use wasi:io/streams@0.2.0.{output-stream}; 9 | 10 | get-stdout: func() -> output-stream; 11 | } 12 | 13 | interface stderr { 14 | use wasi:io/streams@0.2.0.{output-stream}; 15 | 16 | get-stderr: func() -> output-stream; 17 | } 18 | -------------------------------------------------------------------------------- /src/spin_sdk/wit/deps/cli/terminal.wit: -------------------------------------------------------------------------------- 1 | /// Terminal input. 2 | /// 3 | /// In the future, this may include functions for disabling echoing, 4 | /// disabling input buffering so that keyboard events are sent through 5 | /// immediately, querying supported features, and so on. 6 | interface terminal-input { 7 | /// The input side of a terminal. 8 | resource terminal-input; 9 | } 10 | 11 | /// Terminal output. 12 | /// 13 | /// In the future, this may include functions for querying the terminal 14 | /// size, being notified of terminal size changes, querying supported 15 | /// features, and so on. 16 | interface terminal-output { 17 | /// The output side of a terminal. 18 | resource terminal-output; 19 | } 20 | 21 | /// An interface providing an optional `terminal-input` for stdin as a 22 | /// link-time authority. 23 | interface terminal-stdin { 24 | use terminal-input.{terminal-input}; 25 | 26 | /// If stdin is connected to a terminal, return a `terminal-input` handle 27 | /// allowing further interaction with it. 28 | get-terminal-stdin: func() -> option; 29 | } 30 | 31 | /// An interface providing an optional `terminal-output` for stdout as a 32 | /// link-time authority. 33 | interface terminal-stdout { 34 | use terminal-output.{terminal-output}; 35 | 36 | /// If stdout is connected to a terminal, return a `terminal-output` handle 37 | /// allowing further interaction with it. 38 | get-terminal-stdout: func() -> option; 39 | } 40 | 41 | /// An interface providing an optional `terminal-output` for stderr as a 42 | /// link-time authority. 43 | interface terminal-stderr { 44 | use terminal-output.{terminal-output}; 45 | 46 | /// If stderr is connected to a terminal, return a `terminal-output` handle 47 | /// allowing further interaction with it. 48 | get-terminal-stderr: func() -> option; 49 | } 50 | -------------------------------------------------------------------------------- /src/spin_sdk/wit/deps/clocks/monotonic-clock.wit: -------------------------------------------------------------------------------- 1 | package wasi:clocks@0.2.0; 2 | /// WASI Monotonic Clock is a clock API intended to let users measure elapsed 3 | /// time. 4 | /// 5 | /// It is intended to be portable at least between Unix-family platforms and 6 | /// Windows. 7 | /// 8 | /// A monotonic clock is a clock which has an unspecified initial value, and 9 | /// successive reads of the clock will produce non-decreasing values. 10 | /// 11 | /// It is intended for measuring elapsed time. 12 | interface monotonic-clock { 13 | use wasi:io/poll@0.2.0.{pollable}; 14 | 15 | /// An instant in time, in nanoseconds. An instant is relative to an 16 | /// unspecified initial value, and can only be compared to instances from 17 | /// the same monotonic-clock. 18 | type instant = u64; 19 | 20 | /// A duration of time, in nanoseconds. 21 | type duration = u64; 22 | 23 | /// Read the current value of the clock. 24 | /// 25 | /// The clock is monotonic, therefore calling this function repeatedly will 26 | /// produce a sequence of non-decreasing values. 27 | now: func() -> instant; 28 | 29 | /// Query the resolution of the clock. Returns the duration of time 30 | /// corresponding to a clock tick. 31 | resolution: func() -> duration; 32 | 33 | /// Create a `pollable` which will resolve once the specified instant 34 | /// occured. 35 | subscribe-instant: func( 36 | when: instant, 37 | ) -> pollable; 38 | 39 | /// Create a `pollable` which will resolve once the given duration has 40 | /// elapsed, starting at the time at which this function was called. 41 | /// occured. 42 | subscribe-duration: func( 43 | when: duration, 44 | ) -> pollable; 45 | } 46 | -------------------------------------------------------------------------------- /src/spin_sdk/wit/deps/clocks/wall-clock.wit: -------------------------------------------------------------------------------- 1 | package wasi:clocks@0.2.0; 2 | /// WASI Wall Clock is a clock API intended to let users query the current 3 | /// time. The name "wall" makes an analogy to a "clock on the wall", which 4 | /// is not necessarily monotonic as it may be reset. 5 | /// 6 | /// It is intended to be portable at least between Unix-family platforms and 7 | /// Windows. 8 | /// 9 | /// A wall clock is a clock which measures the date and time according to 10 | /// some external reference. 11 | /// 12 | /// External references may be reset, so this clock is not necessarily 13 | /// monotonic, making it unsuitable for measuring elapsed time. 14 | /// 15 | /// It is intended for reporting the current date and time for humans. 16 | interface wall-clock { 17 | /// A time and date in seconds plus nanoseconds. 18 | record datetime { 19 | seconds: u64, 20 | nanoseconds: u32, 21 | } 22 | 23 | /// Read the current value of the clock. 24 | /// 25 | /// This clock is not monotonic, therefore calling this function repeatedly 26 | /// will not necessarily produce a sequence of non-decreasing values. 27 | /// 28 | /// The returned timestamps represent the number of seconds since 29 | /// 1970-01-01T00:00:00Z, also known as [POSIX's Seconds Since the Epoch], 30 | /// also known as [Unix Time]. 31 | /// 32 | /// The nanoseconds field of the output is always less than 1000000000. 33 | /// 34 | /// [POSIX's Seconds Since the Epoch]: https://pubs.opengroup.org/onlinepubs/9699919799/xrat/V4_xbd_chap04.html#tag_21_04_16 35 | /// [Unix Time]: https://en.wikipedia.org/wiki/Unix_time 36 | now: func() -> datetime; 37 | 38 | /// Query the resolution of the clock. 39 | /// 40 | /// The nanoseconds field of the output is always less than 1000000000. 41 | resolution: func() -> datetime; 42 | } 43 | -------------------------------------------------------------------------------- /src/spin_sdk/wit/deps/clocks/world.wit: -------------------------------------------------------------------------------- 1 | package wasi:clocks@0.2.0; 2 | 3 | world imports { 4 | import monotonic-clock; 5 | import wall-clock; 6 | } 7 | -------------------------------------------------------------------------------- /src/spin_sdk/wit/deps/filesystem/preopens.wit: -------------------------------------------------------------------------------- 1 | package wasi:filesystem@0.2.0; 2 | 3 | interface preopens { 4 | use types.{descriptor}; 5 | 6 | /// Return the set of preopened directories, and their path. 7 | get-directories: func() -> list>; 8 | } 9 | -------------------------------------------------------------------------------- /src/spin_sdk/wit/deps/filesystem/world.wit: -------------------------------------------------------------------------------- 1 | package wasi:filesystem@0.2.0; 2 | 3 | world imports { 4 | import types; 5 | import preopens; 6 | } 7 | -------------------------------------------------------------------------------- /src/spin_sdk/wit/deps/http/handler.wit: -------------------------------------------------------------------------------- 1 | /// This interface defines a handler of incoming HTTP Requests. It should 2 | /// be exported by components which can respond to HTTP Requests. 3 | interface incoming-handler { 4 | use types.{incoming-request, response-outparam}; 5 | 6 | /// This function is invoked with an incoming HTTP Request, and a resource 7 | /// `response-outparam` which provides the capability to reply with an HTTP 8 | /// Response. The response is sent by calling the `response-outparam.set` 9 | /// method, which allows execution to continue after the response has been 10 | /// sent. This enables both streaming to the response body, and performing other 11 | /// work. 12 | /// 13 | /// The implementor of this function must write a response to the 14 | /// `response-outparam` before returning, or else the caller will respond 15 | /// with an error on its behalf. 16 | handle: func( 17 | request: incoming-request, 18 | response-out: response-outparam 19 | ); 20 | } 21 | 22 | /// This interface defines a handler of outgoing HTTP Requests. It should be 23 | /// imported by components which wish to make HTTP Requests. 24 | interface outgoing-handler { 25 | use types.{ 26 | outgoing-request, request-options, future-incoming-response, error-code 27 | }; 28 | 29 | /// This function is invoked with an outgoing HTTP Request, and it returns 30 | /// a resource `future-incoming-response` which represents an HTTP Response 31 | /// which may arrive in the future. 32 | /// 33 | /// The `options` argument accepts optional parameters for the HTTP 34 | /// protocol's transport layer. 35 | /// 36 | /// This function may return an error if the `outgoing-request` is invalid 37 | /// or not allowed to be made. Otherwise, protocol errors are reported 38 | /// through the `future-incoming-response`. 39 | handle: func( 40 | request: outgoing-request, 41 | options: option 42 | ) -> result; 43 | } 44 | -------------------------------------------------------------------------------- /src/spin_sdk/wit/deps/http/proxy.wit: -------------------------------------------------------------------------------- 1 | package wasi:http@0.2.0; 2 | 3 | /// The `wasi:http/proxy` world captures a widely-implementable intersection of 4 | /// hosts that includes HTTP forward and reverse proxies. Components targeting 5 | /// this world may concurrently stream in and out any number of incoming and 6 | /// outgoing HTTP requests. 7 | world proxy { 8 | /// HTTP proxies have access to time and randomness. 9 | include wasi:clocks/imports@0.2.0; 10 | import wasi:random/random@0.2.0; 11 | 12 | /// Proxies have standard output and error streams which are expected to 13 | /// terminate in a developer-facing console provided by the host. 14 | import wasi:cli/stdout@0.2.0; 15 | import wasi:cli/stderr@0.2.0; 16 | 17 | /// TODO: this is a temporary workaround until component tooling is able to 18 | /// gracefully handle the absence of stdin. Hosts must return an eof stream 19 | /// for this import, which is what wasi-libc + tooling will do automatically 20 | /// when this import is properly removed. 21 | import wasi:cli/stdin@0.2.0; 22 | 23 | /// This is the default handler to use when user code simply wants to make an 24 | /// HTTP request (e.g., via `fetch()`). 25 | import outgoing-handler; 26 | 27 | /// The host delivers incoming HTTP requests to a component by calling the 28 | /// `handle` function of this exported interface. A host may arbitrarily reuse 29 | /// or not reuse component instance when delivering incoming HTTP requests and 30 | /// thus a component must be able to handle 0..N calls to `handle`. 31 | export incoming-handler; 32 | } 33 | -------------------------------------------------------------------------------- /src/spin_sdk/wit/deps/io/error.wit: -------------------------------------------------------------------------------- 1 | package wasi:io@0.2.0; 2 | 3 | 4 | interface error { 5 | /// A resource which represents some error information. 6 | /// 7 | /// The only method provided by this resource is `to-debug-string`, 8 | /// which provides some human-readable information about the error. 9 | /// 10 | /// In the `wasi:io` package, this resource is returned through the 11 | /// `wasi:io/streams/stream-error` type. 12 | /// 13 | /// To provide more specific error information, other interfaces may 14 | /// provide functions to further "downcast" this error into more specific 15 | /// error information. For example, `error`s returned in streams derived 16 | /// from filesystem types to be described using the filesystem's own 17 | /// error-code type, using the function 18 | /// `wasi:filesystem/types/filesystem-error-code`, which takes a parameter 19 | /// `borrow` and returns 20 | /// `option`. 21 | /// 22 | /// The set of functions which can "downcast" an `error` into a more 23 | /// concrete type is open. 24 | resource error { 25 | /// Returns a string that is suitable to assist humans in debugging 26 | /// this error. 27 | /// 28 | /// WARNING: The returned string should not be consumed mechanically! 29 | /// It may change across platforms, hosts, or other implementation 30 | /// details. Parsing this string is a major platform-compatibility 31 | /// hazard. 32 | to-debug-string: func() -> string; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/spin_sdk/wit/deps/io/poll.wit: -------------------------------------------------------------------------------- 1 | package wasi:io@0.2.0; 2 | 3 | /// A poll API intended to let users wait for I/O events on multiple handles 4 | /// at once. 5 | interface poll { 6 | /// `pollable` represents a single I/O event which may be ready, or not. 7 | resource pollable { 8 | 9 | /// Return the readiness of a pollable. This function never blocks. 10 | /// 11 | /// Returns `true` when the pollable is ready, and `false` otherwise. 12 | ready: func() -> bool; 13 | 14 | /// `block` returns immediately if the pollable is ready, and otherwise 15 | /// blocks until ready. 16 | /// 17 | /// This function is equivalent to calling `poll.poll` on a list 18 | /// containing only this pollable. 19 | block: func(); 20 | } 21 | 22 | /// Poll for completion on a set of pollables. 23 | /// 24 | /// This function takes a list of pollables, which identify I/O sources of 25 | /// interest, and waits until one or more of the events is ready for I/O. 26 | /// 27 | /// The result `list` contains one or more indices of handles in the 28 | /// argument list that is ready for I/O. 29 | /// 30 | /// If the list contains more elements than can be indexed with a `u32` 31 | /// value, this function traps. 32 | /// 33 | /// A timeout can be implemented by adding a pollable from the 34 | /// wasi-clocks API to the list. 35 | /// 36 | /// This function does not return a `result`; polling in itself does not 37 | /// do any I/O so it doesn't fail. If any of the I/O sources identified by 38 | /// the pollables has an error, it is indicated by marking the source as 39 | /// being reaedy for I/O. 40 | poll: func(in: list>) -> list; 41 | } 42 | -------------------------------------------------------------------------------- /src/spin_sdk/wit/deps/io/world.wit: -------------------------------------------------------------------------------- 1 | package wasi:io@0.2.0; 2 | 3 | world imports { 4 | import streams; 5 | import poll; 6 | } 7 | -------------------------------------------------------------------------------- /src/spin_sdk/wit/deps/keyvalue-2024-10-17/atomic.wit: -------------------------------------------------------------------------------- 1 | /// A keyvalue interface that provides atomic operations. 2 | /// 3 | /// Atomic operations are single, indivisible operations. When a fault causes an atomic operation to 4 | /// fail, it will appear to the invoker of the atomic operation that the action either completed 5 | /// successfully or did nothing at all. 6 | /// 7 | /// Please note that this interface is bare functions that take a reference to a bucket. This is to 8 | /// get around the current lack of a way to "extend" a resource with additional methods inside of 9 | /// wit. Future version of the interface will instead extend these methods on the base `bucket` 10 | /// resource. 11 | interface atomics { 12 | use store.{bucket, error}; 13 | 14 | /// The error returned by a CAS operation 15 | variant cas-error { 16 | /// A store error occurred when performing the operation 17 | store-error(error), 18 | /// The CAS operation failed because the value was too old. This returns a new CAS handle 19 | /// for easy retries. Implementors MUST return a CAS handle that has been updated to the 20 | /// latest version or transaction. 21 | cas-failed(cas), 22 | } 23 | 24 | /// A handle to a CAS (compare-and-swap) operation. 25 | resource cas { 26 | /// Construct a new CAS operation. Implementors can map the underlying functionality 27 | /// (transactions, versions, etc) as desired. 28 | new: static func(bucket: borrow, key: string) -> result; 29 | /// Get the current value of the key (if it exists). This allows for avoiding reads if all 30 | /// that is needed to ensure the atomicity of the operation 31 | current: func() -> result>, error>; 32 | } 33 | 34 | /// Atomically increment the value associated with the key in the store by the given delta. It 35 | /// returns the new value. 36 | /// 37 | /// If the key does not exist in the store, it creates a new key-value pair with the value set 38 | /// to the given delta. 39 | /// 40 | /// If any other error occurs, it returns an `Err(error)`. 41 | increment: func(bucket: borrow, key: string, delta: s64) -> result; 42 | 43 | /// Perform the swap on a CAS operation. This consumes the CAS handle and returns an error if 44 | /// the CAS operation failed. 45 | swap: func(cas: cas, value: list) -> result<_, cas-error>; 46 | } 47 | -------------------------------------------------------------------------------- /src/spin_sdk/wit/deps/keyvalue-2024-10-17/batch.wit: -------------------------------------------------------------------------------- 1 | /// A keyvalue interface that provides batch operations. 2 | /// 3 | /// A batch operation is an operation that operates on multiple keys at once. 4 | /// 5 | /// Batch operations are useful for reducing network round-trip time. For example, if you want to 6 | /// get the values associated with 100 keys, you can either do 100 get operations or you can do 1 7 | /// batch get operation. The batch operation is faster because it only needs to make 1 network call 8 | /// instead of 100. 9 | /// 10 | /// A batch operation does not guarantee atomicity, meaning that if the batch operation fails, some 11 | /// of the keys may have been modified and some may not. 12 | /// 13 | /// This interface does has the same consistency guarantees as the `store` interface, meaning that 14 | /// you should be able to "read your writes." 15 | /// 16 | /// Please note that this interface is bare functions that take a reference to a bucket. This is to 17 | /// get around the current lack of a way to "extend" a resource with additional methods inside of 18 | /// wit. Future version of the interface will instead extend these methods on the base `bucket` 19 | /// resource. 20 | interface batch { 21 | use store.{bucket, error}; 22 | 23 | /// Get the key-value pairs associated with the keys in the store. It returns a list of 24 | /// key-value pairs. 25 | /// 26 | /// If any of the keys do not exist in the store, it returns a `none` value for that pair in the 27 | /// list. 28 | /// 29 | /// MAY show an out-of-date value if there are concurrent writes to the store. 30 | /// 31 | /// If any other error occurs, it returns an `Err(error)`. 32 | get-many: func(bucket: borrow, keys: list) -> result>>>, error>; 33 | 34 | /// Set the values associated with the keys in the store. If the key already exists in the 35 | /// store, it overwrites the value. 36 | /// 37 | /// Note that the key-value pairs are not guaranteed to be set in the order they are provided. 38 | /// 39 | /// If any of the keys do not exist in the store, it creates a new key-value pair. 40 | /// 41 | /// If any other error occurs, it returns an `Err(error)`. When an error occurs, it does not 42 | /// rollback the key-value pairs that were already set. Thus, this batch operation does not 43 | /// guarantee atomicity, implying that some key-value pairs could be set while others might 44 | /// fail. 45 | /// 46 | /// Other concurrent operations may also be able to see the partial results. 47 | set-many: func(bucket: borrow, key-values: list>>) -> result<_, error>; 48 | 49 | /// Delete the key-value pairs associated with the keys in the store. 50 | /// 51 | /// Note that the key-value pairs are not guaranteed to be deleted in the order they are 52 | /// provided. 53 | /// 54 | /// If any of the keys do not exist in the store, it skips the key. 55 | /// 56 | /// If any other error occurs, it returns an `Err(error)`. When an error occurs, it does not 57 | /// rollback the key-value pairs that were already deleted. Thus, this batch operation does not 58 | /// guarantee atomicity, implying that some key-value pairs could be deleted while others might 59 | /// fail. 60 | /// 61 | /// Other concurrent operations may also be able to see the partial results. 62 | delete-many: func(bucket: borrow, keys: list) -> result<_, error>; 63 | } 64 | -------------------------------------------------------------------------------- /src/spin_sdk/wit/deps/keyvalue-2024-10-17/store.wit: -------------------------------------------------------------------------------- 1 | /// A keyvalue interface that provides eventually consistent key-value operations. 2 | /// 3 | /// Each of these operations acts on a single key-value pair. 4 | /// 5 | /// The value in the key-value pair is defined as a `u8` byte array and the intention is that it is 6 | /// the common denominator for all data types defined by different key-value stores to handle data, 7 | /// ensuring compatibility between different key-value stores. Note: the clients will be expecting 8 | /// serialization/deserialization overhead to be handled by the key-value store. The value could be 9 | /// a serialized object from JSON, HTML or vendor-specific data types like AWS S3 objects. 10 | /// 11 | /// Data consistency in a key value store refers to the guarantee that once a write operation 12 | /// completes, all subsequent read operations will return the value that was written. 13 | /// 14 | /// Any implementation of this interface must have enough consistency to guarantee "reading your 15 | /// writes." In particular, this means that the client should never get a value that is older than 16 | /// the one it wrote, but it MAY get a newer value if one was written around the same time. These 17 | /// guarantees only apply to the same client (which will likely be provided by the host or an 18 | /// external capability of some kind). In this context a "client" is referring to the caller or 19 | /// guest that is consuming this interface. Once a write request is committed by a specific client, 20 | /// all subsequent read requests by the same client will reflect that write or any subsequent 21 | /// writes. Another client running in a different context may or may not immediately see the result 22 | /// due to the replication lag. As an example of all of this, if a value at a given key is A, and 23 | /// the client writes B, then immediately reads, it should get B. If something else writes C in 24 | /// quick succession, then the client may get C. However, a client running in a separate context may 25 | /// still see A or B 26 | interface store { 27 | /// The set of errors which may be raised by functions in this package 28 | variant error { 29 | /// The host does not recognize the store identifier requested. 30 | no-such-store, 31 | 32 | /// The requesting component does not have access to the specified store 33 | /// (which may or may not exist). 34 | access-denied, 35 | 36 | /// Some implementation-specific error has occurred (e.g. I/O) 37 | other(string) 38 | } 39 | 40 | /// A response to a `list-keys` operation. 41 | record key-response { 42 | /// The list of keys returned by the query. 43 | keys: list, 44 | /// The continuation token to use to fetch the next page of keys. If this is `null`, then 45 | /// there are no more keys to fetch. 46 | cursor: option 47 | } 48 | 49 | /// Get the bucket with the specified identifier. 50 | /// 51 | /// `identifier` must refer to a bucket provided by the host. 52 | /// 53 | /// `error::no-such-store` will be raised if the `identifier` is not recognized. 54 | open: func(identifier: string) -> result; 55 | 56 | /// A bucket is a collection of key-value pairs. Each key-value pair is stored as a entry in the 57 | /// bucket, and the bucket itself acts as a collection of all these entries. 58 | /// 59 | /// It is worth noting that the exact terminology for bucket in key-value stores can very 60 | /// depending on the specific implementation. For example: 61 | /// 62 | /// 1. Amazon DynamoDB calls a collection of key-value pairs a table 63 | /// 2. Redis has hashes, sets, and sorted sets as different types of collections 64 | /// 3. Cassandra calls a collection of key-value pairs a column family 65 | /// 4. MongoDB calls a collection of key-value pairs a collection 66 | /// 5. Riak calls a collection of key-value pairs a bucket 67 | /// 6. Memcached calls a collection of key-value pairs a slab 68 | /// 7. Azure Cosmos DB calls a collection of key-value pairs a container 69 | /// 70 | /// In this interface, we use the term `bucket` to refer to a collection of key-value pairs 71 | resource bucket { 72 | /// Get the value associated with the specified `key` 73 | /// 74 | /// The value is returned as an option. If the key-value pair exists in the 75 | /// store, it returns `Ok(value)`. If the key does not exist in the 76 | /// store, it returns `Ok(none)`. 77 | /// 78 | /// If any other error occurs, it returns an `Err(error)`. 79 | get: func(key: string) -> result>, error>; 80 | 81 | /// Set the value associated with the key in the store. If the key already 82 | /// exists in the store, it overwrites the value. 83 | /// 84 | /// If the key does not exist in the store, it creates a new key-value pair. 85 | /// 86 | /// If any other error occurs, it returns an `Err(error)`. 87 | set: func(key: string, value: list) -> result<_, error>; 88 | 89 | /// Delete the key-value pair associated with the key in the store. 90 | /// 91 | /// If the key does not exist in the store, it does nothing. 92 | /// 93 | /// If any other error occurs, it returns an `Err(error)`. 94 | delete: func(key: string) -> result<_, error>; 95 | 96 | /// Check if the key exists in the store. 97 | /// 98 | /// If the key exists in the store, it returns `Ok(true)`. If the key does 99 | /// not exist in the store, it returns `Ok(false)`. 100 | /// 101 | /// If any other error occurs, it returns an `Err(error)`. 102 | exists: func(key: string) -> result; 103 | 104 | /// Get all the keys in the store with an optional cursor (for use in pagination). It 105 | /// returns a list of keys. Please note that for most KeyValue implementations, this is a 106 | /// can be a very expensive operation and so it should be used judiciously. Implementations 107 | /// can return any number of keys in a single response, but they should never attempt to 108 | /// send more data than is reasonable (i.e. on a small edge device, this may only be a few 109 | /// KB, while on a large machine this could be several MB). Any response should also return 110 | /// a cursor that can be used to fetch the next page of keys. See the `key-response` record 111 | /// for more information. 112 | /// 113 | /// Note that the keys are not guaranteed to be returned in any particular order. 114 | /// 115 | /// If the store is empty, it returns an empty list. 116 | /// 117 | /// MAY show an out-of-date list of keys if there are concurrent writes to the store. 118 | /// 119 | /// If any error occurs, it returns an `Err(error)`. 120 | list-keys: func(cursor: option) -> result; 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/spin_sdk/wit/deps/keyvalue-2024-10-17/watch.wit: -------------------------------------------------------------------------------- 1 | /// A keyvalue interface that provides watch operations. 2 | /// 3 | /// This interface is used to provide event-driven mechanisms to handle 4 | /// keyvalue changes. 5 | interface watcher { 6 | /// A keyvalue interface that provides handle-watch operations. 7 | use store.{bucket}; 8 | 9 | /// Handle the `set` event for the given bucket and key. It includes a reference to the `bucket` 10 | /// that can be used to interact with the store. 11 | on-set: func(bucket: bucket, key: string, value: list); 12 | 13 | /// Handle the `delete` event for the given bucket and key. It includes a reference to the 14 | /// `bucket` that can be used to interact with the store. 15 | on-delete: func(bucket: bucket, key: string); 16 | } 17 | -------------------------------------------------------------------------------- /src/spin_sdk/wit/deps/keyvalue-2024-10-17/world.wit: -------------------------------------------------------------------------------- 1 | package wasi: keyvalue@0.2.0-draft2; 2 | 3 | /// The `wasi:keyvalue/imports` world provides common APIs for interacting with key-value stores. 4 | /// Components targeting this world will be able to do: 5 | /// 6 | /// 1. CRUD (create, read, update, delete) operations on key-value stores. 7 | /// 2. Atomic `increment` and CAS (compare-and-swap) operations. 8 | /// 3. Batch operations that can reduce the number of round trips to the network. 9 | world imports { 10 | /// The `store` capability allows the component to perform eventually consistent operations on 11 | /// the key-value store. 12 | import store; 13 | 14 | /// The `atomic` capability allows the component to perform atomic / `increment` and CAS 15 | /// (compare-and-swap) operations. 16 | import atomics; 17 | 18 | /// The `batch` capability allows the component to perform eventually consistent batch 19 | /// operations that can reduce the number of round trips to the network. 20 | import batch; 21 | } 22 | 23 | world watch-service { 24 | include imports; 25 | export watcher; 26 | } 27 | -------------------------------------------------------------------------------- /src/spin_sdk/wit/deps/random/insecure-seed.wit: -------------------------------------------------------------------------------- 1 | package wasi:random@0.2.0; 2 | /// The insecure-seed interface for seeding hash-map DoS resistance. 3 | /// 4 | /// It is intended to be portable at least between Unix-family platforms and 5 | /// Windows. 6 | interface insecure-seed { 7 | /// Return a 128-bit value that may contain a pseudo-random value. 8 | /// 9 | /// The returned value is not required to be computed from a CSPRNG, and may 10 | /// even be entirely deterministic. Host implementations are encouraged to 11 | /// provide pseudo-random values to any program exposed to 12 | /// attacker-controlled content, to enable DoS protection built into many 13 | /// languages' hash-map implementations. 14 | /// 15 | /// This function is intended to only be called once, by a source language 16 | /// to initialize Denial Of Service (DoS) protection in its hash-map 17 | /// implementation. 18 | /// 19 | /// # Expected future evolution 20 | /// 21 | /// This will likely be changed to a value import, to prevent it from being 22 | /// called multiple times and potentially used for purposes other than DoS 23 | /// protection. 24 | insecure-seed: func() -> tuple; 25 | } 26 | -------------------------------------------------------------------------------- /src/spin_sdk/wit/deps/random/insecure.wit: -------------------------------------------------------------------------------- 1 | package wasi:random@0.2.0; 2 | /// The insecure interface for insecure pseudo-random numbers. 3 | /// 4 | /// It is intended to be portable at least between Unix-family platforms and 5 | /// Windows. 6 | interface insecure { 7 | /// Return `len` insecure pseudo-random bytes. 8 | /// 9 | /// This function is not cryptographically secure. Do not use it for 10 | /// anything related to security. 11 | /// 12 | /// There are no requirements on the values of the returned bytes, however 13 | /// implementations are encouraged to return evenly distributed values with 14 | /// a long period. 15 | get-insecure-random-bytes: func(len: u64) -> list; 16 | 17 | /// Return an insecure pseudo-random `u64` value. 18 | /// 19 | /// This function returns the same type of pseudo-random data as 20 | /// `get-insecure-random-bytes`, represented as a `u64`. 21 | get-insecure-random-u64: func() -> u64; 22 | } 23 | -------------------------------------------------------------------------------- /src/spin_sdk/wit/deps/random/random.wit: -------------------------------------------------------------------------------- 1 | package wasi:random@0.2.0; 2 | /// WASI Random is a random data API. 3 | /// 4 | /// It is intended to be portable at least between Unix-family platforms and 5 | /// Windows. 6 | interface random { 7 | /// Return `len` cryptographically-secure random or pseudo-random bytes. 8 | /// 9 | /// This function must produce data at least as cryptographically secure and 10 | /// fast as an adequately seeded cryptographically-secure pseudo-random 11 | /// number generator (CSPRNG). It must not block, from the perspective of 12 | /// the calling program, under any circumstances, including on the first 13 | /// request and on requests for numbers of bytes. The returned data must 14 | /// always be unpredictable. 15 | /// 16 | /// This function must always return fresh data. Deterministic environments 17 | /// must omit this function, rather than implementing it with deterministic 18 | /// data. 19 | get-random-bytes: func(len: u64) -> list; 20 | 21 | /// Return a cryptographically-secure random or pseudo-random `u64` value. 22 | /// 23 | /// This function returns the same type of data as `get-random-bytes`, 24 | /// represented as a `u64`. 25 | get-random-u64: func() -> u64; 26 | } 27 | -------------------------------------------------------------------------------- /src/spin_sdk/wit/deps/random/world.wit: -------------------------------------------------------------------------------- 1 | package wasi:random@0.2.0; 2 | 3 | world imports { 4 | import random; 5 | import insecure; 6 | import insecure-seed; 7 | } 8 | -------------------------------------------------------------------------------- /src/spin_sdk/wit/deps/sockets/instance-network.wit: -------------------------------------------------------------------------------- 1 | 2 | /// This interface provides a value-export of the default network handle.. 3 | interface instance-network { 4 | use network.{network}; 5 | 6 | /// Get a handle to the default network. 7 | instance-network: func() -> network; 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/spin_sdk/wit/deps/sockets/ip-name-lookup.wit: -------------------------------------------------------------------------------- 1 | 2 | interface ip-name-lookup { 3 | use wasi:io/poll@0.2.0.{pollable}; 4 | use network.{network, error-code, ip-address}; 5 | 6 | 7 | /// Resolve an internet host name to a list of IP addresses. 8 | /// 9 | /// Unicode domain names are automatically converted to ASCII using IDNA encoding. 10 | /// If the input is an IP address string, the address is parsed and returned 11 | /// as-is without making any external requests. 12 | /// 13 | /// See the wasi-socket proposal README.md for a comparison with getaddrinfo. 14 | /// 15 | /// This function never blocks. It either immediately fails or immediately 16 | /// returns successfully with a `resolve-address-stream` that can be used 17 | /// to (asynchronously) fetch the results. 18 | /// 19 | /// # Typical errors 20 | /// - `invalid-argument`: `name` is a syntactically invalid domain name or IP address. 21 | /// 22 | /// # References: 23 | /// - 24 | /// - 25 | /// - 26 | /// - 27 | resolve-addresses: func(network: borrow, name: string) -> result; 28 | 29 | resource resolve-address-stream { 30 | /// Returns the next address from the resolver. 31 | /// 32 | /// This function should be called multiple times. On each call, it will 33 | /// return the next address in connection order preference. If all 34 | /// addresses have been exhausted, this function returns `none`. 35 | /// 36 | /// This function never returns IPv4-mapped IPv6 addresses. 37 | /// 38 | /// # Typical errors 39 | /// - `name-unresolvable`: Name does not exist or has no suitable associated IP addresses. (EAI_NONAME, EAI_NODATA, EAI_ADDRFAMILY) 40 | /// - `temporary-resolver-failure`: A temporary failure in name resolution occurred. (EAI_AGAIN) 41 | /// - `permanent-resolver-failure`: A permanent failure in name resolution occurred. (EAI_FAIL) 42 | /// - `would-block`: A result is not available yet. (EWOULDBLOCK, EAGAIN) 43 | resolve-next-address: func() -> result, error-code>; 44 | 45 | /// Create a `pollable` which will resolve once the stream is ready for I/O. 46 | /// 47 | /// Note: this function is here for WASI Preview2 only. 48 | /// It's planned to be removed when `future` is natively supported in Preview3. 49 | subscribe: func() -> pollable; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/spin_sdk/wit/deps/sockets/network.wit: -------------------------------------------------------------------------------- 1 | 2 | interface network { 3 | /// An opaque resource that represents access to (a subset of) the network. 4 | /// This enables context-based security for networking. 5 | /// There is no need for this to map 1:1 to a physical network interface. 6 | resource network; 7 | 8 | /// Error codes. 9 | /// 10 | /// In theory, every API can return any error code. 11 | /// In practice, API's typically only return the errors documented per API 12 | /// combined with a couple of errors that are always possible: 13 | /// - `unknown` 14 | /// - `access-denied` 15 | /// - `not-supported` 16 | /// - `out-of-memory` 17 | /// - `concurrency-conflict` 18 | /// 19 | /// See each individual API for what the POSIX equivalents are. They sometimes differ per API. 20 | enum error-code { 21 | /// Unknown error 22 | unknown, 23 | 24 | /// Access denied. 25 | /// 26 | /// POSIX equivalent: EACCES, EPERM 27 | access-denied, 28 | 29 | /// The operation is not supported. 30 | /// 31 | /// POSIX equivalent: EOPNOTSUPP 32 | not-supported, 33 | 34 | /// One of the arguments is invalid. 35 | /// 36 | /// POSIX equivalent: EINVAL 37 | invalid-argument, 38 | 39 | /// Not enough memory to complete the operation. 40 | /// 41 | /// POSIX equivalent: ENOMEM, ENOBUFS, EAI_MEMORY 42 | out-of-memory, 43 | 44 | /// The operation timed out before it could finish completely. 45 | timeout, 46 | 47 | /// This operation is incompatible with another asynchronous operation that is already in progress. 48 | /// 49 | /// POSIX equivalent: EALREADY 50 | concurrency-conflict, 51 | 52 | /// Trying to finish an asynchronous operation that: 53 | /// - has not been started yet, or: 54 | /// - was already finished by a previous `finish-*` call. 55 | /// 56 | /// Note: this is scheduled to be removed when `future`s are natively supported. 57 | not-in-progress, 58 | 59 | /// The operation has been aborted because it could not be completed immediately. 60 | /// 61 | /// Note: this is scheduled to be removed when `future`s are natively supported. 62 | would-block, 63 | 64 | 65 | /// The operation is not valid in the socket's current state. 66 | invalid-state, 67 | 68 | /// A new socket resource could not be created because of a system limit. 69 | new-socket-limit, 70 | 71 | /// A bind operation failed because the provided address is not an address that the `network` can bind to. 72 | address-not-bindable, 73 | 74 | /// A bind operation failed because the provided address is already in use or because there are no ephemeral ports available. 75 | address-in-use, 76 | 77 | /// The remote address is not reachable 78 | remote-unreachable, 79 | 80 | 81 | /// The TCP connection was forcefully rejected 82 | connection-refused, 83 | 84 | /// The TCP connection was reset. 85 | connection-reset, 86 | 87 | /// A TCP connection was aborted. 88 | connection-aborted, 89 | 90 | 91 | /// The size of a datagram sent to a UDP socket exceeded the maximum 92 | /// supported size. 93 | datagram-too-large, 94 | 95 | 96 | /// Name does not exist or has no suitable associated IP addresses. 97 | name-unresolvable, 98 | 99 | /// A temporary failure in name resolution occurred. 100 | temporary-resolver-failure, 101 | 102 | /// A permanent failure in name resolution occurred. 103 | permanent-resolver-failure, 104 | } 105 | 106 | enum ip-address-family { 107 | /// Similar to `AF_INET` in POSIX. 108 | ipv4, 109 | 110 | /// Similar to `AF_INET6` in POSIX. 111 | ipv6, 112 | } 113 | 114 | type ipv4-address = tuple; 115 | type ipv6-address = tuple; 116 | 117 | variant ip-address { 118 | ipv4(ipv4-address), 119 | ipv6(ipv6-address), 120 | } 121 | 122 | record ipv4-socket-address { 123 | /// sin_port 124 | port: u16, 125 | /// sin_addr 126 | address: ipv4-address, 127 | } 128 | 129 | record ipv6-socket-address { 130 | /// sin6_port 131 | port: u16, 132 | /// sin6_flowinfo 133 | flow-info: u32, 134 | /// sin6_addr 135 | address: ipv6-address, 136 | /// sin6_scope_id 137 | scope-id: u32, 138 | } 139 | 140 | variant ip-socket-address { 141 | ipv4(ipv4-socket-address), 142 | ipv6(ipv6-socket-address), 143 | } 144 | 145 | } 146 | -------------------------------------------------------------------------------- /src/spin_sdk/wit/deps/sockets/tcp-create-socket.wit: -------------------------------------------------------------------------------- 1 | 2 | interface tcp-create-socket { 3 | use network.{network, error-code, ip-address-family}; 4 | use tcp.{tcp-socket}; 5 | 6 | /// Create a new TCP socket. 7 | /// 8 | /// Similar to `socket(AF_INET or AF_INET6, SOCK_STREAM, IPPROTO_TCP)` in POSIX. 9 | /// On IPv6 sockets, IPV6_V6ONLY is enabled by default and can't be configured otherwise. 10 | /// 11 | /// This function does not require a network capability handle. This is considered to be safe because 12 | /// at time of creation, the socket is not bound to any `network` yet. Up to the moment `bind`/`connect` 13 | /// is called, the socket is effectively an in-memory configuration object, unable to communicate with the outside world. 14 | /// 15 | /// All sockets are non-blocking. Use the wasi-poll interface to block on asynchronous operations. 16 | /// 17 | /// # Typical errors 18 | /// - `not-supported`: The specified `address-family` is not supported. (EAFNOSUPPORT) 19 | /// - `new-socket-limit`: The new socket resource could not be created because of a system limit. (EMFILE, ENFILE) 20 | /// 21 | /// # References 22 | /// - 23 | /// - 24 | /// - 25 | /// - 26 | create-tcp-socket: func(address-family: ip-address-family) -> result; 27 | } 28 | -------------------------------------------------------------------------------- /src/spin_sdk/wit/deps/sockets/udp-create-socket.wit: -------------------------------------------------------------------------------- 1 | 2 | interface udp-create-socket { 3 | use network.{network, error-code, ip-address-family}; 4 | use udp.{udp-socket}; 5 | 6 | /// Create a new UDP socket. 7 | /// 8 | /// Similar to `socket(AF_INET or AF_INET6, SOCK_DGRAM, IPPROTO_UDP)` in POSIX. 9 | /// On IPv6 sockets, IPV6_V6ONLY is enabled by default and can't be configured otherwise. 10 | /// 11 | /// This function does not require a network capability handle. This is considered to be safe because 12 | /// at time of creation, the socket is not bound to any `network` yet. Up to the moment `bind` is called, 13 | /// the socket is effectively an in-memory configuration object, unable to communicate with the outside world. 14 | /// 15 | /// All sockets are non-blocking. Use the wasi-poll interface to block on asynchronous operations. 16 | /// 17 | /// # Typical errors 18 | /// - `not-supported`: The specified `address-family` is not supported. (EAFNOSUPPORT) 19 | /// - `new-socket-limit`: The new socket resource could not be created because of a system limit. (EMFILE, ENFILE) 20 | /// 21 | /// # References: 22 | /// - 23 | /// - 24 | /// - 25 | /// - 26 | create-udp-socket: func(address-family: ip-address-family) -> result; 27 | } 28 | -------------------------------------------------------------------------------- /src/spin_sdk/wit/deps/sockets/world.wit: -------------------------------------------------------------------------------- 1 | package wasi:sockets@0.2.0; 2 | 3 | world imports { 4 | import instance-network; 5 | import network; 6 | import udp; 7 | import udp-create-socket; 8 | import tcp; 9 | import tcp-create-socket; 10 | import ip-name-lookup; 11 | } 12 | -------------------------------------------------------------------------------- /src/spin_sdk/wit/deps/spin-postgres@3.0.0/postgres.wit: -------------------------------------------------------------------------------- 1 | package spin:postgres@3.0.0; 2 | 3 | interface postgres { 4 | /// Errors related to interacting with a database. 5 | variant error { 6 | connection-failed(string), 7 | bad-parameter(string), 8 | query-failed(string), 9 | value-conversion-failed(string), 10 | other(string) 11 | } 12 | 13 | /// Data types for a database column 14 | enum db-data-type { 15 | boolean, 16 | int8, 17 | int16, 18 | int32, 19 | int64, 20 | floating32, 21 | floating64, 22 | str, 23 | binary, 24 | date, 25 | time, 26 | datetime, 27 | timestamp, 28 | other, 29 | } 30 | 31 | /// Database values 32 | variant db-value { 33 | boolean(bool), 34 | int8(s8), 35 | int16(s16), 36 | int32(s32), 37 | int64(s64), 38 | floating32(f32), 39 | floating64(f64), 40 | str(string), 41 | binary(list), 42 | date(tuple), // (year, month, day) 43 | time(tuple), // (hour, minute, second, nanosecond) 44 | /// Date-time types are always treated as UTC (without timezone info). 45 | /// The instant is represented as a (year, month, day, hour, minute, second, nanosecond) tuple. 46 | datetime(tuple), 47 | /// Unix timestamp (seconds since epoch) 48 | timestamp(s64), 49 | db-null, 50 | unsupported, 51 | } 52 | 53 | /// Values used in parameterized queries 54 | variant parameter-value { 55 | boolean(bool), 56 | int8(s8), 57 | int16(s16), 58 | int32(s32), 59 | int64(s64), 60 | floating32(f32), 61 | floating64(f64), 62 | str(string), 63 | binary(list), 64 | date(tuple), // (year, month, day) 65 | time(tuple), // (hour, minute, second, nanosecond) 66 | /// Date-time types are always treated as UTC (without timezone info). 67 | /// The instant is represented as a (year, month, day, hour, minute, second, nanosecond) tuple. 68 | datetime(tuple), 69 | /// Unix timestamp (seconds since epoch) 70 | timestamp(s64), 71 | db-null, 72 | } 73 | 74 | /// A database column 75 | record column { 76 | name: string, 77 | data-type: db-data-type, 78 | } 79 | 80 | /// A database row 81 | type row = list; 82 | 83 | /// A set of database rows 84 | record row-set { 85 | columns: list, 86 | rows: list, 87 | } 88 | 89 | /// A connection to a postgres database. 90 | resource connection { 91 | /// Open a connection to the Postgres instance at `address`. 92 | open: static func(address: string) -> result; 93 | 94 | /// Query the database. 95 | query: func(statement: string, params: list) -> result; 96 | 97 | /// Execute command to the database. 98 | execute: func(statement: string, params: list) -> result; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/spin_sdk/wit/deps/spin@3.0.0/world.wit: -------------------------------------------------------------------------------- 1 | package fermyon:spin@3.0.0; 2 | 3 | world spin-imports { 4 | include fermyon:spin/spin-imports@2.0.0; 5 | include wasi:keyvalue/imports@0.2.0-draft2; 6 | import spin:postgres/postgres@3.0.0; 7 | import wasi:config/store@0.2.0-draft-2024-09-27; 8 | } 9 | 10 | world spin-redis { 11 | include spin-imports; 12 | export fermyon:spin/inbound-redis; 13 | } 14 | 15 | world spin-http { 16 | include spin-imports; 17 | export wasi:http/incoming-handler@0.2.0; 18 | } 19 | 20 | world spin-all { 21 | include spin-redis; 22 | include spin-http; 23 | } 24 | 25 | -------------------------------------------------------------------------------- /src/spin_sdk/wit/deps/spin@unversioned/inbound-redis.wit: -------------------------------------------------------------------------------- 1 | package fermyon:spin; 2 | 3 | interface inbound-redis { 4 | use redis-types.{payload, error}; 5 | 6 | // The entrypoint for a Redis handler. 7 | handle-message: func(message: payload) -> result<_, error>; 8 | } 9 | -------------------------------------------------------------------------------- /src/spin_sdk/wit/deps/spin@unversioned/redis-types.wit: -------------------------------------------------------------------------------- 1 | interface redis-types { 2 | // General purpose error. 3 | enum error { 4 | success, 5 | error, 6 | } 7 | 8 | /// The message payload. 9 | type payload = list; 10 | 11 | /// A parameter type for the general-purpose `execute` function. 12 | variant redis-parameter { 13 | int64(s64), 14 | binary(payload) 15 | } 16 | 17 | /// A return type for the general-purpose `execute` function. 18 | variant redis-result { 19 | nil, 20 | status(string), 21 | int64(s64), 22 | binary(payload) 23 | } 24 | } -------------------------------------------------------------------------------- /src/spin_sdk/wit/deps/wasi-runtime-config-2024-09-27/store.wit: -------------------------------------------------------------------------------- 1 | interface store { 2 | /// An error type that encapsulates the different errors that can occur fetching configuration values. 3 | variant error { 4 | /// This indicates an error from an "upstream" config source. 5 | /// As this could be almost _anything_ (such as Vault, Kubernetes ConfigMaps, KeyValue buckets, etc), 6 | /// the error message is a string. 7 | upstream(string), 8 | /// This indicates an error from an I/O operation. 9 | /// As this could be almost _anything_ (such as a file read, network connection, etc), 10 | /// the error message is a string. 11 | /// Depending on how this ends up being consumed, 12 | /// we may consider moving this to use the `wasi:io/error` type instead. 13 | /// For simplicity right now in supporting multiple implementations, it is being left as a string. 14 | io(string), 15 | } 16 | 17 | /// Gets a configuration value of type `string` associated with the `key`. 18 | /// 19 | /// The value is returned as an `option`. If the key is not found, 20 | /// `Ok(none)` is returned. If an error occurs, an `Err(error)` is returned. 21 | get: func( 22 | /// A string key to fetch 23 | key: string 24 | ) -> result, error>; 25 | 26 | /// Gets a list of configuration key-value pairs of type `string`. 27 | /// 28 | /// If an error occurs, an `Err(error)` is returned. 29 | get-all: func() -> result>, error>; 30 | } 31 | -------------------------------------------------------------------------------- /src/spin_sdk/wit/deps/wasi-runtime-config-2024-09-27/world.wit: -------------------------------------------------------------------------------- 1 | package wasi:config@0.2.0-draft-2024-09-27; 2 | 3 | world imports { 4 | /// The interface for wasi:config/store 5 | import store; 6 | } -------------------------------------------------------------------------------- /src/spin_sdk/wit/exports/__init__.py: -------------------------------------------------------------------------------- 1 | from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self 2 | from types import TracebackType 3 | from enum import Flag, Enum, auto 4 | from dataclasses import dataclass 5 | from abc import abstractmethod 6 | import weakref 7 | 8 | from ..types import Result, Ok, Err, Some 9 | from ..imports import types 10 | 11 | class InboundRedis(Protocol): 12 | 13 | @abstractmethod 14 | def handle_message(self, message: bytes) -> None: 15 | """ 16 | The entrypoint for a Redis handler. 17 | 18 | Raises: `spin_sdk.wit.types.Err(spin_sdk.wit.imports.redis_types.Error)` 19 | """ 20 | raise NotImplementedError 21 | 22 | 23 | class IncomingHandler(Protocol): 24 | 25 | @abstractmethod 26 | def handle(self, request: types.IncomingRequest, response_out: types.ResponseOutparam) -> None: 27 | """ 28 | This function is invoked with an incoming HTTP Request, and a resource 29 | `response-outparam` which provides the capability to reply with an HTTP 30 | Response. The response is sent by calling the `response-outparam.set` 31 | method, which allows execution to continue after the response has been 32 | sent. This enables both streaming to the response body, and performing other 33 | work. 34 | 35 | The implementor of this function must write a response to the 36 | `response-outparam` before returning, or else the caller will respond 37 | with an error on its behalf. 38 | """ 39 | raise NotImplementedError 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/spin_sdk/wit/exports/inbound_redis.py: -------------------------------------------------------------------------------- 1 | from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self 2 | from types import TracebackType 3 | from enum import Flag, Enum, auto 4 | from dataclasses import dataclass 5 | from abc import abstractmethod 6 | import weakref 7 | 8 | from ..types import Result, Ok, Err, Some 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/spin_sdk/wit/exports/incoming_handler.py: -------------------------------------------------------------------------------- 1 | """ 2 | This interface defines a handler of incoming HTTP Requests. It should 3 | be exported by components which can respond to HTTP Requests. 4 | """ 5 | from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self 6 | from types import TracebackType 7 | from enum import Flag, Enum, auto 8 | from dataclasses import dataclass 9 | from abc import abstractmethod 10 | import weakref 11 | 12 | from ..types import Result, Ok, Err, Some 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/spin_sdk/wit/imports/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spinframework/spin-python-sdk/e93a76aabf01ba0be190f72308a468bf5342492d/src/spin_sdk/wit/imports/__init__.py -------------------------------------------------------------------------------- /src/spin_sdk/wit/imports/atomics.py: -------------------------------------------------------------------------------- 1 | """ 2 | A keyvalue interface that provides atomic operations. 3 | 4 | Atomic operations are single, indivisible operations. When a fault causes an atomic operation to 5 | fail, it will appear to the invoker of the atomic operation that the action either completed 6 | successfully or did nothing at all. 7 | 8 | Please note that this interface is bare functions that take a reference to a bucket. This is to 9 | get around the current lack of a way to "extend" a resource with additional methods inside of 10 | wit. Future version of the interface will instead extend these methods on the base `bucket` 11 | resource. 12 | """ 13 | from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self 14 | from types import TracebackType 15 | from enum import Flag, Enum, auto 16 | from dataclasses import dataclass 17 | from abc import abstractmethod 18 | import weakref 19 | 20 | from ..types import Result, Ok, Err, Some 21 | from ..imports import wasi_keyvalue_store 22 | 23 | class Cas: 24 | """ 25 | A handle to a CAS (compare-and-swap) operation. 26 | """ 27 | 28 | @classmethod 29 | def new(cls, bucket: wasi_keyvalue_store.Bucket, key: str) -> Self: 30 | """ 31 | Construct a new CAS operation. Implementors can map the underlying functionality 32 | (transactions, versions, etc) as desired. 33 | 34 | Raises: `spin_sdk.wit.types.Err(spin_sdk.wit.imports.wasi_keyvalue_store.Error)` 35 | """ 36 | raise NotImplementedError 37 | def current(self) -> Optional[bytes]: 38 | """ 39 | Get the current value of the key (if it exists). This allows for avoiding reads if all 40 | that is needed to ensure the atomicity of the operation 41 | 42 | Raises: `spin_sdk.wit.types.Err(spin_sdk.wit.imports.wasi_keyvalue_store.Error)` 43 | """ 44 | raise NotImplementedError 45 | def __enter__(self) -> Self: 46 | """Returns self""" 47 | return self 48 | 49 | def __exit__(self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None) -> bool | None: 50 | """ 51 | Release this resource. 52 | """ 53 | raise NotImplementedError 54 | 55 | 56 | 57 | @dataclass 58 | class CasError_StoreError: 59 | value: wasi_keyvalue_store.Error 60 | 61 | 62 | @dataclass 63 | class CasError_CasFailed: 64 | value: Cas 65 | 66 | 67 | CasError = Union[CasError_StoreError, CasError_CasFailed] 68 | """ 69 | The error returned by a CAS operation 70 | """ 71 | 72 | 73 | 74 | def increment(bucket: wasi_keyvalue_store.Bucket, key: str, delta: int) -> int: 75 | """ 76 | Atomically increment the value associated with the key in the store by the given delta. It 77 | returns the new value. 78 | 79 | If the key does not exist in the store, it creates a new key-value pair with the value set 80 | to the given delta. 81 | 82 | If any other error occurs, it returns an `Err(error)`. 83 | 84 | Raises: `spin_sdk.wit.types.Err(spin_sdk.wit.imports.wasi_keyvalue_store.Error)` 85 | """ 86 | raise NotImplementedError 87 | 88 | def swap(cas: Cas, value: bytes) -> None: 89 | """ 90 | Perform the swap on a CAS operation. This consumes the CAS handle and returns an error if 91 | the CAS operation failed. 92 | 93 | Raises: `spin_sdk.wit.types.Err(spin_sdk.wit.imports.atomics.CasError)` 94 | """ 95 | raise NotImplementedError 96 | 97 | -------------------------------------------------------------------------------- /src/spin_sdk/wit/imports/batch.py: -------------------------------------------------------------------------------- 1 | """ 2 | A keyvalue interface that provides batch operations. 3 | 4 | A batch operation is an operation that operates on multiple keys at once. 5 | 6 | Batch operations are useful for reducing network round-trip time. For example, if you want to 7 | get the values associated with 100 keys, you can either do 100 get operations or you can do 1 8 | batch get operation. The batch operation is faster because it only needs to make 1 network call 9 | instead of 100. 10 | 11 | A batch operation does not guarantee atomicity, meaning that if the batch operation fails, some 12 | of the keys may have been modified and some may not. 13 | 14 | This interface does has the same consistency guarantees as the `store` interface, meaning that 15 | you should be able to "read your writes." 16 | 17 | Please note that this interface is bare functions that take a reference to a bucket. This is to 18 | get around the current lack of a way to "extend" a resource with additional methods inside of 19 | wit. Future version of the interface will instead extend these methods on the base `bucket` 20 | resource. 21 | """ 22 | from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self 23 | from types import TracebackType 24 | from enum import Flag, Enum, auto 25 | from dataclasses import dataclass 26 | from abc import abstractmethod 27 | import weakref 28 | 29 | from ..types import Result, Ok, Err, Some 30 | from ..imports import wasi_keyvalue_store 31 | 32 | 33 | def get_many(bucket: wasi_keyvalue_store.Bucket, keys: List[str]) -> List[Tuple[str, Optional[bytes]]]: 34 | """ 35 | Get the key-value pairs associated with the keys in the store. It returns a list of 36 | key-value pairs. 37 | 38 | If any of the keys do not exist in the store, it returns a `none` value for that pair in the 39 | list. 40 | 41 | MAY show an out-of-date value if there are concurrent writes to the store. 42 | 43 | If any other error occurs, it returns an `Err(error)`. 44 | 45 | Raises: `spin_sdk.wit.types.Err(spin_sdk.wit.imports.wasi_keyvalue_store.Error)` 46 | """ 47 | raise NotImplementedError 48 | 49 | def set_many(bucket: wasi_keyvalue_store.Bucket, key_values: List[Tuple[str, bytes]]) -> None: 50 | """ 51 | Set the values associated with the keys in the store. If the key already exists in the 52 | store, it overwrites the value. 53 | 54 | Note that the key-value pairs are not guaranteed to be set in the order they are provided. 55 | 56 | If any of the keys do not exist in the store, it creates a new key-value pair. 57 | 58 | If any other error occurs, it returns an `Err(error)`. When an error occurs, it does not 59 | rollback the key-value pairs that were already set. Thus, this batch operation does not 60 | guarantee atomicity, implying that some key-value pairs could be set while others might 61 | fail. 62 | 63 | Other concurrent operations may also be able to see the partial results. 64 | 65 | Raises: `spin_sdk.wit.types.Err(spin_sdk.wit.imports.wasi_keyvalue_store.Error)` 66 | """ 67 | raise NotImplementedError 68 | 69 | def delete_many(bucket: wasi_keyvalue_store.Bucket, keys: List[str]) -> None: 70 | """ 71 | Delete the key-value pairs associated with the keys in the store. 72 | 73 | Note that the key-value pairs are not guaranteed to be deleted in the order they are 74 | provided. 75 | 76 | If any of the keys do not exist in the store, it skips the key. 77 | 78 | If any other error occurs, it returns an `Err(error)`. When an error occurs, it does not 79 | rollback the key-value pairs that were already deleted. Thus, this batch operation does not 80 | guarantee atomicity, implying that some key-value pairs could be deleted while others might 81 | fail. 82 | 83 | Other concurrent operations may also be able to see the partial results. 84 | 85 | Raises: `spin_sdk.wit.types.Err(spin_sdk.wit.imports.wasi_keyvalue_store.Error)` 86 | """ 87 | raise NotImplementedError 88 | 89 | -------------------------------------------------------------------------------- /src/spin_sdk/wit/imports/error.py: -------------------------------------------------------------------------------- 1 | from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self 2 | from types import TracebackType 3 | from enum import Flag, Enum, auto 4 | from dataclasses import dataclass 5 | from abc import abstractmethod 6 | import weakref 7 | 8 | from ..types import Result, Ok, Err, Some 9 | 10 | 11 | class Error: 12 | """ 13 | A resource which represents some error information. 14 | 15 | The only method provided by this resource is `to-debug-string`, 16 | which provides some human-readable information about the error. 17 | 18 | In the `wasi:io` package, this resource is returned through the 19 | `wasi:io/streams/stream-error` type. 20 | 21 | To provide more specific error information, other interfaces may 22 | provide functions to further "downcast" this error into more specific 23 | error information. For example, `error`s returned in streams derived 24 | from filesystem types to be described using the filesystem's own 25 | error-code type, using the function 26 | `wasi:filesystem/types/filesystem-error-code`, which takes a parameter 27 | `borrow` and returns 28 | `option`. 29 | 30 | The set of functions which can "downcast" an `error` into a more 31 | concrete type is open. 32 | """ 33 | 34 | def to_debug_string(self) -> str: 35 | """ 36 | Returns a string that is suitable to assist humans in debugging 37 | this error. 38 | 39 | WARNING: The returned string should not be consumed mechanically! 40 | It may change across platforms, hosts, or other implementation 41 | details. Parsing this string is a major platform-compatibility 42 | hazard. 43 | """ 44 | raise NotImplementedError 45 | def __enter__(self) -> Self: 46 | """Returns self""" 47 | return self 48 | 49 | def __exit__(self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None) -> bool | None: 50 | """ 51 | Release this resource. 52 | """ 53 | raise NotImplementedError 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /src/spin_sdk/wit/imports/key_value.py: -------------------------------------------------------------------------------- 1 | from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self 2 | from types import TracebackType 3 | from enum import Flag, Enum, auto 4 | from dataclasses import dataclass 5 | from abc import abstractmethod 6 | import weakref 7 | 8 | from ..types import Result, Ok, Err, Some 9 | 10 | 11 | 12 | @dataclass 13 | class Error_StoreTableFull: 14 | pass 15 | 16 | 17 | @dataclass 18 | class Error_NoSuchStore: 19 | pass 20 | 21 | 22 | @dataclass 23 | class Error_AccessDenied: 24 | pass 25 | 26 | 27 | @dataclass 28 | class Error_Other: 29 | value: str 30 | 31 | 32 | Error = Union[Error_StoreTableFull, Error_NoSuchStore, Error_AccessDenied, Error_Other] 33 | """ 34 | The set of errors which may be raised by functions in this interface 35 | """ 36 | 37 | 38 | class Store: 39 | """ 40 | An open key-value store 41 | """ 42 | 43 | @classmethod 44 | def open(cls, label: str) -> Self: 45 | """ 46 | Open the store with the specified label. 47 | 48 | `label` must refer to a store allowed in the spin.toml manifest. 49 | 50 | `error::no-such-store` will be raised if the `label` is not recognized. 51 | 52 | Raises: `spin_sdk.wit.types.Err(spin_sdk.wit.imports.key_value.Error)` 53 | """ 54 | raise NotImplementedError 55 | def get(self, key: str) -> Optional[bytes]: 56 | """ 57 | Get the value associated with the specified `key` 58 | 59 | Returns `ok(none)` if the key does not exist. 60 | 61 | Raises: `spin_sdk.wit.types.Err(spin_sdk.wit.imports.key_value.Error)` 62 | """ 63 | raise NotImplementedError 64 | def set(self, key: str, value: bytes) -> None: 65 | """ 66 | Set the `value` associated with the specified `key` overwriting any existing value. 67 | 68 | Raises: `spin_sdk.wit.types.Err(spin_sdk.wit.imports.key_value.Error)` 69 | """ 70 | raise NotImplementedError 71 | def delete(self, key: str) -> None: 72 | """ 73 | Delete the tuple with the specified `key` 74 | 75 | No error is raised if a tuple did not previously exist for `key`. 76 | 77 | Raises: `spin_sdk.wit.types.Err(spin_sdk.wit.imports.key_value.Error)` 78 | """ 79 | raise NotImplementedError 80 | def exists(self, key: str) -> bool: 81 | """ 82 | Return whether a tuple exists for the specified `key` 83 | 84 | Raises: `spin_sdk.wit.types.Err(spin_sdk.wit.imports.key_value.Error)` 85 | """ 86 | raise NotImplementedError 87 | def get_keys(self) -> List[str]: 88 | """ 89 | Return a list of all the keys 90 | 91 | Raises: `spin_sdk.wit.types.Err(spin_sdk.wit.imports.key_value.Error)` 92 | """ 93 | raise NotImplementedError 94 | def __enter__(self) -> Self: 95 | """Returns self""" 96 | return self 97 | 98 | def __exit__(self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None) -> bool | None: 99 | """ 100 | Release this resource. 101 | """ 102 | raise NotImplementedError 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /src/spin_sdk/wit/imports/llm.py: -------------------------------------------------------------------------------- 1 | """ 2 | A WASI interface dedicated to performing inferencing for Large Language Models. 3 | """ 4 | from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self 5 | from types import TracebackType 6 | from enum import Flag, Enum, auto 7 | from dataclasses import dataclass 8 | from abc import abstractmethod 9 | import weakref 10 | 11 | from ..types import Result, Ok, Err, Some 12 | 13 | 14 | @dataclass 15 | class InferencingParams: 16 | """ 17 | Inference request parameters 18 | """ 19 | max_tokens: int 20 | repeat_penalty: float 21 | repeat_penalty_last_n_token_count: int 22 | temperature: float 23 | top_k: int 24 | top_p: float 25 | 26 | 27 | @dataclass 28 | class Error_ModelNotSupported: 29 | pass 30 | 31 | 32 | @dataclass 33 | class Error_RuntimeError: 34 | value: str 35 | 36 | 37 | @dataclass 38 | class Error_InvalidInput: 39 | value: str 40 | 41 | 42 | Error = Union[Error_ModelNotSupported, Error_RuntimeError, Error_InvalidInput] 43 | """ 44 | The set of errors which may be raised by functions in this interface 45 | """ 46 | 47 | 48 | @dataclass 49 | class InferencingUsage: 50 | """ 51 | Usage information related to the inferencing result 52 | """ 53 | prompt_token_count: int 54 | generated_token_count: int 55 | 56 | @dataclass 57 | class InferencingResult: 58 | """ 59 | An inferencing result 60 | """ 61 | text: str 62 | usage: InferencingUsage 63 | 64 | @dataclass 65 | class EmbeddingsUsage: 66 | """ 67 | Usage related to an embeddings generation request 68 | """ 69 | prompt_token_count: int 70 | 71 | @dataclass 72 | class EmbeddingsResult: 73 | """ 74 | Result of generating embeddings 75 | """ 76 | embeddings: List[List[float]] 77 | usage: EmbeddingsUsage 78 | 79 | 80 | def infer(model: str, prompt: str, params: Optional[InferencingParams]) -> InferencingResult: 81 | """ 82 | Perform inferencing using the provided model and prompt with the given optional params 83 | 84 | Raises: `spin_sdk.wit.types.Err(spin_sdk.wit.imports.llm.Error)` 85 | """ 86 | raise NotImplementedError 87 | 88 | def generate_embeddings(model: str, text: List[str]) -> EmbeddingsResult: 89 | """ 90 | Generate embeddings for the supplied list of text 91 | 92 | Raises: `spin_sdk.wit.types.Err(spin_sdk.wit.imports.llm.Error)` 93 | """ 94 | raise NotImplementedError 95 | 96 | -------------------------------------------------------------------------------- /src/spin_sdk/wit/imports/monotonic_clock.py: -------------------------------------------------------------------------------- 1 | """ 2 | WASI Monotonic Clock is a clock API intended to let users measure elapsed 3 | time. 4 | 5 | It is intended to be portable at least between Unix-family platforms and 6 | Windows. 7 | 8 | A monotonic clock is a clock which has an unspecified initial value, and 9 | successive reads of the clock will produce non-decreasing values. 10 | 11 | It is intended for measuring elapsed time. 12 | """ 13 | from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self 14 | from types import TracebackType 15 | from enum import Flag, Enum, auto 16 | from dataclasses import dataclass 17 | from abc import abstractmethod 18 | import weakref 19 | 20 | from ..types import Result, Ok, Err, Some 21 | from ..imports import poll 22 | 23 | 24 | def now() -> int: 25 | """ 26 | Read the current value of the clock. 27 | 28 | The clock is monotonic, therefore calling this function repeatedly will 29 | produce a sequence of non-decreasing values. 30 | """ 31 | raise NotImplementedError 32 | 33 | def resolution() -> int: 34 | """ 35 | Query the resolution of the clock. Returns the duration of time 36 | corresponding to a clock tick. 37 | """ 38 | raise NotImplementedError 39 | 40 | def subscribe_instant(when: int) -> poll.Pollable: 41 | """ 42 | Create a `pollable` which will resolve once the specified instant 43 | occured. 44 | """ 45 | raise NotImplementedError 46 | 47 | def subscribe_duration(when: int) -> poll.Pollable: 48 | """ 49 | Create a `pollable` which will resolve once the given duration has 50 | elapsed, starting at the time at which this function was called. 51 | occured. 52 | """ 53 | raise NotImplementedError 54 | 55 | -------------------------------------------------------------------------------- /src/spin_sdk/wit/imports/mqtt.py: -------------------------------------------------------------------------------- 1 | from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self 2 | from types import TracebackType 3 | from enum import Flag, Enum, auto 4 | from dataclasses import dataclass 5 | from abc import abstractmethod 6 | import weakref 7 | 8 | from ..types import Result, Ok, Err, Some 9 | 10 | 11 | 12 | @dataclass 13 | class Error_InvalidAddress: 14 | pass 15 | 16 | 17 | @dataclass 18 | class Error_TooManyConnections: 19 | pass 20 | 21 | 22 | @dataclass 23 | class Error_ConnectionFailed: 24 | value: str 25 | 26 | 27 | @dataclass 28 | class Error_Other: 29 | value: str 30 | 31 | 32 | Error = Union[Error_InvalidAddress, Error_TooManyConnections, Error_ConnectionFailed, Error_Other] 33 | """ 34 | Errors related to interacting with Mqtt 35 | """ 36 | 37 | 38 | class Qos(Enum): 39 | """ 40 | QoS for publishing Mqtt messages 41 | """ 42 | AT_MOST_ONCE = 0 43 | AT_LEAST_ONCE = 1 44 | EXACTLY_ONCE = 2 45 | 46 | class Connection: 47 | 48 | @classmethod 49 | def open(cls, address: str, username: str, password: str, keep_alive_interval_in_secs: int) -> Self: 50 | """ 51 | Open a connection to the Mqtt instance at `address`. 52 | 53 | Raises: `spin_sdk.wit.types.Err(spin_sdk.wit.imports.mqtt.Error)` 54 | """ 55 | raise NotImplementedError 56 | def publish(self, topic: str, payload: bytes, qos: Qos) -> None: 57 | """ 58 | Publish an Mqtt message to the specified `topic`. 59 | 60 | Raises: `spin_sdk.wit.types.Err(spin_sdk.wit.imports.mqtt.Error)` 61 | """ 62 | raise NotImplementedError 63 | def __enter__(self) -> Self: 64 | """Returns self""" 65 | return self 66 | 67 | def __exit__(self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None) -> bool | None: 68 | """ 69 | Release this resource. 70 | """ 71 | raise NotImplementedError 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /src/spin_sdk/wit/imports/mysql.py: -------------------------------------------------------------------------------- 1 | from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self 2 | from types import TracebackType 3 | from enum import Flag, Enum, auto 4 | from dataclasses import dataclass 5 | from abc import abstractmethod 6 | import weakref 7 | 8 | from ..types import Result, Ok, Err, Some 9 | from ..imports import rdbms_types 10 | 11 | class Connection: 12 | """ 13 | A connection to a MySQL database. 14 | """ 15 | 16 | @classmethod 17 | def open(cls, address: str) -> Self: 18 | """ 19 | Open a connection to the MySQL instance at `address`. 20 | 21 | Raises: `spin_sdk.wit.types.Err(spin_sdk.wit.imports.rdbms_types.Error)` 22 | """ 23 | raise NotImplementedError 24 | def query(self, statement: str, params: List[rdbms_types.ParameterValue]) -> rdbms_types.RowSet: 25 | """ 26 | query the database: select 27 | 28 | Raises: `spin_sdk.wit.types.Err(spin_sdk.wit.imports.rdbms_types.Error)` 29 | """ 30 | raise NotImplementedError 31 | def execute(self, statement: str, params: List[rdbms_types.ParameterValue]) -> None: 32 | """ 33 | execute command to the database: insert, update, delete 34 | 35 | Raises: `spin_sdk.wit.types.Err(spin_sdk.wit.imports.rdbms_types.Error)` 36 | """ 37 | raise NotImplementedError 38 | def __enter__(self) -> Self: 39 | """Returns self""" 40 | return self 41 | 42 | def __exit__(self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None) -> bool | None: 43 | """ 44 | Release this resource. 45 | """ 46 | raise NotImplementedError 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /src/spin_sdk/wit/imports/outgoing_handler.py: -------------------------------------------------------------------------------- 1 | """ 2 | This interface defines a handler of outgoing HTTP Requests. It should be 3 | imported by components which wish to make HTTP Requests. 4 | """ 5 | from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self 6 | from types import TracebackType 7 | from enum import Flag, Enum, auto 8 | from dataclasses import dataclass 9 | from abc import abstractmethod 10 | import weakref 11 | 12 | from ..types import Result, Ok, Err, Some 13 | from ..imports import types 14 | 15 | 16 | def handle(request: types.OutgoingRequest, options: Optional[types.RequestOptions]) -> types.FutureIncomingResponse: 17 | """ 18 | This function is invoked with an outgoing HTTP Request, and it returns 19 | a resource `future-incoming-response` which represents an HTTP Response 20 | which may arrive in the future. 21 | 22 | The `options` argument accepts optional parameters for the HTTP 23 | protocol's transport layer. 24 | 25 | This function may return an error if the `outgoing-request` is invalid 26 | or not allowed to be made. Otherwise, protocol errors are reported 27 | through the `future-incoming-response`. 28 | 29 | Raises: `spin_sdk.wit.types.Err(spin_sdk.wit.imports.types.ErrorCode)` 30 | """ 31 | raise NotImplementedError 32 | 33 | -------------------------------------------------------------------------------- /src/spin_sdk/wit/imports/poll.py: -------------------------------------------------------------------------------- 1 | """ 2 | A poll API intended to let users wait for I/O events on multiple handles 3 | at once. 4 | """ 5 | from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self 6 | from types import TracebackType 7 | from enum import Flag, Enum, auto 8 | from dataclasses import dataclass 9 | from abc import abstractmethod 10 | import weakref 11 | 12 | from ..types import Result, Ok, Err, Some 13 | 14 | 15 | class Pollable: 16 | """ 17 | `pollable` represents a single I/O event which may be ready, or not. 18 | """ 19 | 20 | def ready(self) -> bool: 21 | """ 22 | Return the readiness of a pollable. This function never blocks. 23 | 24 | Returns `true` when the pollable is ready, and `false` otherwise. 25 | """ 26 | raise NotImplementedError 27 | def block(self) -> None: 28 | """ 29 | `block` returns immediately if the pollable is ready, and otherwise 30 | blocks until ready. 31 | 32 | This function is equivalent to calling `poll.poll` on a list 33 | containing only this pollable. 34 | """ 35 | raise NotImplementedError 36 | def __enter__(self) -> Self: 37 | """Returns self""" 38 | return self 39 | 40 | def __exit__(self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None) -> bool | None: 41 | """ 42 | Release this resource. 43 | """ 44 | raise NotImplementedError 45 | 46 | 47 | 48 | def poll(in_: List[Pollable]) -> List[int]: 49 | """ 50 | Poll for completion on a set of pollables. 51 | 52 | This function takes a list of pollables, which identify I/O sources of 53 | interest, and waits until one or more of the events is ready for I/O. 54 | 55 | The result `list` contains one or more indices of handles in the 56 | argument list that is ready for I/O. 57 | 58 | If the list contains more elements than can be indexed with a `u32` 59 | value, this function traps. 60 | 61 | A timeout can be implemented by adding a pollable from the 62 | wasi-clocks API to the list. 63 | 64 | This function does not return a `result`; polling in itself does not 65 | do any I/O so it doesn't fail. If any of the I/O sources identified by 66 | the pollables has an error, it is indicated by marking the source as 67 | being reaedy for I/O. 68 | """ 69 | raise NotImplementedError 70 | 71 | -------------------------------------------------------------------------------- /src/spin_sdk/wit/imports/postgres.py: -------------------------------------------------------------------------------- 1 | from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self 2 | from types import TracebackType 3 | from enum import Flag, Enum, auto 4 | from dataclasses import dataclass 5 | from abc import abstractmethod 6 | import weakref 7 | 8 | from ..types import Result, Ok, Err, Some 9 | from ..imports import rdbms_types 10 | 11 | class Connection: 12 | """ 13 | A connection to a postgres database. 14 | """ 15 | 16 | @classmethod 17 | def open(cls, address: str) -> Self: 18 | """ 19 | Open a connection to the Postgres instance at `address`. 20 | 21 | Raises: `spin_sdk.wit.types.Err(spin_sdk.wit.imports.rdbms_types.Error)` 22 | """ 23 | raise NotImplementedError 24 | def query(self, statement: str, params: List[rdbms_types.ParameterValue]) -> rdbms_types.RowSet: 25 | """ 26 | Query the database. 27 | 28 | Raises: `spin_sdk.wit.types.Err(spin_sdk.wit.imports.rdbms_types.Error)` 29 | """ 30 | raise NotImplementedError 31 | def execute(self, statement: str, params: List[rdbms_types.ParameterValue]) -> int: 32 | """ 33 | Execute command to the database. 34 | 35 | Raises: `spin_sdk.wit.types.Err(spin_sdk.wit.imports.rdbms_types.Error)` 36 | """ 37 | raise NotImplementedError 38 | def __enter__(self) -> Self: 39 | """Returns self""" 40 | return self 41 | 42 | def __exit__(self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None) -> bool | None: 43 | """ 44 | Release this resource. 45 | """ 46 | raise NotImplementedError 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /src/spin_sdk/wit/imports/rdbms_types.py: -------------------------------------------------------------------------------- 1 | from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self 2 | from types import TracebackType 3 | from enum import Flag, Enum, auto 4 | from dataclasses import dataclass 5 | from abc import abstractmethod 6 | import weakref 7 | 8 | from ..types import Result, Ok, Err, Some 9 | 10 | 11 | 12 | @dataclass 13 | class Error_ConnectionFailed: 14 | value: str 15 | 16 | 17 | @dataclass 18 | class Error_BadParameter: 19 | value: str 20 | 21 | 22 | @dataclass 23 | class Error_QueryFailed: 24 | value: str 25 | 26 | 27 | @dataclass 28 | class Error_ValueConversionFailed: 29 | value: str 30 | 31 | 32 | @dataclass 33 | class Error_Other: 34 | value: str 35 | 36 | 37 | Error = Union[Error_ConnectionFailed, Error_BadParameter, Error_QueryFailed, Error_ValueConversionFailed, Error_Other] 38 | """ 39 | Errors related to interacting with a database. 40 | """ 41 | 42 | 43 | class DbDataType(Enum): 44 | """ 45 | Data types for a database column 46 | """ 47 | BOOLEAN = 0 48 | INT8 = 1 49 | INT16 = 2 50 | INT32 = 3 51 | INT64 = 4 52 | UINT8 = 5 53 | UINT16 = 6 54 | UINT32 = 7 55 | UINT64 = 8 56 | FLOATING32 = 9 57 | FLOATING64 = 10 58 | STR = 11 59 | BINARY = 12 60 | OTHER = 13 61 | 62 | 63 | @dataclass 64 | class DbValue_Boolean: 65 | value: bool 66 | 67 | 68 | @dataclass 69 | class DbValue_Int8: 70 | value: int 71 | 72 | 73 | @dataclass 74 | class DbValue_Int16: 75 | value: int 76 | 77 | 78 | @dataclass 79 | class DbValue_Int32: 80 | value: int 81 | 82 | 83 | @dataclass 84 | class DbValue_Int64: 85 | value: int 86 | 87 | 88 | @dataclass 89 | class DbValue_Uint8: 90 | value: int 91 | 92 | 93 | @dataclass 94 | class DbValue_Uint16: 95 | value: int 96 | 97 | 98 | @dataclass 99 | class DbValue_Uint32: 100 | value: int 101 | 102 | 103 | @dataclass 104 | class DbValue_Uint64: 105 | value: int 106 | 107 | 108 | @dataclass 109 | class DbValue_Floating32: 110 | value: float 111 | 112 | 113 | @dataclass 114 | class DbValue_Floating64: 115 | value: float 116 | 117 | 118 | @dataclass 119 | class DbValue_Str: 120 | value: str 121 | 122 | 123 | @dataclass 124 | class DbValue_Binary: 125 | value: bytes 126 | 127 | 128 | @dataclass 129 | class DbValue_DbNull: 130 | pass 131 | 132 | 133 | @dataclass 134 | class DbValue_Unsupported: 135 | pass 136 | 137 | 138 | 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] 139 | """ 140 | Database values 141 | """ 142 | 143 | 144 | 145 | @dataclass 146 | class ParameterValue_Boolean: 147 | value: bool 148 | 149 | 150 | @dataclass 151 | class ParameterValue_Int8: 152 | value: int 153 | 154 | 155 | @dataclass 156 | class ParameterValue_Int16: 157 | value: int 158 | 159 | 160 | @dataclass 161 | class ParameterValue_Int32: 162 | value: int 163 | 164 | 165 | @dataclass 166 | class ParameterValue_Int64: 167 | value: int 168 | 169 | 170 | @dataclass 171 | class ParameterValue_Uint8: 172 | value: int 173 | 174 | 175 | @dataclass 176 | class ParameterValue_Uint16: 177 | value: int 178 | 179 | 180 | @dataclass 181 | class ParameterValue_Uint32: 182 | value: int 183 | 184 | 185 | @dataclass 186 | class ParameterValue_Uint64: 187 | value: int 188 | 189 | 190 | @dataclass 191 | class ParameterValue_Floating32: 192 | value: float 193 | 194 | 195 | @dataclass 196 | class ParameterValue_Floating64: 197 | value: float 198 | 199 | 200 | @dataclass 201 | class ParameterValue_Str: 202 | value: str 203 | 204 | 205 | @dataclass 206 | class ParameterValue_Binary: 207 | value: bytes 208 | 209 | 210 | @dataclass 211 | class ParameterValue_DbNull: 212 | pass 213 | 214 | 215 | 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] 216 | """ 217 | Values used in parameterized queries 218 | """ 219 | 220 | 221 | @dataclass 222 | class Column: 223 | """ 224 | A database column 225 | """ 226 | name: str 227 | data_type: DbDataType 228 | 229 | @dataclass 230 | class RowSet: 231 | """ 232 | A set of database rows 233 | """ 234 | columns: List[Column] 235 | rows: List[List[DbValue]] 236 | 237 | 238 | -------------------------------------------------------------------------------- /src/spin_sdk/wit/imports/redis.py: -------------------------------------------------------------------------------- 1 | from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self 2 | from types import TracebackType 3 | from enum import Flag, Enum, auto 4 | from dataclasses import dataclass 5 | from abc import abstractmethod 6 | import weakref 7 | 8 | from ..types import Result, Ok, Err, Some 9 | 10 | 11 | 12 | @dataclass 13 | class Error_InvalidAddress: 14 | pass 15 | 16 | 17 | @dataclass 18 | class Error_TooManyConnections: 19 | pass 20 | 21 | 22 | @dataclass 23 | class Error_TypeError: 24 | pass 25 | 26 | 27 | @dataclass 28 | class Error_Other: 29 | value: str 30 | 31 | 32 | Error = Union[Error_InvalidAddress, Error_TooManyConnections, Error_TypeError, Error_Other] 33 | """ 34 | Errors related to interacting with Redis 35 | """ 36 | 37 | 38 | 39 | @dataclass 40 | class RedisParameter_Int64: 41 | value: int 42 | 43 | 44 | @dataclass 45 | class RedisParameter_Binary: 46 | value: bytes 47 | 48 | 49 | RedisParameter = Union[RedisParameter_Int64, RedisParameter_Binary] 50 | """ 51 | A parameter type for the general-purpose `execute` function. 52 | """ 53 | 54 | 55 | 56 | @dataclass 57 | class RedisResult_Nil: 58 | pass 59 | 60 | 61 | @dataclass 62 | class RedisResult_Status: 63 | value: str 64 | 65 | 66 | @dataclass 67 | class RedisResult_Int64: 68 | value: int 69 | 70 | 71 | @dataclass 72 | class RedisResult_Binary: 73 | value: bytes 74 | 75 | 76 | RedisResult = Union[RedisResult_Nil, RedisResult_Status, RedisResult_Int64, RedisResult_Binary] 77 | """ 78 | A return type for the general-purpose `execute` function. 79 | """ 80 | 81 | 82 | class Connection: 83 | 84 | @classmethod 85 | def open(cls, address: str) -> Self: 86 | """ 87 | Open a connection to the Redis instance at `address`. 88 | 89 | Raises: `spin_sdk.wit.types.Err(spin_sdk.wit.imports.redis.Error)` 90 | """ 91 | raise NotImplementedError 92 | def publish(self, channel: str, payload: bytes) -> None: 93 | """ 94 | Publish a Redis message to the specified channel. 95 | 96 | Raises: `spin_sdk.wit.types.Err(spin_sdk.wit.imports.redis.Error)` 97 | """ 98 | raise NotImplementedError 99 | def get(self, key: str) -> Optional[bytes]: 100 | """ 101 | Get the value of a key. 102 | 103 | Raises: `spin_sdk.wit.types.Err(spin_sdk.wit.imports.redis.Error)` 104 | """ 105 | raise NotImplementedError 106 | def set(self, key: str, value: bytes) -> None: 107 | """ 108 | Set key to value. 109 | 110 | If key already holds a value, it is overwritten. 111 | 112 | Raises: `spin_sdk.wit.types.Err(spin_sdk.wit.imports.redis.Error)` 113 | """ 114 | raise NotImplementedError 115 | def incr(self, key: str) -> int: 116 | """ 117 | Increments the number stored at key by one. 118 | 119 | If the key does not exist, it is set to 0 before performing the operation. 120 | An `error::type-error` is returned if the key contains a value of the wrong type 121 | or contains a string that can not be represented as integer. 122 | 123 | Raises: `spin_sdk.wit.types.Err(spin_sdk.wit.imports.redis.Error)` 124 | """ 125 | raise NotImplementedError 126 | def del_(self, keys: List[str]) -> int: 127 | """ 128 | Removes the specified keys. 129 | 130 | A key is ignored if it does not exist. Returns the number of keys deleted. 131 | 132 | Raises: `spin_sdk.wit.types.Err(spin_sdk.wit.imports.redis.Error)` 133 | """ 134 | raise NotImplementedError 135 | def sadd(self, key: str, values: List[str]) -> int: 136 | """ 137 | Add the specified `values` to the set named `key`, returning the number of newly-added values. 138 | 139 | Raises: `spin_sdk.wit.types.Err(spin_sdk.wit.imports.redis.Error)` 140 | """ 141 | raise NotImplementedError 142 | def smembers(self, key: str) -> List[str]: 143 | """ 144 | Retrieve the contents of the set named `key`. 145 | 146 | Raises: `spin_sdk.wit.types.Err(spin_sdk.wit.imports.redis.Error)` 147 | """ 148 | raise NotImplementedError 149 | def srem(self, key: str, values: List[str]) -> int: 150 | """ 151 | Remove the specified `values` from the set named `key`, returning the number of newly-removed values. 152 | 153 | Raises: `spin_sdk.wit.types.Err(spin_sdk.wit.imports.redis.Error)` 154 | """ 155 | raise NotImplementedError 156 | def execute(self, command: str, arguments: List[RedisParameter]) -> List[RedisResult]: 157 | """ 158 | Execute an arbitrary Redis command and receive the result. 159 | 160 | Raises: `spin_sdk.wit.types.Err(spin_sdk.wit.imports.redis.Error)` 161 | """ 162 | raise NotImplementedError 163 | def __enter__(self) -> Self: 164 | """Returns self""" 165 | return self 166 | 167 | def __exit__(self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None) -> bool | None: 168 | """ 169 | Release this resource. 170 | """ 171 | raise NotImplementedError 172 | 173 | 174 | 175 | -------------------------------------------------------------------------------- /src/spin_sdk/wit/imports/redis_types.py: -------------------------------------------------------------------------------- 1 | from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self 2 | from types import TracebackType 3 | from enum import Flag, Enum, auto 4 | from dataclasses import dataclass 5 | from abc import abstractmethod 6 | import weakref 7 | 8 | from ..types import Result, Ok, Err, Some 9 | 10 | 11 | class Error(Enum): 12 | """ 13 | General purpose error. 14 | """ 15 | SUCCESS = 0 16 | ERROR = 1 17 | 18 | 19 | @dataclass 20 | class RedisParameter_Int64: 21 | value: int 22 | 23 | 24 | @dataclass 25 | class RedisParameter_Binary: 26 | value: bytes 27 | 28 | 29 | RedisParameter = Union[RedisParameter_Int64, RedisParameter_Binary] 30 | """ 31 | A parameter type for the general-purpose `execute` function. 32 | """ 33 | 34 | 35 | 36 | @dataclass 37 | class RedisResult_Nil: 38 | pass 39 | 40 | 41 | @dataclass 42 | class RedisResult_Status: 43 | value: str 44 | 45 | 46 | @dataclass 47 | class RedisResult_Int64: 48 | value: int 49 | 50 | 51 | @dataclass 52 | class RedisResult_Binary: 53 | value: bytes 54 | 55 | 56 | RedisResult = Union[RedisResult_Nil, RedisResult_Status, RedisResult_Int64, RedisResult_Binary] 57 | """ 58 | A return type for the general-purpose `execute` function. 59 | """ 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /src/spin_sdk/wit/imports/spin_postgres_postgres.py: -------------------------------------------------------------------------------- 1 | from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self 2 | from types import TracebackType 3 | from enum import Flag, Enum, auto 4 | from dataclasses import dataclass 5 | from abc import abstractmethod 6 | import weakref 7 | 8 | from ..types import Result, Ok, Err, Some 9 | 10 | 11 | 12 | @dataclass 13 | class Error_ConnectionFailed: 14 | value: str 15 | 16 | 17 | @dataclass 18 | class Error_BadParameter: 19 | value: str 20 | 21 | 22 | @dataclass 23 | class Error_QueryFailed: 24 | value: str 25 | 26 | 27 | @dataclass 28 | class Error_ValueConversionFailed: 29 | value: str 30 | 31 | 32 | @dataclass 33 | class Error_Other: 34 | value: str 35 | 36 | 37 | Error = Union[Error_ConnectionFailed, Error_BadParameter, Error_QueryFailed, Error_ValueConversionFailed, Error_Other] 38 | """ 39 | Errors related to interacting with a database. 40 | """ 41 | 42 | 43 | class DbDataType(Enum): 44 | """ 45 | Data types for a database column 46 | """ 47 | BOOLEAN = 0 48 | INT8 = 1 49 | INT16 = 2 50 | INT32 = 3 51 | INT64 = 4 52 | FLOATING32 = 5 53 | FLOATING64 = 6 54 | STR = 7 55 | BINARY = 8 56 | DATE = 9 57 | TIME = 10 58 | DATETIME = 11 59 | TIMESTAMP = 12 60 | OTHER = 13 61 | 62 | 63 | @dataclass 64 | class DbValue_Boolean: 65 | value: bool 66 | 67 | 68 | @dataclass 69 | class DbValue_Int8: 70 | value: int 71 | 72 | 73 | @dataclass 74 | class DbValue_Int16: 75 | value: int 76 | 77 | 78 | @dataclass 79 | class DbValue_Int32: 80 | value: int 81 | 82 | 83 | @dataclass 84 | class DbValue_Int64: 85 | value: int 86 | 87 | 88 | @dataclass 89 | class DbValue_Floating32: 90 | value: float 91 | 92 | 93 | @dataclass 94 | class DbValue_Floating64: 95 | value: float 96 | 97 | 98 | @dataclass 99 | class DbValue_Str: 100 | value: str 101 | 102 | 103 | @dataclass 104 | class DbValue_Binary: 105 | value: bytes 106 | 107 | 108 | @dataclass 109 | class DbValue_Date: 110 | value: Tuple[int, int, int] 111 | 112 | 113 | @dataclass 114 | class DbValue_Time: 115 | value: Tuple[int, int, int, int] 116 | 117 | 118 | @dataclass 119 | class DbValue_Datetime: 120 | value: Tuple[int, int, int, int, int, int, int] 121 | 122 | 123 | @dataclass 124 | class DbValue_Timestamp: 125 | value: int 126 | 127 | 128 | @dataclass 129 | class DbValue_DbNull: 130 | pass 131 | 132 | 133 | @dataclass 134 | class DbValue_Unsupported: 135 | pass 136 | 137 | 138 | DbValue = Union[DbValue_Boolean, DbValue_Int8, DbValue_Int16, DbValue_Int32, DbValue_Int64, DbValue_Floating32, DbValue_Floating64, DbValue_Str, DbValue_Binary, DbValue_Date, DbValue_Time, DbValue_Datetime, DbValue_Timestamp, DbValue_DbNull, DbValue_Unsupported] 139 | """ 140 | Database values 141 | """ 142 | 143 | 144 | 145 | @dataclass 146 | class ParameterValue_Boolean: 147 | value: bool 148 | 149 | 150 | @dataclass 151 | class ParameterValue_Int8: 152 | value: int 153 | 154 | 155 | @dataclass 156 | class ParameterValue_Int16: 157 | value: int 158 | 159 | 160 | @dataclass 161 | class ParameterValue_Int32: 162 | value: int 163 | 164 | 165 | @dataclass 166 | class ParameterValue_Int64: 167 | value: int 168 | 169 | 170 | @dataclass 171 | class ParameterValue_Floating32: 172 | value: float 173 | 174 | 175 | @dataclass 176 | class ParameterValue_Floating64: 177 | value: float 178 | 179 | 180 | @dataclass 181 | class ParameterValue_Str: 182 | value: str 183 | 184 | 185 | @dataclass 186 | class ParameterValue_Binary: 187 | value: bytes 188 | 189 | 190 | @dataclass 191 | class ParameterValue_Date: 192 | value: Tuple[int, int, int] 193 | 194 | 195 | @dataclass 196 | class ParameterValue_Time: 197 | value: Tuple[int, int, int, int] 198 | 199 | 200 | @dataclass 201 | class ParameterValue_Datetime: 202 | value: Tuple[int, int, int, int, int, int, int] 203 | 204 | 205 | @dataclass 206 | class ParameterValue_Timestamp: 207 | value: int 208 | 209 | 210 | @dataclass 211 | class ParameterValue_DbNull: 212 | pass 213 | 214 | 215 | ParameterValue = Union[ParameterValue_Boolean, ParameterValue_Int8, ParameterValue_Int16, ParameterValue_Int32, ParameterValue_Int64, ParameterValue_Floating32, ParameterValue_Floating64, ParameterValue_Str, ParameterValue_Binary, ParameterValue_Date, ParameterValue_Time, ParameterValue_Datetime, ParameterValue_Timestamp, ParameterValue_DbNull] 216 | """ 217 | Values used in parameterized queries 218 | """ 219 | 220 | 221 | @dataclass 222 | class Column: 223 | """ 224 | A database column 225 | """ 226 | name: str 227 | data_type: DbDataType 228 | 229 | @dataclass 230 | class RowSet: 231 | """ 232 | A set of database rows 233 | """ 234 | columns: List[Column] 235 | rows: List[List[DbValue]] 236 | 237 | class Connection: 238 | """ 239 | A connection to a postgres database. 240 | """ 241 | 242 | @classmethod 243 | def open(cls, address: str) -> Self: 244 | """ 245 | Open a connection to the Postgres instance at `address`. 246 | 247 | Raises: `spin_sdk.wit.types.Err(spin_sdk.wit.imports.spin_postgres_postgres.Error)` 248 | """ 249 | raise NotImplementedError 250 | def query(self, statement: str, params: List[ParameterValue]) -> RowSet: 251 | """ 252 | Query the database. 253 | 254 | Raises: `spin_sdk.wit.types.Err(spin_sdk.wit.imports.spin_postgres_postgres.Error)` 255 | """ 256 | raise NotImplementedError 257 | def execute(self, statement: str, params: List[ParameterValue]) -> int: 258 | """ 259 | Execute command to the database. 260 | 261 | Raises: `spin_sdk.wit.types.Err(spin_sdk.wit.imports.spin_postgres_postgres.Error)` 262 | """ 263 | raise NotImplementedError 264 | def __enter__(self) -> Self: 265 | """Returns self""" 266 | return self 267 | 268 | def __exit__(self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None) -> bool | None: 269 | """ 270 | Release this resource. 271 | """ 272 | raise NotImplementedError 273 | 274 | 275 | 276 | -------------------------------------------------------------------------------- /src/spin_sdk/wit/imports/sqlite.py: -------------------------------------------------------------------------------- 1 | from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self 2 | from types import TracebackType 3 | from enum import Flag, Enum, auto 4 | from dataclasses import dataclass 5 | from abc import abstractmethod 6 | import weakref 7 | 8 | from ..types import Result, Ok, Err, Some 9 | 10 | 11 | 12 | @dataclass 13 | class Error_NoSuchDatabase: 14 | pass 15 | 16 | 17 | @dataclass 18 | class Error_AccessDenied: 19 | pass 20 | 21 | 22 | @dataclass 23 | class Error_InvalidConnection: 24 | pass 25 | 26 | 27 | @dataclass 28 | class Error_DatabaseFull: 29 | pass 30 | 31 | 32 | @dataclass 33 | class Error_Io: 34 | value: str 35 | 36 | 37 | Error = Union[Error_NoSuchDatabase, Error_AccessDenied, Error_InvalidConnection, Error_DatabaseFull, Error_Io] 38 | """ 39 | The set of errors which may be raised by functions in this interface 40 | """ 41 | 42 | 43 | 44 | @dataclass 45 | class Value_Integer: 46 | value: int 47 | 48 | 49 | @dataclass 50 | class Value_Real: 51 | value: float 52 | 53 | 54 | @dataclass 55 | class Value_Text: 56 | value: str 57 | 58 | 59 | @dataclass 60 | class Value_Blob: 61 | value: bytes 62 | 63 | 64 | @dataclass 65 | class Value_Null: 66 | pass 67 | 68 | 69 | Value = Union[Value_Integer, Value_Real, Value_Text, Value_Blob, Value_Null] 70 | """ 71 | A single column's result from a database query 72 | """ 73 | 74 | 75 | @dataclass 76 | class RowResult: 77 | """ 78 | A set of values for each of the columns in a query-result 79 | """ 80 | values: List[Value] 81 | 82 | @dataclass 83 | class QueryResult: 84 | """ 85 | A result of a query 86 | """ 87 | columns: List[str] 88 | rows: List[RowResult] 89 | 90 | class Connection: 91 | """ 92 | A handle to an open sqlite instance 93 | """ 94 | 95 | @classmethod 96 | def open(cls, database: str) -> Self: 97 | """ 98 | Open a connection to a named database instance. 99 | 100 | If `database` is "default", the default instance is opened. 101 | 102 | `error::no-such-database` will be raised if the `name` is not recognized. 103 | 104 | Raises: `spin_sdk.wit.types.Err(spin_sdk.wit.imports.sqlite.Error)` 105 | """ 106 | raise NotImplementedError 107 | def execute(self, statement: str, parameters: List[Value]) -> QueryResult: 108 | """ 109 | Execute a statement returning back data if there is any 110 | 111 | Raises: `spin_sdk.wit.types.Err(spin_sdk.wit.imports.sqlite.Error)` 112 | """ 113 | raise NotImplementedError 114 | def __enter__(self) -> Self: 115 | """Returns self""" 116 | return self 117 | 118 | def __exit__(self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None) -> bool | None: 119 | """ 120 | Release this resource. 121 | """ 122 | raise NotImplementedError 123 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /src/spin_sdk/wit/imports/variables.py: -------------------------------------------------------------------------------- 1 | from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self 2 | from types import TracebackType 3 | from enum import Flag, Enum, auto 4 | from dataclasses import dataclass 5 | from abc import abstractmethod 6 | import weakref 7 | 8 | from ..types import Result, Ok, Err, Some 9 | 10 | 11 | 12 | @dataclass 13 | class Error_InvalidName: 14 | value: str 15 | 16 | 17 | @dataclass 18 | class Error_Undefined: 19 | value: str 20 | 21 | 22 | @dataclass 23 | class Error_Provider: 24 | value: str 25 | 26 | 27 | @dataclass 28 | class Error_Other: 29 | value: str 30 | 31 | 32 | Error = Union[Error_InvalidName, Error_Undefined, Error_Provider, Error_Other] 33 | """ 34 | The set of errors which may be raised by functions in this interface. 35 | """ 36 | 37 | 38 | 39 | def get(name: str) -> str: 40 | """ 41 | Get an application variable value for the current component. 42 | 43 | The name must match one defined in in the component manifest. 44 | 45 | Raises: `spin_sdk.wit.types.Err(spin_sdk.wit.imports.variables.Error)` 46 | """ 47 | raise NotImplementedError 48 | 49 | -------------------------------------------------------------------------------- /src/spin_sdk/wit/imports/wasi_config_store.py: -------------------------------------------------------------------------------- 1 | from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self 2 | from types import TracebackType 3 | from enum import Flag, Enum, auto 4 | from dataclasses import dataclass 5 | from abc import abstractmethod 6 | import weakref 7 | 8 | from ..types import Result, Ok, Err, Some 9 | 10 | 11 | 12 | @dataclass 13 | class Error_Upstream: 14 | value: str 15 | 16 | 17 | @dataclass 18 | class Error_Io: 19 | value: str 20 | 21 | 22 | Error = Union[Error_Upstream, Error_Io] 23 | """ 24 | An error type that encapsulates the different errors that can occur fetching configuration values. 25 | """ 26 | 27 | 28 | 29 | def get(key: str) -> Optional[str]: 30 | """ 31 | Gets a configuration value of type `string` associated with the `key`. 32 | 33 | The value is returned as an `option`. If the key is not found, 34 | `Ok(none)` is returned. If an error occurs, an `Err(error)` is returned. 35 | 36 | Raises: `spin_sdk.wit.types.Err(spin_sdk.wit.imports.wasi_config_store.Error)` 37 | """ 38 | raise NotImplementedError 39 | 40 | def get_all() -> List[Tuple[str, str]]: 41 | """ 42 | Gets a list of configuration key-value pairs of type `string`. 43 | 44 | If an error occurs, an `Err(error)` is returned. 45 | 46 | Raises: `spin_sdk.wit.types.Err(spin_sdk.wit.imports.wasi_config_store.Error)` 47 | """ 48 | raise NotImplementedError 49 | 50 | -------------------------------------------------------------------------------- /src/spin_sdk/wit/key-value.wit: -------------------------------------------------------------------------------- 1 | interface key-value { 2 | /// An open key-value store 3 | resource store { 4 | /// Open the store with the specified label. 5 | /// 6 | /// `label` must refer to a store allowed in the spin.toml manifest. 7 | /// 8 | /// `error::no-such-store` will be raised if the `label` is not recognized. 9 | open: static func(label: string) -> result; 10 | 11 | /// Get the value associated with the specified `key` 12 | /// 13 | /// Returns `ok(none)` if the key does not exist. 14 | get: func(key: string) -> result>, error>; 15 | 16 | /// Set the `value` associated with the specified `key` overwriting any existing value. 17 | set: func(key: string, value: list) -> result<_, error>; 18 | 19 | /// Delete the tuple with the specified `key` 20 | /// 21 | /// No error is raised if a tuple did not previously exist for `key`. 22 | delete: func(key: string) -> result<_, error>; 23 | 24 | /// Return whether a tuple exists for the specified `key` 25 | exists: func(key: string) -> result; 26 | 27 | /// Return a list of all the keys 28 | get-keys: func() -> result, error>; 29 | } 30 | 31 | /// The set of errors which may be raised by functions in this interface 32 | variant error { 33 | /// Too many stores have been opened simultaneously. Closing one or more 34 | /// stores prior to retrying may address this. 35 | store-table-full, 36 | 37 | /// The host does not recognize the store label requested. 38 | no-such-store, 39 | 40 | /// The requesting component does not have access to the specified store 41 | /// (which may or may not exist). 42 | access-denied, 43 | 44 | /// Some implementation-specific error has occurred (e.g. I/O) 45 | other(string) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/spin_sdk/wit/llm.wit: -------------------------------------------------------------------------------- 1 | // A WASI interface dedicated to performing inferencing for Large Language Models. 2 | interface llm { 3 | /// A Large Language Model. 4 | type inferencing-model = string; 5 | 6 | /// Inference request parameters 7 | record inferencing-params { 8 | /// The maximum tokens that should be inferred. 9 | /// 10 | /// Note: the backing implementation may return less tokens. 11 | max-tokens: u32, 12 | /// The amount the model should avoid repeating tokens. 13 | repeat-penalty: f32, 14 | /// The number of tokens the model should apply the repeat penalty to. 15 | repeat-penalty-last-n-token-count: u32, 16 | /// The randomness with which the next token is selected. 17 | temperature: f32, 18 | /// The number of possible next tokens the model will choose from. 19 | top-k: u32, 20 | /// The probability total of next tokens the model will choose from. 21 | top-p: f32 22 | } 23 | 24 | /// The set of errors which may be raised by functions in this interface 25 | variant error { 26 | model-not-supported, 27 | runtime-error(string), 28 | invalid-input(string) 29 | } 30 | 31 | /// An inferencing result 32 | record inferencing-result { 33 | /// The text generated by the model 34 | // TODO: this should be a stream 35 | text: string, 36 | /// Usage information about the inferencing request 37 | usage: inferencing-usage 38 | } 39 | 40 | /// Usage information related to the inferencing result 41 | record inferencing-usage { 42 | /// Number of tokens in the prompt 43 | prompt-token-count: u32, 44 | /// Number of tokens generated by the inferencing operation 45 | generated-token-count: u32 46 | } 47 | 48 | /// Perform inferencing using the provided model and prompt with the given optional params 49 | infer: func(model: inferencing-model, prompt: string, params: option) -> result; 50 | 51 | /// The model used for generating embeddings 52 | type embedding-model = string; 53 | 54 | /// Generate embeddings for the supplied list of text 55 | generate-embeddings: func(model: embedding-model, text: list) -> result; 56 | 57 | /// Result of generating embeddings 58 | record embeddings-result { 59 | /// The embeddings generated by the request 60 | embeddings: list>, 61 | /// Usage related to the embeddings generation request 62 | usage: embeddings-usage 63 | } 64 | 65 | /// Usage related to an embeddings generation request 66 | record embeddings-usage { 67 | /// Number of tokens in the prompt 68 | prompt-token-count: u32, 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/spin_sdk/wit/mqtt.wit: -------------------------------------------------------------------------------- 1 | interface mqtt { 2 | /// Errors related to interacting with Mqtt 3 | variant error { 4 | /// An invalid address string 5 | invalid-address, 6 | /// There are too many open connections 7 | too-many-connections, 8 | /// Connection failure e.g. address not allowed. 9 | connection-failed(string), 10 | /// Some other error occurred 11 | other(string), 12 | } 13 | 14 | /// QoS for publishing Mqtt messages 15 | enum qos { 16 | at-most-once, 17 | at-least-once, 18 | exactly-once, 19 | } 20 | 21 | resource connection { 22 | /// Open a connection to the Mqtt instance at `address`. 23 | open: static func(address: string, username: string, password: string, keep-alive-interval-in-secs: u64) -> result; 24 | 25 | /// Publish an Mqtt message to the specified `topic`. 26 | publish: func(topic: string, payload: payload, qos: qos) -> result<_, error>; 27 | } 28 | 29 | /// The message payload. 30 | type payload = list; 31 | } -------------------------------------------------------------------------------- /src/spin_sdk/wit/mysql.wit: -------------------------------------------------------------------------------- 1 | interface mysql { 2 | use rdbms-types.{parameter-value, row-set, error}; 3 | 4 | /// A connection to a MySQL database. 5 | resource connection { 6 | /// Open a connection to the MySQL instance at `address`. 7 | open: static func(address: string) -> result; 8 | 9 | /// query the database: select 10 | query: func(statement: string, params: list) -> result; 11 | 12 | /// execute command to the database: insert, update, delete 13 | execute: func(statement: string, params: list) -> result<_, error>; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/spin_sdk/wit/postgres.wit: -------------------------------------------------------------------------------- 1 | interface postgres { 2 | use rdbms-types.{parameter-value, row-set, error}; 3 | 4 | /// A connection to a postgres database. 5 | resource connection { 6 | /// Open a connection to the Postgres instance at `address`. 7 | open: static func(address: string) -> result; 8 | 9 | /// Query the database. 10 | query: func(statement: string, params: list) -> result; 11 | 12 | /// Execute command to the database. 13 | execute: func(statement: string, params: list) -> result; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/spin_sdk/wit/rdbms-types.wit: -------------------------------------------------------------------------------- 1 | interface rdbms-types { 2 | /// Errors related to interacting with a database. 3 | variant error { 4 | connection-failed(string), 5 | bad-parameter(string), 6 | query-failed(string), 7 | value-conversion-failed(string), 8 | other(string) 9 | } 10 | 11 | /// Data types for a database column 12 | enum db-data-type { 13 | boolean, 14 | int8, 15 | int16, 16 | int32, 17 | int64, 18 | uint8, 19 | uint16, 20 | uint32, 21 | uint64, 22 | floating32, 23 | floating64, 24 | str, 25 | binary, 26 | other, 27 | } 28 | 29 | /// Database values 30 | variant db-value { 31 | boolean(bool), 32 | int8(s8), 33 | int16(s16), 34 | int32(s32), 35 | int64(s64), 36 | uint8(u8), 37 | uint16(u16), 38 | uint32(u32), 39 | uint64(u64), 40 | floating32(f32), 41 | floating64(f64), 42 | str(string), 43 | binary(list), 44 | db-null, 45 | unsupported, 46 | } 47 | 48 | /// Values used in parameterized queries 49 | variant parameter-value { 50 | boolean(bool), 51 | int8(s8), 52 | int16(s16), 53 | int32(s32), 54 | int64(s64), 55 | uint8(u8), 56 | uint16(u16), 57 | uint32(u32), 58 | uint64(u64), 59 | floating32(f32), 60 | floating64(f64), 61 | str(string), 62 | binary(list), 63 | db-null, 64 | } 65 | 66 | /// A database column 67 | record column { 68 | name: string, 69 | data-type: db-data-type, 70 | } 71 | 72 | /// A database row 73 | type row = list; 74 | 75 | /// A set of database rows 76 | record row-set { 77 | columns: list, 78 | rows: list, 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/spin_sdk/wit/redis.wit: -------------------------------------------------------------------------------- 1 | interface redis { 2 | /// Errors related to interacting with Redis 3 | variant error { 4 | /// An invalid address string 5 | invalid-address, 6 | /// There are too many open connections 7 | too-many-connections, 8 | /// A retrieved value was not of the correct type 9 | type-error, 10 | /// Some other error occurred 11 | other(string), 12 | } 13 | 14 | resource connection { 15 | /// Open a connection to the Redis instance at `address`. 16 | open: static func(address: string) -> result; 17 | 18 | /// Publish a Redis message to the specified channel. 19 | publish: func(channel: string, payload: payload) -> result<_, error>; 20 | 21 | /// Get the value of a key. 22 | get: func(key: string) -> result, error>; 23 | 24 | /// Set key to value. 25 | /// 26 | /// If key already holds a value, it is overwritten. 27 | set: func(key: string, value: payload) -> result<_, error>; 28 | 29 | /// Increments the number stored at key by one. 30 | /// 31 | /// If the key does not exist, it is set to 0 before performing the operation. 32 | /// An `error::type-error` is returned if the key contains a value of the wrong type 33 | /// or contains a string that can not be represented as integer. 34 | incr: func(key: string) -> result; 35 | 36 | /// Removes the specified keys. 37 | /// 38 | /// A key is ignored if it does not exist. Returns the number of keys deleted. 39 | del: func(keys: list) -> result; 40 | 41 | /// Add the specified `values` to the set named `key`, returning the number of newly-added values. 42 | sadd: func(key: string, values: list) -> result; 43 | 44 | /// Retrieve the contents of the set named `key`. 45 | smembers: func(key: string) -> result, error>; 46 | 47 | /// Remove the specified `values` from the set named `key`, returning the number of newly-removed values. 48 | srem: func(key: string, values: list) -> result; 49 | 50 | /// Execute an arbitrary Redis command and receive the result. 51 | execute: func(command: string, arguments: list) -> result, error>; 52 | } 53 | 54 | /// The message payload. 55 | type payload = list; 56 | 57 | /// A parameter type for the general-purpose `execute` function. 58 | variant redis-parameter { 59 | int64(s64), 60 | binary(payload) 61 | } 62 | 63 | /// A return type for the general-purpose `execute` function. 64 | variant redis-result { 65 | nil, 66 | status(string), 67 | int64(s64), 68 | binary(payload) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/spin_sdk/wit/spin.wit: -------------------------------------------------------------------------------- 1 | package fermyon:spin@2.0.0; 2 | 3 | world spin-imports { 4 | import wasi:http/outgoing-handler@0.2.0; 5 | import llm; 6 | import redis; 7 | import postgres; 8 | import mqtt; 9 | import mysql; 10 | import sqlite; 11 | import key-value; 12 | import variables; 13 | } 14 | 15 | world spin-redis { 16 | include spin-imports; 17 | export fermyon:spin/inbound-redis; 18 | } 19 | 20 | world spin-http { 21 | include spin-imports; 22 | export wasi:http/incoming-handler@0.2.0; 23 | } 24 | 25 | world spin-all { 26 | include spin-redis; 27 | include spin-http; 28 | } 29 | -------------------------------------------------------------------------------- /src/spin_sdk/wit/sqlite.wit: -------------------------------------------------------------------------------- 1 | interface sqlite { 2 | /// A handle to an open sqlite instance 3 | resource connection { 4 | /// Open a connection to a named database instance. 5 | /// 6 | /// If `database` is "default", the default instance is opened. 7 | /// 8 | /// `error::no-such-database` will be raised if the `name` is not recognized. 9 | open: static func(database: string) -> result; 10 | 11 | /// Execute a statement returning back data if there is any 12 | execute: func(statement: string, parameters: list) -> result; 13 | } 14 | 15 | /// The set of errors which may be raised by functions in this interface 16 | variant error { 17 | /// The host does not recognize the database name requested. 18 | no-such-database, 19 | /// The requesting component does not have access to the specified database (which may or may not exist). 20 | access-denied, 21 | /// The provided connection is not valid 22 | invalid-connection, 23 | /// The database has reached its capacity 24 | database-full, 25 | /// Some implementation-specific error has occurred (e.g. I/O) 26 | io(string) 27 | } 28 | 29 | /// A result of a query 30 | record query-result { 31 | /// The names of the columns retrieved in the query 32 | columns: list, 33 | /// the row results each containing the values for all the columns for a given row 34 | rows: list, 35 | } 36 | 37 | /// A set of values for each of the columns in a query-result 38 | record row-result { 39 | values: list 40 | } 41 | 42 | /// A single column's result from a database query 43 | variant value { 44 | integer(s64), 45 | real(f64), 46 | text(string), 47 | blob(list), 48 | null 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/spin_sdk/wit/types.py: -------------------------------------------------------------------------------- 1 | from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self 2 | from types import TracebackType 3 | from enum import Flag, Enum, auto 4 | from dataclasses import dataclass 5 | from abc import abstractmethod 6 | import weakref 7 | 8 | 9 | S = TypeVar('S') 10 | @dataclass 11 | class Some(Generic[S]): 12 | value: S 13 | 14 | T = TypeVar('T') 15 | @dataclass 16 | class Ok(Generic[T]): 17 | value: T 18 | 19 | E = TypeVar('E') 20 | @dataclass(frozen=True) 21 | class Err(Generic[E], Exception): 22 | value: E 23 | 24 | Result = Union[Ok[T], Err[E]] 25 | -------------------------------------------------------------------------------- /src/spin_sdk/wit/variables.wit: -------------------------------------------------------------------------------- 1 | interface variables { 2 | /// Get an application variable value for the current component. 3 | /// 4 | /// The name must match one defined in in the component manifest. 5 | get: func(name: string) -> result; 6 | 7 | /// The set of errors which may be raised by functions in this interface. 8 | variant error { 9 | /// The provided variable name is invalid. 10 | invalid-name(string), 11 | /// The provided variable is undefined. 12 | undefined(string), 13 | /// A variables provider specific error has occurred. 14 | provider(string), 15 | /// Some implementation-specific error has occurred. 16 | other(string), 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /templates/http-py/content/.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | *.wasm 3 | .spin -------------------------------------------------------------------------------- /templates/http-py/content/README.md: -------------------------------------------------------------------------------- 1 | # A HTTP python component using componentize-py 2 | 3 | ## Installing the requirements 4 | 5 | To build the component, [`componentize-py`](https://pypi.org/project/componentize-py/) and [`spin-sdk`](https://pypi.org/project/spin-sdk/) are required. To install them, run: 6 | 7 | ```bash 8 | pip3 install -r requirements.txt 9 | ``` 10 | 11 | ## Building and Running 12 | 13 | ``` 14 | spin up --build 15 | ``` 16 | -------------------------------------------------------------------------------- /templates/http-py/content/app.py: -------------------------------------------------------------------------------- 1 | from spin_sdk.http import IncomingHandler, Request, Response 2 | 3 | class IncomingHandler(IncomingHandler): 4 | def handle_request(self, request: Request) -> Response: 5 | return Response( 6 | 200, 7 | {"content-type": "text/plain"}, 8 | bytes("Hello from Python!", "utf-8") 9 | ) 10 | -------------------------------------------------------------------------------- /templates/http-py/content/requirements.txt: -------------------------------------------------------------------------------- 1 | spin-sdk == 3.3.1 2 | componentize-py == 0.16.0 3 | -------------------------------------------------------------------------------- /templates/http-py/content/spin.toml: -------------------------------------------------------------------------------- 1 | spin_manifest_version = 2 2 | 3 | [application] 4 | authors = ["{{authors}}"] 5 | description = "{{project-description}}" 6 | name = "{{project-name}}" 7 | version = "0.1.0" 8 | 9 | [[trigger.http]] 10 | route = "{{http-path}}" 11 | component = "{{project-name | kebab_case}}" 12 | 13 | [component.{{project-name | kebab_case}}] 14 | source = "app.wasm" 15 | [component.{{project-name | kebab_case}}.build] 16 | command = "componentize-py -w spin-http componentize app -o app.wasm" 17 | watch = ["*.py", "requirements.txt"] 18 | -------------------------------------------------------------------------------- /templates/http-py/metadata/snippets/component.txt: -------------------------------------------------------------------------------- 1 | [[trigger.http]] 2 | route = "{{http-path}}" 3 | component = "{{project-name | kebab_case}}" 4 | 5 | [component.{{project-name | kebab_case}}] 6 | source = "{{ output-path }}/app.wasm" 7 | [component.{{project-name | kebab_case}}.build] 8 | command = "componentize-py -w spin-http componentize app -o app.wasm" 9 | workdir = "{{ output-path }}" 10 | watch = ["*.py", "requirements.txt"] 11 | -------------------------------------------------------------------------------- /templates/http-py/metadata/spin-template.toml: -------------------------------------------------------------------------------- 1 | manifest_version = "1" 2 | id = "http-py" 3 | description = "HTTP request handler using Python" 4 | tags = ["http", "python", "py"] 5 | 6 | [add_component] 7 | skip_files = ["spin.toml"] 8 | [add_component.snippets] 9 | component = "component.txt" 10 | 11 | [parameters] 12 | project-description = { type = "string", prompt = "Description", default = "" } 13 | http-path = { type = "string", prompt = "HTTP path", default = "/...", pattern = "^/\\S*$" } 14 | --------------------------------------------------------------------------------