Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions packages/polywrap-client/polywrap_client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
UriPackage,
UriPackageOrWrapper,
UriResolutionContext,
InterfaceImplementations,
Wrapper,
)
from polywrap_msgpack import msgpack_decode, msgpack_encode
Expand Down Expand Up @@ -53,6 +54,17 @@ def get_uri_resolver(
def get_envs(self, options: Optional[GetEnvsOptions] = None) -> List[Env]:
return self._config.envs

def get_interfaces(self) -> List[InterfaceImplementations]:
return self._config.interfaces

def get_implementations(self, uri: Uri) -> Result[List[Uri], Exception]:
if interface_implementations := next(
filter(lambda x: x.interface == uri, self._config.interfaces), None
):
return Ok(interface_implementations.implementations)
else:
return Err(ValueError(f"Unable to find implementations for uri: {uri}"))

def get_env_by_uri(
self, uri: Uri, options: Optional[GetEnvsOptions] = None
) -> Union[Env, None]:
Expand All @@ -62,7 +74,9 @@ async def get_file(self, uri: Uri, options: GetFileOptions) -> Union[bytes, str]
loaded_wrapper = (await self.load_wrapper(uri)).unwrap()
return await loaded_wrapper.get_file(options)

async def get_manifest(self, uri: Uri, options: Optional[GetManifestOptions] = None) -> AnyWrapManifest:
async def get_manifest(
self, uri: Uri, options: Optional[GetManifestOptions] = None
) -> AnyWrapManifest:
loaded_wrapper = (await self.load_wrapper(uri)).unwrap()
return loaded_wrapper.get_manifest()

Expand Down Expand Up @@ -124,4 +138,5 @@ async def invoke(self, options: InvokerOptions) -> InvokeResult:
return result

except Exception as e:
return InvokeResult(result=None, error=e)
raise e
# return InvokeResult(result=None, error=e)
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
42 changes: 34 additions & 8 deletions packages/polywrap-client/tests/test_client.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from pathlib import Path

from polywrap_client import PolywrapClient
from polywrap_core import Uri, InvokerOptions
from polywrap_core import Uri, InvokerOptions, InterfaceImplementations
from polywrap_uri_resolvers import BaseUriResolver, SimpleFileReader

from polywrap_client.client import PolywrapClientConfig
Expand Down Expand Up @@ -31,17 +31,43 @@ async def test_subinvoke():
},
)

client = PolywrapClient(config=PolywrapClientConfig(
envs=[], resolver=uri_resolver
))
client = PolywrapClient(config=PolywrapClientConfig(envs=[], resolver=uri_resolver))
uri = Uri(
f'fs/{Path(__file__).parent.joinpath("cases", "simple-subinvoke", "invoke").absolute()}'
)
args = b'\x82\xa1a\x01\xa1b\x02'
options = InvokerOptions(
uri=uri, method="add", args=args, encode_result=False
)
args = {"a": 1, "b": 2}
options = InvokerOptions(uri=uri, method="add", args=args, encode_result=False)
result = await client.invoke(options)

assert result.result == "1 + 2 = 3"


async def test_interface_implementation():
uri_resolver = BaseUriResolver(
file_reader=SimpleFileReader(),
redirects={},
)

impl_uri = Uri(
f'fs/{Path(__file__).parent.joinpath("cases", "simple-interface", "implementation").absolute()}'
)

client = PolywrapClient(
config=PolywrapClientConfig(
envs=[],
resolver=uri_resolver,
interfaces=[
InterfaceImplementations(
interface=Uri("ens/interface.eth"), implementations=[impl_uri]
)
],
)
)
uri = Uri(
f'fs/{Path(__file__).parent.joinpath("cases", "simple-interface", "wrapper").absolute()}'
)
args = {"arg": {"str": "hello", "uint8": 2}}
options = InvokerOptions(uri=uri, method="moduleMethod", args=args, encode_result=False)
result = await client.invoke(options)

