Skip to content

Commit 2009792

Browse files
committed
#30 Add get_iris_id method to Message classes and update serialization logic
- Implemented get_iris_id method in _Message and its subclasses to retrieve the IRIS ID. - Updated MessageSerializer to handle IRIS ID during serialization and deserialization. - Added tests for get_iris_id functionality in message classes.
1 parent e45c73d commit 2009792

File tree

5 files changed

+111
-29
lines changed

5 files changed

+111
-29
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010
### Added
1111
- Initial support for venv
1212
- New `send_generator_request` method in `iop` module to send generator requests
13+
- Add `get_iris_id` method to `Message*` classes to retrieve the IRIS ID of a message
1314

1415
## [3.4.4] - 2025-06-13
1516
### Added

src/iop/_message.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Any
1+
from typing import Any, Optional
22

33
from pydantic import BaseModel
44

@@ -7,7 +7,11 @@ class _Message:
77
This class has no properties or methods. Users subclass Message and add properties.
88
The IOP framework provides the persistence to objects derived from the Message class.
99
"""
10-
pass
10+
_iris_id: Optional[str] = None
11+
12+
def get_iris_id(self) -> Optional[str]:
13+
"""Get the IRIS ID of the message."""
14+
return self._iris_id
1115

1216
class _PickleMessage(_Message):
1317
""" The abstract class that is the superclass for persistent messages sent from one component to another.
@@ -19,12 +23,18 @@ class _PickleMessage(_Message):
1923
class _PydanticMessage(BaseModel):
2024
"""Base class for Pydantic-based messages that can be serialized to IRIS."""
2125

26+
_iris_id: Optional[str] = None
27+
2228
def __init__(self, **data: Any):
2329
super().__init__(**data)
2430

25-
class _PydanticPickleMessage(BaseModel):
31+
def get_iris_id(self) -> Optional[str]:
32+
"""Get the IRIS ID of the message."""
33+
return self._iris_id
34+
35+
class _PydanticPickleMessage(_PydanticMessage):
2636
"""Base class for Pydantic-based messages that can be serialized to IRIS."""
27-
37+
2838
def __init__(self, **data: Any):
2939
super().__init__(**data)
3040

src/iop/_serialization.py

Lines changed: 43 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -26,19 +26,10 @@ class TempPydanticModel(BaseModel):
2626
class MessageSerializer:
2727
"""Handles message serialization and deserialization."""
2828

29-
@staticmethod
30-
def _convert_to_json_safe(obj: Any) -> Any:
31-
"""Convert objects to JSON-safe format."""
32-
if isinstance(obj, BaseModel):
33-
return obj.model_dump_json()
34-
elif is_dataclass(obj) and isinstance(obj, _Message):
35-
return TempPydanticModel.model_validate(dataclass_to_dict(obj)).model_dump_json()
36-
else:
37-
raise SerializationError(f"Object {obj} must be a Pydantic model or dataclass Message")
38-
3929
@staticmethod
4030
def serialize(message: Any, use_pickle: bool = False, is_generator:bool = False) -> Any:
4131
"""Serializes a message to IRIS format."""
32+
message = remove_iris_id(message)
4233
if use_pickle:
4334
return MessageSerializer._serialize_pickle(message, is_generator)
4435
return MessageSerializer._serialize_json(message, is_generator)
@@ -58,12 +49,34 @@ def _serialize_json(message: Any, is_generator: bool = False) -> Any:
5849
else:
5950
msg.json = json_string
6051
return msg
52+
53+
@staticmethod
54+
def _serialize_pickle(message: Any, is_generator: bool = False) -> Any:
55+
"""Serializes a message to IRIS format using pickle."""
56+
message = remove_iris_id(message)
57+
pickle_string = codecs.encode(pickle.dumps(message), "base64").decode()
58+
if is_generator:
59+
msg = _iris.get_iris().cls('IOP.Generator.Message.StartPickle')._New()
60+
else:
61+
msg = _iris.get_iris().cls('IOP.PickleMessage')._New()
62+
msg.classname = f"{message.__class__.__module__}.{message.__class__.__name__}"
63+
msg.jstr = _Utils.string_to_stream(pickle_string)
64+
return msg
6165

6266
@staticmethod
6367
def deserialize(serial: Any, use_pickle: bool = False) -> Any:
6468
if use_pickle:
65-
return MessageSerializer._deserialize_pickle(serial)
66-
return MessageSerializer._deserialize_json(serial)
69+
msg=MessageSerializer._deserialize_pickle(serial)
70+
else:
71+
msg=MessageSerializer._deserialize_json(serial)
72+
73+
try:
74+
iris_id = serial._Id()
75+
msg._iris_id = iris_id if iris_id else None
76+
except Exception as e:
77+
pass
78+
79+
return msg
6780

