Commit c6106771 by Roman Krejcik

Python3 support

parent cd9ef16a
......@@ -11,18 +11,13 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
try:
from urllib2 import Request, urlopen, HTTPError
from urllib import urlencode
except ImportError:
# is Python3
from urllib.request import Request, urlopen
from urllib.error import HTTPError
from urllib.parse import urlencode
from six.moves.urllib.request import Request, urlopen
from six.moves.urllib.error import HTTPError
from six.moves.urllib.parse import urlencode
import json
import core
from parse_rest import core
API_ROOT = 'https://api.parse.com/1'
ACCESS_KEYS = {}
......@@ -79,6 +74,8 @@ class ParseBase(object):
if http_verb == 'GET' and data:
url += '?%s' % urlencode(kw)
data = None
else:
data = data.encode('utf-8')
request = Request(url, data, headers)
request.add_header('Content-type', 'application/json')
......@@ -101,7 +98,7 @@ class ParseBase(object):
}.get(e.code, core.ParseError)
raise exc(e.read())
return json.loads(response.read())
return json.loads(response.read().decode('utf-8'))
@classmethod
def GET(cls, uri, **kw):
......
......@@ -10,12 +10,14 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import unicode_literals
import base64
import datetime
import six
from connection import API_ROOT, ParseBase
from query import QueryManager
from parse_rest.connection import API_ROOT, ParseBase
from parse_rest.query import QueryManager
class ParseType(object):
......@@ -94,7 +96,7 @@ class Pointer(ParseType):
# also circular refs through more object are now ignored, in fact lazy loaded references will be best solution
objectData = dict(objectData)
# now lets see if we have any references to the parent class here
for key, value in objectData.iteritems():
for key, value in six.iteritems(objectData):
if isinstance(value, dict) and "className" in value and value["className"] == parent_class_name:
# simply put the reference here as a string -- not sure what the drawbacks are for this but it works for me
objectData[key] = value["objectId"]
......@@ -115,7 +117,6 @@ class Pointer(ParseType):
return klass(**objectData)
def __init__(self, obj):
self._object = obj
def _to_native(self):
......@@ -157,7 +158,7 @@ class Date(ParseType):
"""Can be initialized either with a string or a datetime"""
if isinstance(date, datetime.datetime):
self._date = date
elif isinstance(date, unicode):
elif isinstance(date, six.string_types):
self._date = Date._from_str(date)
def _to_native(self):
......@@ -247,7 +248,6 @@ class ParseResource(ParseBase, Pointer):
return dict([(k, v) for k, v in self.__dict__.items() if allowed(k)])
def __init__(self, **kw):
for key, value in kw.items():
setattr(self, key, ParseType.convert_from_parse(value, self.__class__.__name__))
......@@ -324,19 +324,21 @@ class ParseResource(ParseBase, Pointer):
updatedAt = property(_get_updated_datetime, _set_updated_datetime)
def __repr__(self):
return '<%s:%s>' % (unicode(self.__class__.__name__), self.objectId)
return '<%s:%s>' % (self.__class__.__name__, self.objectId)
class ObjectMetaclass(type):
def __new__(cls, name, bases, dct):
cls = super(ObjectMetaclass, cls).__new__(cls, name, bases, dct)
cls.set_endpoint_root()
cls.Query = QueryManager(cls)
def __new__(mcs, name, bases, dct):
cls = super(ObjectMetaclass, mcs).__new__(mcs, name, bases, dct)
# attr check must be here because of specific six.with_metaclass implemetantion where metaclass is used also for
# internal NewBase which hasn't set_endpoint_root method
if hasattr(cls, 'set_endpoint_root'):
cls.set_endpoint_root()
cls.Query = QueryManager(cls)
return cls
class Object(ParseResource):
__metaclass__ = ObjectMetaclass
class Object(six.with_metaclass(ObjectMetaclass, ParseResource)):
ENDPOINT_ROOT = '/'.join([API_ROOT, 'classes'])
@classmethod
......@@ -357,7 +359,8 @@ class Object(ParseResource):
@property
def _absolute_url(self):
if not self.objectId: return None
if not self.objectId:
return None
return '/'.join([self.__class__.ENDPOINT_ROOT, self.objectId])
@property
......
......@@ -11,9 +11,9 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from connection import API_ROOT
from datatypes import ParseResource
from query import QueryManager
from parse_rest.connection import API_ROOT
from parse_rest.datatypes import ParseResource
from parse_rest.query import QueryManager
class Installation(ParseResource):
......
......@@ -14,11 +14,8 @@
import json
import collections
import copy
import six
try:
unicode = unicode
except NameError:
unicode = str
class QueryResourceDoesNotExist(Exception):
'''Query returned no results'''
......@@ -42,8 +39,7 @@ class QueryManager(object):
def _count(self, **kw):
kw.update({"count": 1, "limit": 0})
return self.model_class.GET(self.model_class.ENDPOINT_ROOT,
**kw).get('count')
return self.model_class.GET(self.model_class.ENDPOINT_ROOT, **kw).get('count')
def all(self):
return Queryset(self)
......@@ -60,8 +56,8 @@ class QueryManager(object):
class QuerysetMetaclass(type):
"""metaclass to add the dynamically generated comparison functions"""
def __new__(cls, name, bases, dct):
cls = super(QuerysetMetaclass, cls).__new__(cls, name, bases, dct)
def __new__(mcs, name, bases, dct):
cls = super(QuerysetMetaclass, mcs).__new__(mcs, name, bases, dct)
for fname in ['limit', 'skip']:
def func(self, value, fname=fname):
......@@ -73,16 +69,15 @@ class QuerysetMetaclass(type):
return cls
class Queryset(object):
__metaclass__ = QuerysetMetaclass
class Queryset(six.with_metaclass(QuerysetMetaclass, object)):
OPERATORS = [
'lt', 'lte', 'gt', 'gte', 'ne', 'in', 'nin', 'exists', 'select', 'dontSelect', 'all', 'relatedTo'
]
]
@staticmethod
def convert_to_parse(value):
from datatypes import ParseType
from parse_rest.datatypes import ParseType
return ParseType.convert_to_parse(value, as_pointer=True)
@classmethod
......@@ -143,8 +138,7 @@ class Queryset(object):
self._where[attr]['$' + operator] = parse_value
except TypeError:
# self._where[attr] wasn't settable
raise ValueError("Cannot filter for a constraint " +
"after filtering for a specific value")
raise ValueError("Cannot filter for a constraint after filtering for a specific value")
return self
def order_by(self, order, descending=False):
......@@ -171,4 +165,4 @@ class Queryset(object):
return results[0]
def __repr__(self):
return unicode(self._fetch())
return repr(self._fetch())
......@@ -4,20 +4,21 @@
"""
Contains unit tests for the Python Parse REST API wrapper
"""
from __future__ import print_function
import os
import sys
import subprocess
import unittest
import datetime
import six
from core import ResourceRequestNotFound
from connection import register, ParseBatcher
from datatypes import GeoPoint, Object, Function
from user import User
import query
from installation import Push
from parse_rest.core import ResourceRequestNotFound
from parse_rest.connection import register, ParseBatcher
from parse_rest.datatypes import GeoPoint, Object, Function
from parse_rest.user import User
from parse_rest import query
from parse_rest.installation import Push
try:
import settings_local
......@@ -25,17 +26,12 @@ except ImportError:
sys.exit('You must create a settings_local.py file with APPLICATION_ID, ' \
'REST_API_KEY, MASTER_KEY variables set')
try:
unicode = unicode
except NameError:
# is python3
unicode = str
register(
getattr(settings_local, 'APPLICATION_ID'),
getattr(settings_local, 'REST_API_KEY'),
master_key=getattr(settings_local, 'MASTER_KEY')
)
)
GLOBAL_JSON_TEXT = """{
"applications": {
......@@ -95,48 +91,47 @@ class TestObject(unittest.TestCase):
score.delete()
def testCanInitialize(self):
self.assert_(self.score.score == 1337, 'Could not set score')
self.assertEqual(self.score.score, 1337, 'Could not set score')
def testCanInstantiateParseType(self):
self.assert_(self.sao_paulo.location.latitude == -23.5)
self.assertEqual(self.sao_paulo.location.latitude, -23.5)
def testCanSaveDates(self):
now = datetime.datetime.now()
self.score.last_played = now
self.score.save()
self.assert_(self.score.last_played == now, 'Could not save date')
self.assertEqual(self.score.last_played, now, 'Could not save date')
def testCanCreateNewObject(self):
self.score.save()
object_id = self.score.objectId
self.assert_(object_id is not None, 'Can not create object')
self.assert_(type(object_id) == unicode)
self.assert_(type(self.score.createdAt) == datetime.datetime)
self.assert_(GameScore.Query.filter(objectId=object_id).exists(),
'Can not create object')
self.assertIsNotNone(object_id, 'Can not create object')
self.assertIsInstance(object_id, six.string_types)
self.assertIsInstance(self.score.createdAt, datetime.datetime)
self.assertTrue(GameScore.Query.filter(objectId=object_id).exists(), 'Can not create object')
def testCanUpdateExistingObject(self):
self.sao_paulo.save()
self.sao_paulo.country = 'Brazil'
self.sao_paulo.save()
self.assert_(type(self.sao_paulo.updatedAt) == datetime.datetime)
self.assertIsInstance(self.sao_paulo.updatedAt, datetime.datetime)
city = City.Query.get(name='São Paulo')
self.assert_(city.country == 'Brazil', 'Could not update object')
self.assertEqual(city.country, 'Brazil', 'Could not update object')
def testCanDeleteExistingObject(self):
self.score.save()
object_id = self.score.objectId
self.score.delete()
self.assert_(not GameScore.Query.filter(objectId=object_id).exists(),
'Failed to delete object %s on Parse ' % self.score)
self.assertFalse(GameScore.Query.filter(objectId=object_id).exists(),
'Failed to delete object %s on Parse ' % self.score)
def testCanIncrementField(self):
previous_score = self.score.score
self.score.save()
self.score.increment('score')
self.assert_(GameScore.Query.filter(score=previous_score + 1).exists(),
self.assertTrue(GameScore.Query.filter(score=previous_score + 1).exists(),
'Failed to increment score on backend')
def testAssociatedObject(self):
......@@ -149,10 +144,8 @@ class TestObject(unittest.TestCase):
# get the object, see if it has saved
qs = GameScore.Query.get(objectId=self.score.objectId)
self.assert_(isinstance(qs.item, Object),
"Associated CollectedItem is not an object")
self.assert_(qs.item.type == "Sword",
"Associated CollectedItem does not have correct attributes")
self.assertIsInstance(qs.item, Object, "Associated CollectedItem is not an object")
self.assertEqual(qs.item.type, "Sword", "Associated CollectedItem does not have correct attributes")
def testBatch(self):
"""test saving, updating and deleting objects in batches"""
......@@ -160,9 +153,9 @@ class TestObject(unittest.TestCase):
for s in range(5)]
batcher = ParseBatcher()
batcher.batch_save(scores)
self.assert_(GameScore.Query.filter(player_name='Jane').count() == 5,
self.assertEqual(GameScore.Query.filter(player_name='Jane').count(), 5,
"batch_save didn't create objects")
self.assert_(all(s.objectId is not None for s in scores),
self.assertTrue(all(s.objectId is not None for s in scores),
"batch_save didn't record object IDs")
# test updating
......@@ -172,11 +165,11 @@ class TestObject(unittest.TestCase):
updated_scores = GameScore.Query.filter(player_name='Jane')
self.assertEqual(sorted([s.score for s in updated_scores]),
range(10, 15), msg="batch_save didn't update objects")
list(range(10, 15)), msg="batch_save didn't update objects")
# test deletion
batcher.batch_delete(scores)
self.assert_(GameScore.Query.filter(player_name='Jane').count() == 0,
self.assertEqual(GameScore.Query.filter(player_name='Jane').count(), 0,
"batch_delete didn't delete objects")
......@@ -186,31 +179,31 @@ class TestTypes(unittest.TestCase):
self.score = GameScore(
score=1337, player_name='John Doe', cheat_mode=False,
date_of_birth=self.now
)
)
self.sao_paulo = City(
name='São Paulo', location=GeoPoint(-23.5, -46.6167)
)
)
def testCanConvertToNative(self):
native_data = self.sao_paulo._to_native()
self.assert_(type(native_data) is dict, 'Can not convert object to dict')
self.assertIsInstance(native_data, dict, 'Can not convert object to dict')
def testCanConvertNestedLocation(self):
native_sao_paulo = self.sao_paulo._to_native()
location_dict = native_sao_paulo.get('location')
self.assert_(type(location_dict) is dict,
'Expected dict after conversion. Got %s' % location_dict)
self.assert_(location_dict.get('latitude') == -23.5,
'Can not serialize geopoint data')
self.assertIsInstance(location_dict, dict,
'Expected dict after conversion. Got %s' % location_dict)
self.assertEqual(location_dict.get('latitude'), -23.5,
'Can not serialize geopoint data')
def testCanConvertDate(self):
native_date = self.score._to_native().get('date_of_birth')
self.assert_(type(native_date) is dict,
'Could not serialize date into dict')
self.assertIsInstance(native_date, dict,
'Could not serialize date into dict')
iso_date = native_date.get('iso')
now = '{0}Z'.format(self.now.isoformat()[:-3])
self.assert_(iso_date == now, 'Expected %s. Got %s' % (now, iso_date))
self.assertEqual(iso_date, now, 'Expected %s. Got %s' % (now, iso_date))
class TestQuery(unittest.TestCase):
......@@ -235,23 +228,23 @@ class TestQuery(unittest.TestCase):
def testExists(self):
"""test the Queryset.exists() method"""
for s in range(1, 6):
self.assert_(GameScore.Query.filter(score=s).exists(),
"exists giving false negative")
self.assert_(not GameScore.Query.filter(score=10).exists(),
"exists giving false positive")
self.assertTrue(GameScore.Query.filter(score=s).exists(),
"exists giving false negative")
self.assertFalse(GameScore.Query.filter(score=10).exists(),
"exists giving false positive")
def testCanFilter(self):
'''test the Queryset.filter() method'''
for s in self.scores:
qobj = GameScore.Query.filter(objectId=s.objectId).get()
self.assert_(qobj.objectId == s.objectId,
"Getting object with .filter() failed")
self.assert_(qobj.score == s.score,
"Getting object with .filter() failed")
self.assertEqual(qobj.objectId, s.objectId,
"Getting object with .filter() failed")
self.assertEqual(qobj.score, s.score,
"Getting object with .filter() failed")
# test relational query with other Objects
num_scores = GameScore.Query.filter(game=self.game).count()
self.assert_(num_scores == len(self.scores),
self.assertTrue(num_scores == len(self.scores),
"Relational query with .filter() failed")
def testGetExceptions(self):
......@@ -265,37 +258,37 @@ class TestQuery(unittest.TestCase):
last_week = datetime.datetime.now() - datetime.timedelta(days=7)
score = GameScore(name='test', last_played=last_week)
score.save()
self.assert_(GameScore.Query.filter(last_played=last_week).exists(),
'Could not run query with dates')
self.assertTrue(GameScore.Query.filter(last_played=last_week).exists(),
'Could not run query with dates')
def testComparisons(self):
"""test comparison operators- gt, gte, lt, lte, ne"""
scores_gt_3 = list(GameScore.Query.filter(score__gt=3))
self.assertEqual(len(scores_gt_3), 2)
self.assert_(all([s.score > 3 for s in scores_gt_3]))
self.assertTrue(all([s.score > 3 for s in scores_gt_3]))
scores_gte_3 = list(GameScore.Query.filter(score__gte=3))
self.assertEqual(len(scores_gte_3), 3)
self.assert_(all([s.score >= 3 for s in scores_gt_3]))
self.assertTrue(all([s.score >= 3 for s in scores_gt_3]))
scores_lt_4 = list(GameScore.Query.filter(score__lt=4))
self.assertEqual(len(scores_lt_4), 3)
self.assert_(all([s.score < 4 for s in scores_lt_4]))
self.assertTrue(all([s.score < 4 for s in scores_lt_4]))
scores_lte_4 = list(GameScore.Query.filter(score__lte=4))
self.assertEqual(len(scores_lte_4), 4)
self.assert_(all([s.score <= 4 for s in scores_lte_4]))
self.assertTrue(all([s.score <= 4 for s in scores_lte_4]))
scores_ne_2 = list(GameScore.Query.filter(score__ne=2))
self.assertEqual(len(scores_ne_2), 4)
self.assert_(all([s.score != 2 for s in scores_ne_2]))
self.assertTrue(all([s.score != 2 for s in scores_ne_2]))
# test chaining
lt_4_gt_2 = list(GameScore.Query.filter(score__lt=4).filter(score__gt=2))
self.assert_(len(lt_4_gt_2) == 1, 'chained lt+gt not working')
self.assert_(lt_4_gt_2[0].score == 3, 'chained lt+gt not working')
self.assertEqual(len(lt_4_gt_2), 1, 'chained lt+gt not working')
self.assertEqual(lt_4_gt_2[0].score, 3, 'chained lt+gt not working')
q = GameScore.Query.filter(score__gt=3, score__lt=3)
self.assert_(not q.exists(), "chained lt+gt not working")
self.assertFalse(q.exists(), "chained lt+gt not working")
def testOptions(self):
"""test three options- order, limit, and skip"""
......@@ -308,15 +301,15 @@ class TestQuery(unittest.TestCase):
[5, 4, 3, 2, 1])
scores_limit_3 = list(GameScore.Query.all().limit(3))
self.assert_(len(scores_limit_3) == 3, "Limit did not return 3 items")
self.assertTrue(len(scores_limit_3) == 3, "Limit did not return 3 items")
scores_skip_3 = list(GameScore.Query.all().skip(3))
self.assert_(len(scores_skip_3) == 2, "Skip did not return 2 items")
self.assertTrue(len(scores_skip_3) == 2, "Skip did not return 2 items")
def testCanCompareDateInequality(self):
today = datetime.datetime.today()
tomorrow = today + datetime.timedelta(days=1)
self.assert_(GameScore.Query.filter(createdAt__lte=tomorrow).count() == 5,
self.assertTrue(GameScore.Query.filter(createdAt__lte=tomorrow).count() == 5,
'Could not make inequality comparison with dates')
def tearDown(self):
......@@ -407,13 +400,13 @@ class TestUser(unittest.TestCase):
def testCanSignUp(self):
self._destroy_user()
user = User.signup(self.username, self.password)
self.assert_(user is not None)
self.assert_(user.username == self.username)
self.assertTrue(user is not None)
self.assertTrue(user.username == self.username)
def testCanLogin(self):
self._get_user() # User should be created here.
user = User.login(self.username, self.password)
self.assert_(user.is_authenticated(), 'Login failed')
self.assertTrue(user.is_authenticated(), 'Login failed')
def testCanUpdate(self):
user = self._get_logged_user()
......@@ -423,7 +416,7 @@ class TestUser(unittest.TestCase):
user.phone = phone_number
user.save()
self.assert_(User.Query.filter(phone=phone_number).exists(),
self.assertTrue(User.Query.filter(phone=phone_number).exists(),
'Failed to update user data. New info not on Parse')
def testCanBatchUpdate(self):
......@@ -436,10 +429,10 @@ class TestUser(unittest.TestCase):
batcher = ParseBatcher()
batcher.batch_save([user])
self.assert_(User.Query.filter(phone=phone_number).exists(),
'Failed to batch update user data. New info not on Parse')
self.assert_(user.updatedAt != original_updatedAt,
'Failed to batch update user data: updatedAt not changed')
self.assertTrue(User.Query.filter(phone=phone_number).exists(),
'Failed to batch update user data. New info not on Parse')
self.assertNotEqual(user.updatedAt, original_updatedAt,
'Failed to batch update user data: updatedAt not changed')
class TestPush(unittest.TestCase):
......
......@@ -12,10 +12,10 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from core import ResourceRequestLoginRequired
from connection import API_ROOT
from datatypes import ParseResource, ParseType
from query import QueryManager
from parse_rest.core import ResourceRequestLoginRequired
from parse_rest.connection import API_ROOT
from parse_rest.datatypes import ParseResource, ParseType
from parse_rest.query import QueryManager
def login_required(func):
......
......@@ -27,6 +27,7 @@ setup(
url='https://github.com/dgrtwo/ParsePy',
packages=['parse_rest'],
package_data={"parse_rest": [os.path.join("cloudcode", "*", "*")]},
install_requires=['six'],
maintainer='David Robinson',
maintainer_email='dgrtwo@princeton.edu',
cmdclass={'test': TestCommand},
......@@ -36,6 +37,9 @@ setup(
'Intended Audience :: Developers',
'License :: OSI Approved :: GNU General Public License v3 (GPLv3)',
'Operating System :: OS Independent',
'Programming Language :: Python'
]
)
"Programming Language :: Python :: 2.6",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3.3",
"Programming Language :: Python :: 3.4",
]
)
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