Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
P
ParsePy
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
OpenEdx
ParsePy
Commits
ffacfc4b
Commit
ffacfc4b
authored
Jul 19, 2014
by
David Robinson
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #48 from farin/master
Python3, tests for new features, small fix for __len__ and query copying
parents
cd9ef16a
25acbfb7
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
283 additions
and
295 deletions
+283
-295
README.mkd
+2
-3
parse_rest/connection.py
+15
-15
parse_rest/datatypes.py
+73
-99
parse_rest/installation.py
+3
-3
parse_rest/query.py
+41
-43
parse_rest/tests.py
+137
-124
parse_rest/user.py
+5
-5
setup.py
+7
-3
No files found.
README.mkd
View file @
ffacfc4b
...
@@ -52,10 +52,9 @@ in the app and may accidentally replace or change existing objects.
...
@@ -52,10 +52,9 @@ in the app and may accidentally replace or change existing objects.
*
install the
[
Parse CloudCode tool
](
https://www.parse.com/docs/cloud_code_guide
)
*
install the
[
Parse CloudCode tool
](
https://www.parse.com/docs/cloud_code_guide
)
You can then test the installation by running the following
in a Python prompt
:
You can then test the installation by running the following
command
:
from parse_rest import tests
python -m 'parse_rest.tests'
tests.run_tests()
Usage
Usage
...
...
parse_rest/connection.py
View file @
ffacfc4b
...
@@ -11,18 +11,13 @@
...
@@ -11,18 +11,13 @@
# 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/>.
try
:
from
six.moves.urllib.request
import
Request
,
urlopen
from
urllib2
import
Request
,
urlopen
,
HTTPError
from
six.moves.urllib.error
import
HTTPError
from
urllib
import
urlencode
from
six.moves.urllib.parse
import
urlencode
except
ImportError
:
# is Python3
from
urllib.request
import
Request
,
urlopen
from
urllib.error
import
HTTPError
from
urllib.parse
import
urlencode
import
json
import
json
import
core
from
parse_rest
import
core
API_ROOT
=
'https://api.parse.com/1'
API_ROOT
=
'https://api.parse.com/1'
ACCESS_KEYS
=
{}
ACCESS_KEYS
=
{}
...
@@ -60,8 +55,7 @@ class ParseBase(object):
...
@@ -60,8 +55,7 @@ class ParseBase(object):
command.
command.
"""
"""
if
batch
:
if
batch
:
ret
=
{
"method"
:
http_verb
,
ret
=
{
"method"
:
http_verb
,
"path"
:
uri
.
split
(
"parse.com"
,
1
)[
1
]}
"path"
:
uri
.
split
(
"parse.com"
)[
1
]}
if
kw
:
if
kw
:
ret
[
"body"
]
=
kw
ret
[
"body"
]
=
kw
return
ret
return
ret
...
@@ -79,6 +73,8 @@ class ParseBase(object):
...
@@ -79,6 +73,8 @@ class ParseBase(object):
if
http_verb
==
'GET'
and
data
:
if
http_verb
==
'GET'
and
data
:
url
+=
'?
%
s'
%
urlencode
(
kw
)
url
+=
'?
%
s'
%
urlencode
(
kw
)
data
=
None
data
=
None
else
:
data
=
data
.
encode
(
'utf-8'
)
request
=
Request
(
url
,
data
,
headers
)
request
=
Request
(
url
,
data
,
headers
)
request
.
add_header
(
'Content-type'
,
'application/json'
)
request
.
add_header
(
'Content-type'
,
'application/json'
)
...
@@ -101,7 +97,7 @@ class ParseBase(object):
...
@@ -101,7 +97,7 @@ class ParseBase(object):
}
.
get
(
e
.
code
,
core
.
ParseError
)
}
.
get
(
e
.
code
,
core
.
ParseError
)
raise
exc
(
e
.
read
())
raise
exc
(
e
.
read
())
return
json
.
loads
(
response
.
read
())
return
json
.
loads
(
response
.
read
()
.
decode
(
'utf-8'
)
)
@classmethod
@classmethod
def
GET
(
cls
,
uri
,
**
kw
):
def
GET
(
cls
,
uri
,
**
kw
):
...
@@ -129,7 +125,11 @@ class ParseBatcher(ParseBase):
...
@@ -129,7 +125,11 @@ class ParseBatcher(ParseBase):
Given a list of create, update or delete methods to call, call all
Given a list of create, update or delete methods to call, call all
of them in a single batch operation.
of them in a single batch operation.
"""
"""
queries
,
callbacks
=
zip
(
*
[
m
(
batch
=
True
)
for
m
in
methods
])
methods
=
list
(
methods
)
# methods can be iterator
if
not
methods
:
#accepts also empty list (or generator) - it allows call batch directly with query result (eventually empty)
return
queries
,
callbacks
=
list
(
zip
(
*
[
m
(
batch
=
True
)
for
m
in
methods
]))
# perform all the operations in one batch
# perform all the operations in one batch
responses
=
self
.
execute
(
""
,
"POST"
,
requests
=
queries
)
responses
=
self
.
execute
(
""
,
"POST"
,
requests
=
queries
)
# perform the callbacks with the response data (updating the existing
# perform the callbacks with the response data (updating the existing
...
@@ -139,8 +139,8 @@ class ParseBatcher(ParseBase):
...
@@ -139,8 +139,8 @@ class ParseBatcher(ParseBase):
def
batch_save
(
self
,
objects
):
def
batch_save
(
self
,
objects
):
"""save a list of objects in one operation"""
"""save a list of objects in one operation"""
self
.
batch
(
[
o
.
save
for
o
in
objects
]
)
self
.
batch
(
o
.
save
for
o
in
objects
)
def
batch_delete
(
self
,
objects
):
def
batch_delete
(
self
,
objects
):
"""delete a list of objects in one operation"""
"""delete a list of objects in one operation"""
self
.
batch
(
[
o
.
delete
for
o
in
objects
]
)
self
.
batch
(
o
.
delete
for
o
in
objects
)
parse_rest/datatypes.py
View file @
ffacfc4b
...
@@ -10,18 +10,29 @@
...
@@ -10,18 +10,29 @@
#
#
# 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/>.
from
__future__
import
unicode_literals
import
base64
import
base64
import
datetime
import
datetime
import
six
from
connection
import
API_ROOT
,
ParseBase
from
parse_rest.connection
import
API_ROOT
,
ParseBase
from
query
import
QueryManager
from
parse_rest.query
import
QueryManager
def
complex_type
(
name
=
None
):
'''Decorator for registering complex types'''
def
wrapped
(
cls
):
ParseType
.
type_mapping
[
name
or
cls
.
__name__
]
=
cls
return
cls
return
wrapped
class
ParseType
(
object
):
class
ParseType
(
object
):
type_mapping
=
{}
@staticmethod
@staticmethod
def
convert_from_parse
(
parse_data
,
class_name
):
def
convert_from_parse
(
parse_data
):
is_parse_type
=
isinstance
(
parse_data
,
dict
)
and
'__type'
in
parse_data
is_parse_type
=
isinstance
(
parse_data
,
dict
)
and
'__type'
in
parse_data
...
@@ -29,28 +40,8 @@ class ParseType(object):
...
@@ -29,28 +40,8 @@ class ParseType(object):
if
not
is_parse_type
:
if
not
is_parse_type
:
return
parse_data
return
parse_data
# determine just which kind of parse type this element is - ie: a built in parse type such as File, Pointer, User etc
native
=
ParseType
.
type_mapping
.
get
(
parse_data
[
'__type'
])
parse_type
=
parse_data
[
'__type'
]
return
native
.
from_native
(
**
parse_data
)
if
native
else
parse_data
# if its a pointer, we need to handle to ensure that we don't mishandle a circular reference
if
parse_type
==
"Pointer"
:
# grab the pointer object here
return
Pointer
.
from_native
(
class_name
,
**
parse_data
)
# embedded object by select_related
if
parse_type
==
"Object"
:
return
EmbeddedObject
.
from_native
(
class_name
,
**
parse_data
)
# now handle the other parse types accordingly
native
=
{
'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
@staticmethod
def
convert_to_parse
(
python_object
,
as_pointer
=
False
):
def
convert_to_parse
(
python_object
,
as_pointer
=
False
):
...
@@ -67,7 +58,7 @@ class ParseType(object):
...
@@ -67,7 +58,7 @@ class ParseType(object):
transformation_map
=
{
transformation_map
=
{
datetime
.
datetime
:
Date
,
datetime
.
datetime
:
Date
,
Object
:
Pointer
Object
:
Pointer
}
}
if
python_type
in
transformation_map
:
if
python_type
in
transformation_map
:
klass
=
transformation_map
.
get
(
python_type
)
klass
=
transformation_map
.
get
(
python_type
)
...
@@ -83,39 +74,20 @@ class ParseType(object):
...
@@ -83,39 +74,20 @@ class ParseType(object):
return
cls
(
**
kw
)
return
cls
(
**
kw
)
def
_to_native
(
self
):
def
_to_native
(
self
):
r
eturn
self
.
_value
r
aise
NotImplementedError
(
"_to_native must be overridden"
)
@complex_type
(
'Pointer'
)
class
Pointer
(
ParseType
):
class
Pointer
(
ParseType
):
@classmethod
@classmethod
def
_prevent_circular
(
cls
,
parent_class_name
,
objectData
):
def
from_native
(
cls
,
**
kw
):
# TODO this should be replaced with more clever checking, instead of simple class mathching original id should be compared
# create object with only objectId and unloaded flag. it is automatically loaded when any other field is accessed
# 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
():
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"
]
return
objectData
@classmethod
def
from_native
(
cls
,
parent_class_name
=
None
,
**
kw
):
# grab the object data manually here so we can manipulate it before passing back an actual object
klass
=
Object
.
factory
(
kw
.
get
(
'className'
))
klass
=
Object
.
factory
(
kw
.
get
(
'className'
))
objectData
=
klass
.
GET
(
"/"
+
kw
.
get
(
'objectId'
)
)
return
klass
(
objectId
=
kw
.
get
(
'objectId'
),
_is_loaded
=
False
)
# now lets check if we have circular references here
if
parent_class_name
:
objectData
=
cls
.
_prevent_circular
(
parent_class_name
,
objectData
)
# set a temporary flag that will remove the recursive pointer types etc
klass
=
Object
.
factory
(
kw
.
get
(
'className'
))
return
klass
(
**
objectData
)
def
__init__
(
self
,
obj
):
def
__init__
(
self
,
obj
):
self
.
_object
=
obj
self
.
_object
=
obj
def
_to_native
(
self
):
def
_to_native
(
self
):
...
@@ -123,24 +95,25 @@ class Pointer(ParseType):
...
@@ -123,24 +95,25 @@ class Pointer(ParseType):
'__type'
:
'Pointer'
,
'__type'
:
'Pointer'
,
'className'
:
self
.
_object
.
__class__
.
__name__
,
'className'
:
self
.
_object
.
__class__
.
__name__
,
'objectId'
:
self
.
_object
.
objectId
'objectId'
:
self
.
_object
.
objectId
}
}
@complex_type
(
'Object'
)
class
EmbeddedObject
(
ParseType
):
class
EmbeddedObject
(
ParseType
):
@classmethod
@classmethod
def
from_native
(
cls
,
parent_class_name
=
None
,
**
kw
):
def
from_native
(
cls
,
**
kw
):
if
parent_class_name
:
kw
=
Pointer
.
_prevent_circular
(
parent_class_name
,
kw
)
klass
=
Object
.
factory
(
kw
.
get
(
'className'
))
klass
=
Object
.
factory
(
kw
.
get
(
'className'
))
return
klass
(
**
kw
)
return
klass
(
**
kw
)
@complex_type
()
class
Relation
(
ParseType
):
class
Relation
(
ParseType
):
@classmethod
@classmethod
def
from_native
(
cls
,
**
kw
):
def
from_native
(
cls
,
**
kw
):
pass
pass
@complex_type
()
class
Date
(
ParseType
):
class
Date
(
ParseType
):
FORMAT
=
'
%
Y-
%
m-
%
dT
%
H:
%
M:
%
S.
%
f
%
Z'
FORMAT
=
'
%
Y-
%
m-
%
dT
%
H:
%
M:
%
S.
%
f
%
Z'
...
@@ -157,7 +130,7 @@ class Date(ParseType):
...
@@ -157,7 +130,7 @@ class Date(ParseType):
"""Can be initialized either with a string or a datetime"""
"""Can be initialized either with a string or a datetime"""
if
isinstance
(
date
,
datetime
.
datetime
):
if
isinstance
(
date
,
datetime
.
datetime
):
self
.
_date
=
date
self
.
_date
=
date
elif
isinstance
(
date
,
unicode
):
elif
isinstance
(
date
,
six
.
string_types
):
self
.
_date
=
Date
.
_from_str
(
date
)
self
.
_date
=
Date
.
_from_str
(
date
)
def
_to_native
(
self
):
def
_to_native
(
self
):
...
@@ -166,6 +139,7 @@ class Date(ParseType):
...
@@ -166,6 +139,7 @@ class Date(ParseType):
}
}
@complex_type
(
'Bytes'
)
class
Binary
(
ParseType
):
class
Binary
(
ParseType
):
@classmethod
@classmethod
...
@@ -180,6 +154,7 @@ class Binary(ParseType):
...
@@ -180,6 +154,7 @@ class Binary(ParseType):
return
{
'__type'
:
'Bytes'
,
'base64'
:
self
.
_encoded
}
return
{
'__type'
:
'Bytes'
,
'base64'
:
self
.
_encoded
}
@complex_type
()
class
GeoPoint
(
ParseType
):
class
GeoPoint
(
ParseType
):
@classmethod
@classmethod
...
@@ -198,6 +173,7 @@ class GeoPoint(ParseType):
...
@@ -198,6 +173,7 @@ class GeoPoint(ParseType):
}
}
@complex_type
()
class
File
(
ParseType
):
class
File
(
ParseType
):
@classmethod
@classmethod
...
@@ -232,14 +208,10 @@ class Function(ParseBase):
...
@@ -232,14 +208,10 @@ class Function(ParseBase):
return
self
.
POST
(
'/'
+
self
.
name
,
**
kwargs
)
return
self
.
POST
(
'/'
+
self
.
name
,
**
kwargs
)
class
ParseResource
(
ParseBase
,
Pointer
):
class
ParseResource
(
ParseBase
):
PROTECTED_ATTRIBUTES
=
[
'objectId'
,
'createdAt'
,
'updatedAt'
]
PROTECTED_ATTRIBUTES
=
[
'objectId'
,
'createdAt'
,
'updatedAt'
]
@classmethod
def
retrieve
(
cls
,
resource_id
):
return
cls
(
**
cls
.
GET
(
'/'
+
resource_id
))
@property
@property
def
_editable_attrs
(
self
):
def
_editable_attrs
(
self
):
protected_attrs
=
self
.
__class__
.
PROTECTED_ATTRIBUTES
protected_attrs
=
self
.
__class__
.
PROTECTED_ATTRIBUTES
...
@@ -247,20 +219,23 @@ class ParseResource(ParseBase, Pointer):
...
@@ -247,20 +219,23 @@ class ParseResource(ParseBase, Pointer):
return
dict
([(
k
,
v
)
for
k
,
v
in
self
.
__dict__
.
items
()
if
allowed
(
k
)])
return
dict
([(
k
,
v
)
for
k
,
v
in
self
.
__dict__
.
items
()
if
allowed
(
k
)])
def
__init__
(
self
,
**
kw
):
def
__init__
(
self
,
**
kw
):
self
.
objectId
=
None
self
.
_init_attrs
(
kw
)
def
__getattr__
(
self
,
attr
):
# if object is not loaded and attribute is missing, try to load it
if
not
self
.
__dict__
.
get
(
'_is_loaded'
,
True
):
del
self
.
_is_loaded
self
.
_init_attrs
(
self
.
GET
(
self
.
_absolute_url
))
return
object
.
__getattribute__
(
self
,
attr
)
#preserve default if attr not exists
for
key
,
value
in
kw
.
items
():
def
_init_attrs
(
self
,
args
):
setattr
(
self
,
key
,
ParseType
.
convert_from_parse
(
value
,
self
.
__class__
.
__name__
))
for
key
,
value
in
six
.
iteritems
(
args
):
setattr
(
self
,
key
,
ParseType
.
convert_from_parse
(
value
))
def
_to_native
(
self
):
def
_to_native
(
self
):
return
ParseType
.
convert_to_parse
(
self
)
return
ParseType
.
convert_to_parse
(
self
)
def
_get_object_id
(
self
):
return
self
.
__dict__
.
get
(
'_object_id'
)
def
_set_object_id
(
self
,
value
):
if
'_object_id'
in
self
.
__dict__
:
raise
ValueError
(
'Can not re-set object id'
)
self
.
_object_id
=
value
def
_get_updated_datetime
(
self
):
def
_get_updated_datetime
(
self
):
return
self
.
__dict__
.
get
(
'_updated_at'
)
and
self
.
_updated_at
.
_date
return
self
.
__dict__
.
get
(
'_updated_at'
)
and
self
.
_updated_at
.
_date
...
@@ -294,8 +269,7 @@ class ParseResource(ParseBase, Pointer):
...
@@ -294,8 +269,7 @@ class ParseResource(ParseBase, Pointer):
call_back
(
response
)
call_back
(
response
)
def
_update
(
self
,
batch
=
False
):
def
_update
(
self
,
batch
=
False
):
response
=
self
.
__class__
.
PUT
(
self
.
_absolute_url
,
batch
=
batch
,
response
=
self
.
__class__
.
PUT
(
self
.
_absolute_url
,
batch
=
batch
,
**
self
.
_to_native
())
**
self
.
_to_native
())
def
call_back
(
response_dict
):
def
call_back
(
response_dict
):
self
.
updatedAt
=
response_dict
[
'updatedAt'
]
self
.
updatedAt
=
response_dict
[
'updatedAt'
]
...
@@ -307,46 +281,48 @@ class ParseResource(ParseBase, Pointer):
...
@@ -307,46 +281,48 @@ class ParseResource(ParseBase, Pointer):
def
delete
(
self
,
batch
=
False
):
def
delete
(
self
,
batch
=
False
):
response
=
self
.
__class__
.
DELETE
(
self
.
_absolute_url
,
batch
=
batch
)
response
=
self
.
__class__
.
DELETE
(
self
.
_absolute_url
,
batch
=
batch
)
def
call_back
(
response_dict
):
self
.
__dict__
=
{}
if
batch
:
if
batch
:
return
response
,
call_back
return
response
,
lambda
response_dict
:
None
else
:
call_back
(
response
)
_absolute_url
=
property
(
_absolute_url
=
property
(
lambda
self
:
'/'
.
join
([
self
.
__class__
.
ENDPOINT_ROOT
,
self
.
objectId
]))
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
)
createdAt
=
property
(
_get_created_datetime
,
_set_created_datetime
)
updatedAt
=
property
(
_get_updated_datetime
,
_set_updated_datetime
)
updatedAt
=
property
(
_get_updated_datetime
,
_set_updated_datetime
)
def
__repr__
(
self
):
def
__repr__
(
self
):
return
'<
%
s:
%
s>'
%
(
unicode
(
self
.
__class__
.
__name__
)
,
self
.
objectId
)
return
'<
%
s:
%
s>'
%
(
self
.
__class__
.
__name__
,
self
.
objectId
)
class
ObjectMetaclass
(
type
):
class
ObjectMetaclass
(
type
):
def
__new__
(
cls
,
name
,
bases
,
dct
):
def
__new__
(
mcs
,
name
,
bases
,
dct
):
cls
=
super
(
ObjectMetaclass
,
cls
)
.
__new__
(
cls
,
name
,
bases
,
dct
)
cls
=
super
(
ObjectMetaclass
,
mcs
)
.
__new__
(
mcs
,
name
,
bases
,
dct
)
cls
.
set_endpoint_root
()
# attr check must be here because of specific six.with_metaclass implemetantion where metaclass is used also for
cls
.
Query
=
QueryManager
(
cls
)
# 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
return
cls
class
Object
(
ParseResource
):
class
Object
(
six
.
with_metaclass
(
ObjectMetaclass
,
ParseResource
)):
__metaclass__
=
ObjectMetaclass
ENDPOINT_ROOT
=
'/'
.
join
([
API_ROOT
,
'classes'
])
ENDPOINT_ROOT
=
'/'
.
join
([
API_ROOT
,
'classes'
])
@classmethod
@classmethod
def
factory
(
cls
,
class_name
):
def
factory
(
cls
,
class_name
):
"""find proper Object subclass matching class_name
class
DerivedClass
(
cls
):
system types like _User are mapped to types without underscore (parse_resr.user.User)
pass
If user don't declare matching type, class is created on the fly
DerivedClass
.
__name__
=
str
(
class_name
)
"""
DerivedClass
.
set_endpoint_root
()
class_name
=
str
(
class_name
.
lstrip
(
'_'
))
return
DerivedClass
types
=
ParseResource
.
__subclasses__
()
while
types
:
t
=
types
.
pop
()
if
t
.
__name__
==
class_name
:
return
t
types
.
extend
(
t
.
__subclasses__
())
else
:
return
type
(
class_name
,
(
Object
,),
{})
@classmethod
@classmethod
def
set_endpoint_root
(
cls
):
def
set_endpoint_root
(
cls
):
...
@@ -357,15 +333,13 @@ class Object(ParseResource):
...
@@ -357,15 +333,13 @@ class Object(ParseResource):
@property
@property
def
_absolute_url
(
self
):
def
_absolute_url
(
self
):
if
not
self
.
objectId
:
return
None
if
not
self
.
objectId
:
return
None
return
'/'
.
join
([
self
.
__class__
.
ENDPOINT_ROOT
,
self
.
objectId
])
return
'/'
.
join
([
self
.
__class__
.
ENDPOINT_ROOT
,
self
.
objectId
])
@property
@property
def
as_pointer
(
self
):
def
as_pointer
(
self
):
return
Pointer
(
**
{
return
Pointer
(
self
)
'className'
:
self
.
__class__
.
__name__
,
'objectId'
:
self
.
objectId
})
def
increment
(
self
,
key
,
amount
=
1
):
def
increment
(
self
,
key
,
amount
=
1
):
"""
"""
...
...
parse_rest/installation.py
View file @
ffacfc4b
...
@@ -11,9 +11,9 @@
...
@@ -11,9 +11,9 @@
# 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/>.
from
connection
import
API_ROOT
from
parse_rest.
connection
import
API_ROOT
from
datatypes
import
ParseResource
from
parse_rest.
datatypes
import
ParseResource
from
query
import
QueryManager
from
parse_rest.
query
import
QueryManager
class
Installation
(
ParseResource
):
class
Installation
(
ParseResource
):
...
...
parse_rest/query.py
View file @
ffacfc4b
...
@@ -12,13 +12,9 @@
...
@@ -12,13 +12,9 @@
# 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
json
import
json
import
collections
import
copy
import
copy
import
collections
try
:
unicode
=
unicode
except
NameError
:
unicode
=
str
class
QueryResourceDoesNotExist
(
Exception
):
class
QueryResourceDoesNotExist
(
Exception
):
'''Query returned no results'''
'''Query returned no results'''
...
@@ -41,9 +37,8 @@ class QueryManager(object):
...
@@ -41,9 +37,8 @@ class QueryManager(object):
return
[
klass
(
**
it
)
for
it
in
klass
.
GET
(
uri
,
**
kw
)
.
get
(
'results'
)]
return
[
klass
(
**
it
)
for
it
in
klass
.
GET
(
uri
,
**
kw
)
.
get
(
'results'
)]
def
_count
(
self
,
**
kw
):
def
_count
(
self
,
**
kw
):
kw
.
update
({
"count"
:
1
,
"limit"
:
0
})
kw
.
update
({
"count"
:
1
})
return
self
.
model_class
.
GET
(
self
.
model_class
.
ENDPOINT_ROOT
,
return
self
.
model_class
.
GET
(
self
.
model_class
.
ENDPOINT_ROOT
,
**
kw
)
.
get
(
'count'
)
**
kw
)
.
get
(
'count'
)
def
all
(
self
):
def
all
(
self
):
return
Queryset
(
self
)
return
Queryset
(
self
)
...
@@ -58,31 +53,15 @@ class QueryManager(object):
...
@@ -58,31 +53,15 @@ class QueryManager(object):
return
self
.
filter
(
**
kw
)
.
get
()
return
self
.
filter
(
**
kw
)
.
get
()
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
)
for
fname
in
[
'limit'
,
'skip'
]:
def
func
(
self
,
value
,
fname
=
fname
):
s
=
copy
.
deepcopy
(
self
)
s
.
_options
[
fname
]
=
int
(
value
)
return
s
setattr
(
cls
,
fname
,
func
)
return
cls
class
Queryset
(
object
):
class
Queryset
(
object
):
__metaclass__
=
QuerysetMetaclass
OPERATORS
=
[
OPERATORS
=
[
'lt'
,
'lte'
,
'gt'
,
'gte'
,
'ne'
,
'in'
,
'nin'
,
'exists'
,
'select'
,
'dontSelect'
,
'all'
,
'relatedTo'
'lt'
,
'lte'
,
'gt'
,
'gte'
,
'ne'
,
'in'
,
'nin'
,
'exists'
,
'select'
,
'dontSelect'
,
'all'
,
'relatedTo'
]
]
@staticmethod
@staticmethod
def
convert_to_parse
(
value
):
def
convert_to_parse
(
value
):
from
datatypes
import
ParseType
from
parse_rest.
datatypes
import
ParseType
return
ParseType
.
convert_to_parse
(
value
,
as_pointer
=
True
)
return
ParseType
.
convert_to_parse
(
value
,
as_pointer
=
True
)
@classmethod
@classmethod
...
@@ -100,11 +79,20 @@ class Queryset(object):
...
@@ -100,11 +79,20 @@ class Queryset(object):
self
.
_options
=
{}
self
.
_options
=
{}
self
.
_result_cache
=
None
self
.
_result_cache
=
None
def
__deepcopy__
(
self
,
memo
):
q
=
self
.
__class__
(
self
.
_manager
)
q
.
_where
=
copy
.
deepcopy
(
self
.
_where
,
memo
)
q
.
_options
=
copy
.
deepcopy
(
self
.
_options
,
memo
)
q
.
_select_related
.
extend
(
self
.
_select_related
)
return
q
def
__iter__
(
self
):
def
__iter__
(
self
):
return
iter
(
self
.
_fetch
())
return
iter
(
self
.
_fetch
())
def
__len__
(
self
):
def
__len__
(
self
):
return
self
.
_fetch
(
count
=
True
)
#don't use count query for len operator
#count doesn't return real size of result in all cases (eg if query contains skip option)
return
len
(
self
.
_fetch
())
def
__getitem__
(
self
,
key
):
def
__getitem__
(
self
,
key
):
if
isinstance
(
key
,
slice
):
if
isinstance
(
key
,
slice
):
...
@@ -112,7 +100,7 @@ class Queryset(object):
...
@@ -112,7 +100,7 @@ class Queryset(object):
return
self
.
_fetch
()[
key
]
return
self
.
_fetch
()[
key
]
def
_fetch
(
self
,
count
=
False
):
def
_fetch
(
self
,
count
=
False
):
if
self
.
_result_cache
:
if
self
.
_result_cache
is
not
None
:
return
len
(
self
.
_result_cache
)
if
count
else
self
.
_result_cache
return
len
(
self
.
_result_cache
)
if
count
else
self
.
_result_cache
"""
"""
Return a list of objects matching query, or if count == True return
Return a list of objects matching query, or if count == True return
...
@@ -131,33 +119,43 @@ class Queryset(object):
...
@@ -131,33 +119,43 @@ class Queryset(object):
return
self
.
_result_cache
return
self
.
_result_cache
def
filter
(
self
,
**
kw
):
def
filter
(
self
,
**
kw
):
q
=
copy
.
deepcopy
(
self
)
for
name
,
value
in
kw
.
items
():
for
name
,
value
in
kw
.
items
():
parse_value
=
Queryset
.
convert_to_parse
(
value
)
parse_value
=
Queryset
.
convert_to_parse
(
value
)
attr
,
operator
=
Queryset
.
extract_filter_operator
(
name
)
attr
,
operator
=
Queryset
.
extract_filter_operator
(
name
)
if
operator
is
None
:
if
operator
is
None
:
self
.
_where
[
attr
]
=
parse_value
q
.
_where
[
attr
]
=
parse_value
elif
operator
==
'relatedTo'
:
elif
operator
==
'relatedTo'
:
self
.
_where
[
'$'
+
operator
]
=
parse_value
q
.
_where
[
'$'
+
operator
]
=
parse_value
else
:
else
:
try
:
if
not
isinstance
(
q
.
_where
[
attr
],
dict
):
self
.
_where
[
attr
][
'$'
+
operator
]
=
parse_value
q
.
_where
[
attr
]
=
{}
except
TypeError
:
q
.
_where
[
attr
][
'$'
+
operator
]
=
parse_value
# self._where[attr] wasn't settable
return
q
raise
ValueError
(
"Cannot filter for a constraint "
+
"after filtering for a specific value"
)
def
limit
(
self
,
value
):
return
self
q
=
copy
.
deepcopy
(
self
)
q
.
_options
[
'limit'
]
=
int
(
value
)
return
q
def
skip
(
self
,
value
):
q
=
copy
.
deepcopy
(
self
)
q
.
_options
[
'skip'
]
=
int
(
value
)
return
q
def
order_by
(
self
,
order
,
descending
=
False
):
def
order_by
(
self
,
order
,
descending
=
False
):
q
=
copy
.
deepcopy
(
self
)
# add a minus sign before the order value if descending == True
# add a minus sign before the order value if descending == True
self
.
_options
[
'order'
]
=
descending
and
(
'-'
+
order
)
or
order
q
.
_options
[
'order'
]
=
descending
and
(
'-'
+
order
)
or
order
return
self
return
q
def
select_related
(
self
,
*
fields
):
def
select_related
(
self
,
*
fields
):
self
.
_select_related
.
extend
(
fields
)
q
=
copy
.
deepcopy
(
self
)
return
self
q
.
_select_related
.
extend
(
fields
)
return
q
def
count
(
self
):
def
count
(
self
):
return
len
(
self
)
return
self
.
_fetch
(
count
=
True
)
def
exists
(
self
):
def
exists
(
self
):
return
bool
(
self
)
return
bool
(
self
)
...
@@ -171,4 +169,4 @@ class Queryset(object):
...
@@ -171,4 +169,4 @@ class Queryset(object):
return
results
[
0
]
return
results
[
0
]
def
__repr__
(
self
):
def
__repr__
(
self
):
return
unicode
(
self
.
_fetch
())
return
repr
(
self
.
_fetch
())
parse_rest/tests.py
View file @
ffacfc4b
...
@@ -4,20 +4,22 @@
...
@@ -4,20 +4,22 @@
"""
"""
Contains unit tests for the Python Parse REST API wrapper
Contains unit tests for the Python Parse REST API wrapper
"""
"""
from
__future__
import
print_function
import
os
import
os
import
sys
import
sys
import
subprocess
import
subprocess
import
unittest
import
unittest
import
datetime
import
datetime
import
six
from
itertools
import
chain
from
parse_rest.core
import
ResourceRequestNotFound
from
core
import
ResourceRequestNotFound
from
parse_rest.connection
import
register
,
ParseBatcher
from
connection
import
register
,
ParseBatcher
from
parse_rest.datatypes
import
GeoPoint
,
Object
,
Function
from
datatypes
import
GeoPoint
,
Object
,
Function
from
parse_rest.user
import
User
from
user
import
User
from
parse_rest
import
query
import
query
from
parse_rest.installation
import
Push
from
installation
import
Push
try
:
try
:
import
settings_local
import
settings_local
...
@@ -25,17 +27,12 @@ except ImportError:
...
@@ -25,17 +27,12 @@ except ImportError:
sys
.
exit
(
'You must create a settings_local.py file with APPLICATION_ID, '
\
sys
.
exit
(
'You must create a settings_local.py file with APPLICATION_ID, '
\
'REST_API_KEY, MASTER_KEY variables set'
)
'REST_API_KEY, MASTER_KEY variables set'
)
try
:
unicode
=
unicode
except
NameError
:
# is python3
unicode
=
str
register
(
register
(
getattr
(
settings_local
,
'APPLICATION_ID'
),
getattr
(
settings_local
,
'APPLICATION_ID'
),
getattr
(
settings_local
,
'REST_API_KEY'
),
getattr
(
settings_local
,
'REST_API_KEY'
),
master_key
=
getattr
(
settings_local
,
'MASTER_KEY'
)
master_key
=
getattr
(
settings_local
,
'MASTER_KEY'
)
)
)
GLOBAL_JSON_TEXT
=
"""{
GLOBAL_JSON_TEXT
=
"""{
"applications": {
"applications": {
...
@@ -76,67 +73,63 @@ class CollectedItem(Object):
...
@@ -76,67 +73,63 @@ class CollectedItem(Object):
class
TestObject
(
unittest
.
TestCase
):
class
TestObject
(
unittest
.
TestCase
):
def
setUp
(
self
):
def
setUp
(
self
):
self
.
score
=
GameScore
(
self
.
score
=
GameScore
(
score
=
1337
,
player_name
=
'John Doe'
,
cheat_mode
=
False
)
score
=
1337
,
player_name
=
'John Doe'
,
cheat_mode
=
False
self
.
sao_paulo
=
City
(
name
=
'São Paulo'
,
location
=
GeoPoint
(
-
23.5
,
-
46.6167
))
)
self
.
sao_paulo
=
City
(
name
=
'São Paulo'
,
location
=
GeoPoint
(
-
23.5
,
-
46.6167
)
)
def
tearDown
(
self
):
def
tearDown
(
self
):
city_name
=
getattr
(
self
.
sao_paulo
,
'name'
,
None
)
city_name
=
getattr
(
self
.
sao_paulo
,
'name'
,
None
)
game_score
=
getattr
(
self
.
score
,
'score'
,
None
)
game_score
=
getattr
(
self
.
score
,
'score'
,
None
)
if
city_name
:
if
city_name
:
for
city
in
City
.
Query
.
filter
(
name
=
city_name
):
ParseBatcher
()
.
batch_delete
(
City
.
Query
.
filter
(
name
=
city_name
))
city
.
delete
()
if
game_score
:
if
game_score
:
for
score
in
GameScore
.
Query
.
filter
(
score
=
game_score
):
ParseBatcher
()
.
batch_delete
(
GameScore
.
Query
.
filter
(
score
=
game_score
))
score
.
delete
()
def
testCanInitialize
(
self
):
def
testCanInitialize
(
self
):
self
.
assert
_
(
self
.
score
.
score
==
1337
,
'Could not set score'
)
self
.
assert
Equal
(
self
.
score
.
score
,
1337
,
'Could not set score'
)
def
testCanInstantiateParseType
(
self
):
def
testCanInstantiateParseType
(
self
):
self
.
assert_
(
self
.
sao_paulo
.
location
.
latitude
==
-
23.5
)
self
.
assertEqual
(
self
.
sao_paulo
.
location
.
latitude
,
-
23.5
)
def
testFactory
(
self
):
self
.
assertEqual
(
Object
.
factory
(
'_User'
),
User
)
self
.
assertEqual
(
Object
.
factory
(
'GameScore'
),
GameScore
)
def
testCanSaveDates
(
self
):
def
testCanSaveDates
(
self
):
now
=
datetime
.
datetime
.
now
()
now
=
datetime
.
datetime
.
now
()
self
.
score
.
last_played
=
now
self
.
score
.
last_played
=
now
self
.
score
.
save
()
self
.
score
.
save
()
self
.
assert
_
(
self
.
score
.
last_played
==
now
,
'Could not save date'
)
self
.
assert
Equal
(
self
.
score
.
last_played
,
now
,
'Could not save date'
)
def
testCanCreateNewObject
(
self
):
def
testCanCreateNewObject
(
self
):
self
.
score
.
save
()
self
.
score
.
save
()
object_id
=
self
.
score
.
objectId
object_id
=
self
.
score
.
objectId
self
.
assert_
(
object_id
is
not
None
,
'Can not create object'
)
self
.
assertIsNotNone
(
object_id
,
'Can not create object'
)
self
.
assert_
(
type
(
object_id
)
==
unicode
)
self
.
assertIsInstance
(
object_id
,
six
.
string_types
)
self
.
assert_
(
type
(
self
.
score
.
createdAt
)
==
datetime
.
datetime
)
self
.
assertIsInstance
(
self
.
score
.
createdAt
,
datetime
.
datetime
)
self
.
assert_
(
GameScore
.
Query
.
filter
(
objectId
=
object_id
)
.
exists
(),
self
.
assertTrue
(
GameScore
.
Query
.
filter
(
objectId
=
object_id
)
.
exists
(),
'Can not create object'
)
'Can not create object'
)
def
testCanUpdateExistingObject
(
self
):
def
testCanUpdateExistingObject
(
self
):
self
.
sao_paulo
.
save
()
self
.
sao_paulo
.
save
()
self
.
sao_paulo
.
country
=
'Brazil'
self
.
sao_paulo
.
country
=
'Brazil'
self
.
sao_paulo
.
save
()
self
.
sao_paulo
.
save
()
self
.
assert
_
(
type
(
self
.
sao_paulo
.
updatedAt
)
==
datetime
.
datetime
)
self
.
assert
IsInstance
(
self
.
sao_paulo
.
updatedAt
,
datetime
.
datetime
)
city
=
City
.
Query
.
get
(
name
=
'São Paulo'
)
city
=
City
.
Query
.
get
(
name
=
'São Paulo'
)
self
.
assert
_
(
city
.
country
==
'Brazil'
,
'Could not update object'
)
self
.
assert
Equal
(
city
.
country
,
'Brazil'
,
'Could not update object'
)
def
testCanDeleteExistingObject
(
self
):
def
testCanDeleteExistingObject
(
self
):
self
.
score
.
save
()
self
.
score
.
save
()
object_id
=
self
.
score
.
objectId
object_id
=
self
.
score
.
objectId
self
.
score
.
delete
()
self
.
score
.
delete
()
self
.
assert
_
(
not
GameScore
.
Query
.
filter
(
objectId
=
object_id
)
.
exists
(),
self
.
assert
False
(
GameScore
.
Query
.
filter
(
objectId
=
object_id
)
.
exists
(),
'Failed to delete object
%
s on Parse '
%
self
.
score
)
'Failed to delete object
%
s on Parse '
%
self
.
score
)
def
testCanIncrementField
(
self
):
def
testCanIncrementField
(
self
):
previous_score
=
self
.
score
.
score
previous_score
=
self
.
score
.
score
self
.
score
.
save
()
self
.
score
.
save
()
self
.
score
.
increment
(
'score'
)
self
.
score
.
increment
(
'score'
)
self
.
assert
_
(
GameScore
.
Query
.
filter
(
score
=
previous_score
+
1
)
.
exists
(),
self
.
assert
True
(
GameScore
.
Query
.
filter
(
score
=
previous_score
+
1
)
.
exists
(),
'Failed to increment score on backend'
)
'Failed to increment score on backend'
)
def
testAssociatedObject
(
self
):
def
testAssociatedObject
(
self
):
...
@@ -149,20 +142,17 @@ class TestObject(unittest.TestCase):
...
@@ -149,20 +142,17 @@ class TestObject(unittest.TestCase):
# get the object, see if it has saved
# get the object, see if it has saved
qs
=
GameScore
.
Query
.
get
(
objectId
=
self
.
score
.
objectId
)
qs
=
GameScore
.
Query
.
get
(
objectId
=
self
.
score
.
objectId
)
self
.
assert_
(
isinstance
(
qs
.
item
,
Object
),
self
.
assertIsInstance
(
qs
.
item
,
CollectedItem
)
"Associated CollectedItem is not an object"
)
self
.
assertEqual
(
qs
.
item
.
type
,
"Sword"
,
"Associated CollectedItem does not have correct attributes"
)
self
.
assert_
(
qs
.
item
.
type
==
"Sword"
,
"Associated CollectedItem does not have correct attributes"
)
def
testBatch
(
self
):
def
testBatch
(
self
):
"""test saving, updating and deleting objects in batches"""
"""test saving, updating and deleting objects in batches"""
scores
=
[
GameScore
(
score
=
s
,
player_name
=
'Jane'
,
cheat_mode
=
False
)
scores
=
[
GameScore
(
score
=
s
,
player_name
=
'Jane'
,
cheat_mode
=
False
)
for
s
in
range
(
5
)]
for
s
in
range
(
5
)]
batcher
=
ParseBatcher
()
batcher
=
ParseBatcher
()
batcher
.
batch_save
(
scores
)
batcher
.
batch_save
(
scores
)
self
.
assert
_
(
GameScore
.
Query
.
filter
(
player_name
=
'Jane'
)
.
count
()
==
5
,
self
.
assert
Equal
(
GameScore
.
Query
.
filter
(
player_name
=
'Jane'
)
.
count
(),
5
,
"batch_save didn't create objects"
)
"batch_save didn't create objects"
)
self
.
assert
_
(
all
(
s
.
objectId
is
not
None
for
s
in
scores
),
self
.
assert
True
(
all
(
s
.
objectId
is
not
None
for
s
in
scores
),
"batch_save didn't record object IDs"
)
"batch_save didn't record object IDs"
)
# test updating
# test updating
...
@@ -172,11 +162,11 @@ class TestObject(unittest.TestCase):
...
@@ -172,11 +162,11 @@ class TestObject(unittest.TestCase):
updated_scores
=
GameScore
.
Query
.
filter
(
player_name
=
'Jane'
)
updated_scores
=
GameScore
.
Query
.
filter
(
player_name
=
'Jane'
)
self
.
assertEqual
(
sorted
([
s
.
score
for
s
in
updated_scores
]),
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
# test deletion
batcher
.
batch_delete
(
scores
)
batcher
.
batch_delete
(
scores
)
self
.
assert
_
(
GameScore
.
Query
.
filter
(
player_name
=
'Jane'
)
.
count
()
==
0
,
self
.
assert
Equal
(
GameScore
.
Query
.
filter
(
player_name
=
'Jane'
)
.
count
(),
0
,
"batch_delete didn't delete objects"
)
"batch_delete didn't delete objects"
)
...
@@ -186,72 +176,83 @@ class TestTypes(unittest.TestCase):
...
@@ -186,72 +176,83 @@ class TestTypes(unittest.TestCase):
self
.
score
=
GameScore
(
self
.
score
=
GameScore
(
score
=
1337
,
player_name
=
'John Doe'
,
cheat_mode
=
False
,
score
=
1337
,
player_name
=
'John Doe'
,
cheat_mode
=
False
,
date_of_birth
=
self
.
now
date_of_birth
=
self
.
now
)
)
self
.
sao_paulo
=
City
(
self
.
sao_paulo
=
City
(
name
=
'São Paulo'
,
location
=
GeoPoint
(
-
23.5
,
-
46.6167
)
name
=
'São Paulo'
,
location
=
GeoPoint
(
-
23.5
,
-
46.6167
)
)
)
def
testCanConvertToNative
(
self
):
def
testCanConvertToNative
(
self
):
native_data
=
self
.
sao_paulo
.
_to_native
()
native_data
=
self
.
sao_paulo
.
_to_native
()
self
.
assert
_
(
type
(
native_data
)
is
dict
,
'Can not convert object to dict'
)
self
.
assert
IsInstance
(
native_data
,
dict
,
'Can not convert object to dict'
)
def
testCanConvertNestedLocation
(
self
):
def
testCanConvertNestedLocation
(
self
):
native_sao_paulo
=
self
.
sao_paulo
.
_to_native
()
native_sao_paulo
=
self
.
sao_paulo
.
_to_native
()
location_dict
=
native_sao_paulo
.
get
(
'location'
)
location_dict
=
native_sao_paulo
.
get
(
'location'
)
self
.
assert
_
(
type
(
location_dict
)
is
dict
,
self
.
assert
IsInstance
(
location_dict
,
dict
,
'Expected dict after conversion. Got
%
s'
%
location_dict
)
'Expected dict after conversion. Got
%
s'
%
location_dict
)
self
.
assert
_
(
location_dict
.
get
(
'latitude'
)
==
-
23.5
,
self
.
assert
Equal
(
location_dict
.
get
(
'latitude'
),
-
23.5
,
'Can not serialize geopoint data'
)
'Can not serialize geopoint data'
)
def
testCanConvertDate
(
self
):
def
testCanConvertDate
(
self
):
native_date
=
self
.
score
.
_to_native
()
.
get
(
'date_of_birth'
)
native_date
=
self
.
score
.
_to_native
()
.
get
(
'date_of_birth'
)
self
.
assert
_
(
type
(
native_date
)
is
dict
,
self
.
assert
IsInstance
(
native_date
,
dict
,
'Could not serialize date into dict'
)
'Could not serialize date into dict'
)
iso_date
=
native_date
.
get
(
'iso'
)
iso_date
=
native_date
.
get
(
'iso'
)
now
=
'{0}Z'
.
format
(
self
.
now
.
isoformat
()[:
-
3
])
now
=
'{0}Z'
.
format
(
self
.
now
.
isoformat
()[:
-
3
])
self
.
assert
_
(
iso_date
==
now
,
'Expected
%
s. Got
%
s'
%
(
now
,
iso_date
))
self
.
assert
Equal
(
iso_date
,
now
,
'Expected
%
s. Got
%
s'
%
(
now
,
iso_date
))
class
TestQuery
(
unittest
.
TestCase
):
class
TestQuery
(
unittest
.
TestCase
):
"""Tests of an object's Queryset"""
"""Tests of an object's Queryset"""
def
setUp
(
self
):
@classmethod
def
setUpClass
(
cls
):
"""save a bunch of GameScore objects with varying scores"""
"""save a bunch of GameScore objects with varying scores"""
# first delete any that exist
# first delete any that exist
for
s
in
GameScore
.
Query
.
all
():
ParseBatcher
()
.
batch_delete
(
GameScore
.
Query
.
all
())
s
.
delete
()
ParseBatcher
()
.
batch_delete
(
Game
.
Query
.
all
())
for
g
in
Game
.
Query
.
all
():
g
.
delete
()
self
.
game
=
Game
(
title
=
"Candyland"
)
cls
.
game
=
Game
(
title
=
"Candyland"
)
self
.
game
.
save
()
cls
.
game
.
save
()
self
.
scores
=
[
cls
.
scores
=
[
GameScore
(
score
=
s
,
player_name
=
'John Doe'
,
game
=
cls
.
game
)
for
s
in
range
(
1
,
6
)]
GameScore
(
score
=
s
,
player_name
=
'John Doe'
,
game
=
self
.
game
)
ParseBatcher
()
.
batch_save
(
cls
.
scores
)
for
s
in
range
(
1
,
6
)]
for
s
in
self
.
scores
:
@classmethod
s
.
save
()
def
tearDownClass
(
cls
):
'''delete all GameScore and Game objects'''
ParseBatcher
()
.
batch_delete
(
chain
(
cls
.
scores
,
[
cls
.
game
]))
def
setUp
(
self
):
self
.
test_objects
=
[]
def
tearDown
(
self
):
'''delete additional helper objects created in perticular tests'''
if
self
.
test_objects
:
ParseBatcher
()
.
batch_delete
(
self
.
test_objects
)
self
.
test_objects
=
[]
def
testExists
(
self
):
def
testExists
(
self
):
"""test the Queryset.exists() method"""
"""test the Queryset.exists() method"""
for
s
in
range
(
1
,
6
):
for
s
in
range
(
1
,
6
):
self
.
assert
_
(
GameScore
.
Query
.
filter
(
score
=
s
)
.
exists
(),
self
.
assert
True
(
GameScore
.
Query
.
filter
(
score
=
s
)
.
exists
(),
"exists giving false negative"
)
"exists giving false negative"
)
self
.
assert
_
(
not
GameScore
.
Query
.
filter
(
score
=
10
)
.
exists
(),
self
.
assert
False
(
GameScore
.
Query
.
filter
(
score
=
10
)
.
exists
(),
"exists giving false positive"
)
"exists giving false positive"
)
def
testCanFilter
(
self
):
def
testCanFilter
(
self
):
'''test the Queryset.filter() method'''
'''test the Queryset.filter() method'''
for
s
in
self
.
scores
:
for
s
in
self
.
scores
:
qobj
=
GameScore
.
Query
.
filter
(
objectId
=
s
.
objectId
)
.
get
()
qobj
=
GameScore
.
Query
.
filter
(
objectId
=
s
.
objectId
)
.
get
()
self
.
assert
_
(
qobj
.
objectId
==
s
.
objectId
,
self
.
assert
Equal
(
qobj
.
objectId
,
s
.
objectId
,
"Getting object with .filter() failed"
)
"Getting object with .filter() failed"
)
self
.
assert
_
(
qobj
.
score
==
s
.
score
,
self
.
assert
Equal
(
qobj
.
score
,
s
.
score
,
"Getting object with .filter() failed"
)
"Getting object with .filter() failed"
)
# test relational query with other Objects
# test relational query with other Objects
num_scores
=
GameScore
.
Query
.
filter
(
game
=
self
.
game
)
.
count
()
num_scores
=
GameScore
.
Query
.
filter
(
game
=
self
.
game
)
.
count
()
self
.
assert
_
(
num_scores
==
len
(
self
.
scores
),
self
.
assert
True
(
num_scores
==
len
(
self
.
scores
),
"Relational query with .filter() failed"
)
"Relational query with .filter() failed"
)
def
testGetExceptions
(
self
):
def
testGetExceptions
(
self
):
...
@@ -265,65 +266,78 @@ class TestQuery(unittest.TestCase):
...
@@ -265,65 +266,78 @@ class TestQuery(unittest.TestCase):
last_week
=
datetime
.
datetime
.
now
()
-
datetime
.
timedelta
(
days
=
7
)
last_week
=
datetime
.
datetime
.
now
()
-
datetime
.
timedelta
(
days
=
7
)
score
=
GameScore
(
name
=
'test'
,
last_played
=
last_week
)
score
=
GameScore
(
name
=
'test'
,
last_played
=
last_week
)
score
.
save
()
score
.
save
()
self
.
assert_
(
GameScore
.
Query
.
filter
(
last_played
=
last_week
)
.
exists
(),
self
.
test_objects
.
append
(
score
)
'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
):
def
testComparisons
(
self
):
"""test comparison operators- gt, gte, lt, lte, ne"""
"""test comparison operators- gt, gte, lt, lte, ne"""
scores_gt_3
=
list
(
GameScore
.
Query
.
filter
(
score__gt
=
3
)
)
scores_gt_3
=
GameScore
.
Query
.
filter
(
score__gt
=
3
)
self
.
assertEqual
(
len
(
scores_gt_3
),
2
)
self
.
assertEqual
(
len
(
scores_gt_3
),
2
)
self
.
assert
_
(
all
([
s
.
score
>
3
for
s
in
scores_gt_3
]))
self
.
assert
True
(
all
([
s
.
score
>
3
for
s
in
scores_gt_3
]))
scores_gte_3
=
list
(
GameScore
.
Query
.
filter
(
score__gte
=
3
)
)
scores_gte_3
=
GameScore
.
Query
.
filter
(
score__gte
=
3
)
self
.
assertEqual
(
len
(
scores_gte_3
),
3
)
self
.
assertEqual
(
len
(
scores_gte_3
),
3
)
self
.
assert
_
(
all
([
s
.
score
>=
3
for
s
in
scores_gt_3
]))
self
.
assert
True
(
all
([
s
.
score
>=
3
for
s
in
scores_gt_3
]))
scores_lt_4
=
list
(
GameScore
.
Query
.
filter
(
score__lt
=
4
)
)
scores_lt_4
=
GameScore
.
Query
.
filter
(
score__lt
=
4
)
self
.
assertEqual
(
len
(
scores_lt_4
),
3
)
self
.
assertEqual
(
len
(
scores_lt_4
),
3
)
self
.
assert
_
(
all
([
s
.
score
<
4
for
s
in
scores_lt_4
]))
self
.
assert
True
(
all
([
s
.
score
<
4
for
s
in
scores_lt_4
]))
scores_lte_4
=
list
(
GameScore
.
Query
.
filter
(
score__lte
=
4
)
)
scores_lte_4
=
GameScore
.
Query
.
filter
(
score__lte
=
4
)
self
.
assertEqual
(
len
(
scores_lte_4
),
4
)
self
.
assertEqual
(
len
(
scores_lte_4
),
4
)
self
.
assert
_
(
all
([
s
.
score
<=
4
for
s
in
scores_lte_4
]))
self
.
assert
True
(
all
([
s
.
score
<=
4
for
s
in
scores_lte_4
]))
scores_ne_2
=
list
(
GameScore
.
Query
.
filter
(
score__ne
=
2
)
)
scores_ne_2
=
GameScore
.
Query
.
filter
(
score__ne
=
2
)
self
.
assertEqual
(
len
(
scores_ne_2
),
4
)
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
]))
def
testChaining
(
self
):
lt_4_gt_2
=
GameScore
.
Query
.
filter
(
score__lt
=
4
)
.
filter
(
score__gt
=
2
)
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'
)
# 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'
)
q
=
GameScore
.
Query
.
filter
(
score__gt
=
3
,
score__lt
=
3
)
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"
)
# test original queries are idependent after filting
q_all
=
GameScore
.
Query
.
all
()
q_special
=
q_all
.
filter
(
score__gt
=
3
)
self
.
assertEqual
(
len
(
q_all
),
5
)
self
.
assertEqual
(
len
(
q_special
),
2
)
def
testOptions
(
self
):
q_all
=
GameScore
.
Query
.
all
()
q_limit
=
q_all
.
limit
(
1
)
self
.
assertEqual
(
len
(
q_all
),
5
)
self
.
assertEqual
(
len
(
q_limit
),
1
)
def
testOrderBy
(
self
):
"""test three options- order, limit, and skip"""
"""test three options- order, limit, and skip"""
scores_ordered
=
list
(
GameScore
.
Query
.
all
()
.
order_by
(
"score"
))
scores_ordered
=
GameScore
.
Query
.
all
()
.
order_by
(
"score"
)
self
.
assertEqual
([
s
.
score
for
s
in
scores_ordered
],
self
.
assertEqual
([
s
.
score
for
s
in
scores_ordered
],
[
1
,
2
,
3
,
4
,
5
])
[
1
,
2
,
3
,
4
,
5
])
scores_ordered_desc
=
GameScore
.
Query
.
all
()
.
order_by
(
"score"
,
descending
=
True
)
self
.
assertEqual
([
s
.
score
for
s
in
scores_ordered_desc
],
[
5
,
4
,
3
,
2
,
1
])
scores_ordered_desc
=
list
(
GameScore
.
Query
.
all
()
.
order_by
(
"score"
,
descending
=
True
))
def
testLimit
(
self
):
self
.
assertEqual
([
s
.
score
for
s
in
scores_ordered_desc
],
q
=
GameScore
.
Query
.
all
()
.
limit
(
3
)
[
5
,
4
,
3
,
2
,
1
]
)
self
.
assertEqual
(
len
(
q
),
3
)
scores_limit_3
=
list
(
GameScore
.
Query
.
all
()
.
limit
(
3
))
def
testSkip
(
self
):
self
.
assert_
(
len
(
scores_limit_3
)
==
3
,
"Limit did not return 3 items"
)
q
=
GameScore
.
Query
.
all
()
.
skip
(
3
)
self
.
assertEqual
(
len
(
q
),
2
)
scores_skip_3
=
list
(
GameScore
.
Query
.
all
()
.
skip
(
3
))
def
testSelectRelated
(
self
):
self
.
assert_
(
len
(
scores_skip_3
)
==
2
,
"Skip did not return 2 items"
)
score
=
GameScore
.
Query
.
all
()
.
select_related
(
'game'
)
.
limit
(
1
)[
0
]
self
.
assertTrue
(
score
.
game
.
objectId
)
#nice to have - also check no more then one query is triggered
def
testCanCompareDateInequality
(
self
):
def
testCanCompareDateInequality
(
self
):
today
=
datetime
.
datetime
.
today
()
today
=
datetime
.
datetime
.
today
()
tomorrow
=
today
+
datetime
.
timedelta
(
days
=
1
)
tomorrow
=
today
+
datetime
.
timedelta
(
days
=
1
)
self
.
assert_
(
GameScore
.
Query
.
filter
(
createdAt__lte
=
tomorrow
)
.
count
()
==
5
,
self
.
assertEqual
(
GameScore
.
Query
.
filter
(
createdAt__lte
=
tomorrow
)
.
count
(),
5
,
'Could not make inequality comparison with dates'
)
'Could not make inequality comparison with dates'
)
def
tearDown
(
self
):
'''delete all GameScore and Game objects'''
for
s
in
GameScore
.
Query
.
all
():
s
.
delete
()
self
.
game
.
delete
()
class
TestFunction
(
unittest
.
TestCase
):
class
TestFunction
(
unittest
.
TestCase
):
...
@@ -346,8 +360,7 @@ class TestFunction(unittest.TestCase):
...
@@ -346,8 +360,7 @@ class TestFunction(unittest.TestCase):
os
.
chdir
(
original_dir
)
os
.
chdir
(
original_dir
)
def
tearDown
(
self
):
def
tearDown
(
self
):
for
review
in
Review
.
Query
.
all
():
ParseBatcher
()
.
batch_delete
(
Review
.
Query
.
all
())
review
.
delete
()
def
test_simple_functions
(
self
):
def
test_simple_functions
(
self
):
"""test hello world and averageStars functions"""
"""test hello world and averageStars functions"""
...
@@ -407,13 +420,13 @@ class TestUser(unittest.TestCase):
...
@@ -407,13 +420,13 @@ class TestUser(unittest.TestCase):
def
testCanSignUp
(
self
):
def
testCanSignUp
(
self
):
self
.
_destroy_user
()
self
.
_destroy_user
()
user
=
User
.
signup
(
self
.
username
,
self
.
password
)
user
=
User
.
signup
(
self
.
username
,
self
.
password
)
self
.
assert
_
(
user
is
not
None
)
self
.
assert
IsNotNone
(
user
)
self
.
assert
_
(
user
.
username
==
self
.
username
)
self
.
assert
Equal
(
user
.
username
,
self
.
username
)
def
testCanLogin
(
self
):
def
testCanLogin
(
self
):
self
.
_get_user
()
# User should be created here.
self
.
_get_user
()
# User should be created here.
user
=
User
.
login
(
self
.
username
,
self
.
password
)
user
=
User
.
login
(
self
.
username
,
self
.
password
)
self
.
assert
_
(
user
.
is_authenticated
(),
'Login failed'
)
self
.
assert
True
(
user
.
is_authenticated
(),
'Login failed'
)
def
testCanUpdate
(
self
):
def
testCanUpdate
(
self
):
user
=
self
.
_get_logged_user
()
user
=
self
.
_get_logged_user
()
...
@@ -423,7 +436,7 @@ class TestUser(unittest.TestCase):
...
@@ -423,7 +436,7 @@ class TestUser(unittest.TestCase):
user
.
phone
=
phone_number
user
.
phone
=
phone_number
user
.
save
()
user
.
save
()
self
.
assert
_
(
User
.
Query
.
filter
(
phone
=
phone_number
)
.
exists
(),
self
.
assert
True
(
User
.
Query
.
filter
(
phone
=
phone_number
)
.
exists
(),
'Failed to update user data. New info not on Parse'
)
'Failed to update user data. New info not on Parse'
)
def
testCanBatchUpdate
(
self
):
def
testCanBatchUpdate
(
self
):
...
@@ -436,10 +449,10 @@ class TestUser(unittest.TestCase):
...
@@ -436,10 +449,10 @@ class TestUser(unittest.TestCase):
batcher
=
ParseBatcher
()
batcher
=
ParseBatcher
()
batcher
.
batch_save
([
user
])
batcher
.
batch_save
([
user
])
self
.
assert
_
(
User
.
Query
.
filter
(
phone
=
phone_number
)
.
exists
(),
self
.
assert
True
(
User
.
Query
.
filter
(
phone
=
phone_number
)
.
exists
(),
'Failed to batch update user data. New info not on Parse'
)
'Failed to batch update user data. New info not on Parse'
)
self
.
assert
_
(
user
.
updatedAt
!=
original_updatedAt
,
self
.
assert
NotEqual
(
user
.
updatedAt
,
original_updatedAt
,
'Failed to batch update user data: updatedAt not changed'
)
'Failed to batch update user data: updatedAt not changed'
)
class
TestPush
(
unittest
.
TestCase
):
class
TestPush
(
unittest
.
TestCase
):
...
...
parse_rest/user.py
View file @
ffacfc4b
...
@@ -12,10 +12,10 @@
...
@@ -12,10 +12,10 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from
core
import
ResourceRequestLoginRequired
from
parse_rest.
core
import
ResourceRequestLoginRequired
from
connection
import
API_ROOT
from
parse_rest.
connection
import
API_ROOT
from
datatypes
import
ParseResource
,
ParseType
from
parse_rest.
datatypes
import
ParseResource
,
ParseType
from
query
import
QueryManager
from
parse_rest.
query
import
QueryManager
def
login_required
(
func
):
def
login_required
(
func
):
...
@@ -46,7 +46,7 @@ class User(ParseResource):
...
@@ -46,7 +46,7 @@ class User(ParseResource):
if
password
is
not
None
:
if
password
is
not
None
:
self
=
User
.
login
(
self
.
username
,
password
)
self
=
User
.
login
(
self
.
username
,
password
)
user
=
User
.
retrieve
(
self
.
objectId
)
user
=
User
.
Query
.
get
(
objectId
=
self
.
objectId
)
if
user
.
objectId
==
self
.
objectId
and
user
.
sessionToken
==
session_token
:
if
user
.
objectId
==
self
.
objectId
and
user
.
sessionToken
==
session_token
:
self
.
sessionToken
=
session_token
self
.
sessionToken
=
session_token
...
...
setup.py
View file @
ffacfc4b
...
@@ -27,6 +27,7 @@ setup(
...
@@ -27,6 +27,7 @@ setup(
url
=
'https://github.com/dgrtwo/ParsePy'
,
url
=
'https://github.com/dgrtwo/ParsePy'
,
packages
=
[
'parse_rest'
],
packages
=
[
'parse_rest'
],
package_data
=
{
"parse_rest"
:
[
os
.
path
.
join
(
"cloudcode"
,
"*"
,
"*"
)]},
package_data
=
{
"parse_rest"
:
[
os
.
path
.
join
(
"cloudcode"
,
"*"
,
"*"
)]},
install_requires
=
[
'six'
],
maintainer
=
'David Robinson'
,
maintainer
=
'David Robinson'
,
maintainer_email
=
'dgrtwo@princeton.edu'
,
maintainer_email
=
'dgrtwo@princeton.edu'
,
cmdclass
=
{
'test'
:
TestCommand
},
cmdclass
=
{
'test'
:
TestCommand
},
...
@@ -36,6 +37,9 @@ setup(
...
@@ -36,6 +37,9 @@ setup(
'Intended Audience :: Developers'
,
'Intended Audience :: Developers'
,
'License :: OSI Approved :: GNU General Public License v3 (GPLv3)'
,
'License :: OSI Approved :: GNU General Public License v3 (GPLv3)'
,
'Operating System :: OS Independent'
,
'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"
,
]
)
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment