pyimb is a Python client to the IMB framework¶
This text assumes you are familiar with TNO’s IMB framework.
imb module¶
Client to IMB hub
The module is written in Python 3.4 and only tested with Python 3.4. Backporting to Python 2.7 should be easy. Just be careful with unicode strings and byte arrays.
-
class
imb.
Client
(host, port, owner_id=None, owner_name=None, federation=None)¶ A client that can connect to an IMB hub.
The client tries to open a socket immediately as the object is created. The socket read/write logic is implemented with the python asynchat module, and the asyncore.loop() call is made in a separate thread. That thread will not finish untill
Client.disconnect()
has been called.Examples
>>> import imb >>> host = 'localhost' >>> port = 4000 >>> owner_id = 123 >>> owner_name = 'my name' >>> federation = 'my federation' >>> >>> c = imb.Client(host, port, owner_id, owner_name, federation) # Connect to a hub >>> # Example 1: Send a string in the payload of a NormalEvent >>> e = c.publish('my event') # Now we can send signals on the event >>> e.signal_event(imb.ekNormalEvent, imb.encode_string('This string is sent')) >>> e.signal_string('This is easier but equivalent.') >>> >>> # Example 2: Receive a string in the payload of a NormalEvent >>> def string_handler(payload): ... print('Received string {}'.format(imb.decode_string(payload))) ... >>> # Handle all ekNormalEvent signals on this event with the string_handler >>> e.add_handler(imb.ekNormalEvent, string_handler) >>> e.subscribe() # start listening for signals on the event >>> >>> # Example 3: Send a file as a stream >>> e.signal_stream('stream name', open('test.txt', 'rb')) # Empty the file stream >>> e.unpublish() >>> >>> # Example 4: Receive a stream >>> def create_stream(stream_id, stream_name): ... filename = str(stream_id) + '_' + stream_name ... return open(filename, 'wb+') ... >>> def end_stream(stream): ... pass # do something here, just before automatic stream.close() ... >>> e.create_stream_callback = create_stream >>> e.end_stream_callback = end_stream # this is optional, really >>> # Now just wait for a stream to come flying on the event >>> # (we are already subscribed since Example 2 above!) >>> e.unsubscribe() # Unsubscribe when you are done >>> c.disconnect() # And finally always disconnect
-
collect_incoming_data
(data)¶ Called by async_chat for incoming data on the TCP socket.
See docs for async_chat.
-
disconnect
()¶ Disconnect the underlying socket.
-
found_terminator
()¶ Called by async_chat when a terminator sequence is received.
See docs for async_chat.
-
get_event
(event_name, prefix=True, create=True)¶ Get an EventDefinition object that can be used for communication.
The
EventDefinition
object returned will not be subscribed or published to anything.Parameters: - event_name (str) – The name of the event.
- prefix (bool, optional) – If True, the event name will be prefixed with the client federation + ., so you get something like ` my_federation.my_event_name`.
- create (bool, optional) – If True (the default), the event will be created if it doesn’t exist yet. If False, and the event doesn’t exist yet, this function will return None.
Returns: An EventDefinition object or None.
Example
>>> c = imb.Client(host, port, owner_id, owner_name, federation) # Connect to a hub >>> e = c.get_event('my event') >>> e.publish() # Now we can send signals on the event
-
handle_connect
()¶ Called by async_chat class when the client has connected.
See docs for async_chat.
-
publish
(event_name, prefix=True)¶ Get an EventDefinition object and ensure it is subscribed.
Parameters: - event_name (str) – See docs for
Client.get_event()
. - prefix (bool, optional) – See docs for
Client.get_event()
.
Returns: A published EventDefinition object.
Example
>>> # Two equivalent ways of getting a published EventDefinition object >>> e1 = client.get_event('my_event') >>> e1.publish() >>> e2 = c.publish('my_event') # This gets a reference to the same object >>> e1 is e2 True
- event_name (str) – See docs for
-
signal_normal_event
(event_id, event_kind, event_payload)¶ Send an icEvent command.
You probably want to use e.g.
Event.signal_event()
instead. It will at least simplify things by supplying the event_id for you.Parameters: - event_id (int) – The event_id to send.
- event_kind (int) – One of the event kind constants, e.g. ekNormalEvent or ekChangeObjectEvent.
- event_payload (bytes or similar) – The event payload to send along.
-
signal_publish
(event_id, event_entry_type, event_name)¶ Publish an event.
You probably want to use
Client.publish()
orEventDefinition.publish()
instead.
-
signal_subscribe
(event_id, event_entry_type, event_name)¶ Subscribe to an event.
You probably want to use
Client.subscribe()
orEventDefinition.subscribe()
instead.
-
signal_unpublish
(event_name)¶ Unpublish an event.
You probably want to use
Client.unpublish()
orEventDefinition.unpublish()
instead.
-
signal_unsubscribe
(event_name)¶ Unsubscribe from an event.
You probably want to use
Client.unsubscribe()
orEventDefinition.unsubscribe()
instead.
-
subscribe
(event_name, prefix=True)¶ Get an EventDefinition object and ensure it is subscribed.
Parameters: - event_name (str) – See docs for
Client.get_event()
. - prefix (bool, optional) – See docs for
Client.get_event()
.
Returns: A subscribed EventDefinition object.
Example
>>> e1 = client.get_event('my_event') >>> e1.subscribe() >>> e2 = c.subscribe('my_event') # This gets a reference to the same object >>> e1 is e2 True
- event_name (str) – See docs for
-
unpublish
(event_name, prefix=True)¶ Ensure that the client is not publishing an event.
Parameters: - event_name (str) – See docs for
Client.get_event()
. - prefix (bool, optional) – See docs for
Client.get_event()
.
Returns: The EventDefinition object, if it already existed. Otherwise None.
- event_name (str) – See docs for
-
unsubscribe
(event_name, prefix=True)¶ Ensure that the client is not subscribed to an event.
Parameters: - event_name (str) – See docs for
Client.get_event()
. - prefix (bool, optional) – See docs for
Client.get_event()
.
Returns: The EventDefinition object, if it already existed. Otherwise None.
- event_name (str) – See docs for
-
-
class
imb.
Command
(command_code=None, payload=None)¶ Represents a command that can be sent by a Client
Commands are things like “subscribe to event”, “publish event”, “signal event”, etc.
You don’t need to use this class directly. It is used only by the
Client
class.
-
class
imb.
EventDefinition
(event_id, name, client)¶ Represents an event in the IMB framework
The
EventDefinition
object represents an event (a named communication channel) in the IMB framework. It is created by aClient
instance and can then be used to- subscribe/unsubscribe to the event,
- publish/unpublish the event,
- send signals, and
- setup handlers for incoming signals.
Note
You should not create EventDefinition instances yourself. Instead, call
Client.get_event()
,Client.subscribe()
orClient.publish()
.-
add_handler
(event_kind, handler)¶ Add a handler for received events of a certain kind.
The handler is a callable object which is called after an event has arrived. Your handler is expected to have the right arguments signature for the event kind. Check examples below, or the source of
_decode_event_payload()
if you are unsure.Parameters: - event_kind (int) – One of the event kind constants, e.g. ekNormalEvent or ekChangeObjectEvent.
- handler (callable) – The handler.
Raises: RuntimeError
– If you try to add handlers for stream events.Example
>>> c = imb.Client(url, port, owner_id, owner_name, federation) >>> e = c.subscribe('event name') >>> def my_change_object_handler(action, object_id, short_event_name, attr_name): ... print('ChangeObjectEvent:', action, object_id, short_event_name, attr_name) >>> e.add_handler(imb.ekChangeObjectEvent, my_change_object_handler) >>> def my_normal_event_handler(payload): ... print('NormalEvent:', payload) >>> e.add_handler(imb.ekNormalEvent, my_normal_event_handler)
-
create_stream_callback
¶ Property – Callback function for stream creation.
This property should be either None or a callable to be called when a stream header arrives on the event. The callback function should take two arguments: (stream_id, stream_name), and return a stream object to save the incoming stream in.
If no stream object is returned from the stream callback function, the incoming stream will not be saved.
Note
There is also a property
end_stream_callback()
which is called at the end of the stream, just before it is closed.Example:
>>> def create_stream(stream_id, stream_name): ... filename = str(stream_id) + '_' + stream_name ... return open(filename, 'wb+') ... >>> def end_stream(stream): ... pass # do something here, just before automatic stream.close() ... >>> e = client.subscribe('my-stream') >>> e.create_stream_callback = create_stream >>> e.end_stream_callback = end_stream # this is optional, really
-
end_stream_callback
¶ Property – Callback function for handling end of stream.
Similar to
create_stream_callback()
, this property should either be None or a callable to be called when a stream tail has arrived on the event. The callback function should take one argument stream: it will be passed the same stream object that was created increate_stream_callback()
.Note
The stream is automatically closed right after the end_stream_callback function is called, so you don’t have to do this manually.
Example
See
create_stream_callback()
for an example.
-
handle_event
(event_kind, event_payload)¶ Call all handlers registered for the event_kind.
You never have to call this function directly. It is called by the owning
Client
object.See also
EventDefinition.add_handler()
.
-
publish
()¶ Publish this event with the owning Client
Equivalent to client.signal_publish(...).
This function is provided only for convenience.
-
signal_change_object
(action, object_id, attribute)¶ Send a ChangeObject event.
Parameters: - action (int) – One of the action constants.
- object_id (int) – Id of object to change.
- attribute (string) –
-
signal_event
(event_kind, event_payload)¶ Send a message on the event.
Parameters: - event_kind (int) – One of the event kind constants, e.g. ekNormalEvent or ekChangeObjectEvent.
- event_payload (bytes or similar) – The payload to send.
-
signal_stream
(name, stream, chunk_size=16384)¶ Send a stream on the event.
Parameters: - name (str) – A name for the stream, unique for the Client.
- stream (stream object) – The stream to be sent. Must be implemented as a standard Python stream, e.g. a file object obtained through open(filename, ‘b’).
- chunk_size (int, optional) – Number of bytes to send in each body chunk.
-
signal_string
(value)¶ Send a NormalEvent event with only a string in the payload.
This is provided for convenience. Equivalent to self.signal_event(ekNormalEvent, encode_string(value)).
Parameters: value (str) – The string to send.
-
subscribe
()¶ Subscribe the owning Client to this event
Equivalent to client.signal_subscribe(...).
This function is provided only for convenience.
-
unpublish
()¶ Unpublish this event with the owning Client
Equivalent to client.signal_unpublish(...).
This function is provided only for convenience.
-
unsubscribe
()¶ Unsubscribe the owning Client from this event
Equivalent to client.signal_unsubscribe(...).
This function is provided only for convenience.
-
imb.
decode_int
(buf)¶ Decode a signed integer from bytes, using the default byte order.
Parameters: buf (bytes or similar) – The bytes to decode. Returns: The decoded integer. Return type: int
-
imb.
decode_string
(buf, start=0, nextpos=False)¶ Decode a string prefixed with length.
This function is the reverse of
encode_string()
.Parameters: - buf (bytes or similar) – The bytes to decode.
- start (int, optional) – The index to start decoding from in the bytes.
- nextpos (bool, optional) – If True, the function will return a tuple (decoded_string, nextpos), where nextpos is equal to one plus the index of the last byte of the string. (See example below.)
Examples
>>> encode_string('foo') b' foo'
>>> decode_string(encode_string('bar')) 'bar'
>>> buf = b''.join([encode_string('foo'), encode_string('bar')]) >>> s1, nextpos = decode_string(buf, nextpos=True) >>> s1 'foo' >>> nextpos 7 >>> s2 = decode_string(buf, start=nextpos) >>> s2 'bar'
-
imb.
decode_uint
(buf)¶ Decode an unsigned integer from bytes, using the default byte order.
Parameters: buf (bytes or similar) – The bytes to decode. Returns: The decoded integer. Return type: int
-
imb.
encode_int32
(value)¶ Encode a signed 32-bit integer, using the default byte order.
Parameters: value (int) – The integer to encode. Returns: A bytes object representing the integer. Return type: bytes
-
imb.
encode_string
(value)¶ Encode a string (prefixed with string length).
Strings are encoded as follows. First comes a signed 32-bit integer equal to the number of bytes in the encoded string. Then comes the string, encoded with the default encoding.
Parameters: value (str) – The string to encode. Returns: A bytes object representing the integer. Return type: bytes
-
imb.
encode_uint32
(value)¶ Encode an unsigned 32-bit integer in bytes using the default byte order.
Parameters: value (int) – The integer to encode. Returns: A bytes object representing the integer. Return type: bytes