forked from clan/clan-core
API: init op_key, improve seralisation & signature typing
This commit is contained in:
parent
a89fd31844
commit
cb847cab82
@ -38,8 +38,10 @@ def dataclass_to_dict(obj: Any) -> Any:
|
|||||||
return [dataclass_to_dict(item) for item in obj]
|
return [dataclass_to_dict(item) for item in obj]
|
||||||
elif isinstance(obj, dict):
|
elif isinstance(obj, dict):
|
||||||
return {k: dataclass_to_dict(v) for k, v in obj.items()}
|
return {k: dataclass_to_dict(v) for k, v in obj.items()}
|
||||||
else:
|
elif isinstance(obj, Path):
|
||||||
return str(obj)
|
return str(obj)
|
||||||
|
else:
|
||||||
|
return obj
|
||||||
|
|
||||||
|
|
||||||
# Implement the abstract open_file function
|
# Implement the abstract open_file function
|
||||||
@ -265,6 +267,7 @@ class WebView:
|
|||||||
result = handler_fn()
|
result = handler_fn()
|
||||||
else:
|
else:
|
||||||
reconciled_arguments = {}
|
reconciled_arguments = {}
|
||||||
|
op_key = data.pop("op_key", None)
|
||||||
for k, v in data.items():
|
for k, v in data.items():
|
||||||
# Some functions expect to be called with dataclass instances
|
# Some functions expect to be called with dataclass instances
|
||||||
# But the js api returns dictionaries.
|
# But the js api returns dictionaries.
|
||||||
@ -276,8 +279,13 @@ class WebView:
|
|||||||
else:
|
else:
|
||||||
reconciled_arguments[k] = v
|
reconciled_arguments[k] = v
|
||||||
|
|
||||||
result = handler_fn(**reconciled_arguments)
|
r = handler_fn(**reconciled_arguments)
|
||||||
serialized = json.dumps(dataclass_to_dict(result))
|
# Parse the result to a serializable dictionary
|
||||||
|
# Echo back the "op_key" to the js api
|
||||||
|
result = dataclass_to_dict(r)
|
||||||
|
result["op_key"] = op_key
|
||||||
|
|
||||||
|
serialized = json.dumps(result)
|
||||||
|
|
||||||
# Use idle_add to queue the response call to js on the main GTK thread
|
# Use idle_add to queue the response call to js on the main GTK thread
|
||||||
GLib.idle_add(self.return_data_to_js, method_name, serialized)
|
GLib.idle_add(self.return_data_to_js, method_name, serialized)
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
from inspect import Parameter, signature
|
||||||
from typing import Annotated, Any, Generic, Literal, TypeVar, get_type_hints
|
from typing import Annotated, Any, Generic, Literal, TypeVar, get_type_hints
|
||||||
|
|
||||||
from clan_cli.errors import ClanError
|
from clan_cli.errors import ClanError
|
||||||
@ -21,17 +22,34 @@ class ApiError:
|
|||||||
class SuccessDataClass(Generic[ResponseDataType]):
|
class SuccessDataClass(Generic[ResponseDataType]):
|
||||||
status: Annotated[Literal["success"], "The status of the response."]
|
status: Annotated[Literal["success"], "The status of the response."]
|
||||||
data: ResponseDataType
|
data: ResponseDataType
|
||||||
|
op_key: str | None
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class ErrorDataClass:
|
class ErrorDataClass:
|
||||||
status: Literal["error"]
|
status: Literal["error"]
|
||||||
errors: list[ApiError]
|
errors: list[ApiError]
|
||||||
|
op_key: str | None
|
||||||
|
|
||||||
|
|
||||||
ApiResponse = SuccessDataClass[ResponseDataType] | ErrorDataClass
|
ApiResponse = SuccessDataClass[ResponseDataType] | ErrorDataClass
|
||||||
|
|
||||||
|
|
||||||
|
def update_wrapper_signature(wrapper: Callable, wrapped: Callable) -> None:
|
||||||
|
sig = signature(wrapped)
|
||||||
|
params = list(sig.parameters.values())
|
||||||
|
|
||||||
|
# Add 'op_key' parameter
|
||||||
|
op_key_param = Parameter(
|
||||||
|
"op_key", Parameter.KEYWORD_ONLY, default=None, annotation=str | None
|
||||||
|
)
|
||||||
|
params.append(op_key_param)
|
||||||
|
|
||||||
|
# Create a new signature
|
||||||
|
new_sig = sig.replace(parameters=params)
|
||||||
|
wrapper.__signature__ = new_sig # type: ignore
|
||||||
|
|
||||||
|
|
||||||
class _MethodRegistry:
|
class _MethodRegistry:
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self._orig: dict[str, Callable[[Any], Any]] = {}
|
self._orig: dict[str, Callable[[Any], Any]] = {}
|
||||||
@ -41,13 +59,16 @@ class _MethodRegistry:
|
|||||||
self._orig[fn.__name__] = fn
|
self._orig[fn.__name__] = fn
|
||||||
|
|
||||||
@wraps(fn)
|
@wraps(fn)
|
||||||
def wrapper(*args: Any, **kwargs: Any) -> ApiResponse[T]:
|
def wrapper(
|
||||||
|
*args: Any, op_key: str | None = None, **kwargs: Any
|
||||||
|
) -> ApiResponse[T]:
|
||||||
try:
|
try:
|
||||||
data: T = fn(*args, **kwargs)
|
data: T = fn(*args, **kwargs)
|
||||||
return SuccessDataClass(status="success", data=data)
|
return SuccessDataClass(status="success", data=data, op_key=op_key)
|
||||||
except ClanError as e:
|
except ClanError as e:
|
||||||
return ErrorDataClass(
|
return ErrorDataClass(
|
||||||
status="error",
|
status="error",
|
||||||
|
op_key=op_key,
|
||||||
errors=[
|
errors=[
|
||||||
ApiError(
|
ApiError(
|
||||||
message=e.msg,
|
message=e.msg,
|
||||||
@ -63,6 +84,11 @@ class _MethodRegistry:
|
|||||||
orig_return_type = get_type_hints(fn).get("return")
|
orig_return_type = get_type_hints(fn).get("return")
|
||||||
wrapper.__annotations__["return"] = ApiResponse[orig_return_type] # type: ignore
|
wrapper.__annotations__["return"] = ApiResponse[orig_return_type] # type: ignore
|
||||||
|
|
||||||
|
# Add additional argument for the operation key
|
||||||
|
wrapper.__annotations__["op_key"] = str | None # type: ignore
|
||||||
|
|
||||||
|
update_wrapper_signature(wrapper, fn)
|
||||||
|
|
||||||
self._registry[fn.__name__] = wrapper
|
self._registry[fn.__name__] = wrapper
|
||||||
return fn
|
return fn
|
||||||
|
|
||||||
@ -91,6 +117,12 @@ class _MethodRegistry:
|
|||||||
|
|
||||||
return_type = serialized_hints.pop("return")
|
return_type = serialized_hints.pop("return")
|
||||||
|
|
||||||
|
sig = signature(func)
|
||||||
|
required_args = []
|
||||||
|
for n, param in sig.parameters.items():
|
||||||
|
if param.default == Parameter.empty:
|
||||||
|
required_args.append(n)
|
||||||
|
|
||||||
api_schema["properties"][name] = {
|
api_schema["properties"][name] = {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": ["arguments", "return"],
|
"required": ["arguments", "return"],
|
||||||
@ -99,7 +131,7 @@ class _MethodRegistry:
|
|||||||
"return": return_type,
|
"return": return_type,
|
||||||
"arguments": {
|
"arguments": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [k for k in serialized_hints.keys()],
|
"required": required_args,
|
||||||
"additionalProperties": False,
|
"additionalProperties": False,
|
||||||
"properties": serialized_hints,
|
"properties": serialized_hints,
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user