Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
David Robinson committed Jan 19, 2013
1 parent 895cb68 commit b640110
Show file tree
Hide file tree
Showing 7 changed files with 165 additions and 7 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Expand Up @@ -5,4 +5,5 @@
build/*
settings_local.py
dist
MANIFEST
MANIFEST
global.json
58 changes: 58 additions & 0 deletions README.mkd
Expand Up @@ -27,6 +27,21 @@ and performing the commands:

(again you may have to add `sudo` before `python setup.py install`).

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:

~~~~~ {python}
APPLICATION_ID = "APPLICATION_ID_HERE"
REST_API_KEY = "REST_API_KEY_HERE"
MASTER_KEY = "MASTER_KEY_HERE"
~~~~~

* install the [Parse CloudCode command line tool](https://www.parse.com/docs/cloud_code_guide)

You can then test the installation by running:

python setup.py test
Expand Down Expand Up @@ -164,4 +179,47 @@ We can also order the results using:
* **Order**
* order(_parameter_name_, _decending_=False)

Cloud Functions
---------------

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.

The CloudCode guide describes how to upload a function to the server. Let's say you upload the following `main.js` script:

~~~~~ {javascript}
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.Function` class:

~~~~~ {python}
>>> hello_func = parse_rest.Function("hello")
>>> hello_func()
{u'result': u'Hello world!'}
>>> star_func = parse_rest.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!
12 changes: 11 additions & 1 deletion parse_rest/__init__.py
Expand Up @@ -37,7 +37,7 @@ class ParseBase(object):
def execute(cls, uri, http_verb, extra_headers=None, **kw):
headers = extra_headers or {}
url = uri if uri.startswith(API_ROOT) else cls.ENDPOINT_ROOT + uri
data = kw and json.dumps(kw) or None
data = kw and json.dumps(kw) or "{}"
if http_verb == 'GET' and data:
url += '?%s' % urllib.urlencode(kw)
data = None
Expand Down Expand Up @@ -113,6 +113,16 @@ def _convertToParseType(self, prop):
return (key, value)


class Function(ParseBase):
ENDPOINT_ROOT = "/".join((API_ROOT, "functions"))

def __init__(self, name):
self.name = name

def __call__(self, **kwargs):
return self.POST("/" + self.name, **kwargs)


class ParseResource(ParseBase):
def __init__(self, **kw):
self._object_id = kw.pop('objectId', None)
Expand Down
24 changes: 24 additions & 0 deletions parse_rest/cloudcode/cloud/main.js
@@ -0,0 +1,24 @@

// Use Parse.Cloud.define to define as many cloud functions as you want.
// For example:
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");
}
});
});
4 changes: 4 additions & 0 deletions parse_rest/cloudcode/config/.gitignore
@@ -0,0 +1,4 @@
# Ignore everything in this directory
*
# Except this file
!.gitignore
66 changes: 65 additions & 1 deletion parse_rest/tests.py
Expand Up @@ -2,6 +2,8 @@
Contains unit tests for the Python Parse REST API wrapper
"""

import os
import subprocess
import unittest
import urllib2
import datetime
Expand All @@ -12,12 +14,30 @@
import settings_local
except ImportError:
raise ImportError('You must create a settings_local.py file with an ' +
'example application to run tests')
'APPLICATION_ID, REST_API_KEY, and a MASTER_KEY ' +
'to run tests.')

parse_rest.APPLICATION_ID = settings_local.APPLICATION_ID
parse_rest.REST_API_KEY = settings_local.REST_API_KEY


GLOBAL_JSON_TEXT = """{
"applications": {
"_default": {
"link": "parseapi"
},
"parseapi": {
"applicationId": "%s",
"masterKey": "%s"
}
},
"global": {
"parseVersion": "1.1.16"
}
}
"""


### FUNCTIONS ###
def test_obj(saved=False):
"""Return a test parse_rest.Object (content is from the docs)"""
Expand Down Expand Up @@ -141,6 +161,50 @@ def test_delete(self):
parse_rest.ObjectQuery("GameScore").get, obj_id)


class TestFunction(unittest.TestCase):
def setUp(self):
"""create and deploy cloud functions"""
original_dir = os.getcwd()
cloud_function_dir = os.path.join(os.path.split(__file__)[0],
"cloudcode")
os.chdir(cloud_function_dir)
# write the config file
with open("config/global.json", "w") as outf:
outf.write(GLOBAL_JSON_TEXT % (settings_local.APPLICATION_ID,
settings_local.MASTER_KEY))
try:
subprocess.call(["parse", "deploy"])
except OSError:
raise OSError("parse command line tool must be installed " +
"(see https://www.parse.com/docs/cloud_code_guide)")
os.chdir(original_dir)

# remove all existing Review objects
for review in parse_rest.ObjectQuery("Review").fetch():
review.delete()

def test_simple_functions(self):
"""test hello world and averageStars functions"""
# test the hello function- takes no arguments
hello_world_func = parse_rest.Function("hello")
ret = hello_world_func()
self.assertEqual(ret["result"], u"Hello world!")

# Test the averageStars function- takes simple argument
r1 = parse_rest.Object("Review", {"movie": "The Matrix",
"stars": 5,
"comment": "Too bad they never made any sequels."})
r1.save()
r2 = parse_rest.Object("Review", {"movie": "The Matrix",
"stars": 4,
"comment": "It's OK."})
r2.save()

star_func = parse_rest.Function("averageStars")
ret = star_func(movie="The Matrix")
self.assertAlmostEqual(ret["result"], 4.5)


if __name__ == "__main__":
# command line
unittest.main()
5 changes: 1 addition & 4 deletions setup.py
Expand Up @@ -14,10 +14,7 @@ def finalize_options(self):

def run(self):
"""Run test suite in parse_rest.tests"""
try:
from parse_rest import tests
except ImportError:
raise Exception("parse_rest is not installed, cannot run tests")
from parse_rest import tests
tests = TestLoader().loadTestsFromNames(["parse_rest.tests"])
t = TextTestRunner(verbosity=1)
t.run(tests)
Expand Down

0 comments on commit b640110

Please sign in to comment.