compas-eve 2.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- compas_eve/__init__.py +34 -0
- compas_eve/__main__.py +4 -0
- compas_eve/codecs/__init__.py +166 -0
- compas_eve/codecs/conversions.py +23 -0
- compas_eve/core.py +270 -0
- compas_eve/event_emitter.py +253 -0
- compas_eve/ghpython/__init__.py +61 -0
- compas_eve/ghpython/background.py +276 -0
- compas_eve/ghpython/components/Ce_BackgroundTask/code.py +64 -0
- compas_eve/ghpython/components/Ce_BackgroundTask/icon.png +0 -0
- compas_eve/ghpython/components/Ce_BackgroundTask/metadata.json +35 -0
- compas_eve/ghpython/components/Ce_Message/code.py +62 -0
- compas_eve/ghpython/components/Ce_Message/icon.png +0 -0
- compas_eve/ghpython/components/Ce_Message/metadata.json +25 -0
- compas_eve/ghpython/components/Ce_MqttConnect/code.py +39 -0
- compas_eve/ghpython/components/Ce_MqttConnect/icon.png +0 -0
- compas_eve/ghpython/components/Ce_MqttConnect/metadata.json +40 -0
- compas_eve/ghpython/components/Ce_Publish/code.py +44 -0
- compas_eve/ghpython/components/Ce_Publish/icon.png +0 -0
- compas_eve/ghpython/components/Ce_Publish/metadata.json +40 -0
- compas_eve/ghpython/components/Ce_Subscribe/code.py +65 -0
- compas_eve/ghpython/components/Ce_Subscribe/icon.png +0 -0
- compas_eve/ghpython/components/Ce_Subscribe/metadata.json +41 -0
- compas_eve/ghpython/components/Ce_ZenohConnect/code.py +37 -0
- compas_eve/ghpython/components/Ce_ZenohConnect/icon.png +0 -0
- compas_eve/ghpython/components/Ce_ZenohConnect/metadata.json +30 -0
- compas_eve/ghpython/components/___init__.py +0 -0
- compas_eve/ghpython/yak_template/icon.png +0 -0
- compas_eve/ghpython/yak_template/manifest.yml +27 -0
- compas_eve/memory/__init__.py +129 -0
- compas_eve/mqtt/__init__.py +3 -0
- compas_eve/mqtt/mqtt_paho.py +222 -0
- compas_eve/proto/message.proto +9 -0
- compas_eve/proto/message_pb2.py +41 -0
- compas_eve/ros/__init__.py +3 -0
- compas_eve/ros/ros_transport.py +184 -0
- compas_eve/zenoh/__init__.py +3 -0
- compas_eve/zenoh/zenoh_transport.py +184 -0
- compas_eve-2.4.0.dist-info/METADATA +212 -0
- compas_eve-2.4.0.dist-info/RECORD +44 -0
- compas_eve-2.4.0.dist-info/WHEEL +5 -0
- compas_eve-2.4.0.dist-info/entry_points.txt +2 -0
- compas_eve-2.4.0.dist-info/licenses/LICENSE +21 -0
- compas_eve-2.4.0.dist-info/top_level.txt +1 -0
compas_eve/__init__.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
__author__ = ["Gonzalo Casas"]
|
|
2
|
+
__copyright__ = "COMPAS Association"
|
|
3
|
+
__license__ = "MIT License"
|
|
4
|
+
__email__ = "casas@arch.ethz.ch"
|
|
5
|
+
__version__ = "2.4.0"
|
|
6
|
+
|
|
7
|
+
from .event_emitter import EventEmitterMixin # noqa: F401 needed here to avoid a circular import
|
|
8
|
+
from .core import (
|
|
9
|
+
Message,
|
|
10
|
+
Publisher,
|
|
11
|
+
Subscriber,
|
|
12
|
+
EchoSubscriber,
|
|
13
|
+
Transport,
|
|
14
|
+
Topic,
|
|
15
|
+
get_default_transport,
|
|
16
|
+
set_default_transport,
|
|
17
|
+
)
|
|
18
|
+
from .codecs import MessageCodec
|
|
19
|
+
from .memory import InMemoryTransport
|
|
20
|
+
|
|
21
|
+
set_default_transport(InMemoryTransport())
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
"Message",
|
|
25
|
+
"Publisher",
|
|
26
|
+
"Subscriber",
|
|
27
|
+
"EchoSubscriber",
|
|
28
|
+
"Topic",
|
|
29
|
+
"Transport",
|
|
30
|
+
"MessageCodec",
|
|
31
|
+
"get_default_transport",
|
|
32
|
+
"set_default_transport",
|
|
33
|
+
"InMemoryTransport",
|
|
34
|
+
]
|
compas_eve/__main__.py
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
from typing import Optional
|
|
3
|
+
from typing import Union
|
|
4
|
+
|
|
5
|
+
from compas.data import json_dumps
|
|
6
|
+
from compas.data import json_loads
|
|
7
|
+
|
|
8
|
+
from compas_eve.core import Message
|
|
9
|
+
|
|
10
|
+
try:
|
|
11
|
+
import compas_pb
|
|
12
|
+
|
|
13
|
+
COMPAS_PB_AVAILABLE = True
|
|
14
|
+
except ImportError:
|
|
15
|
+
COMPAS_PB_AVAILABLE = False
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
__all__ = ["MessageCodec", "JsonMessageCodec", "ProtobufMessageCodec"]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class MessageCodec(object):
|
|
22
|
+
"""Abstract base class for message codecs.
|
|
23
|
+
|
|
24
|
+
A codec is responsible for encoding and decoding messages
|
|
25
|
+
to/from a specific representation format (e.g., JSON, Protocol Buffers).
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def encode(self, message: Union[Message, dict, Any]) -> Union[bytes, str]:
|
|
29
|
+
"""Encode a message to the codec's representation format.
|
|
30
|
+
|
|
31
|
+
Parameters
|
|
32
|
+
----------
|
|
33
|
+
message
|
|
34
|
+
Message to encode. Can be a Message instance, a dict, or
|
|
35
|
+
an object implementing the COMPAS data framework.
|
|
36
|
+
|
|
37
|
+
Returns
|
|
38
|
+
-------
|
|
39
|
+
bytes or str
|
|
40
|
+
Encoded representation of the message.
|
|
41
|
+
"""
|
|
42
|
+
raise NotImplementedError("Subclasses must implement encode()")
|
|
43
|
+
|
|
44
|
+
def decode(self, encoded_data: bytes) -> Union[Message, dict, Any]:
|
|
45
|
+
"""Decode data from the codec's representation format.
|
|
46
|
+
|
|
47
|
+
Parameters
|
|
48
|
+
----------
|
|
49
|
+
encoded_data
|
|
50
|
+
Encoded data to decode.
|
|
51
|
+
|
|
52
|
+
Returns
|
|
53
|
+
-------
|
|
54
|
+
Union[Message, dict, Any]
|
|
55
|
+
Decoded message after reconstruction from the encoded data.
|
|
56
|
+
"""
|
|
57
|
+
raise NotImplementedError("Subclasses must implement decode()")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class JsonMessageCodec(MessageCodec):
|
|
61
|
+
"""JSON codec for message serialization.
|
|
62
|
+
|
|
63
|
+
This codec uses the COMPAS framework's JSON serialization functions
|
|
64
|
+
to encode and decode message data. It can handle Message objects,
|
|
65
|
+
COMPAS Data objects, and regular dictionaries.
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
def encode(self, message: Union[Message, dict, Any]) -> str:
|
|
69
|
+
"""Encode a message to JSON string.
|
|
70
|
+
|
|
71
|
+
Parameters
|
|
72
|
+
----------
|
|
73
|
+
message
|
|
74
|
+
Message to encode. Can be a Message instance, a dict, or
|
|
75
|
+
an object implementing the COMPAS data framework.
|
|
76
|
+
|
|
77
|
+
Returns
|
|
78
|
+
-------
|
|
79
|
+
str
|
|
80
|
+
JSON string representation of the message.
|
|
81
|
+
"""
|
|
82
|
+
# Extract data from the message
|
|
83
|
+
try:
|
|
84
|
+
return json_dumps(message.data)
|
|
85
|
+
except (KeyError, AttributeError):
|
|
86
|
+
try:
|
|
87
|
+
return json_dumps(message)
|
|
88
|
+
except (KeyError, AttributeError):
|
|
89
|
+
return json_dumps(dict(message))
|
|
90
|
+
|
|
91
|
+
def decode(self, encoded_data: bytes, message_type: type) -> Message:
|
|
92
|
+
"""Decode JSON message payloads to message object.
|
|
93
|
+
|
|
94
|
+
Parameters
|
|
95
|
+
----------
|
|
96
|
+
encoded_data
|
|
97
|
+
Message bytes to decode into a JSON string.
|
|
98
|
+
message_type
|
|
99
|
+
The message type class to use for parsing.
|
|
100
|
+
|
|
101
|
+
Returns
|
|
102
|
+
-------
|
|
103
|
+
Message
|
|
104
|
+
Decoded message object.
|
|
105
|
+
"""
|
|
106
|
+
data = json_loads(encoded_data.decode())
|
|
107
|
+
if hasattr(data, "__data__"):
|
|
108
|
+
return data
|
|
109
|
+
else:
|
|
110
|
+
return message_type.parse(data)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class ProtobufMessageCodec(MessageCodec):
|
|
114
|
+
"""Protocol Buffers codec for message serialization.
|
|
115
|
+
|
|
116
|
+
This codec uses the compas_pb package to encode and decode message data
|
|
117
|
+
using Protocol Buffers binary format.
|
|
118
|
+
|
|
119
|
+
Note
|
|
120
|
+
----
|
|
121
|
+
This codec requires the `compas_pb` package to be installed.
|
|
122
|
+
If `compas_pb` is not available, attempting to encode or decode
|
|
123
|
+
will raise an [ImportError][].
|
|
124
|
+
"""
|
|
125
|
+
|
|
126
|
+
def __init__(self):
|
|
127
|
+
super(ProtobufMessageCodec, self).__init__()
|
|
128
|
+
if not COMPAS_PB_AVAILABLE:
|
|
129
|
+
raise ImportError("The ProtobufMessageCodec requires 'compas_pb' to be installed. Please install it with: pip install compas_pb")
|
|
130
|
+
|
|
131
|
+
def encode(self, message: Union[Message, dict, Any]) -> bytes:
|
|
132
|
+
"""Encode a message to Protocol Buffers binary format.
|
|
133
|
+
|
|
134
|
+
Parameters
|
|
135
|
+
----------
|
|
136
|
+
message
|
|
137
|
+
Message to encode. Can be a Message instance, a dict, or
|
|
138
|
+
an object implementing the COMPAS data framework.
|
|
139
|
+
|
|
140
|
+
Returns
|
|
141
|
+
-------
|
|
142
|
+
bytes
|
|
143
|
+
Protocol Buffers binary representation of the message.
|
|
144
|
+
"""
|
|
145
|
+
if not COMPAS_PB_AVAILABLE:
|
|
146
|
+
raise ImportError("The ProtobufMessageCodec requires 'compas_pb' to be installed. Please install it with: pip install compas_pb")
|
|
147
|
+
return compas_pb.pb_dump_bts(message)
|
|
148
|
+
|
|
149
|
+
def decode(self, encoded_data: bytes, message_type: Optional[type] = None) -> object:
|
|
150
|
+
"""Decode Protocol Buffers binary data to message object.
|
|
151
|
+
|
|
152
|
+
Parameters
|
|
153
|
+
----------
|
|
154
|
+
encoded_data
|
|
155
|
+
Protocol Buffers binary data to decode.
|
|
156
|
+
message_type
|
|
157
|
+
The message type class (not used for protobuf as it's encoded in the data).
|
|
158
|
+
|
|
159
|
+
Returns
|
|
160
|
+
-------
|
|
161
|
+
object
|
|
162
|
+
Decoded message object.
|
|
163
|
+
"""
|
|
164
|
+
if not COMPAS_PB_AVAILABLE:
|
|
165
|
+
raise ImportError("The ProtobufMessageCodec requires 'compas_pb' to be installed. Please install it with: pip install compas_pb")
|
|
166
|
+
return compas_pb.pb_load_bts(encoded_data)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from compas_pb.core import _deserialize_any
|
|
2
|
+
from compas_pb.core import _serializer_any
|
|
3
|
+
from compas_pb.registry import pb_deserializer
|
|
4
|
+
from compas_pb.registry import pb_serializer
|
|
5
|
+
|
|
6
|
+
from compas_eve import Message
|
|
7
|
+
from compas_eve.proto import message_pb2
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@pb_serializer(Message)
|
|
11
|
+
def message_to_pb(message: Message) -> message_pb2.Message:
|
|
12
|
+
pb = message_pb2.Message()
|
|
13
|
+
for k, v in message.data.items():
|
|
14
|
+
pb.data[k].CopyFrom(_serializer_any(v))
|
|
15
|
+
return pb
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@pb_deserializer(message_pb2.Message)
|
|
19
|
+
def message_from_pb(pb: message_pb2.Message) -> Message:
|
|
20
|
+
message = Message()
|
|
21
|
+
for k, v in pb.data.items():
|
|
22
|
+
message[k] = _deserialize_any(v)
|
|
23
|
+
return message
|
compas_eve/core.py
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
from typing import Callable
|
|
3
|
+
from typing import Dict
|
|
4
|
+
from typing import Optional
|
|
5
|
+
from typing import Type
|
|
6
|
+
from typing import Union
|
|
7
|
+
|
|
8
|
+
DEFAULT_TRANSPORT = None
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def get_default_transport() -> Optional["Transport"]:
|
|
12
|
+
"""Retrieve the default transport implementation to be used system-wide.
|
|
13
|
+
|
|
14
|
+
Returns
|
|
15
|
+
-------
|
|
16
|
+
Transport
|
|
17
|
+
Instance of a transport class. By default, `compas_eve` uses
|
|
18
|
+
[InMemoryTransport][compas_eve.memory.InMemoryTransport].
|
|
19
|
+
"""
|
|
20
|
+
return DEFAULT_TRANSPORT
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def set_default_transport(transport: Optional["Transport"]) -> None:
|
|
24
|
+
"""Assign a default transport implementation to be used system-wide.
|
|
25
|
+
|
|
26
|
+
Parameters
|
|
27
|
+
----------
|
|
28
|
+
transport
|
|
29
|
+
Instance of a transport class. By default, `compas_eve` uses
|
|
30
|
+
[InMemoryTransport][compas_eve.memory.InMemoryTransport].
|
|
31
|
+
"""
|
|
32
|
+
global DEFAULT_TRANSPORT
|
|
33
|
+
DEFAULT_TRANSPORT = transport
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class Transport(object):
|
|
37
|
+
"""Defines the base interface for different transport implementations.
|
|
38
|
+
|
|
39
|
+
Parameters
|
|
40
|
+
----------
|
|
41
|
+
codec
|
|
42
|
+
The codec to use for encoding and decoding messages.
|
|
43
|
+
If not provided, defaults to [JsonMessageCodec][compas_eve.codecs.JsonMessageCodec].
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
def __init__(self, codec: Optional[Any] = None, *args: Any, **kwargs: Any) -> None:
|
|
47
|
+
super(Transport, self).__init__(*args, **kwargs)
|
|
48
|
+
from compas_eve.codecs import JsonMessageCodec
|
|
49
|
+
|
|
50
|
+
self._id_counter = 0
|
|
51
|
+
if codec is None:
|
|
52
|
+
codec = JsonMessageCodec()
|
|
53
|
+
self.codec = codec
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def id_counter(self) -> int:
|
|
57
|
+
"""Generate an auto-incremental ID starting from 1."""
|
|
58
|
+
self._id_counter += 1
|
|
59
|
+
return self._id_counter
|
|
60
|
+
|
|
61
|
+
def publish(self, topic: "Topic", message: Union["Message", dict], **options: Any) -> None:
|
|
62
|
+
pass
|
|
63
|
+
|
|
64
|
+
def subscribe(self, topic: "Topic", callback: Callable) -> Optional[str]:
|
|
65
|
+
pass
|
|
66
|
+
|
|
67
|
+
def unsubscribe(self, topic: "Topic") -> None:
|
|
68
|
+
pass
|
|
69
|
+
|
|
70
|
+
def advertise(self, topic: "Topic") -> Optional[str]:
|
|
71
|
+
pass
|
|
72
|
+
|
|
73
|
+
def unadvertise(self, topic: "Topic") -> None:
|
|
74
|
+
pass
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class Message(object):
|
|
78
|
+
"""Message objects used for publishing and subscribing to/from topics.
|
|
79
|
+
|
|
80
|
+
A message is fundamentally a dictionary and behaves as one."""
|
|
81
|
+
|
|
82
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
83
|
+
super(Message, self).__init__()
|
|
84
|
+
self.data = {}
|
|
85
|
+
self.data.update(*args, **kwargs)
|
|
86
|
+
|
|
87
|
+
def ToString(self) -> str:
|
|
88
|
+
return str(self)
|
|
89
|
+
|
|
90
|
+
def __str__(self) -> str:
|
|
91
|
+
return str(self.data)
|
|
92
|
+
|
|
93
|
+
def __getattr__(self, name: str) -> Any:
|
|
94
|
+
return self.data[name]
|
|
95
|
+
|
|
96
|
+
def __setattr__(self, key: str, value: Any) -> None:
|
|
97
|
+
if key == "data" or key in self.__dict__:
|
|
98
|
+
super(Message, self).__setattr__(key, value)
|
|
99
|
+
else:
|
|
100
|
+
self.data[key] = value
|
|
101
|
+
|
|
102
|
+
def __getitem__(self, key: str) -> Any:
|
|
103
|
+
return self.data[key]
|
|
104
|
+
|
|
105
|
+
def __setitem__(self, key: str, value: Any) -> None:
|
|
106
|
+
self.data[key] = value
|
|
107
|
+
|
|
108
|
+
def __jsondump__(self, minimal: bool = False) -> Dict[str, Any]:
|
|
109
|
+
return self.data
|
|
110
|
+
|
|
111
|
+
@classmethod
|
|
112
|
+
def parse(cls, value: Dict[str, Any]) -> "Message":
|
|
113
|
+
instance = cls(**value)
|
|
114
|
+
return instance
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class Topic(object):
|
|
118
|
+
"""A topic is like a mailbox where messages can be sent and received.
|
|
119
|
+
|
|
120
|
+
Topics are described by a name, a type of message they accept, and a
|
|
121
|
+
set of options.
|
|
122
|
+
|
|
123
|
+
Attributes
|
|
124
|
+
----------
|
|
125
|
+
name
|
|
126
|
+
Name of the topic.
|
|
127
|
+
message_type
|
|
128
|
+
Class defining the message structure, or a backend-native message type
|
|
129
|
+
identifier. Use [Message][] for a generic, non-type-checked message
|
|
130
|
+
implementation. Defaults to [Message][].
|
|
131
|
+
options
|
|
132
|
+
A dictionary of options.
|
|
133
|
+
"""
|
|
134
|
+
|
|
135
|
+
# TODO: Add documentation/examples of possible options
|
|
136
|
+
|
|
137
|
+
def __init__(self, name: str, message_type: Optional[Union[Type[Message], str]] = None, **options: Any) -> None:
|
|
138
|
+
self.name = name
|
|
139
|
+
self.message_type = message_type or Message
|
|
140
|
+
self.options = options
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
class Publisher(object):
|
|
144
|
+
"""Publisher for a specific topic.
|
|
145
|
+
|
|
146
|
+
Parameters
|
|
147
|
+
----------
|
|
148
|
+
topic
|
|
149
|
+
The topic to publish messages to. If a string is provided, a new topic instance
|
|
150
|
+
will be created using the string as topic name.
|
|
151
|
+
transport
|
|
152
|
+
The transport to use for publishing. If not provided, the default transport will be used.
|
|
153
|
+
"""
|
|
154
|
+
|
|
155
|
+
def __init__(self, topic: Union[Topic, str], transport: Optional[Transport] = None) -> None:
|
|
156
|
+
self.topic = topic if isinstance(topic, Topic) else Topic(topic)
|
|
157
|
+
self.transport = transport or get_default_transport()
|
|
158
|
+
self._advertise_id = None
|
|
159
|
+
|
|
160
|
+
@property
|
|
161
|
+
def is_advertised(self) -> bool:
|
|
162
|
+
"""Indicate if the publisher has announced its topic as advertised or not.
|
|
163
|
+
|
|
164
|
+
Returns
|
|
165
|
+
-------
|
|
166
|
+
bool
|
|
167
|
+
True if advertised as publisher of this topic, False otherwise.
|
|
168
|
+
"""
|
|
169
|
+
return self._advertise_id is not None
|
|
170
|
+
|
|
171
|
+
def message_published(self, message: Union[Message, dict]) -> None:
|
|
172
|
+
"""Handler called when a message has been published."""
|
|
173
|
+
pass
|
|
174
|
+
|
|
175
|
+
def publish(self, message: Union[Message, dict], **options: Any) -> None:
|
|
176
|
+
"""Publish a message to the topic.
|
|
177
|
+
|
|
178
|
+
Parameters
|
|
179
|
+
----------
|
|
180
|
+
message
|
|
181
|
+
The message to publish.
|
|
182
|
+
**options
|
|
183
|
+
Transport-specific options passed through to the underlying transport.
|
|
184
|
+
For example, ``retain=True`` on MQTT and InMemory transports.
|
|
185
|
+
"""
|
|
186
|
+
# TODO: check if message type matches self.topic.message_type declared
|
|
187
|
+
if not self.is_advertised:
|
|
188
|
+
self.advertise()
|
|
189
|
+
|
|
190
|
+
self.transport.publish(self.topic, message, **options)
|
|
191
|
+
self.message_published(message)
|
|
192
|
+
|
|
193
|
+
def advertise(self) -> None:
|
|
194
|
+
"""Advertise the publisher for the topic."""
|
|
195
|
+
if self.is_advertised:
|
|
196
|
+
return
|
|
197
|
+
|
|
198
|
+
self._advertise_id = self.transport.advertise(self.topic)
|
|
199
|
+
|
|
200
|
+
def unadvertise(self) -> None:
|
|
201
|
+
"""Unadvertise the publisher for the topic."""
|
|
202
|
+
if not self.is_advertised:
|
|
203
|
+
return
|
|
204
|
+
|
|
205
|
+
self.transport.unadvertise(self.topic)
|
|
206
|
+
self._advertise_id = None
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
class Subscriber(object):
|
|
210
|
+
"""Subscriber for a specific topic.
|
|
211
|
+
|
|
212
|
+
Parameters
|
|
213
|
+
----------
|
|
214
|
+
topic
|
|
215
|
+
The topic to subscribe to. If a string is provided, a new topic instance
|
|
216
|
+
will be created using the string as topic name.
|
|
217
|
+
transport
|
|
218
|
+
The transport to use for subscribing. If not provided, the default transport will be used.
|
|
219
|
+
"""
|
|
220
|
+
|
|
221
|
+
def __init__(self, topic: Union[Topic, str], callback: Optional[Callable] = None, transport: Optional[Transport] = None) -> None:
|
|
222
|
+
self.transport = transport or get_default_transport()
|
|
223
|
+
self.topic = topic if isinstance(topic, Topic) else Topic(topic)
|
|
224
|
+
self._subscribe_id = None
|
|
225
|
+
self._callback = callback
|
|
226
|
+
|
|
227
|
+
def message_received(self, message: Union[Message, dict]) -> None:
|
|
228
|
+
"""Handler called whenever a new message is received.
|
|
229
|
+
|
|
230
|
+
By default, this implementation will simply invoke the callback
|
|
231
|
+
used on init (if any), but sub-classes can override this behavior."""
|
|
232
|
+
if self._callback:
|
|
233
|
+
self._callback(message)
|
|
234
|
+
|
|
235
|
+
@property
|
|
236
|
+
def is_subscribed(self) -> bool:
|
|
237
|
+
"""Indicate if the instace is currently subscribed to its topic or not."""
|
|
238
|
+
return self._subscribe_id is not None
|
|
239
|
+
|
|
240
|
+
def subscribe(self) -> None:
|
|
241
|
+
if self._subscribe_id:
|
|
242
|
+
return
|
|
243
|
+
|
|
244
|
+
self._subscribe_id = self.transport.subscribe(self.topic, self.message_received)
|
|
245
|
+
|
|
246
|
+
def unsubscribe(self) -> None:
|
|
247
|
+
"""Unregister the subscriber from its topic."""
|
|
248
|
+
if not self._subscribe_id:
|
|
249
|
+
return
|
|
250
|
+
|
|
251
|
+
self.transport.unsubscribe_by_id(self._subscribe_id)
|
|
252
|
+
self._subscribe_id = None
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
class EchoSubscriber(Subscriber):
|
|
256
|
+
"""Simple subscriber that prints received messages on the console (ie. `stdout`).
|
|
257
|
+
|
|
258
|
+
Parameters
|
|
259
|
+
----------
|
|
260
|
+
topic
|
|
261
|
+
The topic to subscribe to. If a string is provided, a new topic instance
|
|
262
|
+
will be created using the string as topic name.
|
|
263
|
+
"""
|
|
264
|
+
|
|
265
|
+
def __init__(self, topic: Union[Topic, str], transport: Optional[Transport] = None) -> None:
|
|
266
|
+
super(EchoSubscriber, self).__init__(topic, callback=self.echo, transport=transport)
|
|
267
|
+
|
|
268
|
+
def echo(self, message: Union[Message, dict]) -> None:
|
|
269
|
+
"""Print received messages to the console."""
|
|
270
|
+
print(str(message))
|