Commit ff037984 by Raphael Lullis

breaking apart files from __init__. Fixed bug on querying by date. Added some tests.

parent 9fd21f16
...@@ -16,12 +16,6 @@ import urllib2 ...@@ -16,12 +16,6 @@ import urllib2
import base64 import base64
import json import json
import datetime import datetime
import collections
import re
import logging
from query import QueryManager
API_ROOT = 'https://api.parse.com/1' API_ROOT = 'https://api.parse.com/1'
...@@ -30,125 +24,6 @@ REST_API_KEY = '' ...@@ -30,125 +24,6 @@ REST_API_KEY = ''
MASTER_KEY = '' MASTER_KEY = ''
class ParseType(object):
@staticmethod
def convert(parse_data):
is_parse_type = isinstance(parse_data, dict) and '__type' in parse_data
if not is_parse_type:
return parse_data
parse_type = parse_data['__type']
native = {
'Pointer': Pointer,
'Date': Date,
'Bytes': Binary,
'GeoPoint': GeoPoint,
'File': File,
'Relation': Relation
}.get(parse_type)
return native and native.from_native(**parse_data) or parse_data
@classmethod
def from_native(cls, **kw):
return cls(**kw)
def _to_native(self):
return self._value
class Pointer(ParseType):
@classmethod
def from_native(cls, **kw):
klass = Object.factory(kw.get('className'))
return klass.retrieve(kw.get('objectId'))
def _to_native(self):
return {
'__type': 'Pointer',
'className': self.__class__.__name__,
'objectId': self.objectId
}
class Relation(ParseType):
@classmethod
def from_native(cls, **kw):
pass
class Date(ParseType):
FORMAT = '%Y-%m-%dT%H:%M:%S.%f%Z'
@classmethod
def from_native(cls, **kw):
return cls(self._from_str(kw.get('iso', '')))
@staticmethod
def _from_str(date_str):
"""turn a ISO 8601 string into a datetime object"""
return datetime.datetime.strptime(date_str[:-1] + 'UTC', Date.FORMAT)
def __init__(self, date):
"""Can be initialized either with a string or a datetime"""
if isinstance(date, datetime.datetime):
self._date = date
elif isinstance(date, unicode):
self._date = Date._from_str(date)
def _to_native(self):
return {
'__type': 'Date', 'iso': self._date.isoformat()
}
class Binary(ParseType):
@classmethod
def from_native(cls, **kw):
return cls(kw.get('base64', ''))
def __init__(self, encoded_string):
self._encoded = encoded_string
self._decoded = str(base64.b64decode(self._encoded))
def _to_native(self):
return {'__type': 'Bytes', 'base64': self._encoded}
class GeoPoint(ParseType):
@classmethod
def from_native(cls, **kw):
return cls(kw.get('latitude'), kw.get('longitude'))
def __init__(self, latitude, longitude):
self.latitude = latitude
self.longitude = longitude
def _to_native(self):
return {
'__type': 'GeoPoint',
'latitude': self.latitude,
'longitude': self.longitude
}
class File(ParseType):
@classmethod
def from_native(cls, **kw):
return cls(kw.get('url'), kw.get('name'))
def __init__(self, url, name):
request = urllib2.Request(url)
self._name = name
self._url = url
self._file = urllib2.urlopen(request)
class ParseBase(object): class ParseBase(object):
ENDPOINT_ROOT = API_ROOT ENDPOINT_ROOT = API_ROOT
...@@ -201,211 +76,6 @@ class ParseBase(object): ...@@ -201,211 +76,6 @@ class ParseBase(object):
return cls.execute(uri, 'DELETE', **kw) return cls.execute(uri, 'DELETE', **kw)
class Function(ParseBase):
ENDPOINT_ROOT = "/".join((API_ROOT, "functions"))
def __init__(self, name):
self.name = name
def __call__(self, **kwargs):
return self.POST("/" + self.name, **kwargs)
class ParseResource(ParseBase, Pointer):
PROTECTED_ATTRIBUTES = ['objectId', 'createdAt', 'updatedAt']
@classmethod
def retrieve(cls, resource_id):
return cls(**cls.GET('/' + resource_id))
def __init__(self, **kw):
for key, value in kw.items():
setattr(self, key, ParseType.convert(value))
def _to_dict(self):
# serializes all attributes that need to be persisted on Parse
protected_attributes = self.__class__.PROTECTED_ATTRIBUTES
is_protected = lambda a: a in protected_attributes or a.startswith('_')
return dict([(k, v._to_native() if isinstance(v, ParseType) else v)
for k, v in self.__dict__.items() if not is_protected(k)
])
def _get_object_id(self):
return getattr(self, '_object_id', None)
def _set_object_id(self, value):
if hasattr(self, '_object_id'):
raise ValueError('Can not re-set object id')
self._object_id = value
def _get_updated_datetime(self):
return getattr(self, '_updated_at', None) and self._updated_at._date
def _set_updated_datetime(self, value):
self._updated_at = Date(value)
def _get_created_datetime(self):
return getattr(self, '_created_at', None) and self._created_at._date
def _set_created_datetime(self, value):
self._created_at = Date(value)
def save(self):
if self.objectId:
self._update()
else:
self._create()
def _create(self):
# URL: /1/classes/<className>
# HTTP Verb: POST
uri = self.__class__.ENDPOINT_ROOT
response_dict = self.__class__.POST(uri, **self._to_dict())
self.createdAt = self.updatedAt = response_dict['createdAt']
self.objectId = response_dict['objectId']
def _update(self):
# URL: /1/classes/<className>/<objectId>
# HTTP Verb: PUT
response = self.__class__.PUT(self._absolute_url, **self._to_dict())
self.updatedAt = response['updatedAt']
def delete(self):
self.__class__.DELETE(self._absolute_url)
self.__dict__ = {}
_absolute_url = property(
lambda self: '/'.join([self.__class__.ENDPOINT_ROOT, self.objectId])
)
objectId = property(_get_object_id, _set_object_id)
createdAt = property(_get_created_datetime, _set_created_datetime)
updatedAt = property(_get_updated_datetime, _set_updated_datetime)
def __repr__(self):
return '<%s:%s>' % (unicode(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)
return cls
class Object(ParseResource):
__metaclass__ = ObjectMetaclass
ENDPOINT_ROOT = '/'.join([API_ROOT, 'classes'])
@classmethod
def factory(cls, class_name):
class DerivedClass(cls):
pass
DerivedClass.__name__ = str(class_name)
DerivedClass.set_endpoint_root()
return DerivedClass
@classmethod
def set_endpoint_root(cls):
root = '/'.join([API_ROOT, 'classes', cls.__name__])
if cls.ENDPOINT_ROOT != root:
cls.ENDPOINT_ROOT = root
return cls.ENDPOINT_ROOT
@property
def _absolute_url(self):
if not self.objectId:
return None
return '/'.join([self.__class__.ENDPOINT_ROOT, self.objectId])
def increment(self, key, amount=1):
"""
Increment one value in the object. Note that this happens immediately:
it does not wait for save() to be called
"""
payload = {
key: {
'__op': 'Increment',
'amount': amount
}
}
self.__class__.PUT(self._absolute_url, **payload)
self.__dict__[key] += amount
def login_required(func):
'''decorator describing User methods that need to be logged in'''
def ret(obj, *args, **kw):
if not hasattr(obj, 'sessionToken'):
message = '%s requires a logged-in session' % func.__name__
raise ResourceRequestLoginRequired(message)
func(obj, *args, **kw)
return ret
class User(ParseResource):
'''
A User is like a regular Parse object (can be modified and saved) but
it requires additional methods and functionality
'''
ENDPOINT_ROOT = '/'.join([API_ROOT, 'users'])
PROTECTED_ATTRIBUTES = ParseResource.PROTECTED_ATTRIBUTES + [
'username', 'sessionToken']
def is_authenticated(self):
return getattr(self, 'sessionToken', None) or False
@login_required
def save(self):
session_header = {'X-Parse-Session-Token': self.sessionToken}
return self.__class__.PUT(
self._absolute_url,
extra_headers=session_header,
**self._to_dict())
@login_required
def delete(self):
session_header = {'X-Parse-Session-Token': self.sessionToken}
return self.DELETE(self._absolute_url, extra_headers=session_header)
@staticmethod
def signup(username, password, **kw):
return User(**User.POST('', username=username, password=password,
**kw))
@staticmethod
def login(username, password):
login_url = '/'.join([API_ROOT, 'login'])
return User(**User.GET(login_url, username=username,
password=password))
@staticmethod
def request_password_reset(email):
'''Trigger Parse's Password Process. Return True/False
indicate success/failure on the request'''
url = '/'.join([API_ROOT, 'requestPasswordReset'])
try:
User.POST(url, email=email)
return True
except Exception, why:
return False
def __repr__(self):
return '<User:%s (Id %s)>' % (self.username, self.objectId)
User.Query = QueryManager(User)
class ParseError(Exception): class ParseError(Exception):
'''Base exceptions from requests made to Parse''' '''Base exceptions from requests made to Parse'''
pass pass
......
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import base64
import datetime
from __init__ import ParseBase, API_ROOT
from query import QueryManager
class ParseType(object):
@staticmethod
def convert_from_parse(parse_data):
is_parse_type = isinstance(parse_data, dict) and '__type' in parse_data
if not is_parse_type:
return parse_data
parse_type = parse_data['__type']
native = {
'Pointer': Pointer,
'Date': Date,
'Bytes': Binary,
'GeoPoint': GeoPoint,
'File': File,
'Relation': Relation
}.get(parse_type)
return native and native.from_native(**parse_data) or parse_data
@staticmethod
def convert_to_parse(python_object):
if isinstance(python_object, ParseType):
return python_object._to_native()
# This seems rather pointless now that we are only working
# with dates. Perhaps when files/images start to get a little
# more attention, we will have more things here.
python_type = type(python_object)
klass = {
datetime.datetime: Date
}.get(python_type)
return klass(python_object)._to_native() if klass else python_object
@classmethod
def from_native(cls, **kw):
return cls(**kw)
def _to_native(self):
return self._value
class Pointer(ParseType):
@classmethod
def from_native(cls, **kw):
klass = Object.factory(kw.get('className'))
return klass.retrieve(kw.get('objectId'))
def _to_native(self):
return {
'__type': 'Pointer',
'className': self.__class__.__name__,
'objectId': self.objectId
}
class Relation(ParseType):
@classmethod
def from_native(cls, **kw):
pass
class Date(ParseType):
FORMAT = '%Y-%m-%dT%H:%M:%S.%f%Z'
@classmethod
def from_native(cls, **kw):
return cls._from_str(kw.get('iso', ''))
@staticmethod
def _from_str(date_str):
"""turn a ISO 8601 string into a datetime object"""
return datetime.datetime.strptime(date_str[:-1] + 'UTC', Date.FORMAT)
def __init__(self, date):
"""Can be initialized either with a string or a datetime"""
if isinstance(date, datetime.datetime):
self._date = date
elif isinstance(date, unicode):
self._date = Date._from_str(date)
def _to_native(self):
return {
'__type': 'Date', 'iso': self._date.isoformat()
}
class Binary(ParseType):
@classmethod
def from_native(cls, **kw):
return cls(kw.get('base64', ''))
def __init__(self, encoded_string):
self._encoded = encoded_string
self._decoded = str(base64.b64decode(self._encoded))
def _to_native(self):
return {'__type': 'Bytes', 'base64': self._encoded}
class GeoPoint(ParseType):
@classmethod
def from_native(cls, **kw):
return cls(kw.get('latitude'), kw.get('longitude'))
def __init__(self, latitude, longitude):
self.latitude = latitude
self.longitude = longitude
def _to_native(self):
return {
'__type': 'GeoPoint',
'latitude': self.latitude,
'longitude': self.longitude
}
class File(ParseType):
@classmethod
def from_native(cls, **kw):
return cls(kw.get('url'), kw.get('name'))
def __init__(self, url, name):
request = urllib2.Request(url)
self._name = name
self._url = url
self._file = urllib2.urlopen(request)
def _to_native(self):
return {
'__type': 'File',
'name': self._name
}
class ParseResource(ParseBase, Pointer):
PROTECTED_ATTRIBUTES = ['objectId', 'createdAt', 'updatedAt']
@classmethod
def retrieve(cls, resource_id):
return cls(**cls.GET('/' + resource_id))
def __init__(self, **kw):
for key, value in kw.items():
setattr(self, key, ParseType.convert_from_parse(value))
def _to_dict(self):
# serializes all attributes that need to be persisted on Parse
protected_attributes = self.__class__.PROTECTED_ATTRIBUTES
is_protected = lambda a: a in protected_attributes or a.startswith('_')
return dict([(k, ParseType.convert_to_parse(v))
for k, v in self.__dict__.items() if not is_protected(k)
])
def _get_object_id(self):
return getattr(self, '_object_id', None)
def _set_object_id(self, value):
if hasattr(self, '_object_id'):
raise ValueError('Can not re-set object id')
self._object_id = value
def _get_updated_datetime(self):
return getattr(self, '_updated_at', None) and self._updated_at._date
def _set_updated_datetime(self, value):
self._updated_at = Date(value)
def _get_created_datetime(self):
return getattr(self, '_created_at', None) and self._created_at._date
def _set_created_datetime(self, value):
self._created_at = Date(value)
def save(self):
if self.objectId:
self._update()
else:
self._create()
def _create(self):
# URL: /1/classes/<className>
# HTTP Verb: POST
uri = self.__class__.ENDPOINT_ROOT
response_dict = self.__class__.POST(uri, **self._to_dict())
self.createdAt = self.updatedAt = response_dict['createdAt']
self.objectId = response_dict['objectId']
def _update(self):
# URL: /1/classes/<className>/<objectId>
# HTTP Verb: PUT
response = self.__class__.PUT(self._absolute_url, **self._to_dict())
self.updatedAt = response['updatedAt']
def delete(self):
self.__class__.DELETE(self._absolute_url)
self.__dict__ = {}
_absolute_url = property(
lambda self: '/'.join([self.__class__.ENDPOINT_ROOT, self.objectId])
)
objectId = property(_get_object_id, _set_object_id)
createdAt = property(_get_created_datetime, _set_created_datetime)
updatedAt = property(_get_updated_datetime, _set_updated_datetime)
def __repr__(self):
return '<%s:%s>' % (unicode(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)
return cls
class Object(ParseResource):
__metaclass__ = ObjectMetaclass
ENDPOINT_ROOT = '/'.join([API_ROOT, 'classes'])
@classmethod
def factory(cls, class_name):
class DerivedClass(cls):
pass
DerivedClass.__name__ = str(class_name)
DerivedClass.set_endpoint_root()
return DerivedClass
@classmethod
def set_endpoint_root(cls):
root = '/'.join([API_ROOT, 'classes', cls.__name__])
if cls.ENDPOINT_ROOT != root:
cls.ENDPOINT_ROOT = root
return cls.ENDPOINT_ROOT
@property
def _absolute_url(self):
if not self.objectId:
return None
return '/'.join([self.__class__.ENDPOINT_ROOT, self.objectId])
def increment(self, key, amount=1):
"""
Increment one value in the object. Note that this happens immediately:
it does not wait for save() to be called
"""
payload = {
key: {
'__op': 'Increment',
'amount': amount
}
}
self.__class__.PUT(self._absolute_url, **payload)
self.__dict__[key] += amount
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# 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 __init__ import ParseBase, API_ROOT
class Function(ParseBase):
ENDPOINT_ROOT = '/'.join((API_ROOT, 'functions'))
def __init__(self, name):
self.name = name
def __call__(self, **kwargs):
return self.POST('/' + self.name, **kwargs)
...@@ -15,7 +15,6 @@ import json ...@@ -15,7 +15,6 @@ import json
import collections import collections
import copy import copy
class QueryResourceDoesNotExist(Exception): class QueryResourceDoesNotExist(Exception):
'''Query returned no results''' '''Query returned no results'''
pass pass
...@@ -41,20 +40,20 @@ class QueryManager(object): ...@@ -41,20 +40,20 @@ class QueryManager(object):
def where(self, **kw): def where(self, **kw):
return self.all().where(**kw) return self.all().where(**kw)
def lt(self, name, value): def lt(self, **kw):
return self.all().lt(name=value) return self.all().lt(**kw)
def lte(self, name, value): def lte(self, **kw):
return self.all().lte(name=value) return self.all().lte(**kw)
def ne(self, name, value): def ne(self, **kw):
return self.all().ne(name=value) return self.all().ne(**kw)
def gt(self, name, value): def gt(self, **kw):
return self.all().gt(name=value) return self.all().gt(**kw)
def gte(self, name, value): def gte(self, **kw):
return self.all().gte(name=value) return self.all().gte(**kw)
def fetch(self): def fetch(self):
return self.all().fetch() return self.all().fetch()
...@@ -72,15 +71,15 @@ class QuerysetMetaclass(type): ...@@ -72,15 +71,15 @@ class QuerysetMetaclass(type):
for fname in ['lt', 'lte', 'gt', 'gte', 'ne']: for fname in ['lt', 'lte', 'gt', 'gte', 'ne']:
def func(self, fname=fname, **kwargs): def func(self, fname=fname, **kwargs):
s = copy.deepcopy(self) s = copy.deepcopy(self)
for name, value in kwargs.items(): for k, v in kwargs.items():
s._where[name]['$' + fname] = value s._where[k]['$' + fname] = cls.convert_to_parse(v)
return s return s
setattr(cls, fname, func) setattr(cls, fname, func)
for fname in ['limit', 'skip']: for fname in ['limit', 'skip']:
def func(self, value, fname=fname): def func(self, value, fname=fname):
s = copy.deepcopy(self) s = copy.deepcopy(self)
s._options[fname] = value s._options[fname] = int(value)
return s return s
setattr(cls, fname, func) setattr(cls, fname, func)
...@@ -90,6 +89,11 @@ class QuerysetMetaclass(type): ...@@ -90,6 +89,11 @@ class QuerysetMetaclass(type):
class Queryset(object): class Queryset(object):
__metaclass__ = QuerysetMetaclass __metaclass__ = QuerysetMetaclass
@staticmethod
def convert_to_parse(value):
from datatypes import ParseType
return ParseType.convert_to_parse(value)
def __init__(self, manager): def __init__(self, manager):
self._manager = manager self._manager = manager
self._where = collections.defaultdict(dict) self._where = collections.defaultdict(dict)
...@@ -112,7 +116,7 @@ class Queryset(object): ...@@ -112,7 +116,7 @@ class Queryset(object):
def eq(self, **kw): def eq(self, **kw):
for name, value in kw.items(): for name, value in kw.items():
self._where[name] = value self._where[name] = Queryset.convert_to_parse(value)
return self return self
def order_by(self, order, descending=False): def order_by(self, order, descending=False):
......
...@@ -6,22 +6,23 @@ Contains unit tests for the Python Parse REST API wrapper ...@@ -6,22 +6,23 @@ Contains unit tests for the Python Parse REST API wrapper
""" """
import os import os
import sys
import subprocess import subprocess
import unittest import unittest
import urllib2 import urllib2
import datetime import datetime
import __init__ as parse_rest import __init__ as parse_rest
from __init__ import GeoPoint, Object from datatypes import GeoPoint, Object
from function import Function
from user import User
import query import query
try: try:
import settings_local import settings_local
except ImportError: except ImportError:
raise ImportError('You must create a settings_local.py file with an ' + sys.exit('You must create a settings_local.py file with APPLICATION_ID, ' \
'APPLICATION_ID, REST_API_KEY, and a MASTER_KEY ' + 'REST_API_KEY, MASTER_KEY variables set')
'to run tests.')
parse_rest.APPLICATION_ID = getattr(settings_local, 'APPLICATION_ID', '') parse_rest.APPLICATION_ID = getattr(settings_local, 'APPLICATION_ID', '')
parse_rest.REST_API_KEY = getattr(settings_local, 'REST_API_KEY', '') parse_rest.REST_API_KEY = getattr(settings_local, 'REST_API_KEY', '')
...@@ -86,15 +87,21 @@ class TestObject(unittest.TestCase): ...@@ -86,15 +87,21 @@ class TestObject(unittest.TestCase):
def testCanInstantiateParseType(self): def testCanInstantiateParseType(self):
self.assert_(self.sao_paulo.location.latitude == -23.5) self.assert_(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')
def testCanCreateNewObject(self): def testCanCreateNewObject(self):
self.score.save() self.score.save()
self.assert_(self.score.objectId is not None, 'Can not create object') object_id = self.score.objectId
self.assert_(type(self.score.objectId) == unicode) 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_(type(self.score.createdAt) == datetime.datetime)
self.assert_(GameScore.Query.where( self.assert_(GameScore.Query.where(objectId=object_id).exists(),
objectId=self.score.objectId).exists(), 'Can not create object')
'Can not create object')
def testCanUpdateExistingObject(self): def testCanUpdateExistingObject(self):
self.sao_paulo.save() self.sao_paulo.save()
...@@ -142,8 +149,9 @@ class TestQuery(unittest.TestCase): ...@@ -142,8 +149,9 @@ class TestQuery(unittest.TestCase):
for s in GameScore.Query.all(): for s in GameScore.Query.all():
s.delete() s.delete()
self.scores = [GameScore(score=s, player_name='John Doe') self.scores = [
for s in range(1, 6)] GameScore(score=s, player_name='John Doe') for s in range(1, 6)
]
for s in self.scores: for s in self.scores:
s.save() s.save()
...@@ -166,9 +174,17 @@ class TestQuery(unittest.TestCase): ...@@ -166,9 +174,17 @@ class TestQuery(unittest.TestCase):
# test the two exceptions get can raise # test the two exceptions get can raise
self.assertRaises(query.QueryResourceDoesNotExist, self.assertRaises(query.QueryResourceDoesNotExist,
GameScore.Query.all().gt(score=20).get) GameScore.Query.gt(score=20).get)
self.assertRaises(query.QueryResourceMultipleResultsReturned, self.assertRaises(query.QueryResourceMultipleResultsReturned,
GameScore.Query.all().gt(score=3).get) GameScore.Query.gt(score=3).get)
def testCanQueryDates(self):
last_week = datetime.datetime.now() - datetime.timedelta(days=7)
score = GameScore(name='test', last_played=last_week)
score.save()
self.assert_(GameScore.Query.where(last_played=last_week).exists(),
'Could not run query with dates')
def testComparisons(self): def testComparisons(self):
"""test comparison operators- gt, gte, lt, lte, ne""" """test comparison operators- gt, gte, lt, lte, ne"""
...@@ -215,6 +231,13 @@ class TestQuery(unittest.TestCase): ...@@ -215,6 +231,13 @@ class TestQuery(unittest.TestCase):
scores_skip_3 = list(GameScore.Query.all().skip(3)) scores_skip_3 = list(GameScore.Query.all().skip(3))
self.assert_(len(scores_skip_3) == 2, "Skip did not return 2 items") self.assert_(len(scores_skip_3) == 2, "Skip did not return 2 items")
def testCanCompareDateInequality(self):
today = datetime.datetime.today()
tomorrow = today + datetime.timedelta(days=1)
yesterday = today - datetime.timedelta(days=1)
self.assert_(GameScore.Query.lte(createdAt=tomorrow).count() == 5,
'Could not make inequality comparison with dates')
def tearDown(self): def tearDown(self):
"""delete all GameScore objects""" """delete all GameScore objects"""
for s in GameScore.Query.all(): for s in GameScore.Query.all():
...@@ -225,6 +248,7 @@ class TestFunction(unittest.TestCase): ...@@ -225,6 +248,7 @@ class TestFunction(unittest.TestCase):
def setUp(self): def setUp(self):
"""create and deploy cloud functions""" """create and deploy cloud functions"""
original_dir = os.getcwd() original_dir = os.getcwd()
cloud_function_dir = os.path.join(os.path.split(__file__)[0], cloud_function_dir = os.path.join(os.path.split(__file__)[0],
"cloudcode") "cloudcode")
os.chdir(cloud_function_dir) os.chdir(cloud_function_dir)
...@@ -234,9 +258,10 @@ class TestFunction(unittest.TestCase): ...@@ -234,9 +258,10 @@ class TestFunction(unittest.TestCase):
settings_local.MASTER_KEY)) settings_local.MASTER_KEY))
try: try:
subprocess.call(["parse", "deploy"]) subprocess.call(["parse", "deploy"])
except OSError: except OSError, why:
raise OSError("parse command line tool must be installed " + print "parse command line tool must be installed " \
"(see https://www.parse.com/docs/cloud_code_guide)") "(see https://www.parse.com/docs/cloud_code_guide)"
self.skipTest(why)
os.chdir(original_dir) os.chdir(original_dir)
def tearDown(self): def tearDown(self):
...@@ -246,7 +271,8 @@ class TestFunction(unittest.TestCase): ...@@ -246,7 +271,8 @@ class TestFunction(unittest.TestCase):
def test_simple_functions(self): def test_simple_functions(self):
"""test hello world and averageStars functions""" """test hello world and averageStars functions"""
# test the hello function- takes no arguments # test the hello function- takes no arguments
hello_world_func = parse_rest.Function("hello")
hello_world_func = Function("hello")
ret = hello_world_func() ret = hello_world_func()
self.assertEqual(ret["result"], u"Hello world!") self.assertEqual(ret["result"], u"Hello world!")
...@@ -257,7 +283,7 @@ class TestFunction(unittest.TestCase): ...@@ -257,7 +283,7 @@ class TestFunction(unittest.TestCase):
r2 = Review(movie="The Matrix", stars=4, comment="It's OK.") r2 = Review(movie="The Matrix", stars=4, comment="It's OK.")
r2.save() r2.save()
star_func = parse_rest.Function("averageStars") star_func = Function("averageStars")
ret = star_func(movie="The Matrix") ret = star_func(movie="The Matrix")
self.assertAlmostEqual(ret["result"], 4.5) self.assertAlmostEqual(ret["result"], 4.5)
...@@ -268,9 +294,9 @@ class TestUser(unittest.TestCase): ...@@ -268,9 +294,9 @@ class TestUser(unittest.TestCase):
def _get_user(self): def _get_user(self):
try: try:
user = parse_rest.User.signup(self.username, self.password) user = User.signup(self.username, self.password)
except: except:
user = parse_rest.User.Query.get(username=self.username) user = User.Query.get(username=self.username)
return user return user
def _destroy_user(self): def _destroy_user(self):
...@@ -278,8 +304,8 @@ class TestUser(unittest.TestCase): ...@@ -278,8 +304,8 @@ class TestUser(unittest.TestCase):
user and user.delete() user and user.delete()
def _get_logged_user(self): def _get_logged_user(self):
if parse_rest.User.Query.where(username=self.username).exists(): if User.Query.where(username=self.username).exists():
return parse_rest.User.login(self.username, self.password) return User.login(self.username, self.password)
else: else:
return self._get_user() return self._get_user()
...@@ -288,7 +314,7 @@ class TestUser(unittest.TestCase): ...@@ -288,7 +314,7 @@ class TestUser(unittest.TestCase):
self.password = TestUser.PASSWORD self.password = TestUser.PASSWORD
try: try:
u = parse_rest.User.login(self.USERNAME, self.PASSWORD) u = User.login(self.USERNAME, self.PASSWORD)
except parse_rest.ResourceRequestNotFound as e: except parse_rest.ResourceRequestNotFound as e:
# if the user doesn't exist, that's fine # if the user doesn't exist, that's fine
return return
...@@ -299,12 +325,12 @@ class TestUser(unittest.TestCase): ...@@ -299,12 +325,12 @@ class TestUser(unittest.TestCase):
def testCanSignUp(self): def testCanSignUp(self):
self._destroy_user() self._destroy_user()
user = parse_rest.User.signup(self.username, self.password) user = User.signup(self.username, self.password)
self.assert_(user is not None) self.assert_(user is not None)
def testCanLogin(self): def testCanLogin(self):
self._get_user() # User should be created here. self._get_user() # User should be created here.
user = parse_rest.User.login(self.username, self.password) user = User.login(self.username, self.password)
self.assert_(user.is_authenticated(), 'Login failed') self.assert_(user.is_authenticated(), 'Login failed')
def testCanUpdate(self): def testCanUpdate(self):
...@@ -315,7 +341,7 @@ class TestUser(unittest.TestCase): ...@@ -315,7 +341,7 @@ class TestUser(unittest.TestCase):
user.phone = phone_number user.phone = phone_number
user.save() user.save()
self.assert_(parse_rest.User.Query.where(phone=phone_number).exists(), self.assert_(User.Query.where(phone=phone_number).exists(),
'Failed to update user data. New info not on Parse') 'Failed to update user data. New info not on Parse')
if __name__ == "__main__": if __name__ == "__main__":
......
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import base64
import datetime
from __init__ import API_ROOT
from datatypes import ParseResource
from query import QueryManager
def login_required(func):
'''decorator describing User methods that need to be logged in'''
def ret(obj, *args, **kw):
if not hasattr(obj, 'sessionToken'):
message = '%s requires a logged-in session' % func.__name__
raise ResourceRequestLoginRequired(message)
func(obj, *args, **kw)
return ret
class User(ParseResource):
'''
A User is like a regular Parse object (can be modified and saved) but
it requires additional methods and functionality
'''
ENDPOINT_ROOT = '/'.join([API_ROOT, 'users'])
PROTECTED_ATTRIBUTES = ParseResource.PROTECTED_ATTRIBUTES + [
'username', 'sessionToken']
def is_authenticated(self):
return getattr(self, 'sessionToken', None) or False
@login_required
def save(self):
session_header = {'X-Parse-Session-Token': self.sessionToken}
return self.__class__.PUT(
self._absolute_url,
extra_headers=session_header,
**self._to_dict())
@login_required
def delete(self):
session_header = {'X-Parse-Session-Token': self.sessionToken}
return self.DELETE(self._absolute_url, extra_headers=session_header)
@staticmethod
def signup(username, password, **kw):
return User(**User.POST('', username=username, password=password,
**kw))
@staticmethod
def login(username, password):
login_url = '/'.join([API_ROOT, 'login'])
return User(**User.GET(login_url, username=username,
password=password))
@staticmethod
def request_password_reset(email):
'''Trigger Parse's Password Process. Return True/False
indicate success/failure on the request'''
url = '/'.join([API_ROOT, 'requestPasswordReset'])
try:
User.POST(url, email=email)
return True
except Exception, why:
return False
def __repr__(self):
return '<User:%s (Id %s)>' % (self.username, self.objectId)
User.Query = QueryManager(User)
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