Skip to content
This repository

Unit tests, Auth fix, increment(), etc #9

Open
wants to merge 107 commits into from

13 participants

David Robinson xmxmichael Raphael Lullis Billy Tobon Adam Awan Miguel Galves Robert Hanacek Clément Jourdain Williams Mendez Rafael Bermúdez Guijo Adam Lane rwitten Yosi Taguri
David Robinson

Great work on this really useful module!

I've made a couple of small changes I think will be helpful- I hope you take a look.

-Changed the authorization header- the original one appears to no longer work with the Parse API

-Added simple unit tests in tests.py that should help future development and easily check for API compatibility.

-Added a ParseObject.increment(key) method

-pep8 compliance

dgrtwo and others added some commits
David Robinson dgrtwo Auth fixes, tests.py, increment method, pep8
Fixed authentication, since Parse appears to have changed their API.

Added tests.py with unit tests for ParseObject and ParseQuery classes.

Added ParseObject.increment(key) method.

pep8 compliance changes, mostly removing end of line whitespace
248b756
David Robinson dgrtwo Mention increment method in README and added test case for ParseObjec…
…t.delete().
205a4af
Billy Tobon billyto GeoPoint Support added
GeoPoints use string with format  POINT(lat lon)
da62808
Billy Tobon billyto added geoPoint documentation 5954e65
Billy Tobon billyto markdown tweaks 842c6c5
David Robinson dgrtwo Merge pull request #1 from billyto/master
geoPoints
10e3bb9
David Robinson dgrtwo Changes for pep8 compliance 99fb1d2
Billy Tobon billyto fix regex bug
regex wasn't parsing negative latitudes.
6c3c6b3
Billy Tobon billyto Update README.mkd
fliping POINT(latitude longitude) to POINT(longitude latitude)
557d9ad
Billy Tobon billyto flipping lat - lon on point creation. 86481f6
Raphael Lullis lullis Adding a very basic setup.py file to make it pip-installable. 25b176f
Raphael Lullis lullis Moving code to its own folder, so we can have a namespaced package. R…
…eflecting changes in README
125b0e2
Raphael Lullis lullis Adding 'build' folder to gitignore 3e5d1ae
Raphael Lullis lullis Needed to add 'packages' parameter to setup.py. 5a774a6
Raphael Lullis lullis Cleaning up the code a little bit, and removing all print statements …
…from code (WSGI does not accept them)
6bdb286
Raphael Lullis lullis The code was doing request using the master key as a parameter for th…
…e request. This was completely wrong. The correct key is API_KEY. Fixed.
7f65147
Raphael Lullis lullis Updated README. 11354d7
Raphael Lullis lullis Updating test suite to reflect changes in MASTER -> API KEY e33d167
Raphael Lullis lullis Adding support to ParseUser handling, account creation, login, and qu…
…erying (through ParseUserQuery class)
7228a08
Raphael Lullis lullis Removing all the Parse prefix name from classes (except for ParseBase…
… and ParseResource, which are classes that are just base classes). Also, adding a very basic Push class.
fd97cac
Raphael Lullis lullis Updating README to reflect changes in class names. 949881c
Adam Awan funkotron Replacing MASTER_KEY with REST_API_KEY 0907274
David Robinson dgrtwo Merge pull request #2 from funkotron/master
Rename MASTER_KEY to REST_API_KEY
4ff5f0d
David Robinson dgrtwo Changed Query to ObjectQuery in a few locations, especially in tests.py c321554
David Robinson dgrtwo Changed name and folder of package to 'parse_rest' to conform with pe…
…p8 standards, along with associated tests.
bf35957
David Robinson dgrtwo Made changes consistent between commits (REST_API_KEY), also changed …
…README to refer to parse_rest, and changed contact email to mine.
060d8a0
David Robinson dgrtwo Some modifications for uploading to PyPI, to setup.py and to .gitignore bc7aa3c
David Robinson dgrtwo Modifications to make fully pep8 compliant e5b911a
David Robinson dgrtwo Added installation information to README. Added 'python setup.py test…
…' command.
895cb68
David Robinson dgrtwo Added Function class to handle calling CloudCode functions. Also adde…
…d test cases for hello and averageStars functions, which necessitated adding a cloudcode directory to upload those functions to the test application, as well as requiring MASTER_KEY in the settings_local.py file. Added CloudCode documentation to README.mkd.
b640110
David Robinson dgrtwo Updated version number for PyPI 036b135
Uri Kogan fixing request URL creation (_absolute_url) 8be8e1c
David Robinson dgrtwo Merge pull request #5 from UrK/master
Incorrect usage of string join method
2138893
David Robinson dgrtwo Incremented version for PyPI c715cc3
David Robinson dgrtwo Redesigned User class so that it could work as an object (adding and …
…saving attributes, added delete method, and lets it save a session token). Required changing UserQuery as well. Added a few appropriate test cases in TestUser class. Finally, added ParseError exception class. Addresses issue in #5.
733651f
David Robinson dgrtwo Added details of User class to README.mkd. Added exception if APPLICA…
…TION_ID or REST_API_KEY is not set. Changed parse to parse_rest in a few lines of the docs.
bde3cfc
David Robinson dgrtwo Added ability to access a parse_rest.Object like a dictionary or an i…
…terable, along with test cases for that functionality.
60755bf
David Robinson dgrtwo Fixed #6, added relevant test code 5905c7b
Raphael Lullis lullis Lots of changes. Too many to put in a single commit message. In short…
…, transformed Query objects into a method that belongs to ResourceClass. Also, added classes to represent ParseTypes. It should be possible to use these transparently. Added tests\!
2b5a26b
David Robinson dgrtwo Pep8 compliance changes 250d369
David Robinson dgrtwo Set up the QueryManager in a metaclass instead of in the __new__ meth…
…od, which means one doesn't have to instantiate an instance of the class before querying it. Fixed the __iter__ method of Queryset, which yielded only the first object from the query. Put back test cases for CloudCode functions.
4d66c30
David Robinson dgrtwo Fixed merge conflicts- new version as per #8 should be ready. 37c29cb
David Robinson dgrtwo Brought README.mkd up to date with new version (pull request #8). Als…
…o removed >>> in each of the example Python code snippets- they end up being confusing since they make it harder to cut-and-paste code. Incremented version.
d226b71
David Robinson dgrtwo Made Queryset Consistent, Moved user.py into __init__
Many changes:

- Removed the QueryManager class- all querying is now handled by the
Queryset class. This was necessary to manage chained query lines like

GameScore.Query.gte("score", 1000).lt("score",
2000).order("playerName")

- Queryset methods for comparison and options are now added
programatically (makes it easier to change their operation all at
once).

- Added test coverage of querying methods, including comparison
operators and limit/skip options.

- Removed user.py, moving its classes into `__init__.py`. The reason is
that lines like

import parse_rest
from parse_rest.user import User

parse_rest.APPLICATION_ID = "something"

meant the module attribute APPLICATION_ID was actually not accessible
from the User class. Perhaps there's a way to make this work- I just
couldn't find it yet.

- Set up createdAt and updatedAt to return date time objects rather
than strings
eaaccd9
David Robinson dgrtwo Fixed #9- can save Pointers to Objects
Fixed #9 by allowing ParseResources to be converted into Pointers and
back, such as when one object is set as an attribute of another. This
required:

- ParseResource now inherits Pointer
- All values are now converted to ParseType in the initialization of a
ParseResource
- The `_to_native` method of ParseResource was changed to `_to_dict`,
which allows `_to_native()` to construct a Pointer when one object is
an attribute of another.

Also:

- Added test coverage and example to README

- Fixed bug where many `from_native` methods used `self` instead of
`cls`

- `class_name` is converted to a string before being assigned to
`DerivedClass.__name__` (necessary because it is unicode when returned
from Parse)

- Changed TestUser to rely on parse_user.User instead of
parse_user.user.User
d33a47c
Uri Kogan typo correction in the name of the variable: decending -> descending d7a2276
David Robinson dgrtwo Merge pull request #10 from UrK/master
Small typo correction in parameter name
a8b7bdc
Raphael Lullis

Couple of things:

  1. To get the chaining of querysets, you could do Query.all().lte(...), not having the QueryManager does complicate things quite a bit to customize behavior of query classes. (As in the opened issue)
  2. Now I see why you moved back user.py to __init__.py. In all honesty, I'm not a fan of the original monkey-patching approach. I'd rather have a "setup" method that can be lazily loaded by ParseBase.
lullis and others added some commits
Raphael Lullis lullis Reverting handling of queries through QueryManagers, instead of simpl…
…y using Querysets. Added a the _fetch method on QueryManager to eliminate the dependency of querysets on model_class. Added a method to Queryset. Installation objects now are able to be imported. (Still need test cases, though.)
8f10396
Raphael Lullis lullis Returning to QueryManager eliminated the need to copy_method. Updated…
… README. Added a more friendlt repr method for Object and Users
5126512
Raphael Lullis lullis Fixing some notation for headers 87b95ec
Raphael Lullis lullis Fixing some notation for headers 2bf6ef9
Raphael Lullis lullis Fooled by parse. I thought that I could query by sessionToken in orde…
…r to authenticate users on the client end, but it looks like they (rightly) omit the sessionToken from the allowed queryable parameters, and return a list of all users. Removing tests that gave the idea that such thing was possible.
9fd21f1
Raphael Lullis lullis breaking apart files from __init__. Fixed bug on querying by date. Ad…
…ded some tests.
ff03798
Raphael Lullis lullis Missing import for urllib2, used on File 4035d50
Raphael Lullis lullis A phenomenal hack to allow us to have a connection parameter set. Imp…
…lemented a register method on ParseBase to replace the setting of parse_rest variables (that could not be seen on other modules). Application code can do from parse_rest.__init__ import ParseBase, and from there call register.
c43983b
Raphael Lullis lullis Basic changes on File data type. a47be58
Raphael Lullis lullis Forced authentication method. c54dd05
Raphael Lullis lullis Adding authentication methods, and figured out that Parse does return…
… sessionToken info if requests are made with master key set, so I'm adding a new way to setup the connection. Before any interaction with Parse, simply call ParseBase.register(app_id, rest_key, **optional_keys) and it will save them. So the library now becomes usable for applications both with or without the master key.
413d7ce
Raphael Lullis lullis Updating documentation 19b1163
Raphael Lullis lullis Improved _to_native method, added a few tests for data types. 1e16cff
Raphael Lullis lullis getting _editable_attrs to return a dict. That will make things easie…
…r to get only 'public' attributes from objects'
1098b32
Raphael Lullis lullis Finally getting rid of all the senseless code on __init__, and creati…
…ng the code (for exceptions) and connection (for access keys management and the ParseBase class) modules.
4323b99
Raphael Lullis lullis moving Function class from its own module to datatypes. 97ffeb4
Raphael Lullis lullis Updating README for latest changes in code a7bf688
Raphael Lullis lullis Small fixes to README formatting. 455c25a
Raphael Lullis lullis More updates to README. 5884cff
Raphael Lullis lullis More updates to README. 65c59fb
Raphael Lullis lullis Light reformatting in user module. 3a7d71e
Raphael Lullis lullis Getting installation module to work. Updating setup.py e7602e1
Miguel Galves mgalves Added new push optional parameters and the possibility to send a json…
… payload instead of a simple string message
b3e64ca
Miguel Galves mgalves keyword parameter type renamed to device_type. type is a reserved key…
…word
f8b31e5
Raphael Lullis lullis Found out that User.signup was not actually returning a valid User in…
…stance (missing username). Fixed and improved test to catch the problem.
d0909ac
Raphael Lullis lullis Merge pull request #1 from mgalves/master
Thanks for the work. I will break down the "send text" and "send json" into two distinct methods (Push.message and Push.alert?)
03c6d41
Raphael Lullis lullis breaking down of Push send into two distinct methods, Push.alert (for…
… json data) and Push.message (for text). Uses 'where' as an optional variable to make specific queries, and pass any other optional parameters through **kw)
3ba25c5
Raphael Lullis lullis Update README.mkd
Push and Installation Query seems to be working now. Removing from the list of "PLANNED" items.
befdd54
David Robinson dgrtwo Added ParseBatcher class to execute batch create, update or delete op…
…erations with a single API call, including test cases and documentation. Also added ability to log in a user using Facebook or Twitter authorization.
2ce1d83
Robert Hanacek bitmole Fix pip install command. 120da8e
David Robinson dgrtwo Merge pull request #18 from bitmole/master
Installation instructions
f94ed53
David Robinson dgrtwo Altered count method to use count query rather than fetching all obje…
…cts and finding the length
2f6bfd2
Clément Jourdain joucle add master key header only if no user session is specified fba36a5
David Robinson dgrtwo Merge pull request #20 from joucle/enhancement
Add master key header only if no user session is specified
6d31d58
Raphael Lullis lullis Django-style query filtering added. Documentation updated. 6ebd8e7
Raphael Lullis lullis Merged with latest from upstream 529e3d4
Raphael Lullis lullis Missed a couple of examples that were using 'where' instead of the ne…
…w 'filter'. Fixed.
2001c5e
Williams Mendez wm3ndez Python3 support af90f84
Raphael Lullis lullis Merge pull request #26 from wm3ndez/master
Python3 support
220a34f
David Robinson dgrtwo Relational queries weren't possible because as_pointer was False from…
… the Queryset, fixed and added test for relational query. Updated PyPI version.
8a0300d
xmxmichael

