Skip to content

Commit 36a0af7

Browse files
Copilotgonzalocasas
andcommitted
Address review feedback: move codecs, simplify examples, fix API
- Moved JsonMessageCodec to codecs module alongside ProtobufMessageCodec - Updated ProtobufMessageCodec to use correct compas_pb API (pb_dump_bts/pb_load_bts) - Made decode() symmetric with encode() - now takes message_type parameter and returns message object - Cleaned up CHANGELOG to remove internal implementation details - Simplified README example - removed try/except, updated Rhino section - Simplified example codec - assumes Message type, moved json import to top - Fixed Transport to use lazy import of JsonMessageCodec to avoid circular dependency Co-authored-by: gonzalocasas <[email protected]>
1 parent e9fe014 commit 36a0af7

File tree

7 files changed

+127
-105
lines changed

7 files changed

+127
-105
lines changed

CHANGELOG.md

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1414
* Added `JsonMessageCodec` for JSON-based message serialization (default).
1515
* Added `ProtobufMessageCodec` for binary message serialization using Protocol Buffers (requires `compas_pb`).
1616
* Added `codec` parameter to `Transport`, `InMemoryTransport`, and `MqttTransport` classes.
17-
* Added `_message_to_data()` method to `Topic` class for extracting data from various message types.
1817

1918
### Changed
2019

2120
* Updated dependency on `paho-mqtt` to support `>=1, <3` to include version `2.x` with backward compatibility.
22-
* **BREAKING**: Transport implementations now use codec-based serialization instead of direct JSON calls.
23-
* **BREAKING**: Removed `_message_to_json()` and `_message_from_json()` methods from `Topic` class (internal API).
2421

2522
### Removed
2623

2724
* **BREAKING**: Removed IronPython support and `mqtt_cli` implementation.
28-
* **BREAKING**: Removed IronPython MQTTnet.dll library.
2925
* **BREAKING**: Removed support for Rhino 7 (IronPython-based).
3026

3127

README.md

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -85,30 +85,21 @@ By default, COMPAS EVE uses JSON for message serialization. However, you can use
8585
```python
8686
import compas_eve as eve
8787
from compas_eve import JsonMessageCodec
88+
from compas_eve.codecs import ProtobufMessageCodec
8889
from compas_eve.mqtt import MqttTransport
8990

9091
# Use JSON codec (default)
9192
json_codec = JsonMessageCodec()
9293
tx = MqttTransport("broker.hivemq.com", codec=json_codec)
9394

9495
# Or use Protocol Buffers for binary serialization (requires compas_pb)
95-
try:
96-
from compas_eve.codecs import ProtobufMessageCodec
97-
pb_codec = ProtobufMessageCodec()
98-
tx = MqttTransport("broker.hivemq.com", codec=pb_codec)
99-
except ImportError:
100-
print("Install compas_pb for Protocol Buffers support: pip install compas_pb")
96+
pb_codec = ProtobufMessageCodec()
97+
tx = MqttTransport("broker.hivemq.com", codec=pb_codec)
10198
```
10299

103100

104101
### Usage from Rhinoceros 3D
105102

106103
It is possible to use the same code from within Rhino/Grasshopper.
107104

108-
Make sure you have installed it to Rhino using the COMPAS installation mechanism:
109-
110-
```bash
111-
python -m compas_rhino.install
112-
```
113-
114-
Note: Rhino 7 (IronPython) is no longer supported. Please use Rhino 8 or later.
105+
To install `compas_eve`, use the the syntax `# r: compas_eve` at the top of any Python 3.x script in Rhino/Grasshopper.

examples/codec_usage.py

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
3. Use ProtobufMessageCodec (if compas_pb is installed)
88
"""
99

10+
import json
11+
1012
import compas_eve as eve
1113
from compas_eve import JsonMessageCodec, MessageCodec
1214

@@ -51,15 +53,8 @@ class UpperCaseCodec(MessageCodec):
5153

5254
def encode(self, message):
5355
"""Encode message by converting all string values to uppercase."""
54-
import json
55-
# Extract data from message
56-
try:
57-
data = message.data
58-
except (KeyError, AttributeError):
59-
try:
60-
data = message.__data__
61-
except (KeyError, AttributeError):
62-
data = dict(message)
56+
# Assume message is a Message instance
57+
data = message.data
6358

6459
# Convert string values to uppercase
6560
encoded_data = {}
@@ -70,10 +65,10 @@ def encode(self, message):
7065
encoded_data[key] = value
7166
return json.dumps(encoded_data)
7267

73-
def decode(self, encoded_data):
68+
def decode(self, encoded_data, message_type):
7469
"""Decode data (strings remain uppercase)."""
75-
import json
76-
return json.loads(encoded_data)
70+
data = json.loads(encoded_data)
71+
return message_type.parse(data)
7772

7873

7974
custom_codec = UpperCaseCodec()

src/compas_eve/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,10 @@
4747
Transport,
4848
Topic,
4949
MessageCodec,
50-
JsonMessageCodec,
5150
get_default_transport,
5251
set_default_transport,
5352
)
53+
from .codecs import JsonMessageCodec
5454
from .memory import InMemoryTransport
5555

5656
HERE = os.path.dirname(__file__)

src/compas_eve/codecs.py

Lines changed: 104 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,106 @@
1313
:toctree: generated/
1414
:nosignatures:
1515
16+
MessageCodec
17+
JsonMessageCodec
1618
ProtobufMessageCodec
1719
1820
"""
1921

