Commit cd9ef16a by David Robinson

Merge pull request #47 from farin/master

select_related feature, result cache +  few updates to have more Pythonic code
parents 1dce7759 648db13d
...@@ -7,3 +7,4 @@ settings_local.py ...@@ -7,3 +7,4 @@ settings_local.py
dist dist
MANIFEST MANIFEST
global.json global.json
parse_rest.egg-info
\ No newline at end of file
...@@ -311,6 +311,14 @@ page_one = posts.limit(10) # Will return the most 10 recent posts. ...@@ -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 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 #### Composability/Chaining of Querysets
The example above can show the most powerful aspect of Querysets, that The example above can show the most powerful aspect of Querysets, that
......
...@@ -27,7 +27,6 @@ class ParseType(object): ...@@ -27,7 +27,6 @@ class ParseType(object):
# if its not a parse type -- simply return it. This means it wasn't a "special class" # if its not a parse type -- simply return it. This means it wasn't a "special class"
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 # determine just which kind of parse type this element is - ie: a built in parse type such as File, Pointer, User etc
...@@ -35,12 +34,12 @@ class ParseType(object): ...@@ -35,12 +34,12 @@ class ParseType(object):
# if its a pointer, we need to handle to ensure that we don't mishandle a circular reference # if its a pointer, we need to handle to ensure that we don't mishandle a circular reference
if parse_type == "Pointer": if parse_type == "Pointer":
# grab the pointer object here # grab the pointer object here
return Pointer.from_native(class_name, **parse_data) return Pointer.from_native(class_name, **parse_data)
# return a recursive handled Pointer method # embedded object by select_related
return True if parse_type == "Object":
return EmbeddedObject.from_native(class_name, **parse_data)
# now handle the other parse types accordingly # now handle the other parse types accordingly
native = { native = {
...@@ -90,22 +89,26 @@ class ParseType(object): ...@@ -90,22 +89,26 @@ class ParseType(object):
class Pointer(ParseType): class Pointer(ParseType):
@classmethod @classmethod
def from_native(cls, parent_class_name = None, **kw): 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):
# grab the object data manually here so we can manipulate it before passing back an actual object # 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')) objectData = klass.GET("/" + kw.get('objectId'))
# now lets check if we have circular references here # now lets check if we have circular references here
if parent_class_name: 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 # set a temporary flag that will remove the recursive pointer types etc
klass = Object.factory(kw.get('className')) klass = Object.factory(kw.get('className'))
...@@ -123,6 +126,15 @@ class Pointer(ParseType): ...@@ -123,6 +126,15 @@ class Pointer(ParseType):
} }
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): class Relation(ParseType):
@classmethod @classmethod
def from_native(cls, **kw): def from_native(cls, **kw):
......
...@@ -96,12 +96,24 @@ class Queryset(object): ...@@ -96,12 +96,24 @@ class Queryset(object):
def __init__(self, manager): def __init__(self, manager):
self._manager = manager self._manager = manager
self._where = collections.defaultdict(dict) self._where = collections.defaultdict(dict)
self._select_related = []
self._options = {} self._options = {}
self._result_cache = None
def __iter__(self): def __iter__(self):
return iter(self._fetch()) 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): 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 Return a list of objects matching query, or if count == True return
only the number of objects matching. only the number of objects matching.
...@@ -109,12 +121,14 @@ class Queryset(object): ...@@ -109,12 +121,14 @@ class Queryset(object):
options = dict(self._options) # make a local copy options = dict(self._options) # make a local copy
if self._where: if self._where:
# JSON encode WHERE values # JSON encode WHERE values
where = json.dumps(self._where) options['where'] = json.dumps(self._where)
options.update({'where': where}) if self._select_related:
options['include'] = ','.join(self._select_related)
if count: if count:
return self._manager._count(**options) 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): def filter(self, **kw):
for name, value in kw.items(): for name, value in kw.items():
...@@ -122,8 +136,7 @@ class Queryset(object): ...@@ -122,8 +136,7 @@ class Queryset(object):
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 self._where[attr] = parse_value
else: elif operator == 'relatedTo':
if operator == 'relatedTo':
self._where['$' + operator] = parse_value self._where['$' + operator] = parse_value
else: else:
try: try:
...@@ -139,12 +152,15 @@ class Queryset(object): ...@@ -139,12 +152,15 @@ class Queryset(object):
self._options['order'] = descending and ('-' + order) or order self._options['order'] = descending and ('-' + order) or order
return self return self
def select_related(self, *fields):
self._select_related.extend(fields)
return self
def count(self): def count(self):
return self._fetch(count=True) return len(self)
def exists(self): def exists(self):
results = self._fetch() return bool(self)
return len(results) > 0
def get(self): def get(self):
results = self._fetch() results = self._fetch()
......
import os import os
from distutils.core import setup, Command from setuptools import setup, Command
from unittest import TextTestRunner, TestLoader from unittest import TextTestRunner, TestLoader
...@@ -26,8 +26,7 @@ setup( ...@@ -26,8 +26,7 @@ setup(
description='A client library for Parse.com\'.s REST API', description='A client library for Parse.com\'.s REST API',
url='https://github.com/dgrtwo/ParsePy', url='https://github.com/dgrtwo/ParsePy',
packages=['parse_rest'], packages=['parse_rest'],
package_data={"parse_rest": package_data={"parse_rest": [os.path.join("cloudcode", "*", "*")]},
[os.path.join("cloudcode", "*", "*")]},
maintainer='David Robinson', maintainer='David Robinson',
maintainer_email='dgrtwo@princeton.edu', maintainer_email='dgrtwo@princeton.edu',
cmdclass={'test': TestCommand}, cmdclass={'test': TestCommand},
......
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