assert result.result == {"str": "hello", "uint8": 2}
6 changes: 6 additions & 0 deletions packages/polywrap-core/polywrap_core/types/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

from polywrap_manifest import DeserializeManifestOptions, AnyWrapManifest

from .interface_implementation import InterfaceImplementations
from .env import Env
from .invoke import Invoker
from .uri import Uri
Expand All @@ -16,6 +17,7 @@
@dataclass(slots=True, kw_only=True)
class ClientConfig:
envs: List[Env] = field(default_factory=list)
interfaces: List[InterfaceImplementations] = field(default_factory=list)
resolver: IUriResolver


Expand All @@ -41,6 +43,10 @@ class GetManifestOptions(DeserializeManifestOptions):


class Client(Invoker, UriResolverHandler):
@abstractmethod
def get_interfaces(self) -> List[InterfaceImplementations]:
pass

@abstractmethod
def get_envs(self, options: Optional[GetEnvsOptions] = None) -> List[Env]:
pass
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from dataclasses import dataclass
from typing import List

from .uri import Uri

@dataclass(slots=True, kw_only=True)
class InterfaceImplementations:
interface: Uri
implementations: List[Uri]
8 changes: 7 additions & 1 deletion packages/polywrap-core/polywrap_core/types/invoke.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from typing import Any, Dict, Optional, Union
from typing import Any, Dict, List, Optional, Union

from result import Result

from .uri import Uri
from .uri_resolution_context import IUriResolutionContext
Expand Down Expand Up @@ -57,6 +59,10 @@ class Invoker(ABC):
async def invoke(self, options: InvokerOptions) -> InvokeResult:
pass

@abstractmethod
def get_implementations(self, uri: Uri) -> Result[List[Uri], Exception]:
pass


class Invocable(ABC):
@abstractmethod
Expand Down
152 changes: 152 additions & 0 deletions packages/polywrap-wasm/polywrap_wasm/imports.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
from typing import List
from polywrap_core import Invoker, InvokeResult, InvokerOptions, Uri
from polywrap_msgpack import msgpack_encode
from unsync import Unfuture, unsync
from wasmtime import (
FuncType,
Expand All @@ -11,6 +13,7 @@
Store,
ValType,
)
from result import Err

