Skip to content

Instantly share code, notes, and snippets.

@minrk
Created October 10, 2017 13:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save minrk/1fdc9786a5852584854b7c3f548fa0c9 to your computer and use it in GitHub Desktop.
Save minrk/1fdc9786a5852584854b7c3f548fa0c9 to your computer and use it in GitHub Desktop.
"""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