diff --git a/pkgs/clan-cli/clan_cli/api/__init__.py b/pkgs/clan-cli/clan_cli/api/__init__.py index 7d54871e..937035fb 100644 --- a/pkgs/clan-cli/clan_cli/api/__init__.py +++ b/pkgs/clan-cli/clan_cli/api/__init__.py @@ -1,10 +1,12 @@ from collections.abc import Callable from dataclasses import dataclass -from typing import Any, Generic, Literal, TypeVar +from functools import wraps +from typing import Annotated, Any, Generic, Literal, TypeVar, get_type_hints + +from clan_cli.errors import ClanError T = TypeVar("T") - ResponseDataType = TypeVar("ResponseDataType") @@ -16,22 +18,55 @@ class ApiError: @dataclass -class ApiResponse(Generic[ResponseDataType]): - status: Literal["success", "error"] - errors: list[ApiError] | None - data: ResponseDataType | None +class SuccessDataClass(Generic[ResponseDataType]): + status: Annotated[Literal["success"], "The status of the response."] + data: ResponseDataType + + +@dataclass +class ErrorDataClass: + status: Literal["error"] + errors: list[ApiError] + + +ApiResponse = SuccessDataClass[ResponseDataType] | ErrorDataClass class _MethodRegistry: def __init__(self) -> None: + self._orig: dict[str, Callable[[Any], Any]] = {} self._registry: dict[str, Callable[[Any], Any]] = {} def register(self, fn: Callable[..., T]) -> Callable[..., T]: - self._registry[fn.__name__] = fn + self._orig[fn.__name__] = fn + + @wraps(fn) + def wrapper(*args: Any, **kwargs: Any) -> ApiResponse[T]: + try: + data: T = fn(*args, **kwargs) + return SuccessDataClass(status="success", data=data) + except ClanError as e: + return ErrorDataClass( + status="error", + errors=[ + ApiError( + message=e.msg, + description=e.description, + location=[fn.__name__, e.location], + ) + ], + ) + + # @wraps preserves all metadata of fn + # we need to update the annotation, because our wrapper changes the return type + # This overrides the new return type annotation with the generic typeVar filled in + orig_return_type = get_type_hints(fn).get("return") + wrapper.__annotations__["return"] = ApiResponse[orig_return_type] # type: ignore + + self._registry[fn.__name__] = wrapper return fn def to_json_schema(self) -> dict[str, Any]: - # Import only when needed from typing import get_type_hints from clan_cli.api.util import type_to_dict diff --git a/pkgs/webview-ui/app/src/Config.tsx b/pkgs/webview-ui/app/src/Config.tsx index e737c133..cc1ac0ce 100644 --- a/pkgs/webview-ui/app/src/Config.tsx +++ b/pkgs/webview-ui/app/src/Config.tsx @@ -8,9 +8,8 @@ import { import { OperationResponse, pyApi } from "./message"; export const makeCountContext = () => { - const [machines, setMachines] = createSignal< - OperationResponse<"list_machines"> - >([]); + const [machines, setMachines] = + createSignal>(); const [loading, setLoading] = createSignal(false); pyApi.list_machines.receive((machines) => { @@ -41,7 +40,7 @@ export const CountContext = createContext([ loading: () => false, // eslint-disable-next-line - machines: () => ([]), + machines: () => undefined, }, { // eslint-disable-next-line diff --git a/pkgs/webview-ui/app/src/message.ts b/pkgs/webview-ui/app/src/message.ts index e376d09a..15f5616f 100644 --- a/pkgs/webview-ui/app/src/message.ts +++ b/pkgs/webview-ui/app/src/message.ts @@ -72,6 +72,12 @@ const deserialize = // Create the API object const pyApi: PyApi = {} as PyApi; + +pyApi.create_clan.receive((r) => { + if (r.status === "success") { + r.status; + } +}); operationNames.forEach((opName) => { const name = opName as OperationNames; // @ts-expect-error - TODO: Fix this. Typescript is not recognizing the receive function correctly diff --git a/pkgs/webview-ui/app/src/routes/machines/view.tsx b/pkgs/webview-ui/app/src/routes/machines/view.tsx index dff0e5dc..bb2acab5 100644 --- a/pkgs/webview-ui/app/src/routes/machines/view.tsx +++ b/pkgs/webview-ui/app/src/routes/machines/view.tsx @@ -1,13 +1,30 @@ -import { For, Match, Switch, createEffect, type Component } from "solid-js"; +import { + For, + Match, + Switch, + createEffect, + createSignal, + type Component, +} from "solid-js"; import { useCountContext } from "../../Config"; import { route } from "@/src/App"; export const MachineListView: Component = () => { const [{ machines, loading }, { getMachines }] = useCountContext(); + const [data, setData] = createSignal([]); createEffect(() => { if (route() === "machines") getMachines(); }); + + createEffect(() => { + const response = machines(); + if (response?.status === "success") { + console.log(response.data); + setData(response.data); + } + }); + return (
@@ -32,12 +49,12 @@ export const MachineListView: Component = () => {
- + No machines found
    - + {(entry) => (