from .buffer import read_bytes, read_string, write_bytes, write_string
from .errors import WasmAbortError
Expand All @@ -36,6 +39,18 @@ def create_instance(
"""
mem = Memory(store, MemoryType(Limits(1, None)))

wrap_debug_log_type = FuncType(
[
ValType.i32(),
ValType.i32()
],
[],
)

def wrap_debug_log(ptr: int, len: int) -> None:
msg = read_string(mem.data_ptr(store), mem.data_len(store), ptr, len)
print(msg)

wrap_abort_type = FuncType(
[
ValType.i32(),
Expand Down Expand Up @@ -116,6 +131,9 @@ def wrap_subinvoke(
args_ptr: int,
args_len: int,
) -> bool:
state.subinvoke["result"] = None
state.subinvoke["error"] = None

uri = read_string(mem.data_ptr(store), mem.data_len(store), uri_ptr, uri_len)
method = read_string(
mem.data_ptr(store), mem.data_len(store), method_ptr, method_len
Expand Down Expand Up @@ -173,15 +191,149 @@ def wrap_subinvoke_error(ptr: int) -> None:
mem.data_ptr(store), mem.data_len(store), state.subinvoke["error"], ptr
)

wrap_subinvoke_implementation_type = FuncType(
[
ValType.i32(),
ValType.i32(),
ValType.i32(),
ValType.i32(),
ValType.i32(),
ValType.i32(),
ValType.i32(),
ValType.i32(),
],
[ValType.i32()],
)

def wrap_subinvoke_implementation(
interface_uri_ptr: int,
interface_uri_len: int,
impl_uri_ptr: int,
impl_uri_len: int,
method_ptr: int,
method_len: int,
args_ptr: int,
args_len: int
) -> bool:
state.subinvoke_implementation["result"] = None
state.subinvoke_implementation["error"] = None

interface_uri = read_string(mem.data_ptr(store), mem.data_len(store), interface_uri_ptr, interface_uri_len)
impl_uri = read_string(mem.data_ptr(store), mem.data_len(store), impl_uri_ptr, impl_uri_len)
method = read_string(
mem.data_ptr(store), mem.data_len(store), method_ptr, method_len
)
args = read_bytes(mem.data_ptr(store), mem.data_len(store), args_ptr, args_len)

unfuture_result: Unfuture[InvokeResult] = unsync_invoke(
invoker,
InvokerOptions(uri=Uri(impl_uri), method=method, args=args, encode_result=True),
)
result = unfuture_result.result()

if result.result:
state.subinvoke_implementation["result"] = result.result
return True
elif result.error:
state.subinvoke_implementation["error"] = "".join(str(x) for x in result.error.args)
return False
else:
raise ValueError(f"interface implementation subinvoke failed for uri: {interface_uri}!")

wrap_subinvoke_implementation_result_len_type = FuncType([], [ValType.i32()])

def wrap_subinvoke_implementation_result_len() -> int:
if not state.subinvoke_implementation["result"]:
raise WasmAbortError(
"__wrap_subinvoke_implementation_result_len: subinvoke_implementation.result is not set"
)
return len(state.subinvoke_implementation["result"])

wrap_subinvoke_implementation_result_type = FuncType([ValType.i32()], [])

def wrap_subinvoke_implementation_result(ptr: int) -> None:
if not state.subinvoke_implementation["result"]:
raise WasmAbortError("__wrap_subinvoke_implementation_result: subinvoke_implementation.result is not set")
write_bytes(
mem.data_ptr(store), mem.data_len(store), state.subinvoke_implementation["result"], ptr
)

wrap_subinvoke_implementation_error_len_type = FuncType([], [ValType.i32()])

def wrap_subinvoke_implementation_error_len() -> int:
if not state.subinvoke_implementation["error"]:
raise WasmAbortError(
"__wrap_subinvoke_implementation_error_len: subinvoke_implementation.error is not set"
)
return len(state.subinvoke_implementation["error"])

wrap_subinvoke_implementation_error_type = FuncType([ValType.i32()], [])

def wrap_subinvoke_implementation_error(ptr: int) -> None:
if not state.subinvoke_implementation["error"]:
raise WasmAbortError("__wrap_subinvoke_implementation_error: subinvoke_implementation.error is not set")
write_string(
mem.data_ptr(store), mem.data_len(store), state.subinvoke_implementation["error"], ptr
)

wrap_get_implementations_type = FuncType([ValType.i32(), ValType.i32()], [ValType.i32()])

def wrap_get_implementations(uri_ptr: int, uri_len: int) -> bool:
uri = read_string(mem.data_ptr(store), mem.data_len(store), uri_ptr, uri_len)
result = invoker.get_implementations(uri=Uri(uri))
if isinstance(result, Err):
raise WasmAbortError(" ".join(result.unwrap_err().args))
implementations: List[str] = [uri.uri for uri in result.unwrap()]
state.get_implementations_result = msgpack_encode(implementations)
return len(implementations) > 0

wrap_get_implementations_result_len_type = FuncType([], [ValType.i32()])

def wrap_get_implementations_result_len() -> int:
if not state.get_implementations_result:
raise WasmAbortError(
"__wrap_get_implementations_result_len: get_implementations_result is not set"
)
return len(state.get_implementations_result)

wrap_get_implementations_result_type = FuncType([ValType.i32()], [])

def wrap_get_implementations_result(ptr: int) -> None:
if not state.get_implementations_result:
raise WasmAbortError("__wrap_get_implementations_result: get_implementations_result is not set")
write_bytes(
mem.data_ptr(store), mem.data_len(store), state.get_implementations_result, ptr
)


# TODO: use generics or any on wasmtime codebase to fix typings
linker.define_func("wrap", "__wrap_debug_log", wrap_debug_log_type, wrap_debug_log) # type: ignore partially unknown
linker.define_func("wrap", "__wrap_abort", wrap_abort_type, wrap_abort) # type: ignore partially unknown

# invoke
linker.define_func("wrap", "__wrap_invoke_args", wrap_invoke_args_type, wrap_invoke_args) # type: ignore partially unknown
linker.define_func("wrap", "__wrap_invoke_result", wrap_invoke_result_type, wrap_invoke_result) # type: ignore partially unknown
linker.define_func("wrap", "__wrap_invoke_error", wrap_invoke_error_type, wrap_invoke_error) # type: ignore partially unknown

# subinvoke
linker.define_func("wrap", "__wrap_subinvoke", wrap_subinvoke_type, wrap_subinvoke) # type: ignore partially unknown
linker.define_func("wrap", "__wrap_subinvoke_result_len", wrap_subinvoke_result_len_type, wrap_subinvoke_result_len) # type: ignore partially unknown
linker.define_func("wrap", "__wrap_subinvoke_result", wrap_subinvoke_result_type, wrap_subinvoke_result) # type: ignore partially unknown
linker.define_func("wrap", "__wrap_subinvoke_error_len", wrap_subinvoke_error_len_type, wrap_subinvoke_error_len) # type: ignore partially unknown
linker.define_func("wrap", "__wrap_subinvoke_error", wrap_subinvoke_error_type, wrap_subinvoke_error) # type: ignore partially unknown

# subinvoke implementation
linker.define_func("wrap", "__wrap_subinvokeImplementation", wrap_subinvoke_implementation_type, wrap_subinvoke_implementation) # type: ignore partially unknown
linker.define_func("wrap", "__wrap_subinvokeImplementation_result_len", wrap_subinvoke_implementation_result_len_type, wrap_subinvoke_implementation_result_len) # type: ignore partially unknown
linker.define_func("wrap", "__wrap_subinvokeImplementation_result", wrap_subinvoke_implementation_result_type, wrap_subinvoke_implementation_result) # type: ignore partially unknown
linker.define_func("wrap", "__wrap_subinvokeImplementation_error_len", wrap_subinvoke_implementation_error_len_type, wrap_subinvoke_implementation_error_len) # type: ignore partially unknown
linker.define_func("wrap", "__wrap_subinvokeImplementation_error", wrap_subinvoke_implementation_error_type, wrap_subinvoke_implementation_error) # type: ignore partially unknown

# getImplementations
linker.define_func("wrap", "__wrap_getImplementations", wrap_get_implementations_type, wrap_get_implementations) # type: ignore partially unknown
linker.define_func("wrap", "__wrap_getImplementations_result_len", wrap_get_implementations_result_len_type, wrap_get_implementations_result_len) # type: ignore partially unknown
linker.define_func("wrap", "__wrap_getImplementations_result", wrap_get_implementations_result_type, wrap_get_implementations_result) # type: ignore partially unknown

# memory
linker.define("env", "memory", mem)
return linker.instantiate(store, module)
1 change: 1 addition & 0 deletions packages/polywrap-wasm/polywrap_wasm/types/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ class State:
subinvoke_implementation: RawSubinvokeImplementationResult = field(
default_factory=lambda: {"result": None, "error": None, "args": []}
)
get_implementations_result: Optional[bytes] = None
method: Optional[str] = None
args: Optional[bytes] = None
env: Optional[bytes] = None
7 changes: 7 additions & 0 deletions packages/polywrap-wasm/tests/test_wasm_wrapper.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from typing import List
import pytest

from pathlib import Path
Expand All @@ -9,11 +10,17 @@

from polywrap_wasm.constants import WRAP_MANIFEST_PATH

from result import Result, Err

@pytest.fixture
def mock_invoker():
class MockInvoker(Invoker):
async def invoke(self, options: InvokerOptions) -> InvokeResult:
return InvokeResult()

def get_implementations(self, uri: Uri) -> Result[List[Uri], Exception]:
return Err(NotImplementedError())

return MockInvoker()


Expand Down