Skip to content

Commit

Permalink
Merge pull request #47 from apiad/develop
Browse files Browse the repository at this point in the history
Publish command
  • Loading branch information
apiad authored Jan 1, 2020
2 parents 473803f + 2a7aed9 commit df52fd0
Show file tree
Hide file tree
Showing 9 changed files with 273 additions and 46 deletions.
33 changes: 25 additions & 8 deletions auditorium/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,24 +11,35 @@ class Auditorium:
@staticmethod
def run(
path,
host="127.0.0.1",
port=6789,
debug=False,
instance_name="show",
launch=True,
*,
host: str = "127.0.0.1",
port: int = 6789,
debug: bool = False,
instance_name: str = "show",
):
"Runs a custom Python script as a slideshow."

show = Show.load(path, instance_name)
show.run(host=host, port=port, debug=debug, launch=launch)
show.run(host=host, port=port, debug=debug)

@staticmethod
def demo(host="127.0.0.1", port=6789, debug=False, launch=True):
def publish(
path: str,
name: str,
*,
server: str = "wss://auditorium.apiad.net",
instance_name: str = "show"
):
show = Show.load(path, instance_name)
show.publish(server=server, name=name)

@staticmethod
def demo(host: str = "127.0.0.1", port: int = 6789, debug: bool = False):
"Starts the demo slideshow."

from auditorium.demo import show

show.run(host, port, debug=debug, launch=launch)
show.run(host=host, port=port, debug=debug)

@staticmethod
def render(path, theme="white", instance_name="show"):
Expand All @@ -37,6 +48,12 @@ def render(path, theme="white", instance_name="show"):
show = Show.load(path, instance_name)
print(show.render(theme))

@staticmethod
def server(host: str = "0.0.0.0", port: int = 9876):
from auditorium.server import run_server

run_server(host=host, port=port)

@staticmethod
def test():
return "It's OK!"
Expand Down
104 changes: 104 additions & 0 deletions auditorium/server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# coding: utf8

import warnings
import websockets

from typing import Dict, Tuple
from fastapi import FastAPI, HTTPException
from starlette.responses import HTMLResponse
from starlette.websockets import WebSocket
import asyncio
from jinja2 import Template

from .utils import path
from .show import UpdateData

server = FastAPI()

SERVERS: Dict[str, Tuple[asyncio.Queue, asyncio.Queue]] = {}

with open(path('templates/server.html')) as fp:
TEMPLATE = Template(fp.read())


@server.get("/")
async def index():
return HTMLResponse(TEMPLATE.render(servers_list=list(SERVERS)))


@server.get("/{name}/")
async def render(name: str):
try:
queue_in, queue_out = SERVERS[name]
except KeyError:
raise HTTPException(404)

await queue_in.put(dict(type="render"))

response = await queue_out.get()
queue_out.task_done()

return HTMLResponse(response["content"])


@server.post("/{name}/update")
async def update(name: str, data: UpdateData):
try:
queue_in, queue_out = SERVERS[name]
except KeyError:
raise HTTPException(404)

await queue_in.put(data.dict())

response = await queue_out.get()
queue_out.task_done()

return response


@server.websocket("/ws")
async def ws(websocket: WebSocket):
await websocket.accept()
name = await websocket.receive_text()

if name in SERVERS:
await websocket.send_json(dict(type="error", msg="Name is already taken."))
await websocket.close()
return

print("Registering new server: ", name)

queue_in: asyncio.Queue = asyncio.Queue()
queue_out: asyncio.Queue = asyncio.Queue()

SERVERS[name] = (queue_in, queue_out)

try:
while True:
command = await queue_in.get()
await websocket.send_json(command)
response = await websocket.receive_json()

queue_in.task_done()
await queue_out.put(response)
except:
print("(!) Connection to %s closed by client." % name)

for _ in range(queue_in.qsize()):
queue_in.task_done()

for _ in range(queue_out.qsize()):
queue_out.task_done()

print("Unregistering server:", name)
SERVERS.pop(name)


def run_server(*, host="0.0.0.0", port=9876):
try:
import uvicorn