20-
from compas_eve.core import MessageCodec
22+
from compas.data import json_dumps
23+
from compas.data import json_loads
2124

22-
__all__ = ["ProtobufMessageCodec"]
25+
__all__ = ["MessageCodec", "JsonMessageCodec", "ProtobufMessageCodec"]
26+
27+
28+
class MessageCodec(object):
29+
"""Abstract base class for message codecs.
30+
31+
A codec is responsible for encoding and decoding messages
32+
to/from a specific representation format (e.g., JSON, Protocol Buffers).
33+
"""
34+
35+
def encode(self, message):
36+
"""Encode a message to the codec's representation format.
37+
38+
Parameters
39+
----------
40+
message : :class:`Message` or dict or object
41+
Message to encode. Can be a Message instance, a dict, or
42+
an object implementing the COMPAS data framework.
43+
44+
Returns
45+
-------
46+
bytes or str
47+
Encoded representation of the message.
48+
"""
49+
raise NotImplementedError("Subclasses must implement encode()")
50+
51+
def decode(self, encoded_data):
52+
"""Decode data from the codec's representation format.
53+
54+
Parameters
55+
----------
56+
encoded_data : bytes or str
57+
Encoded data to decode.
58+
59+
Returns
60+
-------
61+
dict
62+
Decoded data dictionary that can be used to reconstruct a message.
63+
"""
64+
raise NotImplementedError("Subclasses must implement decode()")
65+
66+
67+
class JsonMessageCodec(MessageCodec):
68+
"""JSON codec for message serialization.
69+
70+
This codec uses the COMPAS framework's JSON serialization functions
71+
to encode and decode message data. It can handle Message objects,
72+
COMPAS Data objects, and regular dictionaries.
73+
"""
74+
75+
def encode(self, message):
76+
"""Encode a message to JSON string.
77+
78+
Parameters
79+
----------
80+
message : :class:`Message` or dict or object
81+
Message to encode. Can be a Message instance, a dict, or
82+
an object implementing the COMPAS data framework.
83+
84+
Returns
85+
-------
86+
str
87+
JSON string representation of the message.
88+
"""
89+
# Extract data from the message
90+
try:
91+
data = message.data
92+
except (KeyError, AttributeError):
93+
try:
94+
data = message.__data__
95+
except (KeyError, AttributeError):
96+
data = dict(message)
97+
return json_dumps(data)
98+
99+
def decode(self, encoded_data, message_type):
100+
"""Decode JSON string to message object.
101+
102+
Parameters
103+
----------
104+
encoded_data : str
105+
JSON string to decode.
106+
message_type : type
107+
The message type class to use for parsing.
108+
109+
Returns
110+
-------
111+
:class:`Message`
112+
Decoded message object.
113+
"""
114+
data = json_loads(encoded_data)
115+
return message_type.parse(data)
23116

24117

