Need help with ParsePy?
Click the “chat” button below for chat support from the developer who created it, or find similar developers for support.

About the developer

milesrichardson
506 Stars 186 Forks MIT License 270 Commits 32 Opened issues

Description

A relatively up-to-date fork of ParsePy, the Python wrapper for the Parse.com API. Originally maintained by @dgrtwo

Services available

!
?

Need anything else?

Contributors list

Note: As of May 13, 2016, this repository (

milesrichardson/ParsePy)
is the most up-to-date and active python client for the Parse API. It supports self-hosted
parse-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

parse_rest is a Python client for the Parse REST API. 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.
  • 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.

Installation

The easiest way to install this package is by downloading or cloning this repository:

pip install git+https://github.com/milesrichardson/ParsePy.git

Note: The version on PyPI is not up-to-date. The code is still under lots of changes and the stability of the library API - though improving - is not guaranteed. Please file any issues that you may find if documentation/application.

Using with self-hosted
parse-server

To use the library with self-hosted parse-server, set the environment variable

PARSE_API_ROOT
before importing the module.

Example:

import os
os.environ["PARSE_API_ROOT"] = "http://your_server.com:1337/parse"

Everything else same as usual

from parse_rest.datatypes import Function, Object, GeoPoint from parse_rest.connection import register from parse_rest.query import QueryResourceDoesNotExist from parse_rest.connection import ParseBatcher from parse_rest.core import ResourceRequestBadRequest, ParseError

APPLICATION_ID = '...' REST_API_KEY = '...' MASTER_KEY = '...'

register(APPLICATION_ID, REST_API_KEY, master_key=MASTER_KEY)

Testing

To run the tests, you need to:

  • create a
    settings_local.py
    file in your local directory with three variables 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 Do not give the keys of an existing application with data you want to keep: create a new one instead. The test suite will erase any existing CloudCode in the app and may accidentally replace or change existing objects.

You can then test the installation by running the following command:

# test all
python -m unittest parse_rest.tests

or test individually

python -m unittest parse_rest.tests.TestObject.testCanCreateNewObject

Usage

Before the first interaction with the Parse server, you need to register your access credentials. You can do so by calling

parse_rest.connection.register
.

Before getting to code, a word of caution. You need to consider how your application is meant to be deployed. Parse identifies your application through different keys (available from your Parse dashboard) that are used in every 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 the master key in your code. If your application is meant to be running in systems that you fully control (e.g, a web app that needs to integrate with Parse to provide functionality to your client), you may also add your master key.

from parse_rest.connection import register
register(, [, master_key=None])

Once your application calls

register
, you will be able to read, write and query for data at Parse.

Data types

Parse allows us to get data in different base types that have a direct python equivalent (strings, integers, floats, dicts, lists) as well as some more complex ones (e.g.:

File
,
Image
,
Date
). It also allows us to define objects with schema-free structure, and save them, as well to query them later by their attributes.
parse_rest
is handy as a way to serialize/deserialize these objects transparently.

The Object type

In theory, you are able to simply instantiate a

Object
and do everything that you want with it, save it on Parse, retrieve it later, etc.
from parse_rest.datatypes import Object

first_object = Object()

In practice, you will probably want different classes for your application 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 save the scoreboard on Parse. For that, you decide to define a class called

GameScore
. All you need to do to create such a class is to define a Python class that inherts from
parse_rest.datatypes.Object
:
from parse_rest.datatypes import Object

class GameScore(Object): pass

You can also create an Object subclass by string name, with the

Object.factory
method:
from parse_rest.datatypes import Object

myClassName = "GameScore" myClass = Object.factory(myClassName)

print myClass

print myClass.name

GameScore

You 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 = True
gameScore.level = 20

To save our new object, just call the save() method:

gameScore.save()

If we want to make an update, just call save() again after modifying an attribute to send the changes to the server:

gameScore.score = 2061
gameScore.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.

Object Metadata

The attributes objectId, createdAt, and updatedAt show metadata about a Object 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)

Additional Datatypes

We've mentioned that Parse supports more complex types, most of these types are also supported on Python (dates, files). So these types can be converted transparently when you use them. For the types that Parse provided and Python does not support natively,

parse_rest
provides the appropiates classes to work with them. One such example is
GeoPoint
, where you store latitude and longitude
from parse_rest.datatypes import Object, GeoPoint

class Restaurant(Object): pass

restaurant = 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 Object

class CollectedItem(Object): pass

collectedItem = CollectedItem(type="Sword", isAwesome=True) collectedItem.save() # we have to save it before it can be referenced

gameScore.item = collectedItem

File Support

You can upload files to parse (assuming your

parse-server
instance supports it). This has been tested with the default GridStore adapter.

Example:

from parse_rest.datatypes import Object, File

class GameScore(Object): pass

1. Upload file

with 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 save

gs = GameScore.Query.get(objectId='xxxxxxx') gs.screenshot = screenshotFile gs.save()

print gs.file.url

Batch Operations

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:
from parse_rest.connection import ParseBatcher

score1 = 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 mix

save
and
delete
operations in the same query as follows (note the absence of parentheses after each
save
or
delete
):
batcher.batch([score1.save, score2.save, score3.delete])

