Electron Adventures: Bonus Episode 101: Python Eel

Electron Adventures: Bonus Episode 101: Python Eel

The series was planned to have just 100 episodes, but I found a really cool library after I finished the series, so here's a bonus extra special episode about Python Eel.

What is Eel?

Eel basically bundles a Python web server with Chrome running in application mode, quite similar to what we did in Ferrum Sinatra Terminal App with Ruby.

The difference is that Eel comes up with a lot of convenience functions to make it easier for frontend and backend to communicate with each other, so you can spend less time on communication code, and more on your application.

main.py

After we install Eel with pip3 install eel, as well as installing Chrome if you somehow didn't do that yet, let's write main.py:

#!/usr/bin/env python3

import eel

counter = 0

@eel.expose
def change_counter(num):
  global counter
  counter += num
  if counter >= 10:
    eel.display_message("slow down!")
  return counter

eel.init("web")
eel.start("index.html")

We tell Eel to start serving web directory, and open new window in Chrome, serving index.html from it.

We also have one function for changing the counter, and we tell Eel to expose it to the frontend with @eel.expose.

That function also calls eel.display_message, which we'll define later.

web/index.html

The frontend is just some placeholder elements. /eel.js is provided by Eel. app.js is our code.

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
</head>
<body>
  <h1>Counter: <span id="count">0</span></h1>
  <button id="plus">+1</button>
  <button id="minus">-1</button>
  <div id="message"></div>
  <script type="text/javascript" src="/eel.js"></script>
  <script type="text/javascript" src="app.js"></script>
</body>
</html>

web/app.js

let countSpan = document.querySelector("#count")

document.querySelector("#plus").addEventListener("click", async () => {
  let result = await eel.change_counter(1)()
  countSpan.textContent = result
})

document.querySelector("#minus").addEventListener("click", async () => {
  let result = await eel.change_counter(-1)()
  countSpan.textContent = result
})

eel.expose(display_message)
function display_message(message) {
  document.querySelector("#message").textContent = message
}

And finally the frontend code. Whenever we click one of the buttons, it calls eel.change_counter on the backend. It's not clear to me why, but to get return value we need to do the weird double-call with await eel.change_counter(1)(). I'm not sure why Eel calls don't just return a promise without extra steps, but there's probably some reason for it.

We also define display_message function, and expose it to the backend with eel.expose. JavaScript doesn't have function decorators, but this fake decorator works due to function hoisting.

Results

And you can see the results here:

electron-adventures-101-screenshot.png

Python Eel is a surprisingly nice library for creating frontends for your Python projects, and I wish something similar existed for Ruby (Ferrum Sinatra code I created does something similar but much less elegantly).

One big downside is that in default settings Python Eel doesn't launch Chrome as a separate app instance, so if you Cmd-Q, it will also close your regular browser, which is not really what you'd like to happen.

But overall, I definitely recommend it.

As usual, all the code for the episode is here.