Skip to content

Some small fixed to keep it tidy #29

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
*.pyc
/*.egg-info/
/dist/
.cache
.idea
venv
venv*
17 changes: 8 additions & 9 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
language: python
python:
- "2.6"
# Twisted not supported in Python 2.6.
# Even with Twisted 15.4, there are issues with autobahn.
# - "2.6"
- "2.7"
# Disable testing 3.2 since autobahn uses unicode literal syntax that wasn't
# re-added into 3.3. See PEP 414
# - "3.2"
- "3.3"
- "3.4"
- "3.5"
- "3.5-dev" # 3.5 development branch
- "nightly" # currently points to 3.6-dev
install:
- pip install .
# Our test server uses autobahn
- pip install autobahn
# For 2.x, our server needs twisted
- pip install twisted
# For 3.x where x < 4, our server needs trollius
- pip install trollius
script: python tests/test_pusherclient.py
- pip install -r requirements_tests.txt
script: nosetests

1 change: 1 addition & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
include README.rst
include README.md
include LICENSE
75 changes: 75 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
|Build Status|

pusherclient
============

pusherclient is a python module for handling pusher websockets

Installation
------------

Simply run "python setup.py install".

This module depends on websocket-client module available from:
http://github.com/liris/websocket-client

Example
-------

Example of using this pusher client to consume websockets::

::

import pusherclient

# Add a logging handler so we can see the raw communication data
import logging
root = logging.getLogger()
root.setLevel(logging.INFO)
ch = logging.StreamHandler(sys.stdout)
root.addHandler(ch)

global pusher

# We can't subscribe until we've connected, so we use a callback handler
# to subscribe when able
def connect_handler(data):
channel = pusher.subscribe('mychannel')
channel.bind('myevent', callback)

pusher = pusherclient.Pusher(appkey)
pusher.connection.bind('pusher:connection_established', connect_handler)
pusher.connect()

while True:
# Do other things in the meantime here...
time.sleep(1)

Sending pusher events to a channel can be done simply using the pusher
client supplied by pusher. You can get it here:
http://github.com/newbamboo/pusher_client_python

::

import pusher
pusher.app_id = app_id
pusher.key = appkey

p = pusher.Pusher()
p['mychannel'].trigger('myevent', 'mydata')

Thanks
------

Built using the websocket-client module from
http://github.com/liris/websocket-client. The ruby gem by Logan Koester
which provides a similar service was also very helpful for a reference.
Take a look at it here: http://github.com/logankoester/pusher-client.

Copyright
---------

MTI License - See LICENSE for details.

.. |Build Status| image:: https://travis-ci.org/ekulyk/PythonPusherClient.svg?branch=master
:target: https://travis-ci.org/ekulyk/PythonPusherClient
75 changes: 42 additions & 33 deletions pusherclient/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import time

try:
# noinspection PyPackageRequirements
import simplejson as json
except ImportError:
import json
Expand Down Expand Up @@ -36,10 +37,14 @@ def __init__(self, event_handler, url, log_level=logging.INFO, daemon=True, reco

self.state = "initialized"

self.logger = logging.getLogger(self.__module__) # create a new logger
self.root_logger = logging.getLogger(self.__module__) # create a new logger
self.connection_logger = logging.getLogger('{}.connection'.format(self.__module__))
self.events_logger = logging.getLogger('{}.events'.format(self.__module__))
if log_level == logging.DEBUG:
websocket.enableTrace(True)
self.logger.setLevel(log_level)

if log_level is not None:
self.root_logger.setLevel(log_level)

# From Martyn's comment at:
# https://pusher.tenderapp.com/discussions/problems/36-no-messages-received-after-1-idle-minute-heartbeat
Expand Down Expand Up @@ -84,7 +89,7 @@ def reconnect(self, reconnect_interval=None):
if reconnect_interval is None:
reconnect_interval = self.default_reconnect_interval

self.logger.info("Connection: Reconnect in %s" % reconnect_interval)
self.connection_logger.info("Connection: Reconnect in %s" % reconnect_interval)
self.reconnect_interval = reconnect_interval

self.needs_reconnect = True
Expand All @@ -108,8 +113,9 @@ def _connect(self):
self.socket.run_forever()

while self.needs_reconnect and not self.disconnect_called:
self.logger.info("Attempting to connect again in %s seconds."
% self.reconnect_interval)
self.connection_logger.info(
"Attempting to connect again in %s seconds.",
self.reconnect_interval)
self.state = "unavailable"
time.sleep(self.reconnect_interval)

Expand All @@ -118,21 +124,21 @@ def _connect(self):
self.socket.keep_running = True
self.socket.run_forever()

def _on_open(self, ws):
self.logger.info("Connection: Connection opened")
def _on_open(self, _):
self.connection_logger.info("Connection: Connection opened")
# Send a ping right away to inform that the connection is alive. If you
# don't do this, it takes the ping interval to subcribe to channel and
# events
self.send_ping()
self._start_timers()

def _on_error(self, ws, error):
self.logger.info("Connection: Error - %s" % error)
def _on_error(self, _, error):
self.connection_logger.error("Connection: Error - %s" % error)
self.state = "failed"
self.needs_reconnect = True

def _on_message(self, ws, message):
self.logger.info("Connection: Message - %s" % message)
def _on_message(self, _, message):
self.connection_logger.info("Connection: Message - %s" % message)

# Stop our timeout timer, since we got some data
self._stop_timers()
Expand All @@ -144,12 +150,13 @@ def _on_message(self, ws, message):
# We've got a connection event. Lets handle it.
if params['event'] in self.event_callbacks.keys():
for callback in self.event_callbacks[params['event']]:
# noinspection PyBroadException
try:
callback(params['data'])
except Exception:
self.logger.exception("Callback raised unhandled")
except:
self.events_logger.exception("Callback raised unhandled")
else:
self.logger.info("Connection: Unhandled event")
self.events_logger.warning("Connection: Unhandled event")
else:
# We've got a channel event. Lets pass it up to the pusher
# so it can be handled by the appropriate channel.
Expand All @@ -162,8 +169,8 @@ def _on_message(self, ws, message):
# We've handled our data, so restart our connection timeout handler
self._start_timers()

def _on_close(self, ws, *args):
self.logger.info("Connection: Connection closed")
def _on_close(self, *_):
self.connection_logger.info("Connection: Connection closed")
self.state = "disconnected"
self._stop_timers()

Expand Down Expand Up @@ -195,35 +202,36 @@ def send_event(self, event_name, data, channel_name=None):
if channel_name:
event['channel'] = channel_name

self.logger.info("Connection: Sending event - %s" % event)
self.events_logger.info("Connection: Sending event - %s" % event)
try:
self.socket.send(json.dumps(event))
except Exception as e:
self.logger.error("Failed send event: %s" % e)
self.events_logger.error("Failed send event: %s" % e)

def send_ping(self):
self.logger.info("Connection: ping to pusher")
self.connection_logger.info("Connection: ping to pusher")
try:
self.socket.send(json.dumps({'event': 'pusher:ping', 'data': ''}))
except Exception as e:
self.logger.error("Failed send ping: %s" % e)
self.connection_logger.error("Failed send ping: %s" % e)
self.pong_timer = Timer(self.pong_timeout, self._check_pong)
self.pong_timer.start()

def send_pong(self):
self.logger.info("Connection: pong to pusher")
self.connection_logger.info("Connection: pong to pusher")
# noinspection PyBroadException
try:
self.socket.send(json.dumps({'event': 'pusher:pong', 'data': ''}))
except Exception as e:
self.logger.error("Failed send pong: %s" % e)
except:
self.connection_logger.exception("Failed send pong")

def _check_pong(self):
self.pong_timer.cancel()

if self.pong_received:
self.pong_received = False
else:
self.logger.info("Did not receive pong in time. Will attempt to reconnect.")
self.connection_logger.info("Did not receive pong in time. Will attempt to reconnect.")
self.state = "failed"
self.reconnect()

Expand All @@ -232,33 +240,34 @@ def _connect_handler(self, data):
self.socket_id = parsed['socket_id']
self.state = "connected"

def _failed_handler(self, data):
def _failed_handler(self, _):
self.state = "failed"

def _ping_handler(self, data):
def _ping_handler(self, _):
self.send_pong()
# Restart our timers since we received something on the connection
self._start_timers()

def _pong_handler(self, data):
self.logger.info("Connection: pong from pusher")
def _pong_handler(self, _):
self.connection_logger.info("Connection: pong from pusher")
self.pong_received = True

def _pusher_error_handler(self, data):
if 'code' in data:
error_code = None

# noinspection PyBroadException
try:
error_code = int(data['code'])
except:
pass

if error_code is not None:
self.logger.error("Connection: Received error %s" % error_code)
self.connection_logger.error("Connection: Received error %s" % error_code)

if (error_code >= 4000) and (error_code <= 4099):
# The connection SHOULD NOT be re-established unchanged
self.logger.info("Connection: Error is unrecoverable. Disconnecting")
self.connection_logger.warning("Connection: Error is unrecoverable. Disconnecting")
self.disconnect()
elif (error_code >= 4100) and (error_code <= 4199):
# The connection SHOULD be re-established after backing off
Expand All @@ -269,11 +278,11 @@ def _pusher_error_handler(self, data):
else:
pass
else:
self.logger.error("Connection: Unknown error code")
self.connection_logger.error("Connection: Unknown error code")
else:
self.logger.error("Connection: No error code supplied")
self.connection_logger.error("Connection: No error code supplied")

def _connection_timed_out(self):
self.logger.info("Did not receive any data in time. Reconnecting.")
self.connection_logger.info("Did not receive any data in time. Reconnecting.")
self.state = "failed"
self.reconnect()
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
websocket-client==0.37.0
6 changes: 6 additions & 0 deletions requirements_tests.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
autobahn==0.16.1
mock==2.0.0
nose==1.3.7
trollius==2.1
Twisted==16.5.0
websocket-client==0.37.0
39 changes: 23 additions & 16 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
from setuptools import setup
import sys

VERSION = "0.3.0"

requirements = ["websocket-client"]
major, minor1, minor2, release, serial = sys.version_info

def readfile(filename):
with open(filename, **readfile_kwargs) as fp:
contents = fp.read()
return contents

def readme():
with open('README.md') as f:
with open("README.rst") as f:
return f.read()


setup(
name="pusherclient",
version=VERSION,
Expand All @@ -18,21 +25,21 @@ def readme():
author_email="[email protected]",
license="MIT",
url="https://github.com/ekulyk/PythonPusherClient",
install_requires=requirements,
install_requires=readfile(os.path.join(os.path.dirname(__file__), "requirements.txt")),
packages=["pusherclient"],
classifiers=[
'Development Status :: 3 - Alpha',
'Environment :: Web Environment',
'License :: OSI Approved :: MIT License',
'Programming Language :: Python',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.2',
'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3.4',
'Topic :: Internet',
'Topic :: Software Development :: Libraries ',
'Topic :: Software Development :: Libraries :: Python Modules',
"Development Status :: 3 - Alpha",
"Environment :: Web Environment",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python",
"Programming Language :: Python :: 2",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.3",
"Programming Language :: Python :: 3.4",
"Programming Language :: Python :: 3.5",
"Topic :: Internet",
"Topic :: Software Development :: Libraries ",
"Topic :: Software Development :: Libraries :: Python Modules",
]
)
Empty file added tests/__init__.py
Empty file.
Loading