I think this fix caused this issue:
#28

jonmorehouse and others added some commits
Jon Morehouse jonmorehouse Update datatypes to check for circular pointer references. Change cir…
…cular pointer references so that they return a string instead of pointer object for the secondary reference
4f27354
David Robinson dgrtwo Merge pull request #29 from jumper/master
Update datatypes to check for circular pointer references. Change circul...
9e9bbd2
Rafael Bermúdez Guijo rbermudezg Add, Remove, and query Relations 3ace526
Adam Lane Enalmada Add emailVerified to protected_attributes
Without this change, you will not be able to save users with getting an error code":105,"error":"the emailVerified key is a reserved word.
f7c6dbb
David Robinson dgrtwo Merge pull request #33 from Enalmada/patch-1
Add emailVerified to protected_attributes
284b207
David Robinson dgrtwo Fixes #34: adds batch saving for User objects + test case 65bd316
David Robinson dgrtwo Added warning about CloudCode in response to #32 870dd6f
David Robinson dgrtwo Merge pull request #30 from rbermudezg/RelationalSupport
Add, Remove, and query Relations
e6785ea
David Robinson dgrtwo Added informative error message for 'Cannot filter for a constraint a…
…fter filtering for a specific value': e.g. filter(score=20).filter(score__lt=30).
728c492
rwitten rwitten Added saving url to File's "_to_native" 33ceefe
rwitten rwitten Update datatypes.py f54a68a
rwitten rwitten Update datatypes.py 0284fca
David Robinson dgrtwo Set up cloudcode directory as package_data so it would be included in…
… tests not run in the downloaded directory. Also set up a tests.run_tests() function so that one wouldn't have to download setup.py to run tests, and modified README. Fixes #37
18df9f5
David Robinson dgrtwo Merge pull request #38 from rwitten/master
Fixing buggy interaction with Cloud Code
bf23d6f
Yosi Taguri yosit convert Date to iso8601 with 3 digits microseconds 18d1642
David Robinson dgrtwo Merge pull request #42 from yosit/master
convert Date to iso8601 with 3 digits microseconds
de1be9a
David Robinson dgrtwo Update tests to be consistent with commit de1be9a 52f3163
David Robinson dgrtwo Added README and unit tests on Push functionality. Changed functional…
…ity slightly to allow channels= to be specified alongside a where= argument.
17baeac
David Robinson dgrtwo Updated version number to upload to PyPI e42060b
David Robinson dgrtwo Added documentation for request_password_reset method, closes #45 1dce775
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Showing 107 unique commits by 15 authors.

Apr 10, 2012
David Robinson dgrtwo Auth fixes, tests.py, increment method, pep8
Fixed authentication, since Parse appears to have changed their API.

Added tests.py with unit tests for ParseObject and ParseQuery classes.

Added ParseObject.increment(key) method.

pep8 compliance changes, mostly removing end of line whitespace
248b756
David Robinson dgrtwo Mention increment method in README and added test case for ParseObjec…
…t.delete().
205a4af
Nov 04, 2012
Billy Tobon billyto GeoPoint Support added
GeoPoints use string with format  POINT(lat lon)
da62808
Billy Tobon billyto added geoPoint documentation 5954e65
Billy Tobon billyto markdown tweaks 842c6c5
David Robinson dgrtwo Merge pull request #1 from billyto/master
geoPoints
10e3bb9
Nov 05, 2012
David Robinson dgrtwo Changes for pep8 compliance 99fb1d2
Nov 10, 2012
Billy Tobon billyto fix regex bug
regex wasn't parsing negative latitudes.
6c3c6b3
Nov 18, 2012
Billy Tobon billyto Update README.mkd
fliping POINT(latitude longitude) to POINT(longitude latitude)
557d9ad
Billy Tobon billyto flipping lat - lon on point creation. 86481f6
Dec 17, 2012
Raphael Lullis lullis Adding a very basic setup.py file to make it pip-installable. 25b176f
Raphael Lullis lullis Moving code to its own folder, so we can have a namespaced package. R…
…eflecting changes in README
125b0e2
Raphael Lullis lullis Adding 'build' folder to gitignore 3e5d1ae
Raphael Lullis lullis Needed to add 'packages' parameter to setup.py. 5a774a6
Raphael Lullis lullis Cleaning up the code a little bit, and removing all print statements …
…from code (WSGI does not accept them)
6bdb286
Dec 24, 2012
Raphael Lullis lullis The code was doing request using the master key as a parameter for th…
…e request. This was completely wrong. The correct key is API_KEY. Fixed.
7f65147
Raphael Lullis lullis Updated README. 11354d7
Raphael Lullis lullis Updating test suite to reflect changes in MASTER -> API KEY e33d167
Jan 09, 2013
Raphael Lullis lullis Adding support to ParseUser handling, account creation, login, and qu…
…erying (through ParseUserQuery class)
7228a08
Raphael Lullis lullis Removing all the Parse prefix name from classes (except for ParseBase…
… and ParseResource, which are classes that are just base classes). Also, adding a very basic Push class.
fd97cac
Raphael Lullis lullis Updating README to reflect changes in class names. 949881c
Jan 10, 2013
Adam Awan funkotron Replacing MASTER_KEY with REST_API_KEY 0907274
David Robinson dgrtwo Merge pull request #2 from funkotron/master
Rename MASTER_KEY to REST_API_KEY
4ff5f0d
Jan 11, 2013
David Robinson dgrtwo Changed Query to ObjectQuery in a few locations, especially in tests.py c321554
Jan 14, 2013
David Robinson dgrtwo Changed name and folder of package to 'parse_rest' to conform with pe…
…p8 standards, along with associated tests.
bf35957
David Robinson dgrtwo Made changes consistent between commits (REST_API_KEY), also changed …
…README to refer to parse_rest, and changed contact email to mine.
060d8a0
David Robinson dgrtwo Some modifications for uploading to PyPI, to setup.py and to .gitignore bc7aa3c
David Robinson dgrtwo Modifications to make fully pep8 compliant e5b911a
David Robinson dgrtwo Added installation information to README. Added 'python setup.py test…
…' command.
895cb68
Jan 19, 2013
David Robinson dgrtwo Added Function class to handle calling CloudCode functions. Also adde…
…d test cases for hello and averageStars functions, which necessitated adding a cloudcode directory to upload those functions to the test application, as well as requiring MASTER_KEY in the settings_local.py file. Added CloudCode documentation to README.mkd.
b640110
David Robinson dgrtwo Updated version number for PyPI 036b135
Jan 23, 2013
Uri Kogan fixing request URL creation (_absolute_url) 8be8e1c
David Robinson dgrtwo Merge pull request #5 from UrK/master
Incorrect usage of string join method
2138893
David Robinson dgrtwo Incremented version for PyPI c715cc3
Jan 25, 2013
David Robinson dgrtwo Redesigned User class so that it could work as an object (adding and …
…saving attributes, added delete method, and lets it save a session token). Required changing UserQuery as well. Added a few appropriate test cases in TestUser class. Finally, added ParseError exception class. Addresses issue in #5.
733651f
David Robinson dgrtwo Added details of User class to README.mkd. Added exception if APPLICA…
…TION_ID or REST_API_KEY is not set. Changed parse to parse_rest in a few lines of the docs.
bde3cfc
Jan 28, 2013
David Robinson dgrtwo Added ability to access a parse_rest.Object like a dictionary or an i…
…terable, along with test cases for that functionality.
60755bf
Jan 30, 2013
David Robinson dgrtwo Fixed #6, added relevant test code 5905c7b
Jan 31, 2013
Raphael Lullis lullis Lots of changes. Too many to put in a single commit message. In short…
…, transformed Query objects into a method that belongs to ResourceClass. Also, added classes to represent ParseTypes. It should be possible to use these transparently. Added tests\!
2b5a26b
Feb 03, 2013
David Robinson dgrtwo Pep8 compliance changes 250d369
David Robinson dgrtwo Set up the QueryManager in a metaclass instead of in the __new__ meth…
…od, which means one doesn't have to instantiate an instance of the class before querying it. Fixed the __iter__ method of Queryset, which yielded only the first object from the query. Put back test cases for CloudCode functions.
4d66c30
Feb 04, 2013
David Robinson dgrtwo Fixed merge conflicts- new version as per #8 should be ready. 37c29cb
David Robinson dgrtwo Brought README.mkd up to date with new version (pull request #8). Als…
…o removed >>> in each of the example Python code snippets- they end up being confusing since they make it harder to cut-and-paste code. Incremented version.
d226b71
Feb 06, 2013
David Robinson dgrtwo Made Queryset Consistent, Moved user.py into __init__
Many changes:

- Removed the QueryManager class- all querying is now handled by the
Queryset class. This was necessary to manage chained query lines like

GameScore.Query.gte("score", 1000).lt("score",
2000).order("playerName")

- Queryset methods for comparison and options are now added
programatically (makes it easier to change their operation all at
once).

- Added test coverage of querying methods, including comparison
operators and limit/skip options.

- Removed user.py, moving its classes into `__init__.py`. The reason is
that lines like

import parse_rest
from parse_rest.user import User

parse_rest.APPLICATION_ID = "something"

meant the module attribute APPLICATION_ID was actually not accessible
from the User class. Perhaps there's a way to make this work- I just
couldn't find it yet.

- Set up createdAt and updatedAt to return date time objects rather
than strings
eaaccd9
David Robinson dgrtwo Fixed #9- can save Pointers to Objects
Fixed #9 by allowing ParseResources to be converted into Pointers and
back, such as when one object is set as an attribute of another. This
required:

- ParseResource now inherits Pointer
- All values are now converted to ParseType in the initialization of a
ParseResource
- The `_to_native` method of ParseResource was changed to `_to_dict`,
which allows `_to_native()` to construct a Pointer when one object is
an attribute of another.

Also:

- Added test coverage and example to README

- Fixed bug where many `from_native` methods used `self` instead of
`cls`

- `class_name` is converted to a string before being assigned to
`DerivedClass.__name__` (necessary because it is unicode when returned
from Parse)

- Changed TestUser to rely on parse_user.User instead of
parse_user.user.User
d33a47c
Feb 07, 2013
Uri Kogan typo correction in the name of the variable: decending -> descending d7a2276
David Robinson dgrtwo Merge pull request #10 from UrK/master
Small typo correction in parameter name
a8b7bdc
Feb 09, 2013
Raphael Lullis lullis Reverting handling of queries through QueryManagers, instead of simpl…
…y using Querysets. Added a the _fetch method on QueryManager to eliminate the dependency of querysets on model_class. Added a method to Queryset. Installation objects now are able to be imported. (Still need test cases, though.)
8f10396
Raphael Lullis lullis Returning to QueryManager eliminated the need to copy_method. Updated…
… README. Added a more friendlt repr method for Object and Users
5126512
Raphael Lullis lullis Fixing some notation for headers 87b95ec
Raphael Lullis lullis Fixing some notation for headers 2bf6ef9
Raphael Lullis lullis Fooled by parse. I thought that I could query by sessionToken in orde…
…r to authenticate users on the client end, but it looks like they (rightly) omit the sessionToken from the allowed queryable parameters, and return a list of all users. Removing tests that gave the idea that such thing was possible.
9fd21f1
Feb 26, 2013
Raphael Lullis lullis breaking apart files from __init__. Fixed bug on querying by date. Ad…
…ded some tests.
ff03798
Raphael Lullis lullis Missing import for urllib2, used on File 4035d50
Raphael Lullis lullis A phenomenal hack to allow us to have a connection parameter set. Imp…
…lemented a register method on ParseBase to replace the setting of parse_rest variables (that could not be seen on other modules). Application code can do from parse_rest.__init__ import ParseBase, and from there call register.
c43983b
Raphael Lullis lullis Basic changes on File data type. a47be58
Raphael Lullis lullis Forced authentication method. c54dd05
Feb 27, 2013
Raphael Lullis lullis Adding authentication methods, and figured out that Parse does return…
… sessionToken info if requests are made with master key set, so I'm adding a new way to setup the connection. Before any interaction with Parse, simply call ParseBase.register(app_id, rest_key, **optional_keys) and it will save them. So the library now becomes usable for applications both with or without the master key.
413d7ce
Raphael Lullis lullis Updating documentation 19b1163
Feb 28, 2013
Raphael Lullis lullis Improved _to_native method, added a few tests for data types. 1e16cff
Raphael Lullis lullis getting _editable_attrs to return a dict. That will make things easie…
…r to get only 'public' attributes from objects'
1098b32
Mar 10, 2013
Raphael Lullis lullis Finally getting rid of all the senseless code on __init__, and creati…
…ng the code (for exceptions) and connection (for access keys management and the ParseBase class) modules.
4323b99
Raphael Lullis lullis moving Function class from its own module to datatypes. 97ffeb4
Raphael Lullis lullis Updating README for latest changes in code a7bf688
Raphael Lullis lullis Small fixes to README formatting. 455c25a
Mar 11, 2013
Raphael Lullis lullis More updates to README. 5884cff
Raphael Lullis lullis More updates to README. 65c59fb
Mar 12, 2013
Raphael Lullis lullis Light reformatting in user module. 3a7d71e
Raphael Lullis lullis Getting installation module to work. Updating setup.py e7602e1
Mar 14, 2013
Miguel Galves mgalves Added new push optional parameters and the possibility to send a json…
… payload instead of a simple string message
b3e64ca
Miguel Galves mgalves keyword parameter type renamed to device_type. type is a reserved key…
…word
f8b31e5
Mar 15, 2013
Raphael Lullis lullis Found out that User.signup was not actually returning a valid User in…
…stance (missing username). Fixed and improved test to catch the problem.
d0909ac
Raphael Lullis lullis Merge pull request #1 from mgalves/master
Thanks for the work. I will break down the "send text" and "send json" into two distinct methods (Push.message and Push.alert?)
03c6d41
Raphael Lullis lullis breaking down of Push send into two distinct methods, Push.alert (for…
… json data) and Push.message (for text). Uses 'where' as an optional variable to make specific queries, and pass any other optional parameters through **kw)
3ba25c5
Raphael Lullis lullis Update README.mkd
Push and Installation Query seems to be working now. Removing from the list of "PLANNED" items.
befdd54
Mar 24, 2013
David Robinson dgrtwo Added ParseBatcher class to execute batch create, update or delete op…
…erations with a single API call, including test cases and documentation. Also added ability to log in a user using Facebook or Twitter authorization.
2ce1d83
Mar 26, 2013
Robert Hanacek bitmole Fix pip install command. 120da8e
David Robinson dgrtwo Merge pull request #18 from bitmole/master
Installation instructions
f94ed53
Mar 27, 2013
David Robinson dgrtwo Altered count method to use count query rather than fetching all obje…
…cts and finding the length
2f6bfd2
May 29, 2013
Clément Jourdain joucle add master key header only if no user session is specified fba36a5
David Robinson dgrtwo Merge pull request #20 from joucle/enhancement
Add master key header only if no user session is specified
6d31d58
Jul 27, 2013
Raphael Lullis lullis Django-style query filtering added. Documentation updated. 6ebd8e7
Raphael Lullis lullis Merged with latest from upstream 529e3d4
Raphael Lullis lullis Missed a couple of examples that were using 'where' instead of the ne…
…w 'filter'. Fixed.
2001c5e
Jul 31, 2013
Williams Mendez wm3ndez Python3 support af90f84
Aug 07, 2013
Raphael Lullis lullis Merge pull request #26 from wm3ndez/master
Python3 support
220a34f
Oct 01, 2013
David Robinson dgrtwo Relational queries weren't possible because as_pointer was False from…
… the Queryset, fixed and added test for relational query. Updated PyPI version.
8a0300d
Jan 07, 2014
Jon Morehouse jonmorehouse Update datatypes to check for circular pointer references. Change cir…
…cular pointer references so that they return a string instead of pointer object for the secondary reference
4f27354
Jan 10, 2014
David Robinson dgrtwo Merge pull request #29 from jumper/master
Update datatypes to check for circular pointer references. Change circul...
9e9bbd2
Jan 21, 2014
Rafael Bermúdez Guijo rbermudezg Add, Remove, and query Relations 3ace526
Apr 08, 2014
Adam Lane Enalmada Add emailVerified to protected_attributes
Without this change, you will not be able to save users with getting an error code":105,"error":"the emailVerified key is a reserved word.
f7c6dbb
David Robinson dgrtwo Merge pull request #33 from Enalmada/patch-1
Add emailVerified to protected_attributes
284b207
Apr 09, 2014
David Robinson dgrtwo Fixes #34: adds batch saving for User objects + test case 65bd316
David Robinson dgrtwo Added warning about CloudCode in response to #32 870dd6f
David Robinson dgrtwo Merge pull request #30 from rbermudezg/RelationalSupport
Add, Remove, and query Relations
e6785ea
Apr 10, 2014
David Robinson dgrtwo Added informative error message for 'Cannot filter for a constraint a…
…fter filtering for a specific value': e.g. filter(score=20).filter(score__lt=30).
728c492
Apr 17, 2014
rwitten rwitten Added saving url to File's "_to_native" 33ceefe
rwitten rwitten Update datatypes.py f54a68a
rwitten rwitten Update datatypes.py 0284fca
David Robinson dgrtwo Set up cloudcode directory as package_data so it would be included in…
… tests not run in the downloaded directory. Also set up a tests.run_tests() function so that one wouldn't have to download setup.py to run tests, and modified README. Fixes #37
18df9f5
David Robinson dgrtwo Merge pull request #38 from rwitten/master
Fixing buggy interaction with Cloud Code
bf23d6f
Jun 28, 2014
Yosi Taguri yosit convert Date to iso8601 with 3 digits microseconds 18d1642
Jun 30, 2014
David Robinson dgrtwo Merge pull request #42 from yosit/master
convert Date to iso8601 with 3 digits microseconds
de1be9a
Jul 03, 2014
David Robinson dgrtwo Update tests to be consistent with commit de1be9a 52f3163
David Robinson dgrtwo Added README and unit tests on Push functionality. Changed functional…
…ity slightly to allow channels= to be specified alongside a where= argument.
17baeac
Jul 04, 2014
David Robinson dgrtwo Updated version number to upload to PyPI e42060b
Jul 11, 2014
David Robinson dgrtwo Added documentation for request_password_reset method, closes #45 1dce775
This page is out of date. Refresh to see the latest.
9 .gitignore
... ... @@ -0,0 +1,9 @@
  1 +
  2 +.DS_Store
  3 +*.pyc
  4 +*.pyo
  5 +build/*
  6 +settings_local.py
  7 +dist
  8 +MANIFEST
  9 +global.json
452 README.mkd
Source Rendered
... ... @@ -1,47 +1,166 @@
1   -ParsePy
2   -=======
  1 +parse_rest
  2 +==========
3 3
4   -**ParsePy** is a Python client for the [Parse REST API](https://www.parse.com/docs/rest). It provides Python object mapping for Parse objects with methods to save, update, and delete objects, as well as an interface for querying stored objects.
  4 +**parse_rest** is a Python client for the [Parse REST
  5 + API](https://www.parse.com/docs/rest). It provides:
5 6
6   -Basic Usage
  7 + - Python object mapping for Parse objects with methods to save,
  8 + update, and delete objects, as well as an interface for querying
  9 + stored objects.
  10 + - Complex data types provided by Parse with no python equivalent
  11 + - User authentication, account creation** (signup) and querying.
  12 + - Cloud code integration
  13 + - Installation querying
  14 + - push
  15 + - **PLANNED/TODO**: Roles/ACLs**
  16 + - **PLANNED/TODO**: Image/File type support
  17 +
  18 +
  19 +** for applications with access to the MASTER KEY, see details below.
  20 +
  21 +
  22 +Installation
  23 +------------
  24 +
  25 +The easiest way to install this package is by downloading or
  26 +cloning this repository:
  27 +
  28 + pip install git+https://github.com/dgrtwo/ParsePy.git
  29 +
  30 +Note: The version on [PyPI](http://pypi.python.org/pypi) is not
  31 +up-to-date. The code is still under lots of changes and the stability
  32 +of the library API - though improving - is not guaranteed. Please
  33 +file any issues that you may find if documentation/application.
  34 +
  35 +Testing
  36 +-------
  37 +
  38 +To run the tests, you need to:
  39 +
  40 +* create a `settings_local.py` file in your local directory with three
  41 + variables that define a sample Parse application to use for testing:
  42 +
  43 +~~~~~ {python}
  44 +APPLICATION_ID = "APPLICATION_ID_HERE"
  45 +REST_API_KEY = "REST_API_KEY_HERE"
  46 +MASTER_KEY = "MASTER_KEY_HERE"
  47 +~~~~~
  48 +
  49 +Note Do **not** give the keys of an existing application with data you want to
  50 +keep: create a new one instead. The test suite will erase any existing CloudCode
  51 +in the app and may accidentally replace or change existing objects.
  52 +
  53 +* install the [Parse CloudCode tool](https://www.parse.com/docs/cloud_code_guide)
  54 +
  55 +You can then test the installation by running the following in a Python prompt:
  56 +
  57 + from parse_rest import tests
  58 + tests.run_tests()
  59 +
  60 +
  61 +Usage
7 62 -----------
8 63
9   -Let's get everything set up first. You'll need to give **ParsePy** your _Application Id_ and _Master Key_ (available from your Parse dashboard) in order to get access to your data.
  64 +Before the first interaction with the Parse server, you need to
  65 +register your access credentials. You can do so by calling
  66 +`parse_rest.connection.register`.
  67 +
  68 +Before getting to code, a word of caution. You need to consider how your application is
  69 +meant to be deployed. Parse identifies your application though
  70 +different keys (available from your Parse dashboard) that are used in
  71 +every request done to their servers.
  72 +
  73 +If your application is supposed to be distributed to third parties
  74 +(such as a desktop program to be installed), you SHOULD NOT put the
  75 +master key in your code. If your application is meant to be running in
  76 +systems that you fully control (e.g, a web app that needs to integrate
  77 +with Parse to provide functionality to your client), you may also add
  78 +your *master key*.
  79 +
  80 +~~~~~ {python}
  81 +from parse_rest.connection import register
  82 +register(<application_id>, <rest_api_key>[, master_key=None])
  83 +~~~~~
  84 +
  85 +Once your application calls `register`, you will be able to read, write
  86 +and query for data at Parse.
  87 +
  88 +
  89 +Data types
  90 +----------
  91 +
  92 +Parse allows us to get data in different base types that have a direct
  93 +python equivalent (strings, integers, floats, dicts, lists) as well as
  94 +some more complex ones (e.g.:`File`, `Image`, `Date`). It also allows
  95 +us to define objects with schema-free structure, and save them, as
  96 +well to query them later by their attributes. `parse_rest` is
  97 +handy as a way to serialize/deserialize these objects transparently.
  98 +
  99 +The Object type
  100 +---------------
  101 +
  102 +
  103 +In theory, you are able to simply instantiate a `Object` and do
  104 +everything that you want with it, save it on Parse, retrieve it later,
  105 +etc.
  106 +
  107 +~~~~~ {python}
  108 +from parse_rest.datatypes import Object
  109 +
  110 +first_object = Object()
  111 +~~~~~
  112 +
  113 +In practice, you will probably want different classes for your
  114 +application to allow for a better organization in your own code.
  115 +So, let's say you want to make an online game, and you want to save
  116 +the scoreboard on Parse. For that, you decide to define a class called
  117 +`GameScore`. All you need to do to create such a class is to define a
  118 +Python class that inherts from `parse_rest.datatypes.Object`:
10 119
11 120 ~~~~~ {python}
12   ->>> import ParsePy
13   ->>> ParsePy.APPLICATION_ID = "your application id"
14   ->>> ParsePy.MASTER_KEY = "your master key here"
  121 +from parse_rest.datatypes import Object
  122 +
  123 +class GameScore(Object):
  124 + pass
15 125 ~~~~~
16 126
17   -To create a new object of the Parse class _GameScore_:
  127 +And then instantiate it with your parameters:
18 128
19 129 ~~~~~ {python}
20   ->>> gameScore = ParsePy.ParseObject("GameScore")
21   ->>> gameScore.score = 1337
22   ->>> gameScore.playerName = "Sean Plott"
23   ->>> gameScore.cheatMode = False
  130 +gameScore = GameScore(score=1337, player_name='John Doe', cheat_mode=False)
24 131 ~~~~~
25 132
26   -As you can see, we add new properties simply by assigning values to our _ParseObject_'s attributes. Supported data types are any type that can be serialized by JSON and Python's _datetime.datetime_ object. (Binary data and references to other _ParseObject_'s are also supported, as we'll see in a minute.)
  133 +You can change or set new parameters afterwards:
  134 +
  135 +~~~~ {python}
  136 +gameScore.cheat_mode = True
  137 +gameScore.level = 20
  138 +~~~~
27 139
28 140 To save our new object, just call the save() method:
29 141
30 142 ~~~~~ {python}
31   ->>> gameScore.save()
  143 +gameScore.save()
  144 +~~~~~
  145 +
  146 +If we want to make an update, just call save() again after modifying
  147 +an attribute to send the changes to the server:
  148 +
  149 +~~~~~ {python}
  150 +gameScore.score = 2061
  151 +gameScore.save()
32 152 ~~~~~
33 153
34   -If we want to make an update, just call save() again after modifying an attribute to send the changes to the server:
  154 +You can also increment the score in a single API query:
35 155
36 156 ~~~~~ {python}
37   ->>> gameScore.score = 2061
38   ->>> gameScore.save()
  157 +gameScore.increment("score")
39 158 ~~~~~
40 159
41 160 Now that we've done all that work creating our first Parse object, let's delete it:
42 161
43 162 ~~~~~ {python}
44   ->>> gameScore.delete()
  163 +gameScore.delete()
45 164 ~~~~~
46 165
47 166 That's it! You're ready to start saving data on Parse.
@@ -49,74 +168,289 @@ That's it! You're ready to start saving data on Parse.
49 168 Object Metadata
50 169 ---------------
51 170
52   -The methods objectId(), createdAt(), and updatedAt() return metadata about a _ParseObject_ that cannot be modified through the API:
  171 +The attributes objectId, createdAt, and updatedAt show metadata about
  172 +a _Object_ that cannot be modified through the API:
53 173
54 174 ~~~~~ {python}
55   ->>> gameScore.objectId()
56   -'xxwXx9eOec'
57   ->>> gameScore.createdAt()
58   -datetime.datetime(2011, 9, 16, 21, 51, 36, 784000)
59   ->>> gameScore.updatedAt()
60   -datetime.datetime(2011, 9, 118, 14, 18, 23, 152000)
  175 +gameScore.objectId
  176 +# 'xxwXx9eOec'
  177 +gameScore.createdAt
  178 +# datetime.datetime(2011, 9, 16, 21, 51, 36, 784000)
  179 +gameScore.updatedAt
  180 +# datetime.datetime(2011, 9, 118, 14, 18, 23, 152000)
61 181 ~~~~~
62 182
63 183 Additional Datatypes
64 184 --------------------
65 185
66   -If we want to store data in a ParseObject, we should wrap it in a ParseBinaryDataWrapper. The ParseBinaryDataWrapper behaves just like a string, and inherits all of _str_'s methods.
  186 +We've mentioned that Parse supports more complex types, most of these
  187 +types are also supported on Python (dates, files). So these types can
  188 +be converted transparently when you use them. For the types that Parse
  189 +provided and Python does not support natively, `parse_rest` provides
  190 +the appropiates classes to work with them. One such example is
  191 +`GeoPoint`, where you store latitude and longitude
67 192
68 193 ~~~~~ {python}
69   ->>> gameScore.victoryImage = ParsePy.ParseBinaryDataWrapper('\x03\xf3\r\n\xc7\x81\x7fNc ... ')
  194 +from parse_rest.datatypes import Object, GeoPoint
  195 +
  196 +class Restaurant(Object):
  197 + pass
  198 +
  199 +restaurant = Restaurant(name="Los Pollos Hermanos")
  200 +# coordinates as floats.
  201 +restaurant.location = GeoPoint(latitude=12.0, longitude=-34.45)
  202 +restaurant.save()
  203 +~~~~~
  204 +
  205 +We can store a reference to another Object by assigning it to an attribute:
  206 +
  207 +~~~~~ {python}
  208 +from parse_rest.datatypes import Object
  209 +
  210 +class CollectedItem(Object):
  211 + pass
  212 +
  213 +collectedItem = CollectedItem(type="Sword", isAwesome=True)
  214 +collectedItem.save() # we have to save it before it can be referenced
  215 +
  216 +gameScore.item = collectedItem
70 217 ~~~~~
71 218
72   -We can store a reference to another ParseObject by assigning it to an attribute:
  219 +Batch Operations
  220 +----------------
  221 +
  222 +For the sake of efficiency, Parse also supports creating, updating or deleting objects in batches using a single query, which saves on network round trips. You can perform such batch operations using the `connection.ParseBatcher` object:
73 223
74 224 ~~~~~ {python}
75   ->>> collectedItem = ParsePy.ParseObject("CollectedItem")
76   ->>> collectedItem.type = "Sword"
77   ->>> collectedItem.isAwesome = True
78   ->>> collectedItem.save() # we have to save it before it can be referenced
  225 +from parse_rest.connection import ParseBatcher
  226 +
  227 +score1 = GameScore(score=1337, player_name='John Doe', cheat_mode=False)
  228 +score2 = GameScore(score=1400, player_name='Jane Doe', cheat_mode=False)
  229 +score3 = GameScore(score=2000, player_name='Jack Doe', cheat_mode=True)
  230 +scores = [score1, score2, score3]
79 231
80   ->>> gameScore.item = collectedItem
  232 +batcher = ParseBatcher()
  233 +batcher.batch_save(scores)
  234 +batcher.batch_delete(scores)
  235 +~~~~~
  236 +
  237 +You can also mix `save` and `delete` operations in the same query as follows (note the absence of parentheses after each `save` or `delete`):
  238 +
  239 +~~~~~ {python}
  240 +batcher.batch([score1.save, score2.save, score3.delete])
81 241 ~~~~~
82 242
83 243 Querying
84 244 --------
85 245
86   -To retrieve an object with a Parse class of _GameScore_ and an _objectId_ of _xxwXx9eOec_, run:
  246 +Any class inheriting from `parse_rest.Object` has a `Query`
  247 +object. With it, you can perform queries that return a set of objects
  248 +or that will return a object directly.
  249 +
  250 +
  251 +### Retrieving a single object
  252 +
  253 +To retrieve an object with a Parse class of `GameScore` and an
  254 +`objectId` of `xxwXx9eOec`, run:
  255 +
  256 +~~~~~ {python}
  257 +gameScore = GameScore.Query.get(objectId="xxwXx9eOec")
  258 +~~~~~
  259 +
  260 +### Working with Querysets
  261 +
  262 +To query for sets of objects, we work with the concept of
  263 +`Queryset`s. If you are familiar with Django you will be right at home
  264 +\- but be aware that is not a complete implementation of their
  265 +Queryset or Database backend.
  266 +
  267 +The Query object contains a method called `all()`, which will return a
  268 +basic (unfiltered) Queryset. It will represent the set of all objects
  269 +of the class you are querying.
  270 +
  271 +~~~~~ {python}
  272 +all_scores = GameScore.Query.all()
  273 +~~~~~
  274 +
  275 +Querysets are _lazily evaluated_, meaning that it will only actually
  276 +make a request to Parse when you either call a method that needs to
  277 +operate on the data, or when you iterate on the Queryset.
  278 +
  279 +#### Filtering
  280 +
  281 +Like Django, Querysets can have constraints added by appending the name of the filter operator to name of the attribute:
  282 +
  283 +~~~~~ {python}
  284 +high_scores = GameScore.Query.filter(score__gte=1000)
  285 +~~~~~
  286 +
  287 +You can see the [full list of constraint operators defined by
  288 +Parse](https://www.parse.com/docs/rest#queries-constraints)
  289 +
  290 +
  291 +#### Sorting/Ordering
  292 +
  293 +Querysets can also be ordered. Just define the name of the attribute
  294 +that you want to use to sort. Appending a "-" in front of the name
  295 +will sort the set in descending order.
  296 +
  297 +~~~~~ {python}
  298 +low_to_high_score_board = GameScore.Query.all().order_by("score")
  299 +high_to_low_score_board = GameScore.Query.all().order_by("-score") # or order_by("score", descending=True)
  300 +~~~~~
  301 +
  302 +#### Limit/Skip
  303 +
  304 +If you don't want the whole set, you can apply the
  305 +limit and skip function. Let's say you have a have classes
  306 +representing a blog, and you want to implement basic pagination:
  307 +
  308 +~~~~~ {python}
  309 +posts = Post.Query.all().order_by("-publication_date")
  310 +page_one = posts.limit(10) # Will return the most 10 recent posts.
  311 +page_two = posts.skip(10).limit(10) # Will return posts 11-20
  312 +~~~~~
  313 +
  314 +#### Composability/Chaining of Querysets
  315 +
  316 +The example above can show the most powerful aspect of Querysets, that
  317 +is the ability to make complex querying and filtering by chaining calls:
  318 +
  319 +Most importantly, Querysets can be chained together. This allows you
  320 +to make more complex queries:
  321 +
  322 +~~~~~ {python}
  323 +posts_by_joe = Post.Query.all().filter(author='Joe').order_by("view_count")
  324 +popular_posts = posts_by_joe.gte(view_count=200)
  325 +~~~~~
  326 +
  327 +#### Iterating on Querysets
  328 +
  329 +After all the querying/filtering/sorting, you will probably want to do
  330 +something with the results. Querysets can be iterated on:
  331 +
  332 +~~~~~ {python}
  333 +posts_by_joe = Post.Query.all().filter(author='Joe').order_by('view_count')
  334 +for post in posts_by_joe:
  335 + print post.title, post.publication_date, post.text
  336 +~~~~~
  337 +
  338 +**TODO**: Slicing of Querysets
  339 +
  340 +
  341 +Users
  342 +-----
  343 +
  344 +You can sign up, log in, modify or delete users as well, using the `parse_rest.user.User` class. You sign a user up as follows:
  345 +
  346 +~~~~~ {python}
  347 +from parse_rest.user import User
  348 +
  349 +u = User.signup("dhelmet", "12345", phone="555-555-5555")
  350 +~~~~~
  351 +
  352 +or log in an existing user with
  353 +
  354 +~~~~~ {python}
  355 +u = User.login("dhelmet", "12345")
  356 +~~~~~
  357 +
  358 +You can also request a password reset for a specific user with
  359 +
  360 +~~~~~ {python}
  361 +User.request_password_reset(email="dhelmet@gmail.com")
  362 +~~~~~
  363 +
  364 +If you'd like to log in a user with Facebook or Twitter, and have already obtained an access token (including a user ID and expiration date) to do so, you can log in like this:
  365 +
  366 +~~~~ {python}
  367 +authData = {"facebook": {"id": fbID, "access_token": access_token,
  368 + "expiration_date": expiration_date}}
  369 +u = User.login_auth(authData)
  370 +~~~~
  371 +
  372 +Once a `User` has been logged in, it saves its session so that it can be edited or deleted:
  373 +
  374 +~~~~~ {python}
  375 +u.highscore = 300
  376 +u.save()
  377 +u.delete()
  378 +~~~~~
  379 +
  380 +Push
  381 +---------------
  382 +
  383 +You can also send notifications to your users using [Parse's Push functionality](https://parse.com/products/push), through the Push object:
87 384
88 385 ~~~~~ {python}
89   ->>> gameScore = ParsePy.ParseQuery("GameScore").get("xxwXx9eOec")
  386 +from parse_rest.installation import Push
  387 +
  388 +Push.message("The Giants won against the Mets 2-3.",
  389 + channels=["Giants", "Mets"])
90 390 ~~~~~
91 391
92   -We can also run more complex queries to retrieve a range of objects. For example, if we want to get a list of _GameScore_ objects with scores between 1000 and 2000 ordered by _playerName_, we would call:
  392 +This will push a message to all users subscribed to the "Giants" and "Mets" channels. Your alert can be restricted based on [Advanced Targeting](https://www.parse.com/docs/push_guide#sending-queries/REST) by specifying the `where` argument:
93 393
94 394 ~~~~~ {python}
95   ->>> query = ParsePy.ParseQuery("GameScore")
96   ->>> query = query.gte("score", 1000).lt("score", 2000).order("playerName")
97   ->>> GameScores = query.fetch()
  395 +Push.message("Willie Hayes injured by own pop fly.",
  396 + channels=["Giants"], where={"injuryReports": True})
  397 +
  398 +Push.message("Giants scored against the A's! It's now 2-2.",
  399 + channels=["Giants"], where={"scores": True})
98 400 ~~~~~
99 401
100   -Notice how queries are built by chaining filter functions. The available filter functions are:
  402 +If you wish to include more than a simple message in your notification, such as incrementing the app badge in iOS or adding a title in Android, use the `alert` method and pass the actions in a dictionary:
101 403
102   -* **Less Than**
103   - * lt(_parameter_name_, _value_)
104   -* **Less Than Or Equal To**
105   - * lte(_parameter_name_, _value_)
106   -* **Greater Than**
107   - * gt(_parameter_name_, _value_)
108   -* **Greater Than Or Equal To**
109   - * gte(_parameter_name_, _value_)
110   -* **Not Equal To**
111   - * ne(_parameter_name_, _value_)
112   -* **Limit**
113   - * limit(_count_)
114   -* **Skip**
115   - * skip(_count_)
  404 +~~~~~ {python}
  405 +Push.alert({"alert": "The Mets scored! The game is now tied 1-1.",
  406 + "badge": "Increment", "title": "Mets Score"}, channels=["Mets"],
  407 + where={"scores": True})
  408 +~~~~~
116 409
117   -We can also order the results using:
118 410
119   -* **Order**
120   - * order(_parameter_name_, _decending_=False)
  411 +Cloud Functions
  412 +---------------
  413 +
  414 +Parse offers [CloudCode](https://www.parse.com/docs/cloud_code_guide), which has the ability to upload JavaScript functions that will be run on the server. You can use the `parse_rest` client to call those functions.
  415 +
  416 +The CloudCode guide describes how to upload a function to the server. Let's say you upload the following `main.js` script:
  417 +
  418 +~~~~~ {javascript}
  419 +Parse.Cloud.define("hello", function(request, response) {
  420 + response.success("Hello world!");
  421 +});
  422 +
  423 +
  424 +Parse.Cloud.define("averageStars", function(request, response) {
  425 + var query = new Parse.Query("Review");
  426 + query.equalTo("movie", request.params.movie);
  427 + query.find({
  428 + success: function(results) {
  429 + var sum = 0;
  430 + for (var i = 0; i < results.length; ++i) {
  431 + sum += results[i].get("stars");
  432 + }
  433 + response.success(sum / results.length);
  434 + },
  435 + error: function() {
  436 + response.error("movie lookup failed");
  437 + }
  438 + });
  439 +});
  440 +~~~~~
  441 +
  442 +Then you can call either of these functions using the `parse_rest.datatypes.Function` class:
  443 +
  444 +~~~~~ {python}
  445 +from parse_rest.datatypes import Function
  446 +
  447 +hello_func = Function("hello")
  448 +hello_func()
  449 +{u'result': u'Hello world!'}
  450 +star_func = Function("averageStars")
  451 +star_func(movie="The Matrix")
  452 +{u'result': 4.5}
  453 +~~~~~
  454 +
121 455
122   -That's it! This is a first try at a Python library for Parse, and is probably not bug-free. If you run into any issues, please get in touch -- parsepy@paulkastner.com. Thanks!
  456 +That's it! This is a first try at a Python library for Parse, and is probably not bug-free. If you run into any issues, please get in touch -- dgrtwo@princeton.edu. Thanks!
255 __init__.py
... ... @@ -1,255 +0,0 @@
1   -# This program is free software: you can redistribute it and/or modify
2   -# it under the terms of the GNU General Public License as published by
3   -# the Free Software Foundation, either version 3 of the License, or
4   -# (at your option) any later version.
5   -#
6   -# This program is distributed in the hope that it will be useful,
7   -# but WITHOUT ANY WARRANTY; without even the implied warranty of
8   -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9   -# GNU General Public License for more details.
10   -#
11   -# You should have received a copy of the GNU General Public License
12   -# along with this program. If not, see <http://www.gnu.org/licenses/>.
13   -
14   -import urllib, urllib2
15   -import base64
16   -import json
17   -import datetime
18   -import collections
19   -
20   -API_ROOT = 'https://api.parse.com/1/classes'
21   -
22   -APPLICATION_ID = ''
23   -MASTER_KEY = ''
24   -
25   -
26   -class ParseBinaryDataWrapper(str):
27   - pass
28   -
29   -
30   -class ParseBase(object):
31   - def _executeCall(self, uri, http_verb, data=None):
32   - url = API_ROOT + uri
33   -
34   - request = urllib2.Request(url, data)
35   -
36   - request.add_header('Content-type', 'application/json')
37   -
38   - # we could use urllib2's authentication system, but it seems like overkill for this
39   - auth_header = "Basic %s" % base64.b64encode('%s:%s' % (APPLICATION_ID, MASTER_KEY))
40   - request.add_header("Authorization", auth_header)
41   -
42   - request.get_method = lambda: http_verb
43   -
44   - # TODO: add error handling for server response
45   - response = urllib2.urlopen(request)
46   - response_body = response.read()
47   - response_dict = json.loads(response_body)
48   -
49   - return response_dict
50   -
51   - def _ISO8601ToDatetime(self, date_string):
52   - # TODO: verify correct handling of timezone
53   - date_string = date_string[:-1] + 'UTC'
54   - date = datetime.datetime.strptime(date_string, "%Y-%m-%dT%H:%M:%S.%f%Z")
55   - return date
56   -
57   -
58   -class ParseObject(ParseBase):
59   - def __init__(self, class_name, attrs_dict=None):
60   - self._class_name = class_name
61   - self._object_id = None
62   - self._updated_at = None
63   - self._created_at = None
64   -
65   - if attrs_dict:
66   - self._populateFromDict(attrs_dict)
67   -
68   - def objectId(self):
69   - return self._object_id
70   -
71   - def updatedAt(self):
72   - return self._updated_at and self._ISO8601ToDatetime(self._updated_at) or None
73   -
74   - def createdAt(self):
75   - return self._created_at and self._ISO8601ToDatetime(self._created_at) or None
76   -
77   - def save(self):
78   - if self._object_id:
79   - self._update()
80   - else:
81   - self._create()
82   -
83   - def delete(self):
84   - # URL: /1/classes/<className>/<objectId>
85   - # HTTP Verb: DELETE
86   -
87   - uri = '/%s/%s' % (self._class_name, self._object_id)
88   -
89   - self._executeCall(uri, 'DELETE')
90   -
91   - self = self.__init__(None)
92   -
93   - def _populateFromDict(self, attrs_dict):
94   - self._object_id = attrs_dict['objectId']
95   - self._created_at = attrs_dict['createdAt']
96   - self._updated_at = attrs_dict['updatedAt']
97   -
98   - del attrs_dict['objectId']
99   - del attrs_dict['createdAt']
100   - del attrs_dict['updatedAt']
101   -
102   - attrs_dict = dict(map(self._convertFromParseType, attrs_dict.items()))
103   -
104   - self.__dict__.update(attrs_dict)
105   -
106   - def _convertToParseType(self, prop):
107   - key, value = prop
108   -
109   - if type(value) == ParseObject:
110   - value = {'__type': 'Pointer',
111   - 'className': value._class_name,
112   - 'objectId': value._object_id}
113   - elif type(value) == datetime.datetime:
114   - value = {'__type': 'Date',
115   - 'iso': value.isoformat()[:-3] + 'Z'} # take off the last 3 digits and add a Z
116   - elif type(value) == ParseBinaryDataWrapper:
117   - value = {'__type': 'Bytes',
118   - 'base64': base64.b64encode(value)}
119   -
120   - return (key, value)
121   -
122   - def _convertFromParseType(self, prop):
123   - key, value = prop
124   -
125   - if type(value) == dict and value.has_key('__type'):
126   - if value['__type'] == 'Pointer':
127   - value = ParseQuery(value['className']).get(value['objectId'])
128   - elif value['__type'] == 'Date':
129   - value = self._ISO8601ToDatetime(value['iso'])
130   - elif value['__type'] == 'Bytes':
131   - value = ParseBinaryDataWrapper(base64.b64decode(value['base64']))
132   - else:
133   - raise Exception('Invalid __type.')
134   -
135   - return (key, value)
136   -
137   - def _getJSONProperties(self):
138   -
139   - properties_list = self.__dict__.items()
140   -
141   - # filter properties that start with an underscore
142   - properties_list = filter(lambda prop: prop[0][0] != '_', properties_list)
143   -
144   - #properties_list = [(key, value) for key, value in self.__dict__.items() if key[0] != '_']
145   -
146   - properties_list = map(self._convertToParseType, properties_list)
147   -
148   - properties_dict = dict(properties_list)
149   - json_properties = json.dumps(properties_dict)
150   -
151   - return json_properties
152   -
153   - def _create(self):
154   - # URL: /1/classes/<className>
155   - # HTTP Verb: POST
156   -
157   - uri = '/%s' % self._class_name
158   -
159   - data = self._getJSONProperties()
160   -
161   - response_dict = self._executeCall(uri, 'POST', data)
162   -
163   - self._created_at = self._updated_at = response_dict['createdAt']
164   - self._object_id = response_dict['objectId']
165   -
166   - def _update(self):
167   - # URL: /1/classes/<className>/<objectId>
168   - # HTTP Verb: PUT
169   -
170   - uri = '/%s/%s' % (self._class_name, self._object_id)
171   -
172   - data = self._getJSONProperties()
173   -
174   - response_dict = self._executeCall(uri, 'PUT', data)
175   -
176   - self._updated_at = response_dict['updatedAt']
177   -
178   -
179   -class ParseQuery(ParseBase):
180   - def __init__(self, class_name):
181   - self._class_name = class_name
182   - self._where = collections.defaultdict(dict)
183   - self._options = {}
184   - self._object_id = ''
185   -
186   - def eq(self, name, value):
187   - self._where[name] = value
188   - return self
189   -
190   - # It's tempting to generate the comparison functions programatically,
191   - # but probably not worth the decrease in readability of the code.
192   - def lt(self, name, value):
193   - self._where[name]['$lt'] = value
194   - return self
195   -
196   - def lte(self, name, value):
197   - self._where[name]['$lte'] = value
198   - return self
199   -
200   - def gt(self, name, value):
201   - self._where[name]['$gt'] = value
202   - return self
203   -
204   - def gte(self, name, value):
205   - self._where[name]['$gte'] = value
206   - return self
207   -
208   - def ne(self, name, value):
209   - self._where[name]['$ne'] = value
210   - return self
211   -
212   - def order(self, order, decending=False):
213   - # add a minus sign before the order value if decending == True
214   - self._options['order'] = decending and ('-' + order) or order
215   - return self
216   -
217   - def limit(self, limit):
218   - self._options['limit'] = limit
219   - return self
220   -
221   - def skip(self, skip):
222   - self._options['skip'] = skip
223   - return self
224   -
225   - def get(self, object_id):
226   - self._object_id = object_id
227   - return self._fetch(single_result=True)
228   -
229   - def fetch(self):
230   - # hide the single_result param of the _fetch method from the library user
231   - # since it's only useful internally
232   - return self._fetch()
233   -
234   - def _fetch(self, single_result=False):
235   - # URL: /1/classes/<className>/<objectId>
236   - # HTTP Verb: GET
237   -
238   - if self._object_id:
239   - uri = '/%s/%s' % (self._class_name, self._object_id)
240   - else:
241   - options = dict(self._options) # make a local copy
242   - if self._where:
243   - # JSON encode WHERE values
244   - where = json.dumps(self._where)
245   - options.update({'where': where})
246   -
247   - uri = '/%s?%s' % (self._class_name, urllib.urlencode(options))
248   -
249   - response_dict = self._executeCall(uri, 'GET')
250   -
251   - if single_result:
252   - return ParseObject(self._class_name, response_dict)
253   - else:
254   - return [ParseObject(self._class_name, result) for result in response_dict['results']]
255   -
0  parse_rest/__init__.py
No changes.
24 parse_rest/cloudcode/cloud/main.js
... ... @@ -0,0 +1,24 @@
  1 +
  2 +// Use Parse.Cloud.define to define as many cloud functions as you want.
  3 +// For example:
  4 +Parse.Cloud.define("hello", function(request, response) {
  5 + response.success("Hello world!");
  6 +});
  7 +
  8 +
  9 +Parse.Cloud.define("averageStars", function(request, response) {
  10 + var query = new Parse.Query("Review");
  11 + query.equalTo("movie", request.params.movie);
  12 + query.find({
  13 + success: function(results) {
  14 + var sum = 0;
  15 + for (var i = 0; i < results.length; ++i) {
  16 + sum += results[i].get("stars");
  17 + }
  18 + response.success(sum / results.length);
  19 + },
  20 + error: function() {
  21 + response.error("movie lookup failed");