Skip to content

Commit 354cf79

Browse files
committed
clients now take and methods:
is if you are using and want to be notified if an error occurs, otherwise it will be consumed by the thread is the new way to express that you wish the function to be executed asynchornously, in a thread. this can be advantageous in web request loops where you want to return as quickly as possible, additionally, #25 is addressed, providing a wrapper for notify/feedback which will automatically try to re-provision the endpoint if it reports UnknownAppID at some point, (this can happen if there is a pyapns server restart or something)
1 parent d9858ef commit 354cf79

File tree

1 file changed

+63
-15
lines changed

1 file changed

+63
-15
lines changed

pyapns/client.py

Lines changed: 63 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import xmlrpclib
22
import threading
33
import httplib
4+
import functools
45
from sys import hexversion
56

67
OPTIONS = {'CONFIGURED': False, 'TIMEOUT': 20}
@@ -40,32 +41,76 @@ def configure(opts):
4041
class UnknownAppID(Exception): pass
4142
class APNSNotConfigured(Exception): pass
4243

44+
def reprovision_and_retry(func):
45+
"""
46+
Wraps the `errback` callback of the API functions, automatically trying to
47+
re-provision if the app ID can not be found during the operation. If that's
48+
unsuccessful, it will raise the UnknownAppID error.
49+
"""
50+
@functools.wraps(func)
51+
def wrapper(*a, **kw):
52+
errback = kw.get('errback', None)
53+
if errback is None:
54+
def errback(e):
55+
raise e
56+
def errback_wrapper(e):
57+
if isinstance(e, UnknownAppID) and 'INITIAL' in OPTIONS:
58+
try:
59+
for initial in OPTIONS['INITIAL']:
60+
provision(*initial) # retry provisioning the initial setup
61+
func(*a, **kw) # and try the function once more
62+
except Exception, new_exc:
63+
errback(new_exc) # throwing the new exception
64+
else:
65+
errback(e) # not an instance of UnknownAppID - nothing we can do here
66+
kw['errback'] = errback_wrapper
67+
return func(*a, **kw)
68+
return wrapper
69+
70+
def default_callback(func):
71+
@functools.wraps(func)
72+
def wrapper(*a, **kw):
73+
if 'callback' not in kw:
74+
kw['callback'] = lambda c: c
75+
return func(*a, **kw)
76+
return wrapper
4377

44-
def provision(app_id, path_to_cert, environment, timeout=15, callback=None):
78+
@default_callback
79+
@reprovision_and_retry
80+
def provision(app_id, path_to_cert, environment, timeout=15, async=False,
81+
callback=None, errback=None):
4582
args = [app_id, path_to_cert, environment, timeout]
46-
if callback is None:
47-
return _xmlrpc_thread('provision', args, lambda r: r)
48-
t = threading.Thread(target=_xmlrpc_thread, args=['provision', args, callback])
83+
f_args = ['provision', args, callback, errback]
84+
if not async:
85+
return _xmlrpc_thread(*f_args)
86+
t = threading.Thread(target=_xmlrpc_thread, args=f_args)
4987
t.daemon = True
5088
t.start()
5189

52-
def notify(app_id, tokens, notifications, callback=None):
90+
@default_callback
91+
@reprovision_and_retry
92+
def notify(app_id, tokens, notifications, async=False, callback=None,
93+
errback=None):
5394
args = [app_id, tokens, notifications]
54-
if callback is None:
55-
return _xmlrpc_thread('notify', args, lambda r: r)
56-
t = threading.Thread(target=_xmlrpc_thread, args=['notify', args, callback])
95+
f_args = ['notify', args, callback, errback]
96+
if not async:
97+
return _xmlrpc_thread(*f_args)
98+
t = threading.Thread(target=_xmlrpc_thread, args=f_args)
5799
t.daemon = True
58100
t.start()
59101

60-
def feedback(app_id, callback=None):
102+
@default_callback
103+
@reprovision_and_retry
104+
def feedback(app_id, async=False, callback=None, errback=None):
61105
args = [app_id]
62-
if callback is None:
63-
return _xmlrpc_thread('feedback', args, lambda r: r)
64-
t = threading.Thread(target=_xmlrpc_thread, args=['feedback', args, callback])
106+
f_args = ['feedback', args, callback, errback]
107+
if not async:
108+
return _xmlrpc_thread(*f_args)
109+
t = threading.Thread(target=_xmlrpc_thread, args=f_args)
65110
t.daemon = True
66111
t.start()
67112

68-
def _xmlrpc_thread(method, args, callback):
113+
def _xmlrpc_thread(method, args, callback, errback=None):
69114
if not configure({}):
70115
raise APNSNotConfigured('APNS Has not been configured.')
71116
proxy = ServerProxy(OPTIONS['HOST'], allow_none=True, use_datetime=True,
@@ -77,8 +122,11 @@ def _xmlrpc_thread(method, args, callback):
77122
return callback(proxy(*args))
78123
except xmlrpclib.Fault, e:
79124
if e.faultCode == 404:
80-
raise UnknownAppID
81-
raise
125+
e = UnknownAppID()
126+
if errback is not None:
127+
errback(e)
128+
else:
129+
raise e
82130

83131

84132
## --------------------------------------------------------------

0 commit comments

Comments
 (0)