Control the DOM from Python using Websockets
Dependencies: Python 3, Tornado, Bottle
pip install skink
Skink is a Proof of Concept and under development. Even when stable and ready, there will still be plenty of good reasons not to use it in production.
Skink uses Tornado and Websockets to give Python an access to the DOM of all connected users, and get your Python functions called when something is happening on the web page. It also has a blocking mode you can use to get the value of an element without using callbacks.
Skink will keep an open Websocket connection to every browser, and generate Javascript code for the browser to execute from your Python instructions. It does so in a lazy way and avoids unnecessary roundtrips with the browser. For example, the following two instructions will only result in one instruction being sent to the browser:
page.document.getElementById('hello').innerHTML = "Hello You"
page.document.getElementById('hello').innerHTML = page.document.getElementById('hello2').innerHTML
This is done by creating intermediate skink.remote.JSObject
objects that distinguish between basic types, other JSObjects and callable objects.
Skink integrates with the Bottle Web Framework for exposing standard HTTP resources.
Register Python callbacks directly on JavaScript events:
def my_python_function():
print("Hello from Python")
page.document.getElementById('hello').onclick = my_python_function
Decorate functions that have to be called everytime a new client connects to a page:
@page.on_open
def page_open():
bob.document.getElementById('hello').innerHTML = "Hello You"
bob.document.getElementById('message').onkeypress = my_python_function
Expose arbitrary Python functions to Javascript clients:
def my_python_sum(a, b):
print(a + b)
page.register(my_python_function, 'py_sum')
From Javascript:
skink.call('py_sum', [1, 2]);
Run arbitrary Javascript code in a page's clients:
page.run("alert('Hello!');")
Tip: launch with python -i sample.py
to keep playing with Skink in the Python interpreter.
import skink.server as server
import skink.remote as remote
from bottle import Bottle
# Define the static HTTP views with Bottle.py
b = Bottle()
@b.get('/alice')
@b.get('/bob')
def alice():
return '''
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<h1>My super simple chat</h1>
<div id='stderr'></div>
<div id='hello'>
Hello !
</div>
<div id='hello2'>
Hello2 !
</div>
<div>
<input id='message' />
</div>
<script type='application/javascript' src='/skink/skink.js'></script>
</body>
</html>
'''
# Start the web(socket) server with the Bottle app:
server.start_thread(b)
# Define the remote pages you want to control:
alice = remote.RemotePage('/alice')
bob = remote.RemotePage('/bob')
# Define the Python callbacks.
def alice_keypress():
z = alice.document.getElementById('message').value._eval()
bob.document.getElementById('hello2').innerHTML = 'Alice says: ' + z
def bob_keypress():
z = bob.document.getElementById('message').value._eval()
alice.document.getElementById('hello2').innerHTML = 'Bob says: ' + z
# Register the callbacks every time a client connects:
@alice.on_open
def alice_open():
alice.document.getElementById('hello').innerHTML = "Hello Alice"
alice.document.getElementById('message').onkeypress = alice_keypress
@bob.on_open
def bob_open():
bob.document.getElementById('hello').innerHTML = "Hello Bob"
bob.document.getElementById('message').onkeypress = bob_keypress
print("Open your browser on pages http://localhost:8000/alice and http://localhost:8000/bob")
Photo of a garden Skink, Credits Fir0002/Flagstaffotos