Building Julython
by Robert Myers
by Robert Myers
We started Julython to encourage people to write Python.
We work best with artificial deadlines and superficial point/reward systems.
So far we have 555 participants with over 12500 commits in 735 projects!
Here is an example:
application: julython
...
handlers:
- url: /static
static_dir: july/static_root
- url: /_ah/channel/(connected|disconnected)/
script: july.channel.app
- url: /api.*
script: july.api.app
- url: /.*
script: july.main.app
Base Handler:
class API(webapp2.RequestHandler):
def options(self):
"""Be a good netizen citizen and return HTTP verbs allowed."""
valid = ', '.join(webapp2._get_handler_methods(self))
self.response.set_status(200)
self.response.headers['Allow'] = valid
return self.response.out
def respond_json(self, message, status_code=200):
self.response.set_status(status_code)
self.response.headers['Content-type'] = 'application/json'
self.response.headers['Access-Control-Allow-Origin'] = '*'
resp = json.dumps(message)
return self.response.out.write(resp)
class PostCallbackHandler(API):
def parse_commits(self, commits):
"""Takes a list of raw commit data and returns a dict"""
def parse_payload(self):
payload = self.request.params.get('payload')
return self.parse_commits(payload)
def post(self):
success = []
for commit in self.parse_payload():
success.append(Commit.make_commit())
self.respond_json(success, status_code=201)
class GithubHandler(PostCallbackHandler):
"""
payload=>"{
"repository": {
"url": "http://github.com/defunkt/github",
...
},
"commits": [
{
"id": "41a212ee83ca127e3c8cf465891ab7216a705f59",
"url": "http://github.com/defunkt/github/commit/41a212ee83ca127e3c8cf465891ab7216a705f59",
"author": {
"email": "chris@ozmm.org",
"name": "Chris Wanstrath"
},
"message": "okay i give in",
"timestamp": "2008-02-15T14:57:17-08:00",
"added": ["filepath.rb"]
},
{
"id": "de8251ff97ee194a289832576287d6f8ad74e3d0",
"url": "http://github.com/defunkt/github/commit/de8251ff97ee194a289832576287d6f8ad74e3d0",
"author": {
"email": "chris@ozmm.org",
"name": "Chris Wanstrath"
},
"message": "update pricing a tad",
"timestamp": "2008-02-15T14:36:34-08:00"
}
],...
"""
Create the denormalizaion code fix_location then simply import the deferred library and call it:
from google.appengine.ext import deferred
@login_required
def edit_profile(request, username, template_name='people/edit.html'):
from forms import EditUserForm
user = User.get_by_auth_id('own:%s' % username)
existing_team = str(getattr(user, 'team_slug', ''))
form = EditUserForm(request.POST or None, user=request.user)
if form.is_valid():
user.put()
if user.location_slug != existing_slug:
# Defer a couple tasks to update the locations
deferred.defer(fix_location, str(user.location_slug))
deferred.defer(fix_location, existing_slug)
In your view simply add a token:
from google.appengine.api import channel
# Julython live stuffs
token_key = 'live_token:%s' % request.user.username
token = memcache.get(token_key)
if token is None:
token = channel.create_channel(request.user.username)
memcache.set(token_key, token, time=7000)
Add the proper js lib and connect the callbacks:
<script type="text/javascript" src="/_ah/channel/jsapi"></script>
<script type="text/javascript">
var self = this;
var channel = new goog.appengine.Channel({{ token }});
var socket = channel.open();
socket.onmessage = function(message) {
// parse the message and prepend to the ul element
self._newMessage(message);
};
socket.onerror = function(message){
console.log("error");
};
socket.onclose = function() {
self._reconnect();
};
</script>
Create a simple deferred task after a commit is created to send a message:
def send_live_message(key):
"""Deferred task for sending messages to the open channels."""
message_key = model.Key(urlsafe=key)
message = message_key.get()
# Open connections stored in Connection Model
connections = Connection.query().filter(Connection.timestamp >= oldest).fetch(200, keys_only=True)
for connection in connections:
channel.send_message(connection.id(), message.to_json())