Usage
This guide will walk you through Extism concepts using the Python SDK.
Installation
To use extism, first install it using pip:
(.venv) $ pip install extism
Getting Started
To start, let’s use a pre-existing WebAssembly module. We’ll define a Manifest that pulls a pre-compiled Wasm module down from the web. In this case, we’ll start with a vowel counting module:
1import extism
2import json
3
4manifest = {
5 "wasm": [
6 {
7 "url": "https://github.com/extism/plugins/releases/download/v0.3.0/count_vowels.wasm",
8 # Note the hash! We use this to ensure that the remote file matches what we expect.
9 # If the remote version changes, the plugin will raise an `extism.Error` when we try
10 # to construct it.
11 "hash": "cf29364cb62d3bc4de8654b187fae9cf50174634760eb995b1745650e7a38b41"
12 }
13 ]
14}
15with extism.Plugin(manifest, wasi=True) as plugin:
16 ...
Let’s call a Wasm function from the module. Our example module exports a single
guest function, count_vowels
:
15with extism.Plugin(manifest, wasi=True) as plugin:
16 wasm_vowel_count = plugin.call(
17 "count_vowels",
18 "hello world",
19 )
20 print(wasm_vowel_count) # b'{"count": 3, "total": 3, "vowels": "aeiouAEIOU"}'
All Extism guest functions have a simple “bytes-in”, “bytes-out” interface. In this case,
the input is a utf-8 encoded string, and the output is a utf-8 encoded string containing
JSON. We can pass a parse
parameter to call
to automatically decode the output for
us:
15with extism.Plugin(manifest, wasi=True) as plugin:
16
17 wasm_vowel_count = plugin.call(
18 "count_vowels",
19 "hello world",
20 parse = lambda output: json.loads(
21 bytes(output).decode('utf-8')
22 )
23 )
24 print(wasm_vowel_count) # {'count': 3, 'total': 3, 'vowels': 'aeiouAEIOU'}
State and Configuration
Plugins may keep track of their own state:
15with extism.Plugin(manifest, wasi=True) as plugin:
16 [print(plugin.call(
17 "count_vowels",
18 "hello world",
19 parse = lambda output: json.loads(
20 bytes(output).decode('utf-8')
21 )
22 )['total']) for _ in range(0, 3)] # prints 3, 6, 9
23
24# if we re-instantiate the plugin, however, we reset the count:
25with extism.Plugin(manifest, wasi=True) as plugin:
26 [print(plugin.call(
27 "count_vowels",
28 "hello world",
29 parse = lambda output: json.loads(
30 bytes(output).decode('utf-8')
31 )
32 )['total']) for _ in range(0, 3)] # prints 3, 6, 9 all over again
We can also statically configure values for use by the guest:
15with extism.Plugin(manifest, wasi=True) as plugin:
16 print(plugin.call(
17 "count_vowels",
18 "hello world",
19 config = {'vowels': 'h'}
20 parse = lambda output: json.loads(
21 bytes(output).decode('utf-8')
22 )
23 )['count']) # 1
This example:
Downloads Wasm from the web,
Verifies that the Wasm matches a hash before running it,
And executes a function exported from that module from Python.
Host Functions
What if you want your guest plugin to communicate with your host? Extism has your back.
Let’s take a look at a slightly more advanced example. This time, we’re going to call a guest function in a locally defined module in WebAssembly Text Format (AKA “WAT” format), that guest function is going to call a host function, and then the guest function will return the result.
Lets take a look at a new example.
1from extism import host_fn, Plugin
2
3# Our host function. Note the type annotations -- these allow extism to
4# automatically generate Wasm type bindings for the function!
5@host_fn()
6def hello_world(input: str) -> str:
7 print(input)
8 return "Hello from Python!"
9
10with Plugin(open("guest.wat", "rb").read()) as plugin:
11 plugin.call("my_func", b"")
We’ve defined a host function that takes a string and returns a string. If you’re interested in the guest plugin code, check it out below.
The extism.host_fn()
decorator registers our Python function with
extism
, inferring the name we want to use from the function name. Because
hello_world
has type annotations, extism.host_fn()
automatically
created appropriate low-level Wasm bindings for us. We can take this a step
further, though: if we expect to be called with JSON data, we can indicate
as much:
1from extism import host_fn, Plugin, Json
2from typing import Annotated
3
4# Our host function. Note the type annotations -- these allow extism to
5# automatically generate Wasm type bindings for the function!
6@host_fn()
7def hello_world(input: Annotated[dict, Json]) -> str:
8 print(input['message'])
9 return "Hello from Python!"
10
11with Plugin(open("guest.wat", "rb").read()) as plugin:
12 plugin.call("my_func", b"")
Guest Code
Tip
If the WebAssembly here looks intimidating, check out Extism Plugin Dev Kits (PDKs), which allow you to write code in your language of choice.
1(module
2 (; code between winking-emoji parenthesis is a WAT comment! ;)
3
4 (import "env" "hello_world" (func $hello (param i64) (result i64)))
5 (import "env" "extism_alloc" (func $extism_alloc (param i64) (result i64)))
6 (import "env" "extism_store_u8" (func $extism_store_u8 (param i64 i32)))
7 (import "env" "extism_output_set" (func $extism_output_set (param i64 i64)))
8 (import "env" "extism_length" (func $extism_length (param i64) (result i64)))
9
10 (; store a string to send to the host. ;)
11 (memory $memory (export "mem")
12 (data "{\"message\": \"Hello from WAT!\"}\00")
13 )
14 (func $my_func (result i64)
15 (local $result i64)
16 (local $offset i64)
17 (local $i i32)
18 (local.set $offset (call $extism_alloc (i64.const 15)))
19
20 (;
21 You can read this as:
22
23 for(i=0; memory[i] != 0; ++i) {
24 extism_store_u8(offset + i, memory[i])
25 }
26
27 We're copying our local string into extism memory to transport it to the host.
28 ;)
29 (block $end
30 (loop $loop
31 (br_if $end (i32.eq (i32.const 0) (i32.load8_u (local.get $i))))
32
33 (call $extism_store_u8
34 (i64.add
35 (local.get $offset)
36 (i64.extend_i32_u (local.get $i))
37 )
38 (i32.load8_u (local.get $i))
39 )
40
41 (local.set $i (i32.add (i32.const 1) (local.get $i)))
42 br $loop
43 )
44 )
45
46 (; call the host and store the resulting offset into extism memory in a local variable. ;)
47 local.get $offset
48 (local.set $result (call $hello (local.get $offset)))
49
50 (;
51 now tell extism we want to use that offset as our output memory. extism tracks the extent
52 of that memory, so we can call extism_length() to get that data.
53 ;)
54 (call $extism_output_set
55 (local.get $result)
56 (call $extism_length (local.get $result))
57 )
58 )
59 (export "my_func" (func $my_func))
60)