uvicorn.run(server, host=host, port=port)
except ImportError:
warnings.warn("(!) You need `uvicorn` installed in order to call `server`.")
exit(1)
66 changes: 52 additions & 14 deletions auditorium/show.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,27 @@
This module includes the `Show` class and the main functionalities of `auditorium`.
"""

import asyncio
import base64
import io
import json
import runpy
import warnings
import webbrowser
from collections import OrderedDict
from typing import Union

import websockets
from fastapi import FastAPI
from jinja2 import Template
from markdown import markdown
from pydantic import BaseModel
from pygments import highlight
from pygments.formatters.html import HtmlFormatter
from pygments.lexers import get_lexer_by_name
from pygments.styles import get_style_by_name

from fastapi import FastAPI
from starlette.staticfiles import StaticFiles
from starlette.responses import HTMLResponse
from pydantic import BaseModel
from typing import Union
from starlette.staticfiles import StaticFiles

from .components import Animation, Block, Column, Fragment, ShowMode
from .utils import fix_indent, path
Expand Down Expand Up @@ -64,22 +66,58 @@ def __init__(self, title="", theme="white", code_style="monokai"):

## Show functions

def run(self, host: str, port: int, launch: bool, *args, **kwargs) -> None:
def run(self, *, host: str, port: int, debug: bool = False) -> None:
self._content = self._render_content()

# if launch:
# def launch_server():
# webbrowser.open_new_tab(f"http://{host}:{port}")

# self.app.add_task(launch_server)

try:
import uvicorn

uvicorn.run(self.app, host=host, port=port, *args, **kwargs)
uvicorn.run(self.app, host=host, port=port, debug=debug)
except ImportError:
warnings.warn("In order to call `run` you need `uvicorn` installed.")
warnings.warn("(!) You need `uvicorn` installed in order to call `run`.")
exit(1)

def publish(self, server: str, name: str):
url = "{}/ws".format(server)
asyncio.get_event_loop().run_until_complete(self._ws(url, name))

async def _ws(self, url: str, name: str):
try:
async with websockets.connect(url) as websocket:
print("Connected to server")
await websocket.send(name)
print("Starting command loop.")

while True:
command = await websocket.recv()
command = json.loads(command)

response = self._do_ws_command(command)
response = json.dumps(response)
await websocket.send(response)
except ConnectionRefusedError:
print("(!) Could not connect to %s. Make sure server is up." % url)
exit(1)
except websockets.exceptions.ConnectionClosedError:
print("(!) Connection to %s closed by server." % url)
exit(1)


def _do_ws_command(self, command):
if command["type"] == "render":
print("Rendering content")
return dict(content=self.render())
elif command["type"] == "error":
print("(!) %s" % command['msg'])
raise websockets.exceptions.ConnectionClosedError(1006, command['msg'])
else:
print("Executing slide %s" % command["slide"])
values = {}
values[command["id"]] = command["value"]
update = self.do_code(command["slide"], values)
return update

raise ValueError("Unknown command: %s", command["type"])

@property
def show_title(self) -> str:
Expand Down
2 changes: 1 addition & 1 deletion auditorium/static/js/auditorium.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ function setupAnimations(parent) {
value: step
});

fetch("/update", {
fetch("update", {
method: "POST",
headers: {
"Accept": "application/json",
Expand Down
21 changes: 21 additions & 0 deletions auditorium/templates/server.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Auditorium</title>
</head>
<body>
<h1>Welcome to Auditorium</h1>

<h3>The following slideshows have been registered:</h3>
<ul>
{% for name in servers_list %}
<li>
<a href="/{{ name }}/">{{ name }}</a>
</li>
{% endfor %}
</ul>
</body>
</html>
12 changes: 3 additions & 9 deletions docs/consider.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,22 +22,16 @@ That being said, there are some known deficiencies that I might fix, and some ot
## Slides need to be fast

A slide's code is executed completely every time that slide needs to be rendered.
That is, once during loading and then when inputs change or animations tick.
That is, once during the initial rendering and then when inputs change or animations tick.
Hence, you slide logic should be fairly fast.
This is particularly true for animations, so don't expect to be able to train a neural network in real time.
The slide logic is meant to be simple, the kind of one-liners you can run every keystroke, like less than 1 second fast.
The slide logic is meant to be simple, the kind of one-liners you can run every keystroke, in the order of a couple hundred miliseconds at most.
If you need to interactively draw the loss value of a neural network, either is gonna take a while or you will have to fake it, i.e., compute it offline and then simply animate it.

## All slides are executed on load

For now, on the first load all slides are going to be run, which might increase significantly your loading time if you have complex logic in each slide.
At some point, if I run into the problem, I may add a "lazy" loading option so that only the first few slides are executed.
If this is an issue for a lot of people it might become a priority.

## Slides have to be stateless

The code that runs inside a slide should not depend on anything outside of `ctx`, since you have no guarantee when will it be executed.
Right now, slide's code is executed once before any rendering in order to discover vertical slides, then again during the
Right now, slide's code is executed once during the
initial rendering to layout and then everytime an interaction or animation forces the slide to render again.
However, this might be changed at any time, so make no assumptions as to when is that code executed.
The easiest way to do this, is making sure that every slide function is a pure function and all state is handled through
Expand Down
6 changes: 6 additions & 0 deletions docs/history.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# History

### v19.1.4

* Added command `auditorium server` that allows to proxy a slideshow through a public server.
* Added command `auditorium publish` for authors to publish their slides.
* Opened service at [auditorium.apiad.net](http://auditorium.apiad.net) (sorry, no HTTPS yet).

### v19.1.3

* Changed vertical slides API, now slides don't need to be run the first time the slideshow is loaded, since the entire slideshow is flat.
Expand Down
Loading

0 comments on commit df52fd0

Please sign in to comment.