cent
Advanced tools
+111
| CENT | ||
| ==== | ||
| Python tools to communicate with Centrifugo HTTP API. Python 2.6, Python 2.7 and Python >= 3.3 supported. | ||
| To install run: | ||
| ```bash | ||
| pip install cent | ||
| ``` | ||
| ### High-level library API | ||
| First see [available API methods in documentation](https://fzambia.gitbooks.io/centrifugal/content/server/api.html). | ||
| This library contains `Client` class to send messages to Centrifugo from your python-powered backend: | ||
| ```python | ||
| from cent import Client | ||
| url = "http://localhost:8000" | ||
| api_key = "XXX" | ||
| # initialize client instance. | ||
| client = Client(url, api_key=api_key, timeout=1) | ||
| # publish data into channel | ||
| channel = "public:chat" | ||
| data = {"input": "test"} | ||
| client.publish(channel, data) | ||
| # other available methods | ||
| client.unsubscribe("USER_ID") | ||
| client.disconnect("USER_ID") | ||
| messages = client.history("public:chat") | ||
| clients = client.presence("public:chat") | ||
| channels = client.channels() | ||
| stats = client.info() | ||
| client.history_remove("public:chat") | ||
| ``` | ||
| `publish`, `disconnect`, `unsubscribe`, `history_remove` return `None` in case of success. Each of this commands can | ||
| raise an instance of `CentException`. | ||
| I.e.: | ||
| ```python | ||
| from cent import Client, CentException | ||
| client = Client("http://localhost:8000", api_key="XXX", timeout=1) | ||
| try: | ||
| client.publish("public:chat", {"input": "test"}) | ||
| except CentException: | ||
| # handle exception | ||
| ``` | ||
| Depending on problem occurred exceptions can be: | ||
| * RequestException – HTTP request to Centrifugo failed | ||
| * ResponseError - Centrifugo returned some error on request | ||
| Both exceptions inherited from `CentException`. | ||
| ### Low-level library API: | ||
| To send lots of commands in one request: | ||
| ```python | ||
| from cent import Client, CentException | ||
| client = Client("http://localhost:8000", api_key="XXX", timeout=1) | ||
| params = { | ||
| "channel": "python", | ||
| "data": "hello world" | ||
| } | ||
| client.add("publish", params) | ||
| try: | ||
| result = client.send() | ||
| except CentException: | ||
| # handle exception | ||
| else: | ||
| print result | ||
| ``` | ||
| You can use `add` method to add several messages which will be sent. | ||
| You'll get something like this in response: | ||
| ```bash | ||
| [{}] | ||
| ``` | ||
| I.e. list of single response to each command sent. So you need to inspect response on errors (if any) yourself. | ||
| ### Client initialization arguments | ||
| Required: | ||
| * address - Centrifugo address | ||
| Optional: | ||
| * api_key - HTTP API key of Centrifugo | ||
| * timeout (default: `1`) - timeout for HTTP requests to Centrifugo | ||
| * json_encoder (default: `None`) - set custom JSON encoder | ||
| * send_func (default: `None`) - set custom send function | ||
| * verify (default: `True`) - when set to `False` no certificate check will be done during requests. |
| Metadata-Version: 1.1 | ||
| Name: cent | ||
| Version: 2.1.0 | ||
| Summary: python tools to communicate with Centrifugo | ||
| Version: 3.0.0 | ||
| Summary: Python library to communicate with Centrifugo API | ||
| Home-page: https://github.com/centrifugal/cent | ||
@@ -10,3 +10,3 @@ Author: Alexandr Emelin | ||
| Download-URL: https://github.com/centrifugal/cent | ||
| Description: python tools to communicate with Centrifugo | ||
| Description: Python library to communicate with Centrifugo API | ||
| Platform: UNKNOWN | ||
@@ -20,2 +20,4 @@ Classifier: Development Status :: 5 - Production/Stable | ||
| Classifier: Programming Language :: Python :: 3.5 | ||
| Classifier: Programming Language :: Python :: 3.6 | ||
| Classifier: Programming Language :: Python :: 3.7 | ||
| Classifier: Environment :: Console | ||
@@ -22,0 +24,0 @@ Classifier: Intended Audience :: Developers |
@@ -1,1 +0,1 @@ | ||
| requests | ||
| requests |
@@ -0,4 +1,4 @@ | ||
| README.md | ||
| setup.py | ||
| cent/__init__.py | ||
| cent/console.py | ||
| cent/core.py | ||
@@ -5,0 +5,0 @@ cent.egg-info/PKG-INFO |
+1
-2
| # coding: utf-8 | ||
| from .core import Client, CentException, RequestException, ResponseError, ClientNotEmpty, \ | ||
| generate_api_sign, generate_channel_sign, generate_token, get_timestamp | ||
| from .core import Client, CentException, RequestException, ResponseError, ClientNotEmpty |
+50
-105
@@ -8,6 +8,3 @@ # coding: utf-8 | ||
| import sys | ||
| import hmac | ||
| import time | ||
| import json | ||
| from hashlib import sha256 | ||
| import requests | ||
@@ -57,53 +54,2 @@ | ||
| def generate_token(secret, user, timestamp, info=""): | ||
| """ | ||
| When client from browser wants to connect to Centrifuge he must send his | ||
| user ID, timestamp and optional info. To validate that data we use HMAC | ||
| SHA-256 to build token. | ||
| @param secret: Centrifugo secret key | ||
| @param user: user ID from your application | ||
| @param timestamp: current timestamp seconds as string | ||
| @param info: optional json encoded data for this client connection | ||
| """ | ||
| sign = hmac.new(to_bytes(str(secret)), digestmod=sha256) | ||
| sign.update(to_bytes(user)) | ||
| sign.update(to_bytes(timestamp)) | ||
| sign.update(to_bytes(info)) | ||
| token = sign.hexdigest() | ||
| return token | ||
| def generate_channel_sign(secret, client, channel, info=""): | ||
| """ | ||
| Generate HMAC SHA-256 sign for private channel subscription. | ||
| @param secret: Centrifugo secret key | ||
| @param client: client ID | ||
| @param channel: channel client wants to subscribe to | ||
| @param info: optional json encoded data for this channel | ||
| """ | ||
| auth = hmac.new(to_bytes(str(secret)), digestmod=sha256) | ||
| auth.update(to_bytes(str(client))) | ||
| auth.update(to_bytes(str(channel))) | ||
| auth.update(to_bytes(info)) | ||
| return auth.hexdigest() | ||
| def generate_api_sign(secret, encoded_data): | ||
| """ | ||
| Generate HMAC SHA-256 sign for API request. | ||
| @param secret: Centrifugo secret key | ||
| @param encoded_data: json encoded data to send | ||
| """ | ||
| sign = hmac.new(to_bytes(str(secret)), digestmod=sha256) | ||
| sign.update(encoded_data) | ||
| return sign.hexdigest() | ||
| def get_timestamp(): | ||
| """ | ||
| Returns current timestamp seconds string required to make connection to Centrifugo. | ||
| """ | ||
| return str(int(time.time())) | ||
| class Client(object): | ||
@@ -114,12 +60,10 @@ """ | ||
| def __init__(self, address, secret, timeout=1, send_func=None, | ||
| json_encoder=None, insecure_api=False, verify=True, | ||
| def __init__(self, address, api_key="", timeout=1, | ||
| json_encoder=None, verify=True, | ||
| session=None, **kwargs): | ||
| """ | ||
| :param address: Centrifugo address | ||
| :param secret: Centrifugo configuration secret key | ||
| :param api_key: Centrifugo API key | ||
| :param timeout: timeout for HTTP requests to Centrifugo | ||
| :param send_func: custom send function | ||
| :param json_encoder: custom JSON encoder | ||
| :param insecure_api: boolean value, when set to True no signing will be used | ||
| :param verify: boolean flag, when set to False no certificate check will be done during requests. | ||
@@ -130,7 +74,5 @@ :param session: custom requests.Session instance | ||
| self.address = address | ||
| self.secret = secret | ||
| self.api_key = api_key | ||
| self.timeout = timeout | ||
| self.send_func = send_func | ||
| self.json_encoder = json_encoder | ||
| self.insecure_api = insecure_api | ||
| self.verify = verify | ||
@@ -156,18 +98,4 @@ self.session = session or requests.Session() | ||
| address += api_path | ||
| address += "/" | ||
| return address | ||
| def sign_encoded_data(self, encoded_data): | ||
| return generate_api_sign(self.secret, encoded_data) | ||
| def prepare(self, data): | ||
| url = self.prepare_url() | ||
| encoded_data = to_bytes(json.dumps(data, cls=self.json_encoder)) | ||
| if not self.insecure_api: | ||
| sign = self.sign_encoded_data(encoded_data) | ||
| else: | ||
| # no need to generate sign in case of insecure API option on | ||
| sign = "" | ||
| return url, sign, encoded_data | ||
| def add(self, method, params): | ||
@@ -185,13 +113,18 @@ data = { | ||
| self._messages = [] | ||
| if self.send_func: | ||
| return self.send_func(*self.prepare(messages)) | ||
| return self._send(*self.prepare(messages)) | ||
| url = self.prepare_url() | ||
| data = to_bytes("\n".join([json.dumps(x, cls=self.json_encoder) for x in messages])) | ||
| response = self._send(url, data) | ||
| return [json.loads(x) for x in response.split("\n") if x] | ||
| def _send(self, url, sign, encoded_data): | ||
| def _send(self, url, data): | ||
| """ | ||
| Send a request to a remote web server using HTTP POST. | ||
| """ | ||
| headers = {'Content-type': 'application/json', 'X-API-Sign': sign} | ||
| headers = { | ||
| 'Content-type': 'application/json' | ||
| } | ||
| if self.api_key: | ||
| headers['Authorization'] = 'apikey ' + self.api_key | ||
| try: | ||
| resp = self.session.post(url, data=encoded_data, headers=headers, timeout=self.timeout, verify=self.verify) | ||
| resp = self.session.post(url, data=data, headers=headers, timeout=self.timeout, verify=self.verify) | ||
| except requests.RequestException as err: | ||
@@ -201,3 +134,3 @@ raise RequestException(err) | ||
| raise RequestException("wrong status code: %d" % resp.status_code) | ||
| return json.loads(resp.content.decode('utf-8')) | ||
| return resp.content.decode('utf-8') | ||
@@ -208,3 +141,3 @@ def reset(self): | ||
| @staticmethod | ||
| def get_publish_params(channel, data, client=None): | ||
| def get_publish_params(channel, data, uid=None): | ||
| params = { | ||
@@ -214,8 +147,8 @@ "channel": channel, | ||
| } | ||
| if client: | ||
| params['client'] = client | ||
| if uid: | ||
| params['uid'] = uid | ||
| return params | ||
| @staticmethod | ||
| def get_broadcast_params(channels, data, client=None): | ||
| def get_broadcast_params(channels, data, uid=None): | ||
| params = { | ||
@@ -225,4 +158,4 @@ "channels": channels, | ||
| } | ||
| if client: | ||
| params['client'] = client | ||
| if uid: | ||
| params['uid'] = uid | ||
| return params | ||
@@ -256,2 +189,8 @@ | ||
| @staticmethod | ||
| def get_history_remove_params(channel): | ||
| return { | ||
| "channel": channel | ||
| } | ||
| @staticmethod | ||
| def get_channels_params(): | ||
@@ -261,3 +200,3 @@ return {} | ||
| @staticmethod | ||
| def get_stats_params(): | ||
| def get_info_params(): | ||
| return {} | ||
@@ -274,13 +213,13 @@ | ||
| raise ResponseError(data["error"]) | ||
| return data.get("body") | ||
| return data.get("result") | ||
| def publish(self, channel, data, client=None): | ||
| def publish(self, channel, data, uid=None): | ||
| self._check_empty() | ||
| self.add("publish", self.get_publish_params(channel, data, client=client)) | ||
| self.add("publish", self.get_publish_params(channel, data, uid=uid)) | ||
| self._send_one() | ||
| return | ||
| def broadcast(self, channels, data, client=None): | ||
| def broadcast(self, channels, data, uid=None): | ||
| self._check_empty() | ||
| self.add("broadcast", self.get_broadcast_params(channels, data, client=client)) | ||
| self.add("broadcast", self.get_broadcast_params(channels, data, uid=uid)) | ||
| self._send_one() | ||
@@ -304,4 +243,4 @@ return | ||
| self.add("presence", self.get_presence_params(channel)) | ||
| body = self._send_one() | ||
| return body["data"] | ||
| result = self._send_one() | ||
| return result["presence"] | ||
@@ -311,15 +250,21 @@ def history(self, channel): | ||
| self.add("history", self.get_history_params(channel)) | ||
| body = self._send_one() | ||
| return body["data"] | ||
| result = self._send_one() | ||
| return result["history"] | ||
| def history_remove(self, channel): | ||
| self._check_empty() | ||
| self.add("history_remove", self.get_history_remove_params(channel)) | ||
| result = self._send_one() | ||
| return | ||
| def channels(self): | ||
| self._check_empty() | ||
| self.add("channels", self.get_channels_params()) | ||
| body = self._send_one() | ||
| return body["data"] | ||
| result = self._send_one() | ||
| return result["channels"] | ||
| def stats(self): | ||
| def info(self): | ||
| self._check_empty() | ||
| self.add("stats", self.get_stats_params()) | ||
| body = self._send_one() | ||
| return body["data"] | ||
| self.add("info", self.get_info_params()) | ||
| result = self._send_one() | ||
| return result |
+5
-3
| Metadata-Version: 1.1 | ||
| Name: cent | ||
| Version: 2.1.0 | ||
| Summary: python tools to communicate with Centrifugo | ||
| Version: 3.0.0 | ||
| Summary: Python library to communicate with Centrifugo API | ||
| Home-page: https://github.com/centrifugal/cent | ||
@@ -10,3 +10,3 @@ Author: Alexandr Emelin | ||
| Download-URL: https://github.com/centrifugal/cent | ||
| Description: python tools to communicate with Centrifugo | ||
| Description: Python library to communicate with Centrifugo API | ||
| Platform: UNKNOWN | ||
@@ -20,2 +20,4 @@ Classifier: Development Status :: 5 - Production/Stable | ||
| Classifier: Programming Language :: Python :: 3.5 | ||
| Classifier: Programming Language :: Python :: 3.6 | ||
| Classifier: Programming Language :: Python :: 3.7 | ||
| Classifier: Environment :: Console | ||
@@ -22,0 +24,0 @@ Classifier: Intended Audience :: Developers |
+0
-1
| [egg_info] | ||
| tag_build = | ||
| tag_date = 0 | ||
| tag_svn_revision = 0 | ||
+5
-3
@@ -17,3 +17,3 @@ import os | ||
| def long_description(): | ||
| return "python tools to communicate with Centrifugo" | ||
| return "Python library to communicate with Centrifugo API" | ||
@@ -23,4 +23,4 @@ | ||
| name='cent', | ||
| version='2.1.0', | ||
| description="python tools to communicate with Centrifugo", | ||
| version='3.0.0', | ||
| description="Python library to communicate with Centrifugo API", | ||
| long_description=long_description(), | ||
@@ -47,2 +47,4 @@ url='https://github.com/centrifugal/cent', | ||
| 'Programming Language :: Python :: 3.5', | ||
| 'Programming Language :: Python :: 3.6', | ||
| 'Programming Language :: Python :: 3.7', | ||
| 'Environment :: Console', | ||
@@ -49,0 +51,0 @@ 'Intended Audience :: Developers', |
| #!/usr/bin/env python | ||
| # coding: utf-8 | ||
| from __future__ import print_function | ||
| import argparse | ||
| import os | ||
| import sys | ||
| import json | ||
| try: | ||
| import configparser as ConfigParser | ||
| except ImportError: | ||
| import ConfigParser | ||
| from .core import Client, CentException | ||
| def run(): | ||
| parser = argparse.ArgumentParser(description='Centrifuge client') | ||
| parser.add_argument( | ||
| 'section', metavar='SECTION', type=str, help='section key from cent configuration file' | ||
| ) | ||
| parser.add_argument( | ||
| 'method', metavar='METHOD', type=str, help='call method' | ||
| ) | ||
| parser.add_argument( | ||
| '--params', type=str, help='params data', default='{}' | ||
| ) | ||
| parser.add_argument( | ||
| '--config', type=str, default="~/.centrc", help='cent configuration file' | ||
| ) | ||
| options = parser.parse_args() | ||
| config_file = os.path.expanduser(options.config) | ||
| config = ConfigParser.ConfigParser() | ||
| config.read(config_file) | ||
| if options.section not in config.sections(): | ||
| print( | ||
| "Section {0} not found in {1} configuration file".format( | ||
| options.section, options.config | ||
| ) | ||
| ) | ||
| sys.exit(1) | ||
| try: | ||
| address = config.get(options.section, 'address') | ||
| secret = config.get(options.section, 'secret') | ||
| try: | ||
| timeout = config.getint(options.section, 'timeout') | ||
| except: | ||
| timeout = 1 | ||
| except Exception as e: | ||
| print(e) | ||
| sys.exit(1) | ||
| if not sys.stdin.isatty(): | ||
| json_data = sys.stdin.read().strip() | ||
| else: | ||
| json_data = options.params | ||
| if json_data: | ||
| try: | ||
| params = json.loads(json_data) | ||
| except Exception as e: | ||
| print(e) | ||
| sys.exit(1) | ||
| else: | ||
| params = {} | ||
| if not isinstance(params, dict): | ||
| print("params must be dictionary") | ||
| sys.exit(1) | ||
| client = Client( | ||
| address, | ||
| secret, | ||
| timeout=timeout | ||
| ) | ||
| if not isinstance(params, dict): | ||
| print("params must be valid JSON object") | ||
| sys.exit(1) | ||
| client.add(options.method, params) | ||
| try: | ||
| result = client.send() | ||
| except CentException as err: | ||
| print(err.message) | ||
| sys.exit(1) | ||
| else: | ||
| print(result) | ||
| if __name__ == '__main__': | ||
| run() |
Alert delta unavailable
Currently unable to show alert delta for PyPI packages.
14202
-9.66%265
-32.05%