Skip to content

Commit

Permalink
Merge pull request #47 from farin/master
Browse files Browse the repository at this point in the history
select_related feature, result cache +  few updates to have more Pythonic code
  • Loading branch information
David Robinson committed Jul 16, 2014
2 parents 1dce775 + 648db13 commit cd9ef16
Show file tree
Hide file tree
Showing 5 changed files with 72 additions and 36 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ settings_local.py
dist
MANIFEST
global.json
parse_rest.egg-info
8 changes: 8 additions & 0 deletions README.mkd
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,14 @@ page_one = posts.limit(10) # Will return the most 10 recent posts.
page_two = posts.skip(10).limit(10) # Will return posts 11-20
~~~~~

#### Related objects

You can specify "join" attributes to get related object with single query.

~~~~~ {python}
posts = Post.Query.all().select_related("author", "editor")
~~~~~

#### Composability/Chaining of Querysets

The example above can show the most powerful aspect of Querysets, that
Expand Down
48 changes: 30 additions & 18 deletions parse_rest/datatypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,20 +27,19 @@ def convert_from_parse(parse_data, class_name):

# if its not a parse type -- simply return it. This means it wasn't a "special class"
if not is_parse_type:

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
# determine just which kind of parse type this element is - ie: a built in parse type such as File, Pointer, User etc
parse_type = parse_data['__type']

# 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)

# return a recursive handled Pointer method
return True
# 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 = {
Expand Down Expand Up @@ -88,25 +87,29 @@ def _to_native(self):


class Pointer(ParseType):


@classmethod
def _prevent_circular(cls, parent_class_name, objectData):
# TODO this should be replaced with more clever checking, instead of simple class mathching original id should be compared
# 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):

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'))
objectData = klass.GET("/" + kw.get('objectId'))

# now lets check if we have circular references here
if parent_class_name:
objectData = cls._prevent_circular(parent_class_name, objectData)

# now lets see if we have any references to the parent class here
for key, value in objectData.iteritems():

if type(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"]

# set a temporary flag that will remove the recursive pointer types etc
klass = Object.factory(kw.get('className'))
return klass(**objectData)
Expand All @@ -123,6 +126,15 @@ def _to_native(self):
}


class EmbeddedObject(ParseType):
@classmethod
def from_native(cls, parent_class_name=None, **kw):
if parent_class_name:
kw = Pointer._prevent_circular(parent_class_name, kw)
klass = Object.factory(kw.get('className'))
return klass(**kw)


class Relation(ParseType):
@classmethod
def from_native(cls, **kw):
Expand Down Expand Up @@ -381,7 +393,7 @@ def manageRelation(self, action, key, className, objectsId):
"className": className,
"objectId": objectId
} for objectId in objectsId]

payload = {
key: {
"__op": action,
Expand Down
46 changes: 31 additions & 15 deletions parse_rest/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,55 +96,71 @@ def extract_filter_operator(cls, parameter):
def __init__(self, manager):
self._manager = manager
self._where = collections.defaultdict(dict)
self._select_related = []
self._options = {}
self._result_cache = None

def __iter__(self):
return iter(self._fetch())

def __len__(self):
return self._fetch(count=True)

def __getitem__(self, key):
if isinstance(key, slice):
raise AttributeError("Slice is not supported for now.")
return self._fetch()[key]

def _fetch(self, count=False):
if 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
only the number of objects matching.
"""
options = dict(self._options) # make a local copy
if self._where:
# JSON encode WHERE values
where = json.dumps(self._where)
options.update({'where': where})
options['where'] = json.dumps(self._where)
if self._select_related:
options['include'] = ','.join(self._select_related)
if count:
return self._manager._count(**options)

return self._manager._fetch(**options)
self._result_cache = self._manager._fetch(**options)
return self._result_cache

def filter(self, **kw):
for name, value in kw.items():
parse_value = Queryset.convert_to_parse(value)
attr, operator = Queryset.extract_filter_operator(name)
if operator is None:
self._where[attr] = parse_value
elif operator == 'relatedTo':
self._where['$' + operator] = parse_value
else:
if operator == 'relatedTo':
self._where['$' + operator] = parse_value
else:
try:
self._where[attr]['$' + operator] = parse_value
except TypeError:
# self._where[attr] wasn't settable
raise ValueError("Cannot filter for a constraint " +
"after filtering for a specific value")
try:
self._where[attr]['$' + operator] = parse_value
except TypeError:
# self._where[attr] wasn't settable
raise ValueError("Cannot filter for a constraint " +
"after filtering for a specific value")
return self

def order_by(self, order, descending=False):
# add a minus sign before the order value if descending == True
self._options['order'] = descending and ('-' + order) or order
return self

def select_related(self, *fields):
self._select_related.extend(fields)
return self

def count(self):
return self._fetch(count=True)
return len(self)

def exists(self):
results = self._fetch()
return len(results) > 0
return bool(self)

def get(self):
results = self._fetch()
Expand Down
5 changes: 2 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import os
from distutils.core import setup, Command
from setuptools import setup, Command
from unittest import TextTestRunner, TestLoader


Expand All @@ -26,8 +26,7 @@ def run(self):
description='A client library for Parse.com\'.s REST API',
url='https://github.com/dgrtwo/ParsePy',
packages=['parse_rest'],
package_data={"parse_rest":
[os.path.join("cloudcode", "*", "*")]},
package_data={"parse_rest": [os.path.join("cloudcode", "*", "*")]},
maintainer='David Robinson',
maintainer_email='dgrtwo@princeton.edu',
cmdclass={'test': TestCommand},
Expand Down

0 comments on commit cd9ef16

Please sign in to comment.