Skip to content

Commit aaadd6c

Browse files
committed
Introduce Pydantic-based message classes for improved validation and serialization
1 parent 5441f73 commit aaadd6c

File tree

10 files changed

+61
-37
lines changed

10 files changed

+61
-37
lines changed

docs/python-api.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,20 @@ class MyRequest(Message):
1717
request_string: str = None
1818
```
1919

20+
### PydanticMessage 📦
21+
22+
Base class for messages that use Pydantic models for validation and serialization. This class provides additional features for data validation and serialization.
23+
24+
**Usage:** Subclass `PydanticMessage` and define a Pydantic model as a class attribute. This approach provides automatic validation and serialization.
25+
26+
**Example:**
27+
```python
28+
from iop import PydanticMessage
29+
30+
class MyRequest(PydanticMessage):
31+
model : str = None
32+
```
33+
2034
### BusinessService 🔄
2135
Base class for business services that receive and process incoming data. Business services act as entry points for data into your interoperability solution.
2236

src/grongier/pex/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from iop._inbound_adapter import _InboundAdapter
77
from iop._outbound_adapter import _OutboundAdapter
88
from iop._message import _Message
9-
from iop._pickle_message import _PickleMessage
9+
from iop._message import _PickleMessage
1010
from iop._director import _Director
1111
from iop._utils import _Utils
1212

src/iop/__init__.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,8 @@
33
from iop._business_service import _BusinessService
44
from iop._director import _Director
55
from iop._inbound_adapter import _InboundAdapter
6-
from iop._message import _Message
7-
from iop._pydantic_message import _PydanticMessage
6+
from iop._message import _Message, _PickleMessage, _PydanticMessage, _PydanticPickleMessage
87
from iop._outbound_adapter import _OutboundAdapter
9-
from iop._pickle_message import _PickleMessage
108
from iop._private_session_duplex import _PrivateSessionDuplex
119
from iop._private_session_process import _PrivateSessionProcess
1210
from iop._utils import _Utils
@@ -23,4 +21,5 @@ class DuplexProcess(_PrivateSessionProcess): pass
2321
class Message(_Message): pass
2422
class PickleMessage(_PickleMessage): pass
2523
class PydanticMessage(_PydanticMessage): pass
24+
class PydanticPickleMessage(_PydanticPickleMessage): pass
2625
class Director(_Director): pass

src/iop/_message.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,28 @@
1+
from typing import Any
2+
from pydantic import BaseModel
3+
14
class _Message:
25
""" The abstract class that is the superclass for persistent messages sent from one component to another.
36
This class has no properties or methods. Users subclass Message and add properties.
47
The IOP framework provides the persistence to objects derived from the Message class.
58
"""
6-
pass
9+
pass
10+
11+
class _PickleMessage:
12+
""" The abstract class that is the superclass for persistent messages sent from one component to another.
13+
This class has no properties or methods. Users subclass Message and add properties.
14+
The IOP framework provides the persistence to objects derived from the Message class.
15+
"""
16+
pass
17+
18+
class _PydanticMessage(BaseModel):
19+
"""Base class for Pydantic-based messages that can be serialized to IRIS."""
20+
21+
def __init__(self, **data: Any):
22+
super().__init__(**data)
23+
24+
class _PydanticPickleMessage(BaseModel):
25+
"""Base class for Pydantic-based messages that can be serialized to IRIS."""
26+
27+
def __init__(self, **data: Any):
28+
super().__init__(**data)

src/iop/_message_validator.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import dataclasses
22
from typing import Any, Type
3-
from pydantic import BaseModel
4-
from iop._message import _Message
3+
from iop._message import _Message, _PickleMessage, _PydanticPickleMessage, BaseModel
54

65

76
def is_message_instance(obj: Any) -> bool:
@@ -17,6 +16,8 @@ def is_message_instance(obj: Any) -> bool:
1716

1817
def is_pickle_message_instance(obj: Any) -> bool:
1918
"""Check if object is a PickleMessage instance."""
19+
if isinstance(obj, _PydanticPickleMessage):
20+
return True
2021
if is_pickle_message_class(type(obj)):
2122
return True
2223
return False
@@ -31,8 +32,6 @@ def is_iris_object_instance(obj: Any) -> bool:
3132

3233
def is_message_class(klass: Type) -> bool:
3334
"""Check if class is a Message type."""
34-
if issubclass(klass, BaseModel):
35-
return True
3635
if issubclass(klass, _Message):
3736
return True
3837
return False
@@ -41,7 +40,8 @@ def is_message_class(klass: Type) -> bool:
4140

4241
def is_pickle_message_class(klass: Type) -> bool:
4342
"""Check if class is a PickleMessage type."""
44-
name = f"{klass.__module__}.{klass.__qualname__}"
45-
if name in ("iop.PickleMessage", "grongier.pex.PickleMessage"):
43+
if issubclass(klass, _PickleMessage):
4644
return True
47-
return any(is_pickle_message_class(c) for c in klass.__bases__)
45+
if issubclass(klass, _PydanticPickleMessage):
46+
return True
47+
return False

src/iop/_pickle_message.py

Lines changed: 0 additions & 6 deletions
This file was deleted.

src/iop/_pydantic_message.py

Lines changed: 0 additions & 9 deletions
This file was deleted.

src/iop/_serialization.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from dacite import Config, from_dict
1515
import iris
1616

17+
from iop._message import _PydanticPickleMessage
1718
from iop._utils import _Utils
1819
from pydantic import BaseModel
1920

@@ -137,6 +138,9 @@ class MessageSerializer:
137138
@staticmethod
138139
def serialize(message: Any, use_pickle: bool = False) -> iris.cls:
139140
"""Serializes a message to IRIS format."""
141+
# Check for PydanticPickleMessage first
142+
if isinstance(message, _PydanticPickleMessage):
143+
return MessageSerializer._serialize_pickle(message)
140144
if isinstance(message, BaseModel):
141145
return (MessageSerializer._serialize_pickle(message)
142146
if use_pickle else MessageSerializer._serialize_json(message))

src/tests/test_dispatch.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
dataclass_from_dict
1616
)
1717
from iop._message import _Message as Message
18-
from iop._pydantic_message import _PydanticMessage as PydanticMessage
18+
from iop._message import _PydanticMessage as PydanticMessage
1919

2020
class SimpleModel(PydanticMessage):
2121
text: str
@@ -32,7 +32,7 @@ class ComplexModel(PydanticMessage):
3232

3333

3434
@dataclass
35-
class TestMessage(Message):
35+
class MessageTest(Message):
3636
text: str
3737
number: int
3838

@@ -62,17 +62,17 @@ def test_simple_message_serialization():
6262
assert result.number == msg.number
6363

6464
def test_message_serialization():
65-
msg = TestMessage(text="test", number=42)
65+
msg = MessageTest(text="test", number=42)
6666

6767
# Test serialization
6868
serial = serialize_message(msg)
6969
assert type(serial).__module__.startswith('iris')
7070
assert serial._IsA("IOP.Message")
71-
assert serial.classname == f"{TestMessage.__module__}.{TestMessage.__name__}"
71+
assert serial.classname == f"{MessageTest.__module__}.{MessageTest.__name__}"
7272

7373
# Test deserialization
7474
result = deserialize_message(serial)
75-
assert isinstance(result, TestMessage)
75+
assert isinstance(result, MessageTest)
7676
assert result.text == msg.text
7777
assert result.number == msg.number
7878

@@ -91,7 +91,7 @@ def test_pickle_message_serialization():
9191
assert result.number == msg.number
9292

9393
def test_pickle_serialization():
94-
msg = TestMessage(text="test", number=42)
94+
msg = MessageTest(text="test", number=42)
9595

9696
# Test serialization
9797
serial = serialize_pickle_message(msg)
@@ -100,7 +100,7 @@ def test_pickle_serialization():
100100

101101
# Test deserialization
102102
result = deserialize_pickle_message(serial)
103-
assert isinstance(result, TestMessage)
103+
assert isinstance(result, MessageTest)
104104
assert result.text == msg.text
105105
assert result.number == msg.number
106106

@@ -165,8 +165,8 @@ def test_dataclass_from_dict():
165165
'extra_field': 'extra'
166166
}
167167

168-
result = dataclass_from_dict(TestMessage, data)
169-
assert isinstance(result, TestMessage)
168+
result = dataclass_from_dict(MessageTest, data)
169+
assert isinstance(result, MessageTest)
170170
assert result.text == 'test'
171171
assert result.number == 42
172172
assert result.extra_field == 'extra'

src/tests/test_pydantic_message.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import pytest
77
from pydantic import BaseModel
88

9-
from iop._pydantic_message import _PydanticMessage as PydanticMessage
9+
from iop._message import _PydanticMessage as PydanticMessage
1010
from iop._serialization import (
1111
serialize_message,
1212
deserialize_message,

0 commit comments

Comments
 (0)