Code Monkey home page Code Monkey logo

parsepy's Introduction

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

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
# <class 'parse_rest.datatypes.GameScore'>
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 "<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 }'}]

And CRUCIALLY, 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 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 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

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: set_user, set_role, and set_default. For example, using the User and gameScore examples from above:

from parse_rest.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.

from parse_rest.role import Role
from parse_rest.datatypes import ACL

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

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

parsepy's People

Contributors

asfdfdfd avatar billyto avatar bitmole avatar bradddd avatar ctrongminh avatar dankrause avatar danrobinson avatar deanq avatar dgadling avatar farin avatar ivannkf avatar jonmorehouse avatar jpoehnelt avatar konoufo avatar mau21mau avatar mgalves avatar milesrichardson avatar misterfitz avatar mtsgrd avatar niccokunzmann avatar pistatium avatar pktck avatar rbermudezg avatar rwitten avatar teehamaral avatar thelinuxkid avatar thibauddavid avatar tony-osibov avatar wm3ndez avatar yosit avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

parsepy's Issues

MIT/BSD Licensing?

IANAL, but it's my understanding that if all contributors agree to change in terms of the license, we can "un-GPL" it.

I think that it's safe to say that, with all the changes we've done to the original code base, our "fork" is no longer a fork. Given that the original repo has no activity for the past 11 months, I would guess that if we could change our licensing to a more liberal one if we get authorization from the contributors to your repo (who have commits).

Thoughts?

AttributeError: type object 'ParseBase' has no attribute 'CONNECTION'

I installed parse_rest from source by cloning this repository:

git clone [email protected]:dgrtwo/ParsePy.git

and then performing the following commands:

python setup.py build
sudo python setup.py install

I then ran the tests.py, everything worked! Sweet. However, I noticed that the tests.py file does not actually import parse_rest, instead it imports "__ init__ "as parse_rest.

Per the documentation my programs should simply import parse_rest. I used a snippit from tests.py to test this out, however it gives the following error:

Traceback (most recent call last):
File "tests_simple.py", line 29, in <module>
for s in GameScore.Query.all():
File "/Library/Python/2.7/site-packages/parse_rest/query.py", line 103, in __iter__
return iter(self._fetch())
File "/Library/Python/2.7/site-packages/parse_rest/query.py", line 112, in _fetch
return self._manager._fetch(**options)
File "/Library/Python/2.7/site-packages/parse_rest/query.py", line 35, in _fetch
return [klass(**it) for it in klass.GET(uri, **kw).get('results')]
File "/Library/Python/2.7/site-packages/parse_rest/__init__.py", line 110, in GET
return cls.execute(uri, 'GET', **kw)
File "/Library/Python/2.7/site-packages/parse_rest/__init__.py", line 71, in execute
if not ParseBase.CONNECTION:
AttributeError: type object 'ParseBase' has no attribute 'CONNECTION'

Here is the code that fails:

import parse_rest
from parse_rest.datatypes import Object

try:
    import settings_local
except ImportError:
    sys.exit('You must create a settings_local.py file with APPLICATION_ID, ' \
                 'REST_API_KEY, MASTER_KEY variables set')

parse_rest.ParseBase.register(
    getattr(settings_local, 'APPLICATION_ID'),
    getattr(settings_local, 'REST_API_KEY'),
    master_key=getattr(settings_local, 'MASTER_KEY')
    )

class GameScore(Object):
    pass

for s in GameScore.Query.all():
        s.delete()

scores = [GameScore(score=s, player_name='John Doe') for s in range(1, 6)]
for s in scores:
        s.save()

recursion when saving Installation

installations = Installation.Query.all()
for installation in installations:
    installation.save()

got a RuntimeError: maximum recursion depth exceeded in __instancecheck__

TestUser fail with error : Parse::UserCannotBeAlteredWithoutSessionError

Hi,

While running the full test, all the test passed except the 4 last TestUser, they all seem to fail when trying to delete the user. I configured the settings_local.py file with the three app id, rest key and master key as described in the documentation.

Here are the messages i get:

python -m unittest parse_rest.tests
Uploading source files
Finished uploading files
Not creating a release because no files have changed
................................EEEE
======================================================================
ERROR: testCanLogin (parse_rest.tests.TestUser)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "parse_rest/tests.py", line 462, in tearDown
    self._destroy_user()
  File "parse_rest/tests.py", line 441, in _destroy_user
    user and user.delete()
  File "parse_rest/user.py", line 27, in ret
    return func(obj, *args, **kw)
  File "parse_rest/user.py", line 76, in delete
    return User.DELETE(self._absolute_url, extra_headers=session_header)
  File "parse_rest/connection.py", line 142, in DELETE
    return cls.execute(uri, 'DELETE', **kw)
  File "parse_rest/connection.py", line 124, in execute
    raise exc(e.read())
ResourceRequestBadRequest: {"code":206,"error":"Parse::UserCannotBeAlteredWithoutSessionError"}


======================================================================
ERROR: testCanSignUp (parse_rest.tests.TestUser)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "parse_rest/tests.py", line 459, in setUp
    u.delete()
  File "parse_rest/user.py", line 27, in ret
    return func(obj, *args, **kw)
  File "parse_rest/user.py", line 76, in delete
    return User.DELETE(self._absolute_url, extra_headers=session_header)
  File "parse_rest/connection.py", line 142, in DELETE
    return cls.execute(uri, 'DELETE', **kw)
  File "parse_rest/connection.py", line 124, in execute
    raise exc(e.read())
ResourceRequestBadRequest: {"code":206,"error":"Parse::UserCannotBeAlteredWithoutSessionError"}


======================================================================
ERROR: testCanUpdate (parse_rest.tests.TestUser)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "parse_rest/tests.py", line 459, in setUp
    u.delete()
  File "parse_rest/user.py", line 27, in ret
    return func(obj, *args, **kw)
  File "parse_rest/user.py", line 76, in delete
    return User.DELETE(self._absolute_url, extra_headers=session_header)
  File "parse_rest/connection.py", line 142, in DELETE
    return cls.execute(uri, 'DELETE', **kw)
  File "parse_rest/connection.py", line 124, in execute
    raise exc(e.read())
ResourceRequestBadRequest: {"code":206,"error":"Parse::UserCannotBeAlteredWithoutSessionError"}


======================================================================
ERROR: testUserAsQueryArg (parse_rest.tests.TestUser)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "parse_rest/tests.py", line 459, in setUp
    u.delete()
  File "parse_rest/user.py", line 27, in ret
    return func(obj, *args, **kw)
  File "parse_rest/user.py", line 76, in delete
    return User.DELETE(self._absolute_url, extra_headers=session_header)
  File "parse_rest/connection.py", line 142, in DELETE
    return cls.execute(uri, 'DELETE', **kw)
  File "parse_rest/connection.py", line 124, in execute
    raise exc(e.read())
ResourceRequestBadRequest: {"code":206,"error":"Parse::UserCannotBeAlteredWithoutSessionError"}


----------------------------------------------------------------------
Ran 36 tests in 91.651s

FAILED (errors=4)

Relation and Array support

Is anyone working on relation and array support? I see some references to them in the code but it seems like its a work in progress. I'd be happy to flesh these capabilities out if nobody is working on them.

Add an installation entry to Installation table

Any suggestion on how to add a new entry to Installation table? Any convenient way to keep track of current installation like in android api (getCurrentInstallation)?
any comment will be appreciated.

This is what I currently do: (deviceType is required but values are limited to "ios", "android", "winrt", "winphone", or "dotnet", though my device is none of these types)

currentInstallation = Installation(user=username, installationId=uuid, deviceType='android')
currentInstallation.save()
(then serialize currentInstallation for later start up)

GeoPoint doesn't appear to support extra arguments, e.g. maxDistanceInMiles

The Parse REST API supports queries along the lines of "all locations within 10 miles", by passing the argument "maxDistanceInMiles to the filter constraint:

    "location": {
      "$nearSphere": {
        "__type": "GeoPoint",
        "latitude": 30.0,
        "longitude": -20.0
      },
      "$maxDistanceInMiles": 10.0
    }

However, while as of f679b4d we can use nearSphere:

nearby_restaurants = Restaurant.Query.filter(location__nearSphere=my_loc)

I don't see how in the current framework one could add both nearSphere and maxDistanceInMiles to the same constraint. I'm not even sure what it would look like, though perhaps the most plausible is something like

nearby_restaurants = Restaurant.Query.filter(location__nearSphere=my_loc,
                                                                        location__maxDistanceInMiles=10)

This would indicate that instead of having a finite list of operators that can follow __, we should always look for __ in an argument and add what follows to the list of constraints. I'm not sure if there would be undesired consequences. Any thoughts?

Installation classes are broken

In [1]: from parse_rest import installation
-----------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-1-eecd0979afb9> in <module>()
----> 1 from parse_rest import installation

 (...)parse_rest/installation.py in <module>()
     22 
     23 
---> 24 class InstallationQuery(Query):
     25     def _fetch(self):
     26         opts = dict(self._options)  # make a local copy

NameError: name 'Query' is not defined

Add ability to drop whole classes

It's possible to drop whole classes via the API. It doesn't seem to be documented, and it's unintuitive, but here it is:

ParseBase.POST("/schemas/{class}", _method="DELETE", _ClientVersion="browser")

I can add a "drop_class" class method to ParseBase, or to ParseResource - I'm not sure which one would be better.

What would be a use-case for initializing users with password?

Maybe this is part of a larger discussion to see how we are using this library, but I fail to see in which point of the application I would even have information about a user's password.

What I had implemented was simply mimicking the REST API:

  • User.signup would be a static method (and returning a User instance, but no session).
  • User.login would also be static method (returning a User instance and the key for the active session)

In both cases, the password is not part of what is returned in the response from Parse. What am I missing?

Push Success/Failure Response?

Something like:

print Push.alert(
        {"alert": "Testing push through alerts",
         "from": "name"},
         channels=["channel1"]
);

would output:

{"result":True}

or

{"result":False, "error":"..."}

Is anyone on this? If not I can get started with it.

enhancement proposal - objectsId argument for relation methods is error prone

Imagine you want to add one object to relation (imho the most common case)

s.addRelation('network', '_User', ['nUFjOYqcG4'])
vs
s.addRelation('network', '_User', 'nUFjOYqcG4')

first is correct and adds relation, second do nothing silently because string is iterable

There can be explicit check to disallow strings - not much pythonic
Better aPI woudle be accepting *args
s.addRelation('network', '_User', 'nUFjOYqcG4')
s.addRelation('network', '_User', 'nUFjOYqcG4', 'secondID')
but this is API incompatible

There is also option deprecate old style, accepts both *args or list for some time.
What do you think about it?

User.current_user() never seems to be working for me.

Traceback (most recent call last): File "app-test.py", line 152, in test_session_token me = User.current_user() File "/usr/local/lib/python2.7/dist-packages/parse_rest/user.p current_user return cls(**User.GET(user_url)) File "/usr/local/lib/python2.7/dist-packages/parse_rest/connec 30, in GET return cls.execute(uri, 'GET', **kw) File "/usr/local/lib/python2.7/dist-packages/parse_rest/connec 24, in execute raise exc(e.read()) ResourceRequestNotFound: {"code":101,"error":"invalid session"}

This is the error I keep getting. I have tried doing the login and then immediately using the Session Token to get the current user and it never works. Can someone please help?

Breakage in

I upgraded from 6/28/14 (18d1642) and today (7/19/14, ffacfc4).

A script that saved an object that owned a pointer broke. Error was:

File "src/scripts/parseScripts/shipping/shippingReceived.py", line 98, in main
order.save()
File "/usr/local/lib/python2.7/dist-packages/parse_rest/datatypes.py", line 254, in save
return self._update(batch=batch)
File "/usr/local/lib/python2.7/dist-packages/parse_rest/datatypes.py", line 272, in _update
response = self.class.PUT(self._absolute_url, batch=batch, *_self._to_native())
File "/usr/local/lib/python2.7/dist-packages/parse_rest/connection.py", line 112, in PUT
return cls.execute(uri, 'PUT', *_kw)
File "/usr/local/lib/python2.7/dist-packages/parse_rest/connection.py", line 72, in execute
data = kw and json.dumps(kw) or "{}"
File "/usr/lib/python2.7/json/init.py", line 231, in dumps
return _default_encoder.encode(obj)
File "/usr/lib/python2.7/json/encoder.py", line 201, in encode
chunks = self.iterencode(o, _one_shot=True)
File "/usr/lib/python2.7/json/encoder.py", line 264, in iterencode
return _iterencode(o, 0)
File "/usr/lib/python2.7/json/encoder.py", line 178, in default
raise TypeError(repr(o) + " is not JSON serializable")
TypeError: <User:[email protected] (Id N7kzJmloHR)> is not JSON serializable

Proposal for some concessions on pep8.

Now that pep8 is going through a revision, could I propose some slight customizations to the coding guidelines? Nothing big. The pep8 config file that I normally use is:

[pep8]
max-line-length = 96
ignore = E123,E701,E126,E261

max-line-length is obvious. The other errors that I ignore are just to allow one-line conditional statements (eg: if not x: return False), the requirement that inline comments must have two spaces after the end of the statement, and the one about closing brackets being on the same indentation-level as the opening one - which emacs' python-mode seem to hate with a passion.

ParseResource lazy loading fails

I believe that this commit causes ParseResource lazy loading to fail: 2fde657

It completely short-circuits lazy loading my toggling the _is_loaded flag without ever fetching anything. I've left a comment on the commit with a bit more depth. I believe it should be reverted to fix the issue.

exists query seems to give error

When I try and use exists filter, I get bad type for $exists. I assume I should be doing __exists='false'. Totally sorry if this is not supported or I am trying to use the exists filter wrong. I figured it would be either __exists='false' or __exists=false based on my best reading of the docs.

I am trying to find all users with a publisherId of 15 that have LoggedIn as (undefined).

Sample...

users = list(User.Query.filter(publisherId='15').filter(LoggedIn__exists='false').limit(1000))

Error when I do __exists='false'

Traceback (most recent call last):
  File "./parse_user_update_test.py", line 36, in <module>
    users = list(User.Query.filter(publisherId='15').filter(LoggedIn__exists='false').limit(1000).order_by("LoggedIn", descending=False))
  File "/usr/local/Cellar/python/2.7.6/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/parse_rest/query.py", line 102, in __iter__
    return iter(self._fetch())
  File "/usr/local/Cellar/python/2.7.6/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/parse_rest/query.py", line 117, in _fetch
    return self._manager._fetch(**options)
  File "/usr/local/Cellar/python/2.7.6/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/parse_rest/query.py", line 41, in _fetch
    return [klass(**it) for it in klass.GET(uri, **kw).get('results')]
  File "/usr/local/Cellar/python/2.7.6/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/parse_rest/connection.py", line 108, in GET
    return cls.execute(uri, 'GET', **kw)
  File "/usr/local/Cellar/python/2.7.6/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/parse_rest/connection.py", line 102, in execute
    raise exc(e.read())
parse_rest.core.ResourceRequestBadRequest: {"code":102,"error":"bad type for $exists"}

Error when i do __exists=false

Traceback (most recent call last):
  File "./parse_user_update_test.py", line 36, in <module>
    users = list(User.Query.filter(publisherId='15').filter(LoggedIn__exists=false).limit(1000).order_by("LoggedIn", descending=False))
NameError: name 'false' is not defined

Raw example from REST API https://www.parse.com/docs/rest#queries-constraints

curl -X GET \
  -H "X-Parse-Application-Id: v8ZPeTW4nKEHDmmaU6eJUiF2s3tE16yrYZzGzipR" \
  -H "X-Parse-REST-API-Key: 3hT7widAIBwKx3N3vcqCVU9P0CN2WCCKZ16ex7vD" \
  -G \
  --data-urlencode 'where={"score":{"$exists":true}}' \
  https://api.parse.com/1/classes/GameScore

Thanks again for such a great library.

documentation improvement: raw queries

I need request to reset password endpoint. Documentation says nothing about such functionality. Digging code, solution is quite easy

ParseBase.execute('/requestPasswordReset', 'POST', email='[email protected]')

it would be nice document it or offer another public api.

Issue running the test - Windows

I'm running this on Windows 7, using an Anaconda installation.

One thing I had to do which was missing from the instructions (maybe it's obvious to others) was after the pip install step, I also had to manually download setup.py and place it in the same directory as settings_local.py.

Once I did that - I ran the test, and got the following:

running test

E.......................

======================================================================

ERROR: test_simple_functions (parse_rest.tests.TestFunction)

test hello world and averageStars functions

----------------------------------------------------------------------

Traceback (most recent call last):

File "C:\Anaconda\lib\site-packages\parse_rest\tests.py", line 334, in setUp

os.chdir(cloud_function_dir)

WindowsError: [Error 2] The system cannot find the file specified: 'C:\\Anaconda\\lib\\site-packages\\parse_rest\\cloudcode'

----------------------------------------------------------------------

Ran 24 tests in 148.804s

FAILED (errors=1)

Looks like it is expecting a directory named cloudcode (which is not necessarily the name one would have given their cloud directory) and for it to be located under site-packages\parse_rest.

X-Parse-Session-Token support ?

Any plans for supporting something similar to Parse.become ?

Am thinking something like,

register(APPLICATION_ID, REST_API_KEY, master_key=None, session_token='')

Then all subsequent requests would be by whichever user the token belongs to.

I suppose ultimately, would like to have

with sessionToken('token123'):
Appointment.Query.all()
...

Bug with object own `__init__` and Query method

Hi,

I'm still a bit new to Python and after creating a class with its own __init__ method, the Query method doesn't work properly. If a comment my __init__ method, then the object get retrieve properly. I suppose my __init__method override something in the Object class, But it is fairly basic as you can see:

def __init__(self, name):
    self.name = name
    self.tasks = []
    List.listcount = List.listcount + 1

And the retrieving code is also pretty simple:

mylists = List.Query.all()
for mylist in mylists:
    print mylist.name
    print mylist.tasks

I could rewrite everything to not use my own __init__ method, but I would like not to do that as it keeps my code easier to understand. Any ideas on how to fix that? Thanks.

QueryManagers are gone?

The idea of using a metaclass for the creation of a ParseObject class is good. Not so sure about just having a Queryset being instantiated directly, unless it is possible to create custom query objects for different classes.

For instance, Installation can only be queried if we pass the master key. It needs to have a custom _fetch method.

I took the QueryManager/Queryset paradigm from django. Unless you see something wrong with the approach, may I suggest getting it back?

Update PyPI?

Hi:

This is a great module. I have been using it a fair bit and it appears to be fairly stable. Unfortunately the version in PyPI is not. Would it be possible to update PyPI. I'm deploying my code using AWS ELBS and it works well installing modules directly from PyPI , not so much with git.

Once again, thanks for working on this module!

Attempt to set ACL returns "object has no attribute 'ACL'"

    answer.User = user
    answer.ACL.set_user(user, read=True, write=True)
    answer.ACL.set_default(read=True)
    answer.ACL.set_role('admin', read=True, write=True)
    answer.save();

returns:

Traceback (most recent call last):
File "./fix_Question.py", line 32, in
answer.ACL.set_user(user, read=True, write=True)
File "/usr/local/lib/python2.7/dist-packages/parse_rest-0.2.20141004-py2.7.egg/parse_rest/datatypes.py", line 326, in getattr
return object.getattribute(self, attr) #preserve default if attr not exists
AttributeError: 'Question' object has no attribute 'ACL'

Can't save associated items

A ParseObject cannot be saved as the attribute of a different ParseObject:

def testAssociatedObject(self):
    collectedItem = CollectedItem(type="Sword", isAwesome=True)
    collectedItem.save()
    self.score.item = collectedItem
    self.score.save()

raises the exception:

Traceback (most recent call last):
  File "/Users/dgrtwo/Repositories/ParsePy/parse_rest/tests.py", line 114, in testAssociatedObject
    self.score.save()
  File "/Users/dgrtwo/Repositories/ParsePy/parse_rest/__init__.py", line 261, in save
    self._create()
  File "/Users/dgrtwo/Repositories/ParsePy/parse_rest/__init__.py", line 269, in _create
    response_dict = self.__class__.POST(uri, **self._to_native())
  File "/Users/dgrtwo/Repositories/ParsePy/parse_rest/__init__.py", line 194, in POST
    return cls.execute(uri, 'POST', **kw)
  File "/Users/dgrtwo/Repositories/ParsePy/parse_rest/__init__.py", line 160, in execute
    data = kw and json.dumps(kw) or "{}"
  File "/Library/Frameworks/EPD64.framework/Versions/7.3/lib/python2.7/json/__init__.py", line 231, in dumps
    return _default_encoder.encode(obj)
  File "/Library/Frameworks/EPD64.framework/Versions/7.3/lib/python2.7/json/encoder.py", line 201, in encode
    chunks = self.iterencode(o, _one_shot=True)
  File "/Library/Frameworks/EPD64.framework/Versions/7.3/lib/python2.7/json/encoder.py", line 264, in iterencode
    return _iterencode(o, 0)
  File "/Library/Frameworks/EPD64.framework/Versions/7.3/lib/python2.7/json/encoder.py", line 178, in default
    raise TypeError(repr(o) + " is not JSON serializable")
TypeError: <parse_rest.tests.CollectedItem object at 0x101422550> is not JSON serializable

This is because ParseResource does not know how to turn itself into a Pointer.

Querying for a file not existing

Hi guys,

I can't express the below Javascript query using ParsePy. The query returns objects where the "video" field isn't set and "video" is of type File.

var query = new Parse.Query(Classname);
query.notEqualTo("video", null);

I've tried
Classname.Query.all().filter(video__neq = None)
with no success.

Best,
Rafi

config/global.json

After installing the command line tools and setting up the cloud code as dictated here : https://www.parse.com/docs/cloud_code_guide

I keep getting this error:

E..................................

ERROR: test_simple_functions (main.TestFunction)

test hello world and averageStars functions

Traceback (most recent call last):
File "/Library/Python/2.7/site-packages/parse_rest/tests.py", line 394, in setUp
with open("config/global.json", "w") as outf:
IOError: [Errno 2] No such file or directory: 'config/global.json'


Ran 35 tests in 37.125s

FAILED (errors=1)

The directory I am trying to run "python -m 'parse_rest.tests' looks like this:
cloud config public settings_local.py settings_local.pyc

Count queries are limited by the limit

Since count queries, e.g. Object.Query.all().count, seem to run a query and just take the length, they are limited to the number of elements that the query will return, which can never exceed 1000.

For right now, a fix is to sort the objects by something unique to each object, e.g. the objectId, and then add 1000 to the skip value each time until no queries are returned.

bug with bidirectional pointers

For example if I have two objects that points to each other.
objectA.objectB_pointer
objectB.objectA_pointer

When querying one of the objects the code enters an infinite loop and eventually crashes with a "Maximum recursion depth exceeded" Error.

AttributeError: type object 'ParseBase' has no attribute 'register'

I installed parse_base as follows:

sudo easy_install parse_base

Hello, when I try to do a simple register

import parse_rest
parse_rest.ParseBase.register("ab", "cd")

I get this error:

AttributeError: type object 'ParseBase' has no attribute 'register'

Is this expected? Or am I missing something obvious?

[Not Exactly Issue] Has anyone used this library successfuly with Django?

This library looks amazing but I was wondering if anyone has managed to use it with Django instead of the Django models. It looks like both APIs are somewhat similar (I think you did it on purpose) but I'm curious how/where should I put the register(<application_id>, <rest_api_key>[, master_key=None]) function call and get some advice.

I know the Django admin is not going to work out of the box (and actually it may take a lot of effort to make it work with this library) but I like a lot the views' system and project structure of Django.

Thanks in advance and keep up the good work! ๐Ÿ‘

batch_save error with "unexpected keyword argument 'batch'"

I am trying to do a batch save and getting an error I can't figure out. From what I can tell, I am following the readme batch instructions. Anything obvious I am doing wrong or perhaps batch save of users is broken?

Essentially what I am doing in pseudocode...

from parse_rest.connection import register
register('appkey', 'restkey', master_key='masterkey')
from parse_rest.user import User
users = list(User.Query.filter(SomeFieldOfMine='15').limit(1000))
usersBatch = []
for u in users:
    u.SomeFieldOfMine = 'Touched'
    try:
        usersBatch.extend(u)
    except TypeError:
        usersBatch.append(u)

from parse_rest.connection import ParseBatcher
batcher = ParseBatcher()
batcher.batch_save(usersBatch)

ERROR

Traceback (most recent call last):
  File "./update_user.py", line 48, in <module>
    batcher.batch_save(usersBatch)
  File "/usr/local/Cellar/python/2.7.6/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/parse_rest/connection.py", line 142, in batch_save
    self.batch([o.save for o in objects])
  File "/usr/local/Cellar/python/2.7.6/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/parse_rest/connection.py", line 132, in batch
    queries, callbacks = zip(*[m(batch=True) for m in methods])
  File "/usr/local/Cellar/python/2.7.6/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/parse_rest/user.py", line 27, in ret
    return func(obj, *args, **kw)
TypeError: save() got an unexpected keyword argument 'batch'

Object is not callable or unexpected keyword

I was instantiating the parse object with no arguments and set the properties after the instantiation and the error that appeared was "foo is no callable". So i wrote all the properties of the Object in the instantiation, and now the error is "_parse() got an unexpected keyword argument 'bar'.
The class is:
class foo(Object):
pass.

What do i do? (Sorry for my english)

containedIn/$in query

Is there a way to do a "containedIn" style query as described by the Parse API docs? In the REST API this is the $in parameter key.

Running into - ResourceRequestNotFound: {"code":101,"error":"invalid session"}

The user logs and may perform a few requests but after some time they end up with
ResourceRequestNotFound: {"code":101,"error":"invalid session"}
I guess this is because the user is logged out or sessionToken is lost.
I'm using flask for this.

  File "/Library/Python/2.7/site-packages/flask/app.py", line 1836, in __call__
    return self.wsgi_app(environ, start_response)
  File "/Library/Python/2.7/site-packages/flask/app.py", line 1820, in wsgi_app
    response = self.make_response(self.handle_exception(e))
  File "/Library/Python/2.7/site-packages/flask/app.py", line 1403, in handle_exception
    reraise(exc_type, exc_value, tb)
  File "/Library/Python/2.7/site-packages/flask/app.py", line 1817, in wsgi_app
    response = self.full_dispatch_request()
  File "/Library/Python/2.7/site-packages/flask/app.py", line 1477, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/Library/Python/2.7/site-packages/flask/app.py", line 1381, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File "/Library/Python/2.7/site-packages/flask/app.py", line 1475, in full_dispatch_request
    rv = self.dispatch_request()
  File "/Library/Python/2.7/site-packages/flask/app.py", line 1461, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "/Users/rohitmittal/Downloads/NewResikshaServer/stilt_api.py", line 79, in get_current_user
    current_user = User.current_user()
  File "/Library/Python/2.7/site-packages/parse_rest/user.py", line 97, in current_user
    return cls(**User.GET(user_url))
  File "/Library/Python/2.7/site-packages/parse_rest/connection.py", line 133, in GET
    return cls.execute(uri, 'GET', **kw)
  File "/Library/Python/2.7/site-packages/parse_rest/connection.py", line 127, in execute
    raise exc(e.read())
ResourceRequestNotFound: {"code":101,"error":"invalid session"}

Unable to import ACL class

When I try to import the ACL class as described in the doc, I get the following error:

ImportError: cannot import name ACL

Upload File/Images.

Right now there is no way to upload files/images to Parse through our library. We need to implement these functions on the File/Image data types.

object.save() make all the keys dirty

I modified few attributes on an object and saved it.
gameScore.score = 2061
gameScore.save()
When querying from beforesave cloud code, request.object.dirtyKeys() returns all keys dirty... instead of just the modified keys.

Saving JSON object

I could not figure out how to save a dictionary in parse.com as JSON object. I was only able to convert the dictionary to a string and save it to parse.com string data-type which will make JSON elements inaccessible in queries

UserQuery is now broken.

Let's say I want to get a list of users:

>>> from parse_rest import UserQuery

>>> UserQuery().fetch()
*** AttributeError: 'UserQuery' object has no attribute '_options'

It looks like the new __init__ for UserQuery is at fault.

Update PyPi

The README is no longer valid for the current version in PyPi, and the lack of versions means it can't be included in a requirements.txt via -e <githubURL>

This is the library Parse refers people to, please keep it up to date :(

Tests.py overwrites Cloud Code

Running tests.py writes into Parse as it clearly must in order to validate the installation. However, in contrast to adding new Classes and new instances of those classes in the appropriate store, the tests replace any existing cloud code. This is partially due to the way Parse implements cloud code.

At a minimum it seems like a good idea for the test module to confirm with the user prior to writing cloud code if there is some present. At the very least the README section on testing the installation could warn people about this.

Recursion on datatypes.py convert_to_parse method

Due to the fact that str objects in Python 3 have an __iter__ attribute using hasattr(python_object, '__iter__') is no longer sufficient. It is necessary to also check the object type for str. I have submitted a PR that solves one such instance of this issue.

#61

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.