diff --git a/bindgen-test.sh b/bindgen-test.sh index 32257d3..6d73731 100755 --- a/bindgen-test.sh +++ b/bindgen-test.sh @@ -33,6 +33,6 @@ case $1 in echo "building '$PLUGIN_NAME'..." xtp plugin build --path $PLUGIN_NAME echo "testing '$PLUGIN_NAME'..." - xtp plugin test $PLUGIN_NAME/plugin.wasm --with test.wasm --mock-host mock.wasm + EXTISM_ENABLE_WASI_OUTPUT=1 xtp plugin test $PLUGIN_NAME/plugin.wasm --with test.wasm --mock-host mock.wasm ;; esac diff --git a/src/index.ts b/src/index.ts index 19c6376..98c7d0b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,7 +6,8 @@ import { EnumType, ArrayType, XtpNormalizedType, - XtpTyped + XtpTyped, + Parameter } from "@dylibso/xtp-bindgen" function pythonTypeName(s: string): string { @@ -78,6 +79,14 @@ function toPythonParamType(property: XtpTyped, required?: boolean): string { return t; } +function toPythonHostParamType(param: Parameter): string { + if (param.contentType === 'application/x-binary') { + return 'bytes' + } else { + return 'str' + } +} + export function render() { const tmpl = Host.inputString(); const ctx = { @@ -86,7 +95,8 @@ export function render() { toPythonType, toPythonParamType, pythonTypeName, - pythonFunctionName + pythonFunctionName, + toPythonHostParamType }; const output = ejs.render(tmpl, ctx); diff --git a/template/plugin/__init__.py.ejs b/template/plugin/__init__.py.ejs index ae0dcf9..a3daf91 100644 --- a/template/plugin/__init__.py.ejs +++ b/template/plugin/__init__.py.ejs @@ -2,6 +2,7 @@ from typing import Optional, List # noqa: F401 from datetime import datetime # noqa: F401 +import json import extism # pyright: ignore import plugin @@ -21,7 +22,7 @@ from pdk_types import <%- Object.values(schema.schemas).map(schema => pythonType # And it returns an output <%- toPythonParamType(imp.output) %> (<%- formatCommentLine(imp.output.description) %>) <% } -%> @extism.import_fn("extism:host/user", "<%- imp.name %>") -def <%- pythonFunctionName(imp.name) %>(<% if (imp.input) { -%>input: <%- toPythonParamType(imp.input) %><%} -%>) <% if (imp.output) { %> -> <%- toPythonParamType(imp.output) %><% } %>: # pyright: ignore [reportReturnType] +def _<%- pythonFunctionName(imp.name) %>(<% if (imp.input) { -%>input: <%- toPythonHostParamType(imp.input) %> <%} -%>) <% if (imp.output) { %> -> <%- toPythonHostParamType(imp.output) %> <% } %>: # pyright: ignore [reportReturnType] pass <% }) %> @@ -34,7 +35,28 @@ def <%- pythonFunctionName(imp.name) %>(<% if (imp.input) { -%>input: <%- toPyth <% } -%> @extism.plugin_fn def <%- ex.name %>(): - res = plugin.<%- pythonFunctionName(ex.name) %>(<% if (ex.input) { %> extism.input(<%- toPythonParamType(ex.input) %>) <% } %>) - extism.output(res) - -<% }) %> +<% if (ex.input) { -%> + input = <% if (ex.input.contentType === 'application/x-binary') { %> extism.input_bytes() <% } else { %> extism.input_str() <% } %> +<% if (ex.input.contentType === 'application/json') { -%> +<% if (ex.input.xtpType.kind === 'object') { -%> + input = <%- toPythonParamType(ex.input) %>.from_json(input) +<% } else { -%> + input = json.loads(input) +<% } -%> +<% } -%> +<% } -%> + res = plugin.<%- pythonFunctionName(ex.name) %>(<% if (ex.input) { %> input <% } %>) +<% if (ex.output) { -%> +<% if (ex.output.contentType === 'application/x-binary') { %> extism.output_bytes( <% } else { %> extism.output_str( <% } %> +<% if (ex.output.contentType === 'application/json') { -%> +<% if (ex.output.xtpType.kind === 'object') { -%> + res.to_json() +<% } else { -%> + json.dumps(res) +<% } %> +<% } else { -%> + res +<% } -%> + ) +<% } -%> +<% }) -%> diff --git a/template/plugin/pdk_imports.py.ejs b/template/plugin/pdk_imports.py.ejs index 59ac0f2..8b35130 100644 --- a/template/plugin/pdk_imports.py.ejs +++ b/template/plugin/pdk_imports.py.ejs @@ -3,6 +3,7 @@ from typing import Optional, List # noqa: F401 from datetime import datetime # noqa: F401 import extism # noqa: F401 # pyright: ignore +import json <% if (Object.values(schema.schemas).length > 0) { %> from pdk_types import <%- Object.values(schema.schemas).map(schema => pythonTypeName(schema.name)).join(", ") %> # noqa: F401 @@ -18,8 +19,39 @@ from pdk_types import <%- Object.values(schema.schemas).map(schema => pythonType # And it returns an output <%- toPythonParamType(imp.output) %> (<%- formatCommentLine(imp.output.description) %>) <% } -%> @extism.import_fn("extism:host/user", "<%- imp.name %>") -def <%- pythonFunctionName(imp.name) %>(<% if (imp.input) { -%>input: <%- toPythonParamType(imp.input) %><%} -%>) <% if (imp.output) { %> -> <%- toPythonParamType(imp.output) %><% } %>: # pyright: ignore [reportReturnType] +def _<%- pythonFunctionName(imp.name) %>(<% if (imp.input) { -%>input: <%- toPythonHostParamType(imp.input) %> <%} -%>) <% if (imp.output) { %> -> <%- toPythonHostParamType(imp.output) %> <% } %>: # pyright: ignore [reportReturnType] pass + +def <%- pythonFunctionName(imp.name) %>(<% if (imp.input) { -%>input: <%- toPythonParamType(imp.input) %><%} -%>) <% if (imp.output) { %> -> <%- toPythonParamType(imp.output) %><% } %>: # pyright: ignore [reportReturnType] +<% if (imp.output) { %> + return ( +<% if (imp.output.contentType === 'application/json') { %> +<% if (imp.output.xtpType.kind === 'object') { %> + <%- toPythonParamType(imp.output) %>.from_json( +<% } else { %> + json.loads( +<% } -%> +<% } -%> +<% } -%> + _<%- pythonFunctionName(imp.name) %>( +<% if (imp.input) { -%> +<% if (imp.input.contentType === 'application/json') { %> +<% if (imp.input.xtpType.kind === 'object') { %> + input.to_json() +<% } else { %> + json.dumps(input) +<% } -%> +<% } else { %> + input +<% } -%> +<% } -%> + ) +<% if (imp.output) { %> + ) +<% if (imp.output.contentType === 'application/json') { %> + ) +<% } -%> +<% } -%> <% }) %> diff --git a/template/plugin/pdk_types.py.ejs b/template/plugin/pdk_types.py.ejs index e6f24e7..edea53e 100644 --- a/template/plugin/pdk_types.py.ejs +++ b/template/plugin/pdk_types.py.ejs @@ -5,8 +5,9 @@ from enum import Enum # noqa: F401 from typing import Optional, List # noqa: F401 from datetime import datetime # noqa: F401 from dataclasses import dataclass # noqa: F401 - -import extism # noqa: F401 # pyright: ignore +from dataclass_wizard import JSONWizard # noqa: F401 +from dataclass_wizard.type_def import JSONObject +from base64 import b64encode, b64decode <% Object.values(schema.schemas).forEach(schema => { %> <% if (schema.enum) { %> @@ -16,7 +17,7 @@ class <%- pythonTypeName(schema.name) %>(Enum): <% }) %> <% } else { %> @dataclass -class <%- pythonTypeName(schema.name) %>(extism.Json): +class <%- pythonTypeName(schema.name) %>(JSONWizard): <% schema.properties.forEach(p => { -%> <% if (!p.nullable && p.required) {%> <% if (p.description) { -%> @@ -35,6 +36,23 @@ class <%- pythonTypeName(schema.name) %>(extism.Json): <% } %> <% }) %> + @classmethod + def _pre_from_dict(cls, o: JSONObject) -> JSONObject: +<% schema.properties.forEach(p => { -%> +<% if (p.xtpType.kind === 'buffer') {%> + o['<%- p.name %>'] = b64decode(o['<%- p.name %>']) +<% } -%> +<% }) -%> + return o + + def _pre_dict(self): +<% schema.properties.forEach(p => { -%> +<% if (p.xtpType.kind === 'buffer') {%> + self.<%- p.name %> = b64encode(self.<%- p.name %>).decode("utf-8") +<% } -%> +<% }) -%> + return + <% } %> <% }); %> diff --git a/template/prepare.sh b/template/prepare.sh index 546e38f..8411ac7 100644 --- a/template/prepare.sh +++ b/template/prepare.sh @@ -40,5 +40,3 @@ if ! command_exists extism-py; then sleep 2 exit 1 fi - - diff --git a/template/pyproject.toml.ejs b/template/pyproject.toml.ejs index 158682a..68d8d17 100644 --- a/template/pyproject.toml.ejs +++ b/template/pyproject.toml.ejs @@ -3,8 +3,10 @@ name = "<%= project.name %>" version = "0.1.0" description = "Add your description here" readme = "README.md" -requires-python = ">=3.12" -dependencies = [] +requires-python = ">=3.13,<3.14" +dependencies = [ + "dataclass-wizard>=0.33.0,<0.34.0" +] [tool.uv] dev-dependencies = [ diff --git a/template/xtp.toml.ejs b/template/xtp.toml.ejs index 2ff3cad..5b6ed23 100644 --- a/template/xtp.toml.ejs +++ b/template/xtp.toml.ejs @@ -8,7 +8,7 @@ name = "<%= project.name %>" [scripts] # xtp plugin build runs this script to generate the wasm file -build = "PYTHONPATH=./plugin extism-py -o plugin.wasm plugin/__init__.py" +build = "PYTHONPATH=./plugin:./.venv/lib/python3.13/site-packages extism-py -o plugin.wasm plugin/__init__.py" # xtp plugin init runs this script to format the code format = "uv run ruff format plugin/*.py"