Skip to content

danlipert/ParsePy

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

99 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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
  • PLANNED/TODO: Installation querying**
  • PLANNED/TODO: push/channel querying**
  • PLANNED/TODO: Roles/ACLs**
  • PLANNED/TODO: Image/File type support

** 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/dgrtwo/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.

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"

You can then test the installation by running:

python setup.py test

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 though 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(<application_id>, <rest_api_key>[, 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

And then instantiate it with your 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

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

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

Querysets can be filtered:

high_scores = GameScore.Query.all().gte(score=1000)

The available filter functions are:

  • Less Than
    • lt(**parameters)
  • Less Than Or Equal To
    • lte(**parameters)
  • Greater Than
    • gt(**parameters)
  • Greater Than Or Equal To
    • gte(**parameters)
  • Not Equal To
    • ne(**parameters)
  • Equal to
    • eq(**parameters) // alias: where

Warning: We may change the way to use filtering functions in the near future, and favor a parameter-suffix based approach (similar to Django)

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

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().where(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().where(author='Joe').order_by('view_count')
for post in posts_by_joe:
   print post.title, post.publication_date, post.text

TODO: Slicing of Querysets

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

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

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}

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!

About

A Python wrapper for the Parse.com API

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Python 98.4%
  • JavaScript 1.6%