Permalink
Browse files

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...
1 parent 895cb68 commit b640110ec87fc9c2fac43f92d898dfe45dd73aa3 David Robinson committed Jan 19, 2013
Showing with 165 additions and 7 deletions.
  1. +2 −1 .gitignore
  2. +58 −0 README.mkd
  3. +11 −1 parse_rest/__init__.py
  4. +24 −0 parse_rest/cloudcode/cloud/main.js
  5. +4 −0 parse_rest/cloudcode/config/.gitignore
  6. +65 −1 parse_rest/tests.py
  7. +1 −4 setup.py
View
@@ -5,4 +5,5 @@
build/*
settings_local.py
dist
-MANIFEST
+MANIFEST
+global.json
View
@@ -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
@@ -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!
@@ -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
@@ -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)
@@ -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");
+ }
+ });
+});
@@ -0,0 +1,4 @@
+# Ignore everything in this directory
+*
+# Except this file
+!.gitignore
View
@@ -2,6 +2,8 @@
Contains unit tests for the Python Parse REST API wrapper
"""
+import os
+import subprocess
import unittest
import urllib2
import datetime
@@ -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)"""
@@ -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()
View
@@ -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)

0 comments on commit b640110

Please sign in to comment.