25118
try:
@@ -34,8 +127,7 @@ class ProtobufMessageCodec(MessageCodec):
34127
"""Protocol Buffers codec for message serialization.
35128
36129
This codec uses the compas_pb package to encode and decode message data
37-
using Protocol Buffers binary format. It can handle Message objects,
38-
COMPAS Data objects, and regular dictionaries.
130+
using Protocol Buffers binary format.
39131
40132
Note
41133
----
@@ -71,32 +163,26 @@ def encode(self, message):
71163
"The ProtobufMessageCodec requires 'compas_pb' to be installed. "
72164
"Please install it with: pip install compas_pb"
73165
)
74-
# Extract data from the message
75-
try:
76-
data = message.data
77-
except (KeyError, AttributeError):
78-
try:
79-
data = message.__data__
80-
except (KeyError, AttributeError):
81-
data = dict(message)
82-
return compas_pb.encode(data)
166+
return compas_pb.pb_dump_bts(message)
83167

84-
def decode(self, encoded_data):
85-
"""Decode Protocol Buffers binary data to data dictionary.
168+
def decode(self, encoded_data, message_type=None):
169+
"""Decode Protocol Buffers binary data to message object.
86170
87171
Parameters
88172
----------
89173
encoded_data : bytes
90174
Protocol Buffers binary data to decode.
175+
message_type : type, optional
176+
The message type class (not used for protobuf as it's encoded in the data).
91177
92178
Returns
93179
-------
94-
dict
95-
Decoded data dictionary.
180+
object
181+
Decoded message object.
96182
"""
97183
if not COMPAS_PB_AVAILABLE:
98184
raise ImportError(
99185
"The ProtobufMessageCodec requires 'compas_pb' to be installed. "
100186
"Please install it with: pip install compas_pb"
101187
)
102-
return compas_pb.decode(encoded_data)
188+
return compas_pb.pb_load_bts(encoded_data)

src/compas_eve/core.py

Lines changed: 10 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
from compas.data import json_dumps
2-
from compas.data import json_loads
3-
41
DEFAULT_TRANSPORT = None
52

63

@@ -27,70 +24,24 @@ def encode(self, message):
2724
"""
2825
raise NotImplementedError("Subclasses must implement encode()")
2926

30-
def decode(self, encoded_data):
27+
def decode(self, encoded_data, message_type):
3128
"""Decode data from the codec's representation format.
3229
3330
Parameters
3431
----------
3532
encoded_data : bytes or str
3633
Encoded data to decode.
34+
message_type : type
35+
The message type class to use for parsing.
3736
3837
Returns
3938
-------
40-
dict
41-
Decoded data dictionary that can be used to reconstruct a message.
39+
:class:`Message`
40+
Decoded message object.
4241
"""
4342
raise NotImplementedError("Subclasses must implement decode()")
4443

4544

46-
class JsonMessageCodec(MessageCodec):
47-
"""JSON codec for message serialization.
48-
49-
This codec uses the COMPAS framework's JSON serialization functions
50-
to encode and decode message data. It can handle Message objects,
51-
COMPAS Data objects, and regular dictionaries.
52-
"""
53-
54-
def encode(self, message):
55-
"""Encode a message to JSON string.
56-
57-
Parameters
58-
----------
59-
message : :class:`Message` or dict or object
60-
Message to encode. Can be a Message instance, a dict, or
61-
an object implementing the COMPAS data framework.
62-
63-
Returns
64-
-------
65-
str
66-
JSON string representation of the message.
67-
"""
68-
# Extract data from the message
69-
try:
70-
data = message.data
71-
except (KeyError, AttributeError):
72-
try:
73-
data = message.__data__
74-
except (KeyError, AttributeError):
75-
data = dict(message)
76-
return json_dumps(data)
77-
78-
def decode(self, encoded_data):
79-
"""Decode JSON string to data dictionary.
80-
81-
Parameters
82-
----------
83-
encoded_data : str
84-
JSON string to decode.
85-
86-
Returns
87-
-------
88-
dict
89-
Decoded data dictionary.
90-
"""
91-
return json_loads(encoded_data)
92-
93-
9445
def get_default_transport():
9546
"""Retrieve the default transport implementation to be used system-wide.
9647
@@ -129,7 +80,11 @@ class Transport(object):
12980
def __init__(self, codec=None, *args, **kwargs):
13081
super(Transport, self).__init__(*args, **kwargs)
13182
self._id_counter = 0
132-
self.codec = codec if codec is not None else JsonMessageCodec()
83+
if codec is None:
84+
from compas_eve.codecs import JsonMessageCodec
85+
86+
codec = JsonMessageCodec()
87+
self.codec = codec
13388

13489
@property
13590
def id_counter(self):

src/compas_eve/mqtt/mqtt_paho.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,7 @@ def subscribe(self, topic, callback):
106106
subscribe_id = "{}:{}".format(event_key, id(callback))
107107

108108
def _local_callback(msg):
109-
decoded_data = self.codec.decode(msg.payload.decode())
110-
message_obj = topic.message_type.parse(decoded_data)
109+
message_obj = self.codec.decode(msg.payload.decode(), topic.message_type)
111110
callback(message_obj)
112111

113112
def _subscribe_callback(**kwargs):

0 commit comments

Comments
 (0)