6881
@staticmethod
6982
def _deserialize_json(serial: Any) -> Any:
@@ -90,17 +103,6 @@ def _deserialize_json(serial: Any) -> Any:
90103
except Exception as e:
91104
raise SerializationError(f"Failed to deserialize JSON: {str(e)}")
92105

93-
@staticmethod
94-
def _serialize_pickle(message: Any, is_generator: bool = False) -> Any:
95-
pickle_string = codecs.encode(pickle.dumps(message), "base64").decode()
96-
if is_generator:
97-
msg = _iris.get_iris().cls('IOP.Generator.Message.StartPickle')._New()
98-
else:
99-
msg = _iris.get_iris().cls('IOP.PickleMessage')._New()
100-
msg.classname = f"{message.__class__.__module__}.{message.__class__.__name__}"
101-
msg.jstr = _Utils.string_to_stream(pickle_string)
102-
return msg
103-
104106
@staticmethod
105107
def _deserialize_pickle(serial: Any) -> Any:
106108
string = _Utils.stream_to_string(serial.jstr)
@@ -112,6 +114,24 @@ def _parse_classname(classname: str) -> tuple[str, str]:
112114
if j <= 0:
113115
raise SerializationError(f"Classname must include a module: {classname}")
114116
return classname[:j], classname[j+1:]
117+
118+
@staticmethod
119+
def _convert_to_json_safe(obj: Any) -> Any:
120+
"""Convert objects to JSON-safe format."""
121+
if isinstance(obj, BaseModel):
122+
return obj.model_dump_json()
123+
elif is_dataclass(obj) and isinstance(obj, _Message):
124+
return TempPydanticModel.model_validate(dataclass_to_dict(obj)).model_dump_json()
125+
else:
126+
raise SerializationError(f"Object {obj} must be a Pydantic model or dataclass Message")
127+
128+
129+
def remove_iris_id(message: Any) -> Any:
130+
try:
131+
del message._iris_id
132+
except AttributeError:
133+
pass
134+
return message
115135

116136
def dataclass_from_dict(klass: Type | Any, dikt: Dict) -> Any:
117137
"""Converts a dictionary to a dataclass instance.

src/tests/bench/bench_bp.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,4 @@ def on_init(self):
99

1010
def on_message(self, request):
1111
for _ in range(self.size):
12-
_ = self.send_request_sync(self.target,request)
12+
rsp = self.send_request_sync(self.target,request)

src/tests/test_message.py

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,61 @@
11
import iris
22

3+
from iop import Message, PickleMessage, PydanticMessage, PydanticPickleMessage
4+
from iop._dispatch import dispatch_serializer, dispatch_deserializer
5+
6+
from dataclasses import dataclass
7+
8+
@dataclass
9+
class SimpleMessage(Message):
10+
"""A simple message class for testing"""
11+
12+
integer: int
13+
string: str
14+
15+
@dataclass
16+
class SimplePickleMessage(PickleMessage):
17+
"""A simple pickle message class for testing"""
18+
19+
integer: int
20+
string: str
21+
22+
class SimplePydanticMessage(PydanticMessage):
23+
"""A simple Pydantic message class for testing"""
24+
25+
integer: int
26+
string: str
27+
28+
class SimplePydanticPickleMessage(PydanticPickleMessage):
29+
"""A simple Pydantic pickle message class for testing"""
30+
31+
integer: int
32+
string: str
33+
34+
import pytest
35+
336
def test_iop_message_set_json():
437
# test set_json
538
iop_message = iris.cls('IOP.Message')._New()
639
iop_message.json = 'test'
740
assert iop_message.jstr.Read() == 'test'
841
assert iop_message.type == 'String'
942
assert iop_message.jsonString == 'test'
10-
assert iop_message.json == 'test'
43+
assert iop_message.json == 'test'
44+
45+
@pytest.mark.parametrize("message_class", [
46+
SimpleMessage,
47+
SimplePickleMessage,
48+
SimplePydanticMessage,
49+
SimplePydanticPickleMessage
50+
]
51+
)
52+
def test_get_iris_id(message_class):
53+
message = message_class(integer=42, string='test')
54+
assert message.get_iris_id() is None
55+
56+
serialized_message = dispatch_serializer(message)
57+
serialized_message._Save()
58+
deserialized_message = dispatch_deserializer(serialized_message)
59+
60+
assert deserialized_message.get_iris_id() is not None
61+

0 commit comments

Comments
 (0)