Skip to content

Random cleanups #52

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

Merged
merged 3 commits into from
Mar 11, 2015
Merged
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
124 changes: 81 additions & 43 deletions gcm/gcm.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,43 +8,71 @@
GCM_URL = 'https://android.googleapis.com/gcm/send'


class GCMException(Exception): pass
class GCMMalformedJsonException(GCMException): pass
class GCMConnectionException(GCMException): pass
class GCMAuthenticationException(GCMException): pass
class GCMTooManyRegIdsException(GCMException): pass
class GCMInvalidTtlException(GCMException): pass
class GCMException(Exception):
pass


class GCMMalformedJsonException(GCMException):
pass


class GCMConnectionException(GCMException):
pass


class GCMAuthenticationException(GCMException):
pass


class GCMTooManyRegIdsException(GCMException):
pass


class GCMInvalidTtlException(GCMException):
pass

# Exceptions from Google responses
class GCMMissingRegistrationException(GCMException): pass
class GCMMismatchSenderIdException(GCMException): pass
class GCMNotRegisteredException(GCMException): pass
class GCMMessageTooBigException(GCMException): pass
class GCMInvalidRegistrationException(GCMException): pass
class GCMUnavailableException(GCMException): pass


class GCMMissingRegistrationException(GCMException):
pass


class GCMMismatchSenderIdException(GCMException):
pass


class GCMNotRegisteredException(GCMException):
pass


class GCMMessageTooBigException(GCMException):
pass


class GCMInvalidRegistrationException(GCMException):
pass


class GCMUnavailableException(GCMException):
pass


# TODO: Refactor this to be more human-readable
def group_response(response, registration_ids, key):
# Pair up results and reg_ids
mapping = zip(registration_ids, response['results'])
# Filter by key
filtered = filter(lambda x: key in x[1], mapping)
# Only consider the value in the dict
tupled = [(s[0], s[1][key]) for s in filtered]
filtered = ((reg_id, res[key]) for reg_id, res in mapping if key in res)
# Grouping of errors and mapping of ids
if key is 'registration_id':
grouping = {}
for k, v in tupled:
grouping[k] = v
grouping = dict(filtered)
else:
grouping = defaultdict(list)
for k, v in tupled:
for k, v in filtered:
grouping[v].append(k)

if len(grouping) == 0:
return
return grouping
return grouping or None


def urlencode_utf8(params):
Expand All @@ -56,7 +84,7 @@ def urlencode_utf8(params):
if hasattr(params, 'items'):
params = params.items()

