# Emitter Python SDK
[![PyPI - Emitter version](https://img.shields.io/pypi/v/emitter-io.svg)](https://pypi.org/project/emitter-io) [![PyPI - Python versions](https://img.shields.io/pypi/pyversions/emitter-io.svg?logo=python)](https://github.com/emitter-io/python) [![GitHub - License](https://img.shields.io/github/license/emitter-io/python.svg)](https://github.com/emitter-io/python/blob/master/LICENSE)
This repository contains a Python client for [Emitter](https://emitter.io) (see also [Emitter GitHub](https://github.com/emitter-io/emitter)). Emitter is an **open-source** real-time communication service for connecting online devices. At its core, emitter.io is a distributed, scalable and fault-tolerant publish-subscribe messaging platform based on MQTT protocol and featuring message storage.
This library provides a nicer high-level MQTT interface fine-tuned and extended with specific features provided by [Emitter](https://emitter.io). The code uses the [Eclipse Paho MQTT Python Client](https://github.com/eclipse/paho.mqtt.python) for handling all the network communication and MQTT protocol.
* [Installation](#install)
* [Examples](#examples)
* [API reference](#api)
* [ToDo](#todo)
* [License](#license)
<a id="install"></a>
## Installation
This SDK is available as a pip package. Install with:
```
pip install emitter-io
```
<a id="examples"></a>
## Examples
These examples show you the whole communication process.
* Python 2: [*sample-python2.py*](emitter/sample-python2.py)
* Python 3: [*sample-python3.py*](emitter/sample-python3.py)
![alt screenshot of the sample app](SampleAppScreenshot.png?raw=true)
<a id="api"></a>
## API reference
* [`Client()`](#client)
* [`.connect()`](#connect)
* [`.disconnect()`](#disconnect)
* [`.keyban()`](#keyban)
* [`.keygen()`](#keygen)
* [`.link()`](#link)
* [`.me()`](#me)
* [`.on_connect`](#on_connect)
* [`.on_disconnect`](#on_disconnect)
* [`.on_error`](#on_error)
* [`.on_keyban`](#on_keyban)
* [`.on_keygen`](#on_keygen)
* [`.on_me`](#on_me)
* [`.on_message`](#on_message)
* [`.on_presence`](#on_presence)
* [`.presence()`](#presence)
* [`.publish()`](#publish)
* [`.publish_with_link()`](#publish_with_link)
* [`.subscribe()`](#subscribe)
* [`.subscribe_with_group()`](#subscribe_with_group)
* [`.unsubscribe()`](#unsubscribe)
* [`EmitterMessage()`](#message)
* [`.as_string()`](#as_string)
* [`.as_object()`](#as_object)
* [`.as_binary()`](#as_binary)
-------------------------------------------------------
<a id="client"></a>
### Client()
The `Client` class represents the client connection to an Emitter server.
-------------------------------------------------------
<a id="connect"></a>
### Emitter#connect(host="api.emitter.io", port=443, secure=True, keepalive=30)
```python
emitter = Client()
emitter.connect()
```
Connects to an Emitter server.
* `host` is the address of the Emitter broker. (Optional | `Str` | Default: `"api.emitter.io"`)
* `port` is the port of the emitter broker. (Optional | `Int` | Default: `443`)
* `secure` whether the connection should be secure. (Optional | `Bool` | Default: `True`)
* `keepalive` is the time the connection is kept alive (Optional | `Int` | Default: `30`)
If you don't want a secure connection, set the port to 8080, unless your broker is configured differently.
To handle connection events, see the [`.on_connect`](#on_connect) property.
-------------------------------------------------------
<a id="disconnect"></a>
### Emitter#disconnect()
```python
emitter.disconnect()
```
Disconnects from the connected Emitter server.
To handle disconnection events, see the [`.on_disconnect`](#on_disconnect) property.
-------------------------------------------------------
<a id="keyban"></a>
### Emitter#keyban(master_key, target_key, ban)
```python
instance.keyban("MEj8QNnzy6pKtE887hpXbD0KyKXi4w4f", "ftibXtPMKXI5p2FjhyINf8tvl2GAHaNG", True)
```
Sends a request to ban/unban a channel key.
* `master_key` is your *master key* to use for the operation. (Required | `Str`)
* `target_key` is the key ban or unban. (Required | `Str`)
* `ban` is whether to ban or unban the key. (Required | `Bool`)
To handle keyban responses, see the [`.on_keyban`](#on_keyban) property.
It will take a minute for the change to take effect.
-------------------------------------------------------
<a id="keygen"></a>
### Emitter#keygen(key, channel, permissions, ttl=0)
```python
instance.keygen("Z5auMQhNr0eVnGBAgWThXus1dgtSsvuQ", "channel/", "rwslpex")
```
Sends a key generation request to the server.
* `key` is your *master key* to use for the operation. (Required | `Str`)
* `channel` is the channel name to generate a key for. (Required | `Str`)
* `permissions` are the permissions associated to the key. (Required | `Str`)
- `r` for read
- `w` for write
- `s` for store
- `l` for load
- `p` for presence
- `e` for extend
- `x` for execute
* `ttl` is the time to live of the key. `0` means it never expires (Optional | `Int` | Default: `0`)
To handle keygen responses, see the [`.on_keygen`](#on_keygen) property.
Requesting a keygen with an extendable channel creates a private channel.
-------------------------------------------------------
<a id="link"></a>
### Emitter#link(key, channel, name, private, subscribe, options={})
```python
instance.link("5xZjIQp6GA9fpxso1Kslqnv8d4XVWChb",
"channel",
"a0",
True,
{Client.with_ttl(604800), Client.without_echo()}) // one week
```
Sends a link creation request to the server. This allows for the creation of a link between a short 2-character name and an actual channel. This function no longer allows the creation of a private channel. For this, use [`.keygen`](#keygen).
* `key` is the key to the channel. (Required | `Str`)
* `channel` is the channel name. (Required | `Str`)
* `name` is the short name for the channel. (Required | `Str`)
* `subscribe` whether or not to subscribe to the channel. (Required | `Bool`)
* `options` a set of options. Currently available options are:
- `with_at_most_once()` to send with QoS0.
- `with_at_least_once()` to send with QoS1.
- `with_retain()` to retain this message.
- `with_ttl(ttl)` to set a time to live for the message.
- `without_echo()` to tell the broker not to send the message back to this client.
-------------------------------------------------------
<a id="me"></a>
### Emitter#me()
```python
instance.me()
```
Requests information about the connection. Information provided in the response contains the id of the connection, as well as the links that were established with [`.link()`](#link) requests.
To handle the responses, see the [`.on_me`](#on_me) property.
-------------------------------------------------------
<a id="on_connect"></a>
### Emitter#on_connect
Property used to get or set the connection handler, that handle events emitted upon successful (re)connection. No arguments provided.
-------------------------------------------------------
<a id="on_disconnect"></a>
### Emitter#on_disconnect
Property used to get or set the disconnection handler, that handle events emitted after a disconnection. No arguments provided.
-------------------------------------------------------
<a id="on_error"></a>
### Emitter#on_error
Property used to get or set the error handler, that handle events emitted when an error occurs following any request. The event comes with a status code and a text message describing the error.
```json
{"status": 400,
"message": "the request was invalid or cannot be otherwise served"}
```
-------------------------------------------------------
<a id="on_keyban"></a>
### Emitter#on_keyban
Property used to get or set the handler for [`.keyban()`](#keyban) requests. Here is a sample of the message received after such a request:
```
{"status": 200,
"banned": True}
```
-------------------------------------------------------
<a id="on_keygen"></a>
### Emitter#on_keygen
**ToDo: Description!**
-------------------------------------------------------
<a id="on_me"></a>
### Emitter#on_me
Property used to get or set the handler that handle responses to [`.me()`](#me) requests. Information provided in the response contains the id of the connection, as well as the links that were established with [`.link()`](#link) requests.
```json
{"id": "74W77OC5OXDBQRUUMSHROHRQPE",
"links": {"a0": "test/",
"a1": "test/"}}
```
-------------------------------------------------------
<a id="on_message"></a>
### Emitter#on_message
Emitted when the client receives a message packet. The message object will be of [EmitterMessage](#message) class, encapsulating the channel and the payload.
-------------------------------------------------------
<a id="on_presence"></a>
### Emitter#on_presence
Emitted either when a presence call was made requesting a status, using the [`Emitter#presence()`](#presence) function, or when a user subscribed/unsubscribed to the channel and updates were previously requested using again a call to the [`Emitter#presence()`](#presence) function. Example arguments below.
```json
{"time": 1577833210,
"event": "status",
"channel": "<channel name>",
"who": [{"id": "ABCDE12345FGHIJ678910KLMNO", "username": "User1"},
{"id": "PQRST12345UVWXY678910ZABCD"}]}
{"time": 1577833220,
"event": "subscribe",
"channel": "<channel name>",
"who": {"id": "ABCDE12345FGHIJ678910KLMNO", "username": "User1"}}
{"time": 1577833230,
"event": "unsubscribe",
"channel": "<channel name>",
"who": {"id": "ABCDE12345FGHIJ678910KLMNO"}}
````
* `time` is the time of the event as *Unix time*.
* `event` is the event type: `subscribe` when an remote instance subscribed to the channel, `unsubscribe` when an remote instance unsubscribed from the channel and `status` when [`Emitter#presence()`](#presence) is called the first time.
* `channel` is the channel name.
* `who` in case of the `event` is `(un)subscribe` one dict with the user id, when the `event` is `status`, it is a list with the users. When more than 1000 users at the moment subscribed to the channel, 1000 randomly selected are displayed.
* `id` is an internal generated id of the remote instance.
* `username` is a custom chosen name by the remote instance. Please note that it is **optional** and check always if this parameter exists.
-------------------------------------------------------
<a id="presence"></a>
### Emitter#presence(key, channel, status=False, changes=False, optional_handler=None)
```python
instance.presence(""5xZjIQp6GA9fpxso1Kslqnv8d4XVWChb"",
"channel",
True,
True)
```
Sends a presence request to the server.
* `key` is the channel key to use for the operation. (Required | `Str`)
* `channel` is the channel name of which you want to call the presence. (Required | `Str`)
* `status` is whether the broker should send a full status of the channel. (Optional | `Bool` | Default: `False`)
* `changes` is whether to subscribe to presence changes on the channel. (Optional | `Bool` | Default: `False`)
* `optional_handler` is the handler to insert in the handler trie. (Optional | `callable` | Default: `None`)
Note: if you do not provide a handler here, make sure you did set the default handler for all presence messages using the [`.on_presence`](#on_presence) property.
-------------------------------------------------------
<a id="publish"></a>
### Emitter#publish(key, channel, message, options={})
```python
emitter.publish("5xZjIQp6GA9fpxso1Kslqnv8d4XVWChb",
"channel",
"Hello Emitter!",
{Client.with_ttl(604800), Client.without_echo()}) // one week
```
Publishes a message to a particual channel.
* `key` is the channel key to use for the operation. (Required | `Str`)
* `channel` is the channel name to publish to. (Required | `Str`)
* `message` is the message to publish (Required | `String`)
* `options` a set of options. Currently available options are:
- `with_at_most_once()` to send with QoS0.
- `with_at_least_once()` to send with QoS1.
- `with_retain()` to retain this message.
- `with_ttl(ttl)` to set a time to live for the message.
- `without_echo()` to tell the broker not to send the message back to this client.
-------------------------------------------------------
<a id="publish_with_link"></a>
### Emitter#publish_with_link(link, message)
```python
instance.publishWithLink("a0",
"Hello Emitter!")
```
Sends a message through the link.
* `link` is the 2-character name of the link. (Required | `Str`)
* `message` is the message to send through the link. (Required | `Str`)
-------------------------------------------------------
<a id="subscribe"></a>
### Emitter#subscribe(key, channel, optional_handler=None, options={})
```python
instance.subscribe("5xZjIQp6GA9fpxso1Kslqnv8d4XVWChb",
"channel",
options={Client.with_last(5)})
```
Subscribes to a particular channel.
* `key` is the channel key to use for the operation. (Required | `Str`)
* `channel` is the channel name to subscribe to. (Required | `Str`)
* `optional_handler` is the handler to insert in the handler trie. (Optional | `callable` | Default: `None`)
* `options` a set of options. Currently available options are:
- `with_last(x)` to receive the last `x` messages stored on the channel.
TODO
- `with_from`
- `with_until`
Note: if you do not provide a handler here, make sure you did set the default handler for all messages using the [`.on_message`](#on_message) property.
-------------------------------------------------------
<a id="subscribe_with_group"></a>
### Emitter#subscribe_with_group(key, channel, share_group, optional_handler=None, options={})
```python
instance.subscribe("5xZjIQp6GA9fpxso1Kslqnv8d4XVWChb",
"channel",
"sg")
```
Subscribes to a particular share group for a channel. A message sent to that channel will be forwarded to only one member of the share group, chosen randomly. For more information about share groups, see
[Emitter: Load-balance Messages using Subscriber Groups (on YouTube)](https://youtu.be/Vl7iGKEQrTg).
* `key` is the channel key to use for the operation. (Required | `Str`)
* `channel` is the channel name to subscribe to. (Required | `Str`)
* `share_group` is the name of the group to join. (Required | `Str`)
* `optional_handler` is the handler to insert in the handler trie. (Optional | `callable` | Default: `None`)
* `options` a set of options.
Note: if you do not provide a handler here, make sure you did set the default handler for all messages using the [`.on_message`](#on_message) property.
-------------------------------------------------------
<a id="unsubscribe"></a>
### Emitter#unsubscribe(key, channel)
```python
instance.unsubscribe("5xZjIQp6GA9fpxso1Kslqnv8d4XVWChb",
"channel")
```
Unsubscribes from a particual channel.
* `key` is the channel key to use for the operation. (Required | `Str`)
* `channel` is the channel name to unsubscribe from. (Required | `Str`)
This deletes handlers for that channel from the trie.
-------------------------------------------------------
<a id="message"></a>
### EmitterMessage()
The `EmitterMessage` class represents a message received from the Emitter server. It contains two properties:
* `channel` is the channel name the message was published to. (`Str`)
* `binary` is the buffer associated with the payload. (Binary `Str`)
-------------------------------------------------------
<a id="asString"></a>
### EmitterMessage#asString()
```python
message.asString()
```
Returns the payload as a utf-8 `String`.
-------------------------------------------------------
<a id="asObject"></a>
### EmitterMessage#asObject()
```python
message.asObject()
```
Returns the payload as a JSON-deserialized dictionary.
-------------------------------------------------------
<a id="asBinary"></a>
### EmitterMessage#asBinary()
```python
message.asBinary()
```
Returns the payload as a raw binary buffer.
<a id="todo"></a>
## ToDo
There are some points where the Python libary can be improved:
- Complete the [keygen](#client-keygen) entry in the README (see the **ToDo** markings)
- Describe how to use the trie of handlers for regular messages and presence.
- Add `with_from` and `with_until`.
- asObject should return an actual object instead of a dictionary.
<a id="license"></a>
## License
Eclipse Public License 1.0 (EPL-1.0)
Copyright (c) 2016-2019 [Misakai Ltd.](http://misakai.com)