From b10c41f0a2079e1442caf6edebb78ce50c803664 Mon Sep 17 00:00:00 2001 From: b123400 Date: Fri, 28 Nov 2014 16:09:39 +0800 Subject: [PATCH 1/2] Added version checking for class definition so client.py works on python3 --- pyapns/client.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/pyapns/client.py b/pyapns/client.py index ca106a2..f26fea2 100644 --- a/pyapns/client.py +++ b/pyapns/client.py @@ -154,10 +154,11 @@ class TimeoutHTTPConnection(httplib.HTTPConnection): def connect(self): httplib.HTTPConnection.connect(self) self.sock.settimeout(self.timeout) + +if hexversion < 0x02070000: + class TimeoutHTTP(httplib.HTTP): + _connection_class = TimeoutHTTPConnection -class TimeoutHTTP(httplib.HTTP): - _connection_class = TimeoutHTTPConnection + def set_timeout(self, timeout): + self._conn.timeout = timeout - def set_timeout(self, timeout): - self._conn.timeout = timeout - \ No newline at end of file From 6353b5aa530ef2052ead896a5c51630d697d55b5 Mon Sep 17 00:00:00 2001 From: b123400 Date: Fri, 28 Nov 2014 16:31:01 +0800 Subject: [PATCH 2/2] Created client for python3 using 2to3 --- pyapns/__init__.py | 2 +- pyapns/client3.py | 164 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 165 insertions(+), 1 deletion(-) create mode 100644 pyapns/client3.py diff --git a/pyapns/__init__.py b/pyapns/__init__.py index aa3eb86..2cc78ec 100644 --- a/pyapns/__init__.py +++ b/pyapns/__init__.py @@ -1 +1 @@ -from .client import notify, provision, feedback, configure __version__ = "0.4.0" __author__ = "Samuel Sutch" __license__ = "MIT" __copyright__ = "Copyrighit 2012 Samuel Sutch" \ No newline at end of file +from sys import hexversion if hexversion < 0x02070000: from .client import notify, provision, feedback, configure else: from .client3 import notify, provision, feedback, configure __version__ = "0.4.0" __author__ = "Samuel Sutch" __license__ = "MIT" __copyright__ = "Copyrighit 2012 Samuel Sutch" \ No newline at end of file diff --git a/pyapns/client3.py b/pyapns/client3.py new file mode 100644 index 0000000..7d24a17 --- /dev/null +++ b/pyapns/client3.py @@ -0,0 +1,164 @@ +import xmlrpc.client +import threading +import http.client +import functools +from sys import hexversion + +OPTIONS = {'CONFIGURED': False, 'TIMEOUT': 20} + +def configure(opts): + if not OPTIONS['CONFIGURED']: + try: # support for django + import django.conf + OPTIONS.update(django.conf.settings.PYAPNS_CONFIG) + OPTIONS['CONFIGURED'] = True + except: + pass + if not OPTIONS['CONFIGURED']: + try: # support for programatic configuration + OPTIONS.update(opts) + OPTIONS['CONFIGURED'] = True + except: + pass + if not OPTIONS['CONFIGURED']: + try: # pylons support + import pylons.config + OPTIONS.update({'HOST': pylons.config.get('pyapns_host')}) + try: + OPTIONS.update({'TIMEOUT': int(pylons.config.get('pyapns_timeout'))}) + except: + pass # ignore, an optional value + OPTIONS['CONFIGURED'] = True + except: + pass + # provision initial app_ids + if 'INITIAL' in OPTIONS: + for args in OPTIONS['INITIAL']: + provision(*args) + return OPTIONS['CONFIGURED'] + + +class UnknownAppID(Exception): pass +class APNSNotConfigured(Exception): pass + +def reprovision_and_retry(func): + """ + Wraps the `errback` callback of the API functions, automatically trying to + re-provision if the app ID can not be found during the operation. If that's + unsuccessful, it will raise the UnknownAppID error. + """ + @functools.wraps(func) + def wrapper(*a, **kw): + errback = kw.get('errback', None) + if errback is None: + def errback(e): + raise e + def errback_wrapper(e): + if isinstance(e, UnknownAppID) and 'INITIAL' in OPTIONS: + try: + for initial in OPTIONS['INITIAL']: + provision(*initial) # retry provisioning the initial setup + func(*a, **kw) # and try the function once more + except Exception as new_exc: + errback(new_exc) # throwing the new exception + else: + errback(e) # not an instance of UnknownAppID - nothing we can do here + kw['errback'] = errback_wrapper + return func(*a, **kw) + return wrapper + +def default_callback(func): + @functools.wraps(func) + def wrapper(*a, **kw): + if 'callback' not in kw: + kw['callback'] = lambda c: c + return func(*a, **kw) + return wrapper + +@default_callback +@reprovision_and_retry +def provision(app_id, path_to_cert, environment, timeout=15, async=False, + callback=None, errback=None): + args = [app_id, path_to_cert, environment, timeout] + f_args = ['provision', args, callback, errback] + if not async: + return _xmlrpc_thread(*f_args) + t = threading.Thread(target=_xmlrpc_thread, args=f_args) + t.daemon = True + t.start() + +@default_callback +@reprovision_and_retry +def notify(app_id, tokens, notifications, async=False, callback=None, + errback=None): + args = [app_id, tokens, notifications] + f_args = ['notify', args, callback, errback] + if not async: + return _xmlrpc_thread(*f_args) + t = threading.Thread(target=_xmlrpc_thread, args=f_args) + t.daemon = True + t.start() + +@default_callback +@reprovision_and_retry +def feedback(app_id, async=False, callback=None, errback=None): + args = [app_id] + f_args = ['feedback', args, callback, errback] + if not async: + return _xmlrpc_thread(*f_args) + t = threading.Thread(target=_xmlrpc_thread, args=f_args) + t.daemon = True + t.start() + +def _xmlrpc_thread(method, args, callback, errback=None): + if not configure({}): + raise APNSNotConfigured('APNS Has not been configured.') + proxy = ServerProxy(OPTIONS['HOST'], allow_none=True, use_datetime=True, + timeout=OPTIONS['TIMEOUT']) + try: + parts = method.strip().split('.') + for part in parts: + proxy = getattr(proxy, part) + return callback(proxy(*args)) + except xmlrpc.client.Fault as e: + if e.faultCode == 404: + e = UnknownAppID() + if errback is not None: + errback(e) + else: + raise e + + +## -------------------------------------------------------------- +## Thank you Volodymyr Orlenko: +## http://blog.bjola.ca/2007/08/using-timeout-with-xmlrpclib.html +## -------------------------------------------------------------- + +def ServerProxy(url, *args, **kwargs): + t = TimeoutTransport() + t.timeout = kwargs.pop('timeout', 20) + kwargs['transport'] = t + return xmlrpc.client.ServerProxy(url, *args, **kwargs) + +class TimeoutTransport(xmlrpc.client.Transport): + def make_connection(self, host): + if hexversion < 0x02070000: + conn = TimeoutHTTP(host) + conn.set_timeout(self.timeout) + else: + conn = TimeoutHTTPConnection(host) + conn.timeout = self.timeout + return conn + +class TimeoutHTTPConnection(http.client.HTTPConnection): + def connect(self): + http.client.HTTPConnection.connect(self) + self.sock.settimeout(self.timeout) + +if hexversion < 0x02070000: + class TimeoutHTTP(http.client.HTTP): + _connection_class = TimeoutHTTPConnection + + def set_timeout(self, timeout): + self._conn.timeout = timeout +