Skip to content

Commit 0641477

Browse files
committed
add register_object
1 parent 77c4977 commit 0641477

File tree

2 files changed

+122
-0
lines changed

2 files changed

+122
-0
lines changed

tests/codec_test.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,3 +100,82 @@ def event_handler(event: Event):
100100
session.publish_object("io.xconn.object", String("hello"))
101101

102102
session.leave()
103+
104+
105+
def test_register_object_one_param_with_return_type():
106+
session = connect_anonymous("ws://localhost:8080/ws", "realm1")
107+
session.set_payload_codec(ProtobufCodec())
108+
109+
def create_profile_handler(prof: ProfileCreate) -> ProfileGet:
110+
return ProfileGet(
111+
id="356",
112+
username=prof.username,
113+
email=prof.email,
114+
age=prof.age,
115+
created_at="2025-10-30T17:00:00Z",
116+
)
117+
118+
session.register_object("io.xconn.profile.create", create_profile_handler)
119+
120+
profile_create = ProfileCreate(username="john", email="[email protected]", age=25)
121+
result = session.call("io.xconn.profile.create", [profile_create.SerializeToString()])
122+
123+
profile = ProfileGet()
124+
profile.ParseFromString(result.args[0])
125+
126+
assert profile.id == "356"
127+
assert profile.username == "john"
128+
assert profile.email == "[email protected]"
129+
assert profile.age == 25
130+
assert profile.created_at == "2025-10-30T17:00:00Z"
131+
132+
session.leave()
133+
134+
135+
def test_register_object_no_param():
136+
session = connect_anonymous("ws://localhost:8080/ws", "realm1")
137+
session.set_payload_codec(ProtobufCodec())
138+
139+
options = {"flag": False}
140+
141+
def invocation_handler() -> None:
142+
options["flag"] = True
143+
144+
session.register_object("io.xconn.param.none", invocation_handler)
145+
146+
result = session.call("io.xconn.param.none")
147+
148+
assert options["flag"] is True
149+
assert result.args is None
150+
assert result.kwargs is None
151+
152+
session.leave()
153+
154+
155+
def test_register_object_no_param_with_return():
156+
session = connect_anonymous("ws://localhost:8080/ws", "realm1")
157+
session.set_payload_codec(ProtobufCodec())
158+
159+
def get_profile_handler() -> ProfileGet:
160+
return ProfileGet(
161+
id="636",
162+
username="admin",
163+
164+
age=30,
165+
created_at="2025-10-30T17:00:00Z",
166+
)
167+
168+
session.register_object("io.xconn.profile.get", get_profile_handler)
169+
170+
result = session.call("io.xconn.profile.get")
171+
172+
profile = ProfileGet()
173+
profile.ParseFromString(result.args[0])
174+
175+
assert profile.id == "636"
176+
assert profile.username == "admin"
177+
assert profile.email == "[email protected]"
178+
assert profile.age == 30
179+
assert profile.created_at == "2025-10-30T17:00:00Z"
180+
181+
session.leave()

xconn/session.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import annotations
22

3+
import inspect
34
from concurrent.futures import Future
45
from threading import Thread
56
from typing import Callable, Any, TypeVar, Type
@@ -231,6 +232,48 @@ def publish_object(self, topic: str, request: TReq):
231232
encoded = self._payload_codec.encode(request)
232233
return self.publish(topic, [encoded])
233234

235+
def register_object(
236+
self,
237+
procedure: str,
238+
invocation_handler: Callable[[TReq], TRes | None] | Callable[[], TRes | None],
239+
):
240+
if self._payload_codec is None:
241+
raise ValueError("no payload codec set")
242+
243+
sig = inspect.signature(invocation_handler)
244+
245+
params = list(sig.parameters.values())
246+
if len(params) > 1:
247+
raise ValueError("invocation handler must accept 0 or 1 argument")
248+
249+
if len(params) == 1:
250+
# get parameter's type hint
251+
param_type = params[0].annotation
252+
if param_type is inspect._empty:
253+
raise TypeError("invocation handler parameter must have a type annotation")
254+
else:
255+
param_type = None
256+
257+
def _invocation_handler(invocation: types.Invocation):
258+
request_obj = None
259+
if param_type is not None:
260+
if len(invocation.args) != 1:
261+
raise ValueError("only one argument expected in invocation")
262+
263+
request_obj = self._payload_codec.decode(invocation.args[0], param_type)
264+
265+
result = invocation_handler(request_obj) if param_type is not None else invocation_handler()
266+
267+
# no return type in invocation handler
268+
if sig.return_annotation is inspect._empty or result is None:
269+
return None
270+
271+
encoded = self._payload_codec.encode(result)
272+
273+
return types.Result(args=[encoded])
274+
275+
return self.register(procedure, _invocation_handler)
276+
234277
def call(
235278
self,
236279
procedure: str,

0 commit comments

Comments
 (0)