params = (
params = (
'='.join((
urllib.quote_plus(k.encode('utf8'), safe='/'),
urllib.quote_plus(v.encode('utf8'), safe='/')
Expand All @@ -69,8 +97,8 @@ def urlencode_utf8(params):
class GCM(object):

# Timeunit is milliseconds.
BACKOFF_INITIAL_DELAY = 1000;
MAX_BACKOFF_DELAY = 1024000;
BACKOFF_INITIAL_DELAY = 1000
MAX_BACKOFF_DELAY = 1024000

def __init__(self, api_key, url=GCM_URL, proxy=None):
""" api_key : google api key
Expand All @@ -80,17 +108,17 @@ def __init__(self, api_key, url=GCM_URL, proxy=None):
self.api_key = api_key
self.url = url
if proxy:
if isinstance(proxy,basestring):
if isinstance(proxy, basestring):
protocol = url.split(':')[0]
proxy={protocol:proxy}
proxy = {protocol: proxy}

auth = urllib2.HTTPBasicAuthHandler()
opener = urllib2.build_opener(urllib2.ProxyHandler(proxy), auth, urllib2.HTTPHandler)
opener = urllib2.build_opener(
urllib2.ProxyHandler(proxy), auth, urllib2.HTTPHandler)
urllib2.install_opener(opener)


def construct_payload(self, registration_ids, data=None, collapse_key=None,
delay_while_idle=False, time_to_live=None, is_json=True, dry_run=False):
delay_while_idle=False, time_to_live=None, is_json=True, dry_run=False):
"""
Construct the dictionary mapping of parameters.
Encodes the dictionary into JSON if for json requests.
Expand All @@ -101,7 +129,8 @@ def construct_payload(self, registration_ids, data=None, collapse_key=None,
"""

if time_to_live:
if time_to_live > 2419200 or time_to_live < 0:
four_weeks_in_secs = 2419200
if not (0 <= time_to_live <= four_weeks_in_secs):
raise GCMInvalidTtlException("Invalid time to live value")

if is_json:
Expand All @@ -119,7 +148,7 @@ def construct_payload(self, registration_ids, data=None, collapse_key=None,
if delay_while_idle:
payload['delay_while_idle'] = delay_while_idle

if time_to_live >= 0:
if time_to_live:
payload['time_to_live'] = time_to_live

if collapse_key:
Expand All @@ -146,7 +175,8 @@ def make_request(self, data, is_json=True):
headers = {
'Authorization': 'key=%s' % self.api_key,
}
# Default Content-Type is defaulted to application/x-www-form-urlencoded;charset=UTF-8
# Default Content-Type is defaulted to
# application/x-www-form-urlencoded;charset=UTF-8
if is_json:
headers['Content-Type'] = 'application/json'

Expand All @@ -158,16 +188,19 @@ def make_request(self, data, is_json=True):
response = urllib2.urlopen(req).read()
except urllib2.HTTPError as e:
if e.code == 400:
raise GCMMalformedJsonException("The request could not be parsed as JSON")
raise GCMMalformedJsonException(
"The request could not be parsed as JSON")
elif e.code == 401:
raise GCMAuthenticationException("There was an error authenticating the sender account")
raise GCMAuthenticationException(
"There was an error authenticating the sender account")
elif e.code == 503:
raise GCMUnavailableException("GCM service is unavailable")
else:
error = "GCM service error: %d" % e.code
raise GCMUnavailableException(error)
except urllib2.URLError as e:
raise GCMConnectionException("There was an internal error in the GCM server while trying to process the request")
raise GCMConnectionException(
"There was an internal error in the GCM server while trying to process the request")

if is_json:
response = json.loads(response)
Expand All @@ -179,11 +212,14 @@ def raise_error(self, error):
elif error == 'Unavailable':
# Plain-text requests will never return Unavailable as the error code.
# http://developer.android.com/guide/google/gcm/gcm.html#error_codes
raise GCMUnavailableException("Server unavailable. Resent the message")
raise GCMUnavailableException(
"Server unavailable. Resent the message")
elif error == 'NotRegistered':
raise GCMNotRegisteredException("Registration id is not valid anymore")
raise GCMNotRegisteredException(
"Registration id is not valid anymore")
elif error == 'MismatchSenderId':
raise GCMMismatchSenderIdException("A Registration ID is tied to a certain group of senders")
raise GCMMismatchSenderIdException(
"A Registration ID is tied to a certain group of senders")
elif error == 'MessageTooBig':
raise GCMMessageTooBigException("Message can't exceed 4096 bytes")

Expand All @@ -202,7 +238,8 @@ def handle_plaintext_response(self, response):

def handle_json_response(self, response, registration_ids):
errors = group_response(response, registration_ids, 'error')
canonical = group_response(response, registration_ids, 'registration_id')
canonical = group_response(
response, registration_ids, 'registration_id')

info = {}
if errors:
Expand All @@ -218,7 +255,7 @@ def extract_unsent_reg_ids(self, info):
return []

def plaintext_request(self, registration_id, data=None, collapse_key=None,
delay_while_idle=False, time_to_live=None, retries=5, dry_run=False):
delay_while_idle=False, time_to_live=None, retries=5, dry_run=False):
"""
Makes a plaintext request to GCM servers

Expand Down Expand Up @@ -251,7 +288,7 @@ def plaintext_request(self, registration_id, data=None, collapse_key=None,
raise IOError("Could not make request after %d attempts" % attempt)

def json_request(self, registration_ids, data=None, collapse_key=None,
delay_while_idle=False, time_to_live=None, retries=5, dry_run=False):
delay_while_idle=False, time_to_live=None, retries=5, dry_run=False):
"""
Makes a JSON request to GCM servers

Expand All @@ -265,7 +302,8 @@ def json_request(self, registration_ids, data=None, collapse_key=None,
if not registration_ids:
raise GCMMissingRegistrationException("Missing registration_ids")
if len(registration_ids) > 1000:
raise GCMTooManyRegIdsException("Exceded number of registration_ids")
raise GCMTooManyRegIdsException(
"Exceded number of registration_ids")

attempt = 0
backoff = self.BACKOFF_INITIAL_DELAY
Expand Down