Skip to content

Commit d5c46f5

Browse files
ctruedenclaude
andcommitted
Make object decoding extensible, too
And make object encoding symmetric with decoding: both directions are defined by functions specified when registering custom object types via message.register. This commit changes the wire protocol to nest the encoded content in a single "data" key of the outer dict. Better to change it now than later! Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 35625c3 commit d5c46f5

2 files changed

Lines changed: 46 additions & 27 deletions

File tree

src/appose/shm.py

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -105,13 +105,6 @@ def dispose(self) -> None:
105105
else:
106106
self.close()
107107

108-
def for_json(self):
109-
return {
110-
"appose_type": "shm",
111-
"name": self.name,
112-
"rsize": self.rsize,
113-
}
114-
115108
def __enter__(self) -> "SharedMemory":
116109
return self
117110

@@ -169,21 +162,27 @@ def ndarray(self):
169162
except ModuleNotFoundError:
170163
raise ImportError("NumPy is not available.")
171164

172-
def for_json(self):
173-
return {
174-
"appose_type": "ndarray",
175-
"dtype": self.dtype,
176-
"shape": self.shape,
177-
"shm": self.shm,
178-
}
179-
180165
def __enter__(self) -> "NDArray":
181166
return self
182167

183168
def __exit__(self, exc_type, exc_value, exc_tb) -> None:
184169
self.shm.dispose()
185170

186171

172+
message.register(
173+
SharedMemory,
174+
"shm",
175+
lambda shm: {"name": shm.name, "rsize": shm.rsize},
176+
lambda m: SharedMemory(name=m["name"], rsize=m["rsize"]),
177+
)
178+
message.register(
179+
NDArray,
180+
"ndarray",
181+
lambda nda: {"dtype": nda.dtype, "shape": nda.shape, "shm": nda.shm},
182+
lambda m: NDArray(m["dtype"], m["shape"], m["shm"]),
183+
)
184+
185+
187186
def _bytes_per_element(dtype: str) -> int | float:
188187
try:
189188
bits = int(re.sub("[^0-9]", "", dtype))

src/appose/util/message.py

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,33 @@
1111
import json
1212
from typing import Any
1313

14-
from ..shm import NDArray, SharedMemory
15-
1614
Args = dict[str, Any]
1715

16+
_encoders: dict[type, tuple[str, Any]] = {}
17+
_decoders: dict[str, Any] = {}
18+
19+
20+
def register(obj_type: type, appose_type: str, encoder, decoder) -> None:
21+
"""
22+
Register encoder and decoder functions for a custom Appose type.
23+
24+
When encoding, if an object is an instance of ``obj_type``, ``encoder``
25+
is called with the object and its return value is wrapped as
26+
``{"appose_type": appose_type, "data": <encoded>}``.
27+
28+
When decoding, if a JSON object has the given ``appose_type``, ``decoder``
29+
is called with the ``"data"`` field value and should return the
30+
reconstructed Python object.
31+
32+
:param obj_type: The Python type to encode.
33+
:param appose_type: The ``appose_type`` string used on the wire.
34+
:param encoder: Callable ``(obj) -> JSON-compatible value``.
35+
:param decoder: Callable ``(data) -> obj``.
36+
"""
37+
_encoders[obj_type] = (appose_type, encoder)
38+
_decoders[appose_type] = decoder
39+
40+
1841
# Flag indicating whether this process is running as an Appose worker.
1942
# Set to True by python_worker.Worker.__init__().
2043
_worker_mode = False
@@ -36,8 +59,9 @@ def decode(the_json: str) -> Args:
3659

3760
class _ApposeJSONEncoder(json.JSONEncoder):
3861
def default(self, obj):
39-
if hasattr(obj, "for_json"):
40-
return obj.for_json()
62+
for obj_type, (appose_type, encoder) in _encoders.items():
63+
if isinstance(obj, obj_type):
64+
return {"appose_type": appose_type, **encoder(obj)}
4165

4266
# If in worker mode and object is not JSON-serializable,
4367
# auto-export it and return a worker_object reference.
@@ -60,17 +84,13 @@ def default(self, obj):
6084

6185
def _appose_object_hook(obj: dict):
6286
atype = obj.get("appose_type")
63-
if atype == "shm":
64-
# Attach to existing shared memory block.
65-
return SharedMemory(name=(obj["name"]), rsize=(obj["rsize"]))
66-
elif atype == "ndarray":
67-
return NDArray(obj["dtype"], obj["shape"], obj["shm"])
68-
elif atype == "worker_object":
87+
if atype == "worker_object":
6988
# Keep worker_object dicts as-is for now.
7089
# They will be converted to proxies by proxify_worker_objects().
7190
return obj
72-
else:
73-
return obj
91+
if atype in _decoders:
92+
return _decoders[atype](obj)
93+
return obj
7494

7595

7696
def proxify_worker_objects(data: Any, service: Any) -> Any:

0 commit comments

Comments
 (0)