- Notifications
You must be signed in to change notification settings - Fork185
A relatively up-to-date fork of ParsePy, the Python wrapper for the Parse.com API. Originally maintained by@dgrtwo
License
milesrichardson/ParsePy
Folders and files
| Name | Name | Last commit message | Last commit date | |
|---|---|---|---|---|
Repository files navigation
Note: As of May 13, 2016, this repository (milesrichardson/ParsePy) is themost up-to-date and active python client for the Parse API. It supports self-hostedparse-server via the REST API. Note that some features will not work with parse-server,if they are not supported by the REST API (e.g. push).
See the section below, "using with self-hosted parse-server," for instructions.
parse_rest is a Python client for theParse RESTAPI. It provides:
- Python object mapping for Parse objects with methods to save,update, and delete objects, as well as an interface for queryingstored objects.
- Complex data types provided by Parse with no python equivalent
- User authentication, account creation** (signup) and querying.
- Cloud code integration
- Installation querying
- push
- Roles/ACLs**
- Image/File type support (done 1/14/17)
** for applications with access to the MASTER KEY, see details below.
The easiest way to install this package is by downloading orcloning this repository:
pip install git+https://github.com/milesrichardson/ParsePy.gitNote: The version onPyPI is notup-to-date. The code is still under lots of changes and the stabilityof the library API - though improving - is not guaranteed. Pleasefile any issues that you may find if documentation/application.
To use the library with self-hosted parse-server, set the environment variablePARSE_API_ROOT before importing the module.
Example:
import osos.environ["PARSE_API_ROOT"] = "http://your_server.com:1337/parse"# Everything else same as usualfrom parse_rest.datatypes import Function, Object, GeoPointfrom parse_rest.connection import registerfrom parse_rest.query import QueryResourceDoesNotExistfrom parse_rest.connection import ParseBatcherfrom parse_rest.core import ResourceRequestBadRequest, ParseErrorAPPLICATION_ID = '...'REST_API_KEY = '...'MASTER_KEY = '...'register(APPLICATION_ID, REST_API_KEY, master_key=MASTER_KEY)To run the tests, you need to:
- create a
settings_local.pyfile in your local directory with threevariables that define a sample Parse application to use for testing:
APPLICATION_ID = "APPLICATION_ID_HERE"REST_API_KEY = "REST_API_KEY_HERE"MASTER_KEY = "MASTER_KEY_HERE"Note Donot give the keys of an existing application with data you want tokeep: create a new one instead. The test suite will erase any existing CloudCodein the app and may accidentally replace or change existing objects.
- install theParse CloudCode tool
You can then test the installation by running the following command:
# test allpython -m unittest parse_rest.tests# or test individuallypython -m unittest parse_rest.tests.TestObject.testCanCreateNewObjectBefore the first interaction with the Parse server, you need toregister your access credentials. You can do so by callingparse_rest.connection.register.
Before getting to code, a word of caution. You need to consider how your application ismeant to be deployed. Parse identifies your application throughdifferent keys (available from your Parse dashboard) that are used inevery request done to their servers.
If your application is supposed to be distributed to third parties(such as a desktop program to be installed), you SHOULD NOT put themaster key in your code. If your application is meant to be running insystems that you fully control (e.g, a web app that needs to integratewith Parse to provide functionality to your client), you may also addyourmaster key.
from parse_rest.connection import registerregister(<application_id>, <rest_api_key>[, master_key=None])Once your application callsregister, you will be able to read, writeand query for data at Parse.
Parse allows us to get data in different base types that have a directpython equivalent (strings, integers, floats, dicts, lists) as well assome more complex ones (e.g.:File,Image,Date). It also allowsus to define objects with schema-free structure, and save them, aswell to query them later by their attributes.parse_rest ishandy as a way to serialize/deserialize these objects transparently.
In theory, you are able to simply instantiate aObject and doeverything that you want with it, save it on Parse, retrieve it later,etc.
from parse_rest.datatypes import Objectfirst_object = Object()In practice, you will probably want different classes for yourapplication to allow for a better organization in your own code.So, let's say you want to make an online game, and you want to savethe scoreboard on Parse. For that, you decide to define a class calledGameScore. All you need to do to create such a class is to define aPython class that inherts fromparse_rest.datatypes.Object:
from parse_rest.datatypes import Objectclass GameScore(Object): passYou can also create an Object subclass by string name, with theObject.factorymethod:
from parse_rest.datatypes import ObjectmyClassName = "GameScore"myClass = Object.factory(myClassName)print myClass# <class 'parse_rest.datatypes.GameScore'>print myClass.__name__# GameScoreYou can then instantiate your new class with some parameters:
gameScore = GameScore(score=1337, player_name='John Doe', cheat_mode=False)You can change or set new parameters afterwards:
gameScore.cheat_mode = TruegameScore.level = 20To save our new object, just call the save() method:
gameScore.save()If we want to make an update, just call save() again after modifyingan attribute to send the changes to the server:
gameScore.score = 2061gameScore.save()You can also increment the score in a single API query:
gameScore.increment("score")Now that we've done all that work creating our first Parse object, let's delete it:
gameScore.delete()That's it! You're ready to start saving data on Parse.
The attributes objectId, createdAt, and updatedAt show metadata aboutaObject that cannot be modified through the API:
gameScore.objectId# 'xxwXx9eOec'gameScore.createdAt# datetime.datetime(2011, 9, 16, 21, 51, 36, 784000)gameScore.updatedAt# datetime.datetime(2011, 9, 118, 14, 18, 23, 152000)We've mentioned that Parse supports more complex types, most of thesetypes are also supported on Python (dates, files). So these types canbe converted transparently when you use them. For the types that Parseprovided and Python does not support natively,parse_rest providesthe appropiates classes to work with them. One such example isGeoPoint, where you store latitude and longitude
from parse_rest.datatypes import Object, GeoPointclass Restaurant(Object): passrestaurant = Restaurant(name="Los Pollos Hermanos")# coordinates as floats.restaurant.location = GeoPoint(latitude=12.0, longitude=-34.45)restaurant.save()We can store a reference to another Object by assigning it to an attribute:
from parse_rest.datatypes import Objectclass CollectedItem(Object): passcollectedItem = CollectedItem(type="Sword", isAwesome=True)collectedItem.save() # we have to save it before it can be referencedgameScore.item = collectedItemYou can upload files to parse (assuming yourparse-server instance supports it).This has been tested with the default GridStore adapter.
Example:
from parse_rest.datatypes import Object, Fileclass GameScore(Object): pass# 1. Upload filewith open('/path/to/screenshot.png', 'rb') as fh: rawdata = fh.read()screenshotFile = File('arbitraryNameOfFile', rawdata, 'image/png')screenshotFile.save()print screenshotFile.url# 2. Attach file to gamescore object and savegs = GameScore.Query.get(objectId='xxxxxxx')gs.screenshot = screenshotFilegs.save()print gs.file.urlFor 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 theconnection.ParseBatcher object:
from parse_rest.connection import ParseBatcherscore1 = GameScore(score=1337, player_name='John Doe', cheat_mode=False)score2 = GameScore(score=1400, player_name='Jane Doe', cheat_mode=False)score3 = GameScore(score=2000, player_name='Jack Doe', cheat_mode=True)scores = [score1, score2, score3]batcher = ParseBatcher()batcher.batch_save(scores)batcher.batch_delete(scores)You can also mixsave anddelete operations in the same query as follows (note the absence of parentheses after eachsave ordelete):
batcher.batch([score1.save, score2.save, score3.delete])If an error occurs during one or multiple of the operations, it will not affectthe execution of the remaining operations. Instead, thebatcher.batch_save orbatcher.batch_delete orbatcher.batch will raise aParseBatchError(child ofParseError) exception with.message set to alist of the errorsencountered. For example:
# Batch save a list of two objects:# dupe_object is a duplicate violating a unique key constraint# dupe_object2 is a duplicate violating a unique key constraint# new_object is a new object satisfying the unique key constraint## dupe_object and dupe_object2 will fail to save, and new_object will save successfullydupe_object = list(MyClass.Query.all().limit(2))[0]dupe_object2 = list(MyClass.Query.all().limit(2))[1]new_object = MyClass(some_column=11111)objects = [dupe_object, dupe_object2, new_object]batcher = ParseBatcher()batcher.batch_save(objects)will raise an exception:
Traceback (most recent call last): File "<console>", line 1, in <module> File "/Users/miles/ParsePy/parse_rest/connection.py", line 199, in batch_save self.batch(o.save for o in objects) File "/Users/miles/ParsePy/parse_rest/connection.py", line 195, in batch raise core.ParseBatchError(batched_errors)ParseBatchError: [{u'code': 11000, u'error': u'E11000 duplicate key error index: myapp.MyClass.$my_column_1 dup key: { : 555555 }'}, {u'code': 11000, u'error': u'E11000 duplicate key error index: myapp.MyClass.$my_column_1 dup key: { : 44444 }'}]AndCRUCIALLY, the objectId field of the NON-duplicate object will be correctly set:
>>> #batch_save as above...>>> print objects[<MyClass:None>, <MyClass:None>, <MyClass:gOHuhPbGZJ>]Therefore, one way to tell which objects saved successfully after a batch save operationis to check which objects haveobjectId set.
Any class inheriting fromparse_rest.Object has aQueryobject. With it, you can perform queries that return a set of objectsor that will return a object directly.
To retrieve an object with a Parse class ofGameScore and anobjectId ofxxwXx9eOec, run:
gameScore = GameScore.Query.get(objectId="xxwXx9eOec")To query for sets of objects, we work with the concept ofQuerysets. If you are familiar with Django you will be right at home- but be aware that is not a complete implementation of theirQueryset or Database backend.
The Query object contains a method calledall(), which will return abasic (unfiltered) Queryset. It will represent the set of all objectsof the class you are querying.
all_scores = GameScore.Query.all()Querysets arelazily evaluated, meaning that it will only actuallymake a request to Parse when you either call a method that needs tooperate on the data, or when you iterate on the Queryset.
Like Django, Querysets can have constraints added by appending the name of the filter operator to name of the attribute:
high_scores = GameScore.Query.filter(score__gte=1000)You can similarly perform queries on GeoPoint objects by using thenearSphere operator:
my_loc = GeoPoint(latitude=12.0, longitude=-34.55)nearby_restaurants = Restaurant.Query.filter(location__nearSphere=my_loc)You can see thefull list of constraint operators defined byParse
Querysets can also be ordered. Just define the name of the attributethat you want to use to sort. Appending a "-" in front of the namewill sort the set in descending order.
low_to_high_score_board = GameScore.Query.all().order_by("score")high_to_low_score_board = GameScore.Query.all().order_by("-score") # or order_by("score", descending=True)If you don't want the whole set, you can apply thelimit and skip function. Let's say you have a have classesrepresenting a blog, and you want to implement basic pagination:
posts = Post.Query.all().order_by("-publication_date")page_one = posts.limit(10) # Will return the most 10 recent posts.page_two = posts.skip(10).limit(10) # Will return posts 11-20You can specify "join" attributes to get related object with single query.
posts = Post.Query.all().select_related("author", "editor")The example above can show the most powerful aspect of Querysets, thatis the ability to make complex querying and filtering by chaining calls:
Most importantly, Querysets can be chained together. This allows youto make more complex queries:
posts_by_joe = Post.Query.all().filter(author='Joe').order_by("view_count")popular_posts = posts_by_joe.gte(view_count=200)After all the querying/filtering/sorting, you will probably want to dosomething with the results. Querysets can be iterated on:
posts_by_joe = Post.Query.all().filter(author='Joe').order_by('view_count')for post in posts_by_joe: print post.title, post.publication_date, post.textTODO: Slicing of Querysets
A Relation is field that contains references to multiple objects.You can query this subset of objects.
(Note that Parse's relations are "one sided" and don't involve a join table.See the docs.)
For example, if we have Game and GameScore classes, and one gamecan have multiple GameScores, you can use relations to associatethose GameScores with a Game.
game = Game(name="3-way Battle")game.save()score1 = GameScore(player_name='Ronald', score=100)score2 = GameScore(player_name='Rebecca', score=140)score3 = GameScore(player_name='Sara', score=190)relation = game.relation('scores')relation.add([score1, score2, score3])A Game gets added, three GameScores get added, and three relationsare created associating the GameScores with the Game.
To retreive the related scores for a game, you use query() to get aQueryset for the relation.
scores = relation.query()for gamescore in scores: print gamescore.player_name, gamescore.scoreThe query is limited to the objects previously added to therelation.
scores = relation.query().order_by('score', descending=True)for gamescore in scores: print gamescore.player_name, gamescore.scoreTo remove objects from a relation, you use remove(). This exampleremoves all the related objects.
scores = relation.query()for gamescore in scores: relation.remove(gamescore)You can sign up, log in, modify or delete users as well, using theparse_rest.user.User class. You sign a user up as follows:
from parse_rest.user import Useru = User.signup("dhelmet", "12345", phone="555-555-5555")or log in an existing user with
u = User.login("dhelmet", "12345")You can also request a password reset for a specific user with
User.request_password_reset(email="dhelmet@gmail.com")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:
authData = {"facebook": {"id": fbID, "access_token": access_token, "expiration_date": expiration_date}}u = User.login_auth(authData)Once aUser has been logged in, it saves its session so that it can be edited or deleted:
u.highscore = 300u.save()u.delete()To get the current user from a Parse session:
from parse_rest.connection import SessionToken, register# Acquire a valid parse session somewhere# Example: token = request.session.get('session_token')# Method 1: Using a `with` statement# Do this to isolate use of session token in this block onlywith SessionToken(token): me = User.current_user()# Method 2: register your parse connection with `session_token` parameter# Do this to use the session token for all subsequent queriesregister(PARSE_APPID, PARSE_APIKEY, session_token=token)me = User.current_user()You can also send notifications to your users usingParse's Push functionality, through the Push object:
from parse_rest.installation import PushPush.message("The Giants won against the Mets 2-3.", channels=["Giants", "Mets"])This will push a message to all users subscribed to the "Giants" and "Mets" channels. Your alert can be restricted based onAdvanced Targeting by specifying thewhere argument:
Push.message("Willie Hayes injured by own pop fly.", channels=["Giants"], where={"injuryReports": True})Push.message("Giants scored against the A's! It's now 2-2.", channels=["Giants"], where={"scores": True})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 thealert method and pass the actions in a dictionary:
Push.alert({"alert": "The Mets scored! The game is now tied 1-1.", "badge": "Increment", "title": "Mets Score"}, channels=["Mets"], where={"scores": True})Parse offersCloudCode, which has the ability to upload JavaScript functions that will be run on the server. You can use theparse_rest client to call those functions.
The CloudCode guide describes how to upload a function to the server. Let's say you upload the followingmain.js script:
Parse.Cloud.define("hello", function(request, response) { response.success("Hello world!");});Parse.Cloud.define("averageStars", function(request, response) { var query = new Parse.Query("Review"); query.equalTo("movie", request.params.movie); query.find({ success: function(results) { var sum = 0; for (var i = 0; i < results.length; ++i) { sum += results[i].get("stars"); } response.success(sum / results.length); }, error: function() { response.error("movie lookup failed"); } });});Then you can call either of these functions using theparse_rest.datatypes.Function class:
from parse_rest.datatypes import Functionhello_func = Function("hello")hello_func(){u'result': u'Hello world!'}star_func = Function("averageStars")star_func(movie="The Matrix"){u'result': 4.5}The ACL for an object can be updated using theparse_rest.datatypes.ACL class. This class provides three methods for setting an ACL: set_user, set_role, and set_default. For example, using the User and gameScore examples from above:
from parse_rest.datatypes import ACLfrom parse_rest.user import Useru = User.login('dhelmet', '12345')gameScore.ACL.set_user(u, read=True, write=True)# allows user 'dhelmet' to read and write to gameScoregameScore.ACL.set_default(read=True)# allows public to read but not write to gameScoregameScore.ACL.set_role('moderators', read=True, write=True)# allows role 'moderators' to read and write to gameScore. Can alternatively pass the role object instead of the# role name. See below for more info on Roles.gameScore.save()You can create, update or delete roles as well, using theparse_rest.role.Role class. Creating a role requires you to pass a name and an ACL to Role.
from parse_rest.role import Rolefrom parse_rest.datatypes import ACLadmin_role = Role(name='moderators')admin_role.ACL.set_default(read=True)admin_role.save()This, for example, creates a role with the name 'moderators', with an ACL that allows the public to read but not write to this role object.
When querying or updating an object protected by an ACL, parse.com requires the session token of the user with read and write privileges, respectively. You can pass the session token to such queries and updates by using theparse_rest.connection.SessionToken class.
from parse_rest.connection import SessionTokenfrom parse_rest.user import Useru = User.login('dhelmet', '12345')token = u.sessionTokenwith SessionToken(token): collectedItem = CollectedItem.Query.get(type="Sword") # Get a collected item, Sword, that is protected by ACL print collectedItem u.logout()Assuming the CollectedItem 'Sword' is read-protected from the public by an ACL and is readable only by the user, SessionToken allows the user to bypass the ACL and get the 'Sword' item.
Sometimes it is useful to only allow privileged use of the master key for specific uses.
from parse_rest.connection import MasterKeywith MasterKey('master key'): # do privileged callsAbout
A relatively up-to-date fork of ParsePy, the Python wrapper for the Parse.com API. Originally maintained by@dgrtwo
Topics
Resources
License
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Releases
Packages0
Uh oh!
There was an error while loading.Please reload this page.