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
import base64
import json
import datetime
import collections
import re
import logging
from query import QueryManager
API_ROOT = 'https://api.parse.com/1'
......@@ -30,125 +24,6 @@ REST_API_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):
ENDPOINT_ROOT = API_ROOT
......@@ -201,211 +76,6 @@ class ParseBase(object):
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):
'''Base exceptions from requests made to Parse'''
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
import collections
import copy
class QueryResourceDoesNotExist(Exception):
'''Query returned no results'''
pass
......@@ -41,20 +40,20 @@ class QueryManager(object):
def where(self, **kw):
return self.all().where(**kw)
def lt(self, name, value):
return self.all().lt(name=value)
def lt(self, **kw):
return self.all().lt(**kw)
def lte(self, name, value):
return self.all().lte(name=value)
def lte(self, **kw):
return self.all().lte(**kw)
def ne(self, name, value):
return self.all().ne(name=value)
def ne(self, **kw):
return self.all().ne(**kw)
def gt(self, name, value):
return self.all().gt(name=value)
def gt(self, **kw):
return self.all().gt(**kw)
def gte(self, name, value):
return self.all().gte(name=value)
def gte(self, **kw):
return self.all().gte(**kw)
def fetch(self):
return self.all().fetch()
......@@ -72,15 +71,15 @@ class QuerysetMetaclass(type):
for fname in ['lt', 'lte', 'gt', 'gte', 'ne']:
def func(self, fname=fname, **kwargs):
s = copy.deepcopy(self)
for name, value in kwargs.items():
s._where[name]['$' + fname] = value
for k, v in kwargs.items():
s._where[k]['$' + fname] = cls.convert_to_parse(v)
return s
setattr(cls, fname, func)
for fname in ['limit', 'skip']:
def func(self, value, fname=fname):
s = copy.deepcopy(self)
s._options[fname] = value
s._options[fname] = int(value)
return s
setattr(cls, fname, func)
......@@ -90,6 +89,11 @@ class QuerysetMetaclass(type):
class Queryset(object):
__metaclass__ = QuerysetMetaclass
@staticmethod
def convert_to_parse(value):
from datatypes import ParseType
return ParseType.convert_to_parse(value)
def __init__(self, manager):
self._manager = manager
self._where = collections.defaultdict(dict)
......@@ -112,7 +116,7 @@ class Queryset(object):
def eq(self, **kw):
for name, value in kw.items():
self._where[name] = value
self._where[name] = Queryset.convert_to_parse(value)
return self
def order_by(self, order, descending=False):
......
......@@ -6,22 +6,23 @@ Contains unit tests for the Python Parse REST API wrapper
"""
import os
import sys
import subprocess
import unittest
import urllib2
import datetime
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
try:
import settings_local
except ImportError:
raise ImportError('You must create a settings_local.py file with an ' +
'APPLICATION_ID, REST_API_KEY, and a MASTER_KEY ' +
'to run tests.')
sys.exit('You must create a settings_local.py file with APPLICATION_ID, ' \
'REST_API_KEY, MASTER_KEY variables set')
parse_rest.APPLICATION_ID = getattr(settings_local, 'APPLICATION_ID', '')
parse_rest.REST_API_KEY = getattr(settings_local, 'REST_API_KEY', '')
......@@ -86,14 +87,20 @@ class TestObject(unittest.TestCase):
def testCanInstantiateParseType(self):
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):
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_(GameScore.Query.where(
objectId=self.score.objectId).exists(),
self.assert_(GameScore.Query.where(objectId=object_id).exists(),
'Can not create object')
def testCanUpdateExistingObject(self):
......@@ -142,8 +149,9 @@ class TestQuery(unittest.TestCase):
for s in GameScore.Query.all():
s.delete()
self.scores = [GameScore(score=s, player_name='John Doe')
for s in range(1, 6)]
self.scores = [
GameScore(score=s, player_name='John Doe') for s in range(1, 6)
]
for s in self.scores:
s.save()
......@@ -166,9 +174,17 @@ class TestQuery(unittest.TestCase):
# test the two exceptions get can raise
self.assertRaises(query.QueryResourceDoesNotExist,
GameScore.Query.all().gt(score=20).get)
GameScore.Query.gt(score=20).get)
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):
"""test comparison operators- gt, gte, lt, lte, ne"""
......@@ -215,6 +231,13 @@ class TestQuery(unittest.TestCase):
scores_skip_3 = list(GameScore.Query.all().skip(3))
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):
"""delete all GameScore objects"""
for s in GameScore.Query.all():
......@@ -225,6 +248,7 @@ class TestFunction(unittest.TestCase):
def setUp(self):
"""create and deploy cloud functions"""
original_dir = os.getcwd()
cloud_function_dir = os.path.join(os.path.split(__file__)[0],
"cloudcode")
os.chdir(cloud_function_dir)
......@@ -234,9 +258,10 @@ class TestFunction(unittest.TestCase):
settings_local.MASTER_KEY))
try:
subprocess.call(["parse", "deploy"])
except OSError:
raise OSError("parse command line tool must be installed " +
"(see https://www.parse.com/docs/cloud_code_guide)")
except OSError, why:
print "parse command line tool must be installed " \
"(see https://www.parse.com/docs/cloud_code_guide)"
self.skipTest(why)
os.chdir(original_dir)
def tearDown(self):
......@@ -246,7 +271,8 @@ class TestFunction(unittest.TestCase):
def test_simple_functions(self):
"""test hello world and averageStars functions"""
# test the hello function- takes no arguments
hello_world_func = parse_rest.Function("hello")
hello_world_func = Function("hello")
ret = hello_world_func()
self.assertEqual(ret["result"], u"Hello world!")
......@@ -257,7 +283,7 @@ class TestFunction(unittest.TestCase):
r2 = Review(movie="The Matrix", stars=4, comment="It's OK.")
r2.save()
star_func = parse_rest.Function("averageStars")
star_func = Function("averageStars")
ret = star_func(movie="The Matrix")
self.assertAlmostEqual(ret["result"], 4.5)
......@@ -268,9 +294,9 @@ class TestUser(unittest.TestCase):
def _get_user(self):
try:
user = parse_rest.User.signup(self.username, self.password)
user = User.signup(self.username, self.password)
except:
user = parse_rest.User.Query.get(username=self.username)
user = User.Query.get(username=self.username)
return user
def _destroy_user(self):
......@@ -278,8 +304,8 @@ class TestUser(unittest.TestCase):
user and user.delete()
def _get_logged_user(self):
if parse_rest.User.Query.where(username=self.username).exists():
return parse_rest.User.login(self.username, self.password)
if User.Query.where(username=self.username).exists():
return User.login(self.username, self.password)
else:
return self._get_user()
......@@ -288,7 +314,7 @@ class TestUser(unittest.TestCase):
self.password = TestUser.PASSWORD
try:
u = parse_rest.User.login(self.USERNAME, self.PASSWORD)
u = User.login(self.USERNAME, self.PASSWORD)
except parse_rest.ResourceRequestNotFound as e:
# if the user doesn't exist, that's fine
return
......@@ -299,12 +325,12 @@ class TestUser(unittest.TestCase):
def testCanSignUp(self):
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)
def testCanLogin(self):
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')
def testCanUpdate(self):
......@@ -315,7 +341,7 @@ class TestUser(unittest.TestCase):
user.phone = phone_number
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')
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