Commit 248b7569 by David Robinson

Auth fixes, tests.py, increment method, pep8

Fixed authentication, since Parse appears to have changed their API.

Added tests.py with unit tests for ParseObject and ParseQuery classes.

Added ParseObject.increment(key) method.

pep8 compliance changes, mostly removing end of line whitespace
parent f93309cf
...@@ -11,7 +11,8 @@ ...@@ -11,7 +11,8 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import urllib, urllib2 import urllib
import urllib2
import base64 import base64
import json import json
import datetime import datetime
...@@ -35,9 +36,11 @@ class ParseBase(object): ...@@ -35,9 +36,11 @@ class ParseBase(object):
request.add_header('Content-type', 'application/json') request.add_header('Content-type', 'application/json')
# we could use urllib2's authentication system, but it seems like overkill for this #auth_header = "Basic %s" % base64.b64encode('%s:%s' %
auth_header = "Basic %s" % base64.b64encode('%s:%s' % (APPLICATION_ID, MASTER_KEY)) # (APPLICATION_ID, MASTER_KEY))
request.add_header("Authorization", auth_header) #request.add_header("Authorization", auth_header)
request.add_header("X-Parse-Application-Id", APPLICATION_ID)
request.add_header("X-Parse-REST-API-Key", MASTER_KEY)
request.get_method = lambda: http_verb request.get_method = lambda: http_verb
...@@ -51,7 +54,8 @@ class ParseBase(object): ...@@ -51,7 +54,8 @@ class ParseBase(object):
def _ISO8601ToDatetime(self, date_string): def _ISO8601ToDatetime(self, date_string):
# TODO: verify correct handling of timezone # TODO: verify correct handling of timezone
date_string = date_string[:-1] + 'UTC' date_string = date_string[:-1] + 'UTC'
date = datetime.datetime.strptime(date_string, "%Y-%m-%dT%H:%M:%S.%f%Z") date = datetime.datetime.strptime(date_string,
"%Y-%m-%dT%H:%M:%S.%f%Z")
return date return date
...@@ -69,10 +73,12 @@ class ParseObject(ParseBase): ...@@ -69,10 +73,12 @@ class ParseObject(ParseBase):
return self._object_id return self._object_id
def updatedAt(self): def updatedAt(self):
return self._updated_at and self._ISO8601ToDatetime(self._updated_at) or None return (self._updated_at and self._ISO8601ToDatetime(self._updated_at)
or None)
def createdAt(self): def createdAt(self):
return self._created_at and self._ISO8601ToDatetime(self._created_at) or None return (self._created_at and self._ISO8601ToDatetime(self._created_at)
or None)
def save(self): def save(self):
if self._object_id: if self._object_id:
...@@ -90,14 +96,37 @@ class ParseObject(ParseBase): ...@@ -90,14 +96,37 @@ class ParseObject(ParseBase):
self = self.__init__(None) self = self.__init__(None)
def _populateFromDict(self, attrs_dict): def increment(self, key, amount=1):
self._object_id = attrs_dict['objectId'] """
self._created_at = attrs_dict['createdAt'] Increment one value in the object. Note that this happens immediately:
self._updated_at = attrs_dict['updatedAt'] it does not wait for save() to be called
"""
uri = '/%s/%s' % (self._class_name, self._object_id)
txdata = '{"%s": {"__op": "Increment", "amount": %d}}' % (key, amount)
ret = self._executeCall(uri, 'PUT', txdata)
self._populateFromDict(ret)
del attrs_dict['objectId'] def has(self, attr):
del attrs_dict['createdAt'] return attr in self.__dict__
del attrs_dict['updatedAt']
def remove(self, attr):
self.__dict__.pop(attr)
def refresh(self):
uri = '/%s/%s' % (self._class_name, self._object_id)
response_dict = self._executeCall(uri, 'GET')
self._populateFromDict(response_dict)
def _populateFromDict(self, attrs_dict):
if 'objectId' in attrs_dict:
self._object_id = attrs_dict['objectId']
del attrs_dict['objectId']
if 'createdAt' in attrs_dict:
self._created_at = attrs_dict['createdAt']
del attrs_dict['createdAt']
if 'updatedAt' in attrs_dict:
self._updated_at = attrs_dict['updatedAt']
del attrs_dict['updatedAt']
attrs_dict = dict(map(self._convertFromParseType, attrs_dict.items())) attrs_dict = dict(map(self._convertFromParseType, attrs_dict.items()))
...@@ -111,8 +140,8 @@ class ParseObject(ParseBase): ...@@ -111,8 +140,8 @@ class ParseObject(ParseBase):
'className': value._class_name, 'className': value._class_name,
'objectId': value._object_id} 'objectId': value._object_id}
elif type(value) == datetime.datetime: elif type(value) == datetime.datetime:
value = {'__type': 'Date', # take off the last 3 digits and add a Z
'iso': value.isoformat()[:-3] + 'Z'} # take off the last 3 digits and add a Z value = {'__type': 'Date', 'iso': value.isoformat()[:-3] + 'Z'}
elif type(value) == ParseBinaryDataWrapper: elif type(value) == ParseBinaryDataWrapper:
value = {'__type': 'Bytes', value = {'__type': 'Bytes',
'base64': base64.b64encode(value)} 'base64': base64.b64encode(value)}
...@@ -122,13 +151,14 @@ class ParseObject(ParseBase): ...@@ -122,13 +151,14 @@ class ParseObject(ParseBase):
def _convertFromParseType(self, prop): def _convertFromParseType(self, prop):
key, value = prop key, value = prop
if type(value) == dict and value.has_key('__type'): if type(value) == dict and '__type' in value:
if value['__type'] == 'Pointer': if value['__type'] == 'Pointer':
value = ParseQuery(value['className']).get(value['objectId']) value = ParseQuery(value['className']).get(value['objectId'])
elif value['__type'] == 'Date': elif value['__type'] == 'Date':
value = self._ISO8601ToDatetime(value['iso']) value = self._ISO8601ToDatetime(value['iso'])
elif value['__type'] == 'Bytes': elif value['__type'] == 'Bytes':
value = ParseBinaryDataWrapper(base64.b64decode(value['base64'])) value = ParseBinaryDataWrapper(base64.b64decode(
value['base64']))
else: else:
raise Exception('Invalid __type.') raise Exception('Invalid __type.')
...@@ -139,12 +169,14 @@ class ParseObject(ParseBase): ...@@ -139,12 +169,14 @@ class ParseObject(ParseBase):
properties_list = self.__dict__.items() properties_list = self.__dict__.items()
# filter properties that start with an underscore # filter properties that start with an underscore
properties_list = filter(lambda prop: prop[0][0] != '_', properties_list) properties_list = filter(lambda prop: prop[0][0] != '_',
properties_list)
#properties_list = [(key, value) for key, value in self.__dict__.items() if key[0] != '_'] #properties_list = [(key, value) for key, value
# in self.__dict__.items() if key[0] != '_']
properties_list = map(self._convertToParseType, properties_list) properties_list = map(self._convertToParseType, properties_list)
properties_dict = dict(properties_list) properties_dict = dict(properties_list)
json_properties = json.dumps(properties_dict) json_properties = json.dumps(properties_dict)
...@@ -159,7 +191,7 @@ class ParseObject(ParseBase): ...@@ -159,7 +191,7 @@ class ParseObject(ParseBase):
data = self._getJSONProperties() data = self._getJSONProperties()
response_dict = self._executeCall(uri, 'POST', data) response_dict = self._executeCall(uri, 'POST', data)
self._created_at = self._updated_at = response_dict['createdAt'] self._created_at = self._updated_at = response_dict['createdAt']
self._object_id = response_dict['objectId'] self._object_id = response_dict['objectId']
...@@ -192,15 +224,15 @@ class ParseQuery(ParseBase): ...@@ -192,15 +224,15 @@ class ParseQuery(ParseBase):
def lt(self, name, value): def lt(self, name, value):
self._where[name]['$lt'] = value self._where[name]['$lt'] = value
return self return self
def lte(self, name, value): def lte(self, name, value):
self._where[name]['$lte'] = value self._where[name]['$lte'] = value
return self return self
def gt(self, name, value): def gt(self, name, value):
self._where[name]['$gt'] = value self._where[name]['$gt'] = value
return self return self
def gte(self, name, value): def gte(self, name, value):
self._where[name]['$gte'] = value self._where[name]['$gte'] = value
return self return self
...@@ -227,9 +259,9 @@ class ParseQuery(ParseBase): ...@@ -227,9 +259,9 @@ class ParseQuery(ParseBase):
return self._fetch(single_result=True) return self._fetch(single_result=True)
def fetch(self): def fetch(self):
# hide the single_result param of the _fetch method from the library user # hide the single_result param of the _fetch method from the library
# since it's only useful internally # user since it's only useful internally
return self._fetch() return self._fetch()
def _fetch(self, single_result=False): def _fetch(self, single_result=False):
# URL: /1/classes/<className>/<objectId> # URL: /1/classes/<className>/<objectId>
...@@ -238,7 +270,7 @@ class ParseQuery(ParseBase): ...@@ -238,7 +270,7 @@ class ParseQuery(ParseBase):
if self._object_id: if self._object_id:
uri = '/%s/%s' % (self._class_name, self._object_id) uri = '/%s/%s' % (self._class_name, self._object_id)
else: else:
options = dict(self._options) # make a local copy options = dict(self._options) # make a local copy
if self._where: if self._where:
# JSON encode WHERE values # JSON encode WHERE values
where = json.dumps(self._where) where = json.dumps(self._where)
...@@ -251,5 +283,5 @@ class ParseQuery(ParseBase): ...@@ -251,5 +283,5 @@ class ParseQuery(ParseBase):
if single_result: if single_result:
return ParseObject(self._class_name, response_dict) return ParseObject(self._class_name, response_dict)
else: else:
return [ParseObject(self._class_name, result) for result in response_dict['results']] return [ParseObject(self._class_name, result)
for result in response_dict['results']]
"""
Contains unit tests for the Python Parse REST API wrapper
"""
import unittest
import urllib2
import datetime
import __init__ as ParsePy
try:
import settings_local
except ImportError:
raise ImportError("You must create a settings_local.py file " +
"with an example application to run tests")
ParsePy.APPLICATION_ID = settings_local.APPLICATION_ID
ParsePy.MASTER_KEY = settings_local.MASTER_KEY
### FUNCTIONS ###
def test_obj(saved=False):
"""Return a test ParsePy.ParseObject (content is from the docs)"""
ret = ParsePy.ParseObject("GameScore")
ret.score = 1337
ret.playerName = "Sean Plott"
ret.cheatMode = False
if saved:
ret.save()
return ret
### CLASSES ###
class TestParseObjectAndQuery(unittest.TestCase):
"""
Tests for the ParsePy.ParseObject interface for creating and updating Parse
objects, as well as the ParsePy.ParseQuery interface for retrieving them
"""
def check_test_obj(self, o):
"""check that the object is consistent with the test object"""
self.assertEqual(o.objectId().__class__, unicode)
self.assertEqual(o.updatedAt().__class__, datetime.datetime)
self.assertEqual(o.createdAt().__class__, datetime.datetime)
self.assertEqual(o.score, 1337)
# TODO: str vs unicode
#self.assertEqual(o.playerName.__class__, unicode)
self.assertEqual(o.cheatMode.__class__, bool)
def test_object(self):
"""Test the creation, retrieval and updating of a ParseObject"""
gameScore = test_obj()
gameScore.save()
self.check_test_obj(gameScore)
# retrieve a new one
query = ParsePy.ParseQuery("GameScore")
obj1 = query.get(gameScore.objectId())
self.check_test_obj(obj1)
# now update it
current_updated = obj1.updatedAt()
obj1.score = 1000
obj1.save()
self.assertGreater(obj1.updatedAt(), current_updated)
self.assertEqual(obj1.score, 1000)
# re-retrieve it
obj2 = query.get(obj1.objectId())
self.assertEqual(obj2.score, 1000)
# change one object, check that others can be refreshed
obj2.score = 2000
obj2.save()
self.assertEqual(obj1.score, 1000)
obj1.refresh()
self.assertEqual(obj1.score, 2000)
# try removing a field
obj2.remove("score")
obj2.save()
self.assertEqual(obj2.has("score"), False)
def test_increment(self):
"""Test incrementation of fields"""
o = test_obj(True)
self.check_test_obj(o)
o.save()
o.increment("score")
self.assertEqual(o.score, 1338)
query = ParsePy.ParseQuery("GameScore")
o2 = query.get(o.objectId())
self.assertEqual(o2.score, 1338)
# one more time
o.increment("score")
self.assertEqual(o.score, 1339)
o3 = query.get(o.objectId())
self.assertEqual(o3.score, 1339)
def test_relationship(self):
"""Test relationship between objects"""
post = ParsePy.ParseObject("Post")
post.title = "I'm Hungry"
post.content = "Where should we go for lunch?"
post.save()
comment = ParsePy.ParseObject("Comment")
comment.content = "Let's do Sushirrito"
comment.parent = post
comment.save()
# that should have saved both post and comment
post_id = post.objectId()
comment_id = comment.objectId()
self.assertEqual(post_id.__class__, unicode)
self.assertEqual(comment_id.__class__, unicode)
# retrieve new ones
post2 = ParsePy.ParseQuery("Post").get(post_id)
comment2 = ParsePy.ParseQuery("Comment").get(comment_id)
# check the relationship between the saved post and comment
self.assertEqual(comment2.parent.objectId(), post_id)
self.assertEqual(comment2.parent.title, "I'm Hungry")
if __name__ == "__main__":
# command line
unittest.main()
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment