diff --git a/appyter/parse/nb.py b/appyter/parse/nb.py index c2314f00..f7164c6d 100644 --- a/appyter/parse/nb.py +++ b/appyter/parse/nb.py @@ -1,3 +1,4 @@ +import json import nbformat as nbf def nb_from_ipynb_string(string): @@ -7,6 +8,9 @@ def nb_from_ipynb_file(filename): with open(filename, 'r') as fr: return nbf.read(fr, as_version=4) +def nb_from_json(j): + return nb_from_ipynb_string(json.dumps(j)) + def nb_to_ipynb_string(nb): return nbf.writes(nb) @@ -16,3 +20,6 @@ def nb_to_ipynb_io(nb, io): def nb_to_ipynb_file(nb, filename): with open(filename, 'w') as fw: nb_to_ipynb_io(nb, fw) + +def nb_to_json(nb): + return json.loads(nb_to_ipynb_string(nb)) diff --git a/appyter/remote/__init__.py b/appyter/remote/__init__.py new file mode 100644 index 00000000..e9e24533 --- /dev/null +++ b/appyter/remote/__init__.py @@ -0,0 +1,5 @@ +''' Remote interaction with an appyter +''' +import os +from appyter.util import importdir +importdir(os.path.join(os.path.dirname(__file__)), __package__, globals()) diff --git a/appyter/remote/cli.py b/appyter/remote/cli.py new file mode 100644 index 00000000..514eafb6 --- /dev/null +++ b/appyter/remote/cli.py @@ -0,0 +1,7 @@ +import click + +from appyter.cli import cli + +@cli.group(help='Interact with a remote appyter') +def remote(): + pass diff --git a/appyter/remote/nbevalutate.py b/appyter/remote/nbevalutate.py new file mode 100644 index 00000000..a6cef3dc --- /dev/null +++ b/appyter/remote/nbevalutate.py @@ -0,0 +1,223 @@ +import os +import sys +import json +import click +import asyncio +import socketio +import requests + +from appyter.remote.cli import remote +from appyter.util import join_routes, is_remote + +def read_chunks(fr, chunk_size=1024*100): + ''' Read a file in chunks generating the chunks + ''' + while True: + data = fr.read(chunk_size) + if not data: + break + yield data + +async def get_queue_assert(input_msg_queue, **conditions): + response = await input_msg_queue.get() + assert response['type'] not in {'error', 'connection_error', 'disconnect'}, response['data'] + for key, value in conditions.items(): + assert response[key] == value, f"Failure, expected {conditions} got {response}" + return response + +async def do_remote_download(sio, input_msg_queue, output_msg_queue, data): + ''' Handle submitting a download, and watching it complete on remote. + ''' + await sio.emit('download_start', data) + msg = await get_queue_assert(input_msg_queue, type='download_queued') + await output_msg_queue.put(msg) + while True: + msg = await input_msg_queue.get() + await output_msg_queue.put(msg) + if msg['type'] == 'download_complete': + break + assert msg['type'] == 'download_progress', f"Failure, expected `download_progress` got {msg}" + +async def do_remote_upload(sio, input_msg_queue, output_msg_queue, data): + ''' Handle upload to remote. + ''' + with open(data['name'], 'rb') as fr: + # request upload + await sio.emit('siofu_start', data) + # wait for server to be ready + msg = await input_msg_queue.get() + await output_msg_queue.put(msg) + assert msg['type'] == 'siofu_ready' and msg['data']['id'] == data['id'], f"Error: received {msg}" + # read file in chunks + for chunk in read_chunks(fr): + await sio.emit('siofu_progress', dict(id=data['id'], content=chunk)) + msg = await input_msg_queue.get() + await output_msg_queue.put(msg) + assert msg['type'] == 'siofu_chunk' and msg['data']['id'] == data['id'], f"Error: received {msg}" + # + await sio.emit('siofu_done', data) + msg = await input_msg_queue.get() + await output_msg_queue.put(msg) + assert msg['type'] == 'siofu_complete' and msg['data']['id'] == data['id'], f"Error: received {msg}" + +async def remote_message_producer(sio, url, input_msg_queue): + ''' Listen for events and put them on a async queue + ''' + # Step 1. Capture messages from remote onto a queue for processing + # socketio + @sio.on('connect') + async def _(): + await input_msg_queue.put(dict(type='connect', data='')) + @sio.on('connect_error') + async def _(data): + await input_msg_queue.put(dict(type='connect_error', data=data)) + @sio.on('disconnect') + async def _(): + await input_msg_queue.put(dict(type='disconnect', data='')) + # api + @sio.on('session') + async def _(data): + await input_msg_queue.put(dict(type='session', data=data)) + # notebook generation + @sio.on('status') + async def _(data): + await input_msg_queue.put(dict(type='status', data=data)) + @sio.on('progress') + async def _(data): + await input_msg_queue.put(dict(type='progress', data=data)) + @sio.on('nb') + async def _(data): + await input_msg_queue.put(dict(type='nb', data=data)) + @sio.on('cell') + async def _(data): + await input_msg_queue.put(dict(type='cell', data=data)) + @sio.on('error') + async def _(data): + await input_msg_queue.put(dict(type='error', data=data)) + # remote download + @sio.on('download_start') + async def _(data): + await input_msg_queue.put(dict(type='download_start', data=data)) + @sio.on('download_queued') + async def _(data): + await input_msg_queue.put(dict(type='download_queued', data=data)) + @sio.on('download_complete') + async def _(data): + await input_msg_queue.put(dict(type='download_complete', data=data)) + @sio.on('download_error') + async def _(data): + await input_msg_queue.put(dict(type='download_error', data=data)) + # remote upload + @sio.on('siofu_ready') + async def _(data): + await input_msg_queue.put(dict(type='siofu_ready', data=data)) + @sio.on('siofu_chunk') + async def _(data): + await input_msg_queue.put(dict(type='siofu_chunk', data=data)) + @sio.on('siofu_complete') + async def _(data): + await input_msg_queue.put(dict(type='siofu_complete', data=data)) + @sio.on('siofu_error') + async def _(data): + await input_msg_queue.put(dict(type='siofu_error', data=data)) + # + # Step 2. Establish a connection with remote + await sio.connect(url) + # wait until disconnect (pushing any received events onto the asyncio queue) + await sio.wait() + +async def evaluate_saga(sio, url, context, params, output, input_msg_queue, output_msg_queue): + ''' Consume events from remote when necessary + ''' + try: + # Ensure we connected properly + response = await get_queue_assert(input_msg_queue, type='connect') + # Step 3. Request session on remote + await sio.emit('session', {}) + response = await get_queue_assert(input_msg_queue, type='session') + session = response['data'] + # Step 4. Get file(s) onto remote + for param in params: + if param['field'] != 'FileField': + continue + if param['args']['name'] in context: + context_value = context[param['args']['name']] + if is_remote(context_value): + # have remote download the file + await do_remote_download(sio, input_msg_queue, output_msg_queue, dict(session, name=param['args']['name'], url=context_value, filename=os.path.basename(context_value))) + else: + # upload the file + await do_remote_upload(sio, input_msg_queue, output_msg_queue, dict(session, id=0, name=context_value)) + else: + await output_msg_queue.put({ 'type': 'warning', 'data': f"Missing file for `{param['args']['name']}`" }) + # Step 5. Create notebook on remote + await sio.emit('submit', dict(session, **context)) + # Step 6. Start execution on remote + await sio.emit('init', session) + # Process results + while True: + response = await get_queue_assert(input_msg_queue) + await output_msg_queue.put(response) + if response['type'] == 'status' and response['data'] == 'Success': + # Step 7. Grab final notebook from remote + # NOTE: not async but doesn't really matter here.. + response = requests.get(join_routes(url, session['_session'])[1:], headers={'Accept': 'application/json'}) + assert response.status_code <= 299, f"Error ({response.status_code}): {response.text}" + json.dump(response.json(), output) + break + except Exception as e: + await output_msg_queue.put(dict(type='error', data=str(e))) + # + # Step 8. Disconnect from server + await sio.disconnect() + await output_msg_queue.put(None) +# +async def output_msg_consumer(output_msg_queue, emit): + while True: + msg = await output_msg_queue.get() + if msg is None: + break + print(json.dumps(msg), file=emit) + +@remote.command(help='Construct and execute the appyter provided parameters') +@click.option( + '--context', + envvar='CONTEXT', + default='-', + type=click.Path(readable=True, dir_okay=False, allow_dash=True), + help='JSON serialized context mapping field names to values (- for stdin)', +) +@click.option( + '--emit', + envvar='EMIT', + default='-', + type=click.Path(writable=True, dir_okay=False, allow_dash=True), + help='The output location of the progress stream (- for stderr)', +) +@click.option( + '--output', + envvar='OUTPUT', + default='-', + type=click.Path(writable=True, dir_okay=False, allow_dash=True), + help='The output location of the inspection json (- for stdout)', +) +@click.argument('url', envvar='URL') +def nbevaluate(url, context, emit, output): + context = json.load(sys.stdin if context == '-' else open(context, 'r')) + emit = sys.stderr if emit == '-' else open(emit, 'w') + output = sys.stdout if output == '-' else open(output, 'w') + # Step 1. Inspect remote ensuring it's up and grabbing notebook parameters + response = requests.get(url, headers={'Accept': 'application/json'}) + assert response.status_code <= 299, f"Error ({response.status_code}): {response.text}" + params = response.json() + # Execute async saga for evaluation + sio = socketio.AsyncClient() + loop = asyncio.get_event_loop() + input_msg_queue = asyncio.Queue(loop=loop) + output_msg_queue = asyncio.Queue(loop=loop) + loop.run_until_complete(asyncio.gather( + remote_message_producer(sio, join_routes(url, 'socket.io')[1:], input_msg_queue), + evaluate_saga(sio, url, context, params, output, input_msg_queue, output_msg_queue), + output_msg_consumer(output_msg_queue, emit), + )) + loop.close() diff --git a/appyter/remote/nbinspect.py b/appyter/remote/nbinspect.py new file mode 100644 index 00000000..dbddddf9 --- /dev/null +++ b/appyter/remote/nbinspect.py @@ -0,0 +1,12 @@ +import click +import requests + +from appyter.remote.cli import remote + +@remote.command(help='Inspect appyter for arguments (fields)') +@click.option('--output', envvar='OUTPUT', default='-', type=click.File('w'), help='The output location of the inspection json') +@click.argument('url', envvar='URL') +def nbinspect(url, output): + response = requests.get(url, headers={'Accept': 'application/json'}) + assert response.status_code <= 299, f"Error ({response.status_code}): {response.text}" + json.dump(response.json(), output) diff --git a/appyter/remote/nbviewer.py b/appyter/remote/nbviewer.py new file mode 100644 index 00000000..1a104c0b --- /dev/null +++ b/appyter/remote/nbviewer.py @@ -0,0 +1,30 @@ +import sys +import json +import tempfile +from subprocess import Popen, PIPE + +from appyter.context import get_env, get_jinja2_env +from appyter.render.nbviewer import render_nb_from_stream + +def jupyter_inline_evaluate(url, context): + def stream_generator(): + with tempfile.NamedTemporaryFile('w') as tmp: + tmp.close() + with open(tmp.name, 'w') as fw: + json.dump(context, fw) + + with Popen([ + sys.executable, + '-u', + '-m', 'appyter', + 'remote', 'nbevaluate', + f"--context={tmp.name}", + url, + ], stdout=PIPE, stderr=PIPE) as proc: + packet = proc.stderr.readline() + while packet: + yield packet.decode() + packet = proc.stderr.readline() + + env = get_jinja2_env(get_env(**dict(ipynb='app.ipynb'))) + render_nb_from_stream(env, stream_generator()) diff --git a/appyter/render/nbexecute.py b/appyter/render/nbexecute.py index 23161ba0..2e81dd6d 100644 --- a/appyter/render/nbexecute.py +++ b/appyter/render/nbexecute.py @@ -7,7 +7,7 @@ from appyter.cli import cli from appyter.ext.nbclient import NotebookClientIOPubHook from appyter.render.nbviewer import render_nbviewer_from_nb -from appyter.parse.nb import nb_from_ipynb_file, nb_to_ipynb_file +from appyter.parse.nb import nb_from_ipynb_file, nb_to_ipynb_file, nb_to_json def cell_is_code(cell): @@ -48,6 +48,7 @@ def nbexecute(ipynb='', emit=json_emitter, cwd=''): resources={ 'metadata': {'path': cwd} }, iopub_hook=iopub_hook_factory(nb, emit), ) + emit({ 'type': 'nb', 'data': nb_to_json(nb) }) with client.setup_kernel(): n_cells = len(nb.cells) emit({ 'type': 'status', 'data': 'Executing...' }) diff --git a/appyter/render/nbviewer.py b/appyter/render/nbviewer.py index e27a2be3..5a53921a 100644 --- a/appyter/render/nbviewer.py +++ b/appyter/render/nbviewer.py @@ -1,7 +1,7 @@ import nbconvert from bs4 import BeautifulSoup -def render_nbviewer_from_nb(env, nb): +def render_nbviewer_from_nb_soup(env, nb): ''' Render an nbviewer (HTML serialization of the notebook) ''' exporter = nbconvert.HTMLExporter() @@ -15,7 +15,65 @@ def render_nbviewer_from_nb(env, nb): soup.find('script').decompose() # soup.find('style').decompose() # remove first style (bootstrap) soup.find('link').decompose() # remove link to custom stylesheet + return soup + +def render_nbviewer_cell_from_nb(env, nb, cell_index): + ''' Render a single cell from a notebook + + TODO: Surely this can be more efficient.. + ''' + exporter = nbconvert.HTMLExporter() + export, _ = exporter.from_notebook_node(nb) + soup = BeautifulSoup(export, 'html.parser') + return soup.select('.cell')[cell_index] + +def render_nbviewer_from_nb(env, nb): + ''' Render an nbviewer (HTML serialization of the notebook) + ''' + soup = render_nbviewer_from_nb_soup(env, nb) nb_container = soup.select('#notebook-container')[0] nb_container['class'] = '' nb_container['id'] = '' return str(soup) + +def render_nb_from_stream(env, stream): + ''' Render a jupyter notebook and update it *in* a jupyter notebook from an nbexecute progress stream. + + :param nb: (ipynb) The loaded jupyter notebook + :param stream: (generator) The stream of messages coming from nbexecute. + ''' + import json + import uuid + from IPython.display import display, update_display, HTML + from appyter.util import try_json_loads + from appyter.parse.nb import nb_from_json + id = '_' + str(uuid.uuid4()) + nb = None + display(HTML('Starting...'), display_id=id+'_status') + for msg in stream: + msg = try_json_loads(msg) + if type(msg) == dict: + if nb is None and msg['type'] == 'nb': + # received the constructed notebook parse and render it + nb = msg['data'] + nb_html = render_nbviewer_from_nb_soup(env, nb_from_json(nb)) + nb_html.select('#notebook-container')[0]['id'] = '#' + id + # show each cell separately with an id based on the index + for cell_index, cell in enumerate(nb_html.select('.cell')): + display(HTML(str(cell)), display_id=id+'_%d' % (cell_index)) + elif nb is not None and msg['type'] == 'cell': + cell, cell_index = msg['data'] + # when a cell updates, we'll update the notebook and update the cell display + nb['cells'][cell_index] = cell + cell_rendered = str(render_nbviewer_cell_from_nb(env, nb_from_json(nb), cell_index)) + update_display(HTML(cell_rendered), display_id=id+'_%d' % (cell_index)) + elif msg['type'] == 'status': + update_display(HTML(str(msg['data'])), display_id=id+'_status') + elif msg['type'] == 'error': + update_display(HTML('Error'), display_id=id+'_status') + raise Exception(msg['data']) + else: + update_display(HTML(str(msg)), display_id=id+'_status') + update_display(HTML(''), display_id=id+'_status') + # + return nb diff --git a/example/example.ipynb b/example/example.ipynb index f1667976..73915325 100644 --- a/example/example.ipynb +++ b/example/example.ipynb @@ -46,12 +46,16 @@ }, "outputs": [ { - "output_type": "display_data", "data": { - "text/plain": "", - "text/markdown": "### Sub Section b" + "text/markdown": [ + "### Sub Section b" + ], + "text/plain": [ + "" + ] }, - "metadata": {} + "metadata": {}, + "output_type": "display_data" } ], "source": [ @@ -67,9 +71,11 @@ }, "outputs": [ { - "output_type": "stream", "name": "stdout", - "text": " 9" + "output_type": "stream", + "text": [ + " 9" + ] } ], "source": [ @@ -134,9 +140,11 @@ }, "outputs": [ { - "output_type": "stream", "name": "stderr", - "text": "UsageError: Cell magic `%%appyter` not found.\n" + "output_type": "stream", + "text": [ + "UsageError: Cell magic `%%appyter` not found.\n" + ] } ], "source": [ @@ -295,9 +303,9 @@ "metadata": { "file_extension": ".py", "kernelspec": { - "display_name": "Python 3.8.2 64-bit", + "display_name": "Python 3.8.1 64-bit", "language": "python", - "name": "python38264bit2ba397cf2bc44e92827d2ba15784a3d0" + "name": "python38164bit1a447d370127449d809db2bf16ffa68f" }, "language_info": { "codemirror_mode": { @@ -309,7 +317,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.3-final" + "version": "3.8.3" }, "mimetype": "text/x-python", "name": "python", @@ -319,4 +327,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} \ No newline at end of file +} diff --git a/example/remote_example.ipynb b/example/remote_example.ipynb new file mode 100644 index 00000000..5547bed9 --- /dev/null +++ b/example/remote_example.ipynb @@ -0,0 +1,495 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "scrolled": false + }, + "outputs": [ + { + "data": { + "text/html": [ + "Error" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
\n", + "
\n", + "
\n", + "

My Title

Section 1

\n", + "
\n", + "
\n", + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
\n", + "
\n", + "
\n", + "

Sub Section a

\n", + "
\n", + "
\n", + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
\n", + "
\n", + "
In [1]:
\n", + "
\n", + "
\n", + "
### Comment, not subsection\n",
+       "
\n", + "
\n", + "
\n", + "
\n", + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
\n", + "
\n", + "
\n", + "

Sub Section b

\n", + "
\n", + "
\n", + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
\n", + "
\n", + "
In [2]:
\n", + "
\n", + "
\n", + "
import sys\n",
+       "import time\n",
+       "for n in range(10):\n",
+       "    time.sleep(0.5)\n",
+       "    print('\\r', n, end='')\n",
+       "
\n", + "
\n", + "
\n", + "
\n", + "
\n", + "
\n", + "
\n", + "
\n", + "
\n", + "
 9
\n", + "
\n", + "
\n", + "
\n", + "
\n", + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
\n", + "
\n", + "
\n", + "

My Title

\n", + "
\n", + "
\n", + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
\n", + "
\n", + "
In [3]:
\n", + "
\n", + "
\n", + "
answer = 11 + 5\n",
+       "answer # add 11, 5 = 16\n",
+       "
\n", + "
\n", + "
\n", + "
\n", + "
\n", + "
\n", + "
\n", + "
Out[3]:
\n", + "
\n", + "
16
\n", + "
\n", + "
\n", + "
\n", + "
\n", + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
\n", + "
\n", + "\n", + "
\n", + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
\n", + "
\n", + "
In [4]:
\n", + "
\n", + "
\n", + "
open('test_example.py', 'r').read()\n",
+       "
\n", + "
\n", + "
\n", + "
\n", + "
\n", + "
\n", + "
\n", + "
\n", + "
\n", + "
\n",
+       "---------------------------------------------------------------------------\n",
+       "FileNotFoundError                         Traceback (most recent call last)\n",
+       "<ipython-input-4-92960564e284> in <module>\n",
+       "----> 1 open('test_example.py', 'r').read()\n",
+       "\n",
+       "FileNotFoundError: [Errno 2] No such file or directory: 'test_example.py'
\n", + "
\n", + "
\n", + "
\n", + "
\n", + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
\n", + "
\n", + "
In [ ]:
\n", + "
\n", + "
\n", + "
from IPython.display import display, Markdown\n",
+       "\n",
+       "display(Markdown(\"**test**\"))\n",
+       "display(Markdown(\"**test2**\"))\n",
+       "
\n", + "
\n", + "
\n", + "
\n", + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
\n", + "
\n", + "
In [ ]:
\n", + "
\n", + "
\n", + "
result = ['a', 'c']\n",
+       "result\n",
+       "
\n", + "
\n", + "
\n", + "
\n", + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
\n", + "
\n", + "
In [ ]:
\n", + "
\n", + "
\n", + "
result = ['a', 'c']\n",
+       "result\n",
+       "
\n", + "
\n", + "
\n", + "
\n", + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
\n", + "
\n", + "
In [ ]:
\n", + "
\n", + "
\n", + "
from test import my_test_func\n",
+       "my_test_func()\n",
+       "
\n", + "
\n", + "
\n", + "
\n", + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
\n", + "
\n", + "
In [ ]:
\n", + "
\n", + "
\n", + "
import numpy as np\n",
+       "import pandas as pd\n",
+       "n1 = np.linspace(5.5, 22)\n",
+       "n2 = np.linspace(2.5, 10)\n",
+       "z = n1 + n2\n",
+       "df = pd.DataFrame({'n1': n1, 'n2': n2, 'z': z})\n",
+       "df.to_csv('test.tsv', sep='\\t')\n",
+       "df\n",
+       "
\n", + "
\n", + "
\n", + "
\n", + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
\n", + "
\n", + "
In [ ]:
\n", + "
\n", + "
\n", + "
['hello', 'world']\n",
+       "
\n", + "
\n", + "
\n", + "
\n", + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
\n", + "
\n", + "
\n", + "

Section 2

\n", + "
\n", + "
\n", + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
\n", + "
\n", + "
\n", + "

this
\n", + "is\n", + "valid

\n", + "

markdown

\n", + "
\n", + "
\n", + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
\n", + "
\n", + "
\n", + "

\"\"

\n", + "
\n", + "
\n", + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "ename": "Exception", + "evalue": "Cell execution error on cell 8", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mException\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;32mfrom\u001b[0m \u001b[0mappyter\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mremote\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mnbviewer\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mjupyter_inline_evaluate\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 2\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 3\u001b[0;31m jupyter_inline_evaluate(\n\u001b[0m\u001b[1;32m 4\u001b[0m \u001b[0murl\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m'http://127.0.0.1:5000/'\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0mcontext\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m{\u001b[0m\u001b[0;34m}\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/.local/lib/python3.8/site-packages/appyter/remote/nbviewer.py\u001b[0m in \u001b[0;36mjupyter_inline_evaluate\u001b[0;34m(url, context)\u001b[0m\n\u001b[1;32m 28\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 29\u001b[0m \u001b[0menv\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mget_jinja2_env\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mget_env\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m**\u001b[0m\u001b[0mdict\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mipynb\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m'app.ipynb'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 30\u001b[0;31m \u001b[0mrender_nb_from_stream\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0menv\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mstream_generator\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;32m~/.local/lib/python3.8/site-packages/appyter/render/nbviewer.py\u001b[0m in \u001b[0;36mrender_nb_from_stream\u001b[0;34m(env, stream)\u001b[0m\n\u001b[1;32m 73\u001b[0m \u001b[0;32melif\u001b[0m \u001b[0mmsg\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'type'\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;34m'error'\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 74\u001b[0m \u001b[0mupdate_display\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mHTML\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'Error'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mdisplay_id\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mid\u001b[0m\u001b[0;34m+\u001b[0m\u001b[0;34m'_status'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 75\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mException\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmsg\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'data'\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 76\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 77\u001b[0m \u001b[0mupdate_display\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mHTML\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mstr\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmsg\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mdisplay_id\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mid\u001b[0m\u001b[0;34m+\u001b[0m\u001b[0;34m'_status'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mException\u001b[0m: Cell execution error on cell 8" + ] + } + ], + "source": [ + "from appyter.remote.nbviewer import jupyter_inline_evaluate\n", + "\n", + "jupyter_inline_evaluate(\n", + " url = 'http://127.0.0.1:5000/',\n", + " context={},\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3.8.1 64-bit", + "language": "python", + "name": "python38164bit1a447d370127449d809db2bf16ffa68f" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.3" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/requirements.txt b/requirements.txt index 7baddf61..4c0abc22 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ +aiohttp bs4 click click-default-group @@ -14,4 +15,5 @@ nbclient nbconvert nbformat python-dotenv +python-socketio requests \ No newline at end of file