If an error occurs during one or multiple of the operations, it will not affect the execution of the remaining operations. Instead, the

batcher.batch_save
or
batcher.batch_delete
or
batcher.batch
will raise a
ParseBatchError
(child of
ParseError
) exception with
.message
set to a list of the errors encountered. 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 successfully

dupe_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 "", line 1, in 
  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 }'}]

And

CRUCIALLY
, the objectId field of the NON-duplicate object will be correctly set:
>>> #batch_save as above...
>>> print objects
[, , ]

Therefore, one way to tell which objects saved successfully after a batch save operation is to check which objects have

objectId
set.

Querying

Any class inheriting from

parse_rest.Object
has a
Query
object. With it, you can perform queries that return a set of objects or that will return a object directly.

Retrieving a single object

To retrieve an object with a Parse class of

GameScore
and an
objectId
of
xxwXx9eOec
, run:
gameScore = GameScore.Query.get(objectId="xxwXx9eOec")

Working with Querysets

To query for sets of objects, we work with the concept of

Queryset
s. If you are familiar with Django you will be right at home - but be aware that is not a complete implementation of their Queryset or Database backend.

The Query object contains a method called

all()
, which will return a basic (unfiltered) Queryset. It will represent the set of all objects of the class you are querying.
all_scores = GameScore.Query.all()

Querysets are lazily evaluated, meaning that it will only actually make a request to Parse when you either call a method that needs to operate on the data, or when you iterate on the Queryset.

Filtering

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 the

nearSphere
operator:
my_loc = GeoPoint(latitude=12.0, longitude=-34.55)
nearby_restaurants = Restaurant.Query.filter(location__nearSphere=my_loc)

You can see the full list of constraint operators defined by Parse

Sorting/Ordering

Querysets can also be ordered. Just define the name of the attribute that you want to use to sort. Appending a "-" in front of the name will 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)

Limit/Skip

If you don't want the whole set, you can apply the limit and skip function. Let's say you have a have classes representing 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-20

Related objects

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

posts = Post.Query.all().select_related("author", "editor")

Composability/Chaining of Querysets

The example above can show the most powerful aspect of Querysets, that is the ability to make complex querying and filtering by chaining calls:

Most importantly, Querysets can be chained together. This allows you to 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)

Iterating on Querysets

After all the querying/filtering/sorting, you will probably want to do something 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.text

TODO: Slicing of Querysets

Relations

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 game can have multiple GameScores, you can use relations to associate those 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 relations are created associating the GameScores with the Game.

To retreive the related scores for a game, you use query() to get a Queryset for the relation.

scores = relation.query()
for gamescore in scores:
    print gamescore.player_name, gamescore.score

The query is limited to the objects previously added to the relation.

scores = relation.query().order_by('score', descending=True)
for gamescore in scores:
    print gamescore.player_name, gamescore.score

To remove objects from a relation, you use remove(). This example removes all the related objects.

scores = relation.query()
for gamescore in scores:
    relation.remove(gamescore)

Users

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:
from parse_rest.user import User

u = 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="[email protected]")

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 a

User
has been logged in, it saves its session so that it can be edited or deleted:
u.highscore = 300
u.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 only

with 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 queries

register(PARSE_APPID, PARSE_APIKEY, session_token=token) me = User.current_user()

Push

You can also send notifications to your users using Parse's Push functionality, through the Push object:

from parse_rest.installation import Push

Push.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 on Advanced Targeting by specifying the

where
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 the

alert
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})

Cloud Functions

Parse offers CloudCode, 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.

The CloudCode guide describes how to upload a function to the server. Let's say you upload the following

main.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 the

parse_rest.datatypes.Function
class:
from parse_rest.datatypes import Function

hello_func = Function("hello") hello_func() {u'result': u'Hello world!'} star_func = Function("averageStars") star_func(movie="The Matrix") {u'result': 4.5}

ACLs

The ACL for an object can be updated using the

parse_rest.datatypes.ACL
class. This class provides three methods for setting an ACL: setuser, setrole, and setdefault. For example, using the User and gameScore examples from above: ~~~~~ {python} from parserest.datatypes import ACL from parse_rest.user import User

u = User.login('dhelmet', '12345')

gameScore.ACL.set_user(u, read=True, write=True)

allows user 'dhelmet' to read and write to gameScore

gameScore.ACL.set_default(read=True)

allows public to read but not write to gameScore

gameScore.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() ~~~~~

Roles

You can create, update or delete roles as well, using the

parse_rest.role.Role
class. Creating a role requires you to pass a name and an ACL to Role. ~~~~~ {python} from parserest.role import Role from parserest.datatypes import ACL

adminrole = Role(name='moderators') adminrole.ACL.setdefault(read=True) adminrole.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.

Session Tokens

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 the

parse_rest.connection.SessionToken
class.
from parse_rest.connection import SessionToken
from parse_rest.user import User

u = User.login('dhelmet', '12345') token = u.sessionToken

with 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.

Elevating Access to Master

Sometimes it is useful to only allow privileged use of the master key for specific uses.

from parse_rest.connection import MasterKey

with MasterKey('master key'): # do privileged calls

We use cookies. If you continue to browse the site, you agree to the use of cookies. For more information on our use of cookies please see our Privacy Policy.