on master help sign in
add magic commands to jupyter kernel
commitd9aa03ab8839ff284194547ea425e7582c6ac05e authorAgentM <agentm@themactionfaction.com> dateWed 15 Nov 2017, 23:40 (9 years ago) parentcf55b30
2 files changed+90−34
jupyter/itutd/ITutorialDKernel.py+89−33 jupyter/setup.py+1−1
jupyter/itutd/ITutorialDKernel.py+89−33
@@ -1,11 +1,12 @@
11from ipykernel.kernelbase import Kernel
22import subprocess
3−from websocket import create_connection
3+from websocket import create_connection, WebSocketConnectionClosedException
44import time
55import select
66import json
77import socket
88import errno
9+import urlparse
910
1011class ITutorialDKernel(Kernel):
1112 implementation = 'ITutorialD'
@@ -23,11 +24,12 @@ class ITutorialDKernel(Kernel):
2324 dbserver = None
2425 ws = None
2526
27+# todo: implement wrong database name handling
2628 def runtutd(self, tutd):
2729 #setup server
28− port = 63000
2930 dbname = 'jupyter'
30− if not self.dbserver:
31+ port = 63000
32+ if not self.ws and not self.dbserver:
3133 try:
3234 self.dbserver = subprocess.Popen(['project-m36-websocket-server',
3335 '--database', dbname,
@@ -35,39 +37,48 @@ class ITutorialDKernel(Kernel):
3537 )
3638 except OSError as err:
3739 if err.errno == errno.ENOENT:
38− return ({'displayerror':{'html':'"project-m36-websocket-server" is not in the PATH environment variable and could not be started.'}}, None)
40+ return ('"project-m36-websocket-server" is not in the PATH environment variable and could not be started.', None)
3941 else:
4042 raise
41− if not self.ws:
42− attempts = 0
43− while 1:
44− try:
45− ws = create_connection('ws://127.0.0.1:{}'.format(port))
46− except socket.error as err:
47− if err.errno in (errno.ECONNREFUSED, ):
48− time.sleep(0.3)
49− attempts += 1
50− if attempts > 10:
51− assert False, "websocket timeout"
52− else:
53− raise
54− else:
55− break
56− self.ws = ws
57− self.ws.send('connectdb:{}'.format(dbname))
58− #receive and discard prompt info
59− self.ws.recv()
60− self.ws.recv()
43+ res = self.connect_ws('ws://127.0.0.1:63000', dbname, retries=10)
44+ if res:
45+ return ('Connection attempt failed.', None)
46+
6147 self.ws.send('executetutd/html+text:' + tutd)
62− jsonresult = self.discard_prompts()
48+ try:
49+ jsonresult = self.discard_prompts()
50+ except WebSocketConnectionClosedException:
51+ self.ws = None
52+ return ('Websocket server closed the connection.',None)
6353 res = json.loads(jsonresult)
64− if 'displayerror' in jsonresult:
65− return (res, None)
66− elif 'acknowledged' in jsonresult:
54+ if 'displayerror' in res:
55+ return (res['displayerror']['text'], None)
56+ elif 'acknowledged' in res:
6757 return (None, {'html':'ok', 'text':'ok'})
6858 else:
6959 return (None, res['displayrelation'])
7060
61+ def connect_ws(self, url, dbname, retries=0):
62+ while 1:
63+ try:
64+ ws = create_connection(url)
65+ except socket.error as err:
66+ if err.errno in (errno.ECONNREFUSED, ):
67+ time.sleep(0.3)
68+ if retries == 0:
69+ return 'Failed to connect to websocket.'
70+ retries -= 1
71+ else:
72+ raise
73+ else:
74+ break
75+ self.ws = ws
76+ self.ws.send('connectdb:{}'.format(dbname))
77+ msg = json.loads(self.ws.recv())
78+ if 'displayerror' in msg:
79+ return msg['displayerror']['tag']
80+ return None
81+
7182 def discard_prompts(self):
7283 #read from the websocket until a non-prompt message is received
7384 while 1:
@@ -85,27 +96,54 @@ class ITutorialDKernel(Kernel):
8596 self.send_response(self.iopub_socket, 'error', stream_content)
8697
8798
99+ def process_magic(self, code):
100+ #handle "magic" processing of commands which are not sent to the tutd interpreter
101+ cmds = code.split(' ')
102+ ok = {'text':'ok', 'html':'ok'}
103+
104+ if cmds[0] == '%connect' and len(cmds) == 3:
105+ #connect to alternate database
106+ url = urlparse.urlparse(cmds[1])
107+ dbname = cmds[2]
108+ if not url.scheme.startswith('ws'):
109+ return ('Rejected attempt to connect to non-websocket. Use ws:// or wss:// scheme',None)
110+ res = self.connect_ws(url.geturl(), dbname)
111+ if res: #error from connection
112+ return (res, None, None)
113+ else: #connection successful
114+ return (None, ok, None)
115+ elif cmds[0] == '%help' and len(cmds) == 1:
116+ #display some help
117+ return (None, ok, self.show_help())
118+ elif cmds[0].startswith('%'):
119+ return ('Invalid magic command.', None)
120+
88121 def do_execute(self, code, silent,
89122 store_history=True,
90123 user_expressions=False,
91124 allow_stdin=False):
92− (err, relresult) = self.runtutd(code)
125+ magic = self.process_magic(code)
126+ payload = None
127+ if magic:
128+ (err, result, payload) = magic
129+ else:
130+ (err, result) = self.runtutd(code)
93131 if not silent:
94132 if err:
95133 status = 'error'
96− self.send_error(err['displayerror']['html'])
134+ self.send_error(err)
97135 else:
98136 status = 'ok'
99137 display_content = {
100− 'data':{'text/html':relresult['html'],
101− 'text/plain':relresult['text']},
138+ 'data':{'text/html':result['html'],
139+ 'text/plain':result['text']},
102140 'metadata':{},
103141 'transient':{}
104142 }
105143 self.send_response(self.iopub_socket, 'display_data', display_content)
106144 return {'status': status,
107145 'execution_count': self.execution_count,
108− 'payload':[],
146+ 'payload':[payload],
109147 'user_expressions':{}}
110148
111149 def do_shutdown(self, restart):
@@ -113,6 +151,24 @@ class ITutorialDKernel(Kernel):
113151 self.dbserver.terminate()
114152 self.dbserver.kill()
115153
154+ def show_help(self):
155+ return {'source': 'page',
156+ 'start':0,
157+ 'data':{'text/html':'''<h1>Project:M36 TutorialD Jupyter Kernel Help</h1>
158+<p>TutorialD is an interactive language for the relational algebra.</p>
159+
160+<p><a href="https://github.com/agentm/project-m36/blob/master/docs/15_minute_tutorial.markdown">15 Minute TutorialD Introduction</a></p>
161+<p><a href="https://github.com/agentm/project-m36/blob/master/docs/tutd_tutorial.markdown">TutorialD Documentation</a></p>
162+''',
163+ 'text/plain':
164+'''Project:M36 TutorialD Jupyter Kernel Help
165+
166+TutorialD is an interactive language for the relational algebra.
167+
168+15 Minute TutorialD Introduction: https://github.com/agentm/project-m36/blob/master/docs/15_minute_tutorial.markdown
169+TutorialD Documentation: https://github.com/agentm/project-m36/blob/master/docs/tutd_tutorial.markdown
170+'''}}
171+
116172if __name__ == '__main__':
117173 from ipykernel.kernelapp import IPKernelApp
118174 IPKernelApp.launch_instance(kernel_class=ITutorialDKernel)
jupyter/setup.py+1−1
@@ -1,4 +1,4 @@
1−from distutils.core import setup
1+from setuptools import setup
22
33setup(
44 name='itutd',