Created
October 10, 2017 13:25
-
-
Save minrk/1fdc9786a5852584854b7c3f548fa0c9 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"""Launching a binder via API | |
The binder build API yields a sequence of messages via event-stream. | |
This example demonstrates how to consume events from the stream | |
and redirect to the URL when it is ready. | |
Events: | |
- request build fro binder | |
- request file-listing from notebook API | |
- request kernel from notebook API | |
- connect websocket to kernel | |
- run code via websocket | |
- collect and print output from websocket | |
""" | |
import json | |
import pprint | |
import sys | |
from textwrap import indent | |
from jupyter_client.session import Session, date_default | |
import requests | |
from tornado.ioloop import IOLoop | |
from tornado.httpclient import HTTPRequest | |
from tornado.websocket import websocket_connect | |
def binder_events(repo, | |
ref='master', | |
*, | |
binder_url='https://beta.mybinder.org'): | |
"""Launch a binder | |
Yields Binder's event-stream events (dicts) | |
""" | |
print("Building binder for {repo}@{ref}".format(repo=repo, ref=ref)) | |
url = binder_url + '/build/gh/{repo}/{ref}'.format(repo=repo, ref=ref) | |
r = requests.get(url, stream=True) | |
r.raise_for_status() | |
for line in r.iter_lines(): | |
line = line.decode('utf8', 'replace') | |
if line.startswith('data:'): | |
yield json.loads(line.split(':', 1)[1]) | |
def build_binder(repo): | |
for evt in binder_events(repo): | |
if 'message' in evt: | |
print("[{phase}] {message}".format( | |
phase=evt.get('phase', ''), | |
message=evt['message'].rstrip(), | |
)) | |
if evt.get('phase') == 'ready': | |
# return the ready event, | |
# which has 'url' and 'token' keys | |
return evt | |
if evt.get('phase') == 'failed': | |
raise ValueError("Build failed: %s" % evt) | |
raise ValueError("Build never finished") | |
async def run_code(ws, code, session): | |
print("Running code: \n%s" % indent(code, ' ')) | |
msg = session.msg('execute_request', { | |
'code': code, | |
'silent': False, | |
}) | |
msg_id = msg['header']['msg_id'] | |
json_msg = json.dumps(msg, default=date_default) | |
ws.write_message(json_msg) | |
# watch for output from that code | |
while True: | |
msg = await ws.read_message() | |
msg = json.loads(msg) | |
if msg['parent_header'].get('msg_id') != msg_id: | |
continue | |
msg_type = msg['header']['msg_type'] | |
# print('received', msg_type, msg['content']) | |
# reprint stream outputs | |
if msg_type == 'stream': | |
sys.stdout.write(msg['content']['text']) | |
# stop when we get to status=idle | |
if ( | |
msg_type == 'status' | |
and msg['content']['execution_state'] == 'idle' | |
): | |
return | |
async def main(): | |
repo = 'yuvipanda/example-requirements' | |
info = build_binder(repo) | |
s = requests.Session() | |
s.headers['Authorization'] = 'token ' + info['token'] | |
base_url = info['url'] | |
if not base_url.endswith('/'): | |
base_url = base_url + '/' | |
r = s.get(base_url + 'api/contents') | |
r.raise_for_status() | |
print("files in binder:") | |
pprint.pprint(r.json()['content']) | |
r = s.post(base_url + 'api/kernels') | |
r.raise_for_status() | |
kernel_id = r.json()['id'] | |
print("got kernel %s" % kernel_id) | |
pprint.pprint(r.json()) | |
# connect to websocket with tornado | |
ws_url = 'ws' + base_url[4:] + f'api/kernels/{kernel_id}/channels' | |
print("Connecting to %s" % ws_url) | |
ws = await websocket_connect(HTTPRequest(ws_url, headers=s.headers)) | |
# run some code | |
await run_code(ws, 'print("hi")', session=Session()) | |
if __name__ == '__main__': | |
IOLoop.instance().run_sync(main) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment