Pywebview staples together Python backend with OS-specific web engine frontend.
I'll say in advance that this is a total disaster. On many Windows machines you will literally get IE11 engine rendering your app. Even in best case scenario, you won't even have console.log
available, and there's no reloading other than by quitting the whole app and restarting. Depending on not just the OS, but what's installed on the OS, you'll face completely different engine with completely different limitations, so developing anything nontrivial is going to be a huge pain. But for now, let's ignore all such issues.
Also Python situation with installing libraries is a lot messier than JavaScript or Ruby. I ran these on OSX 11.4, with pip3 install pywebview
. If you have trouble installing that and following along, you'll need to refer to pywebview documentation.
hello1
We can start with the simplest possible program - just create a window passing a title and a URL
#!/usr/bin/env python3
import webview
window = webview.create_window(
"Hello, World!",
"https://en.wikipedia.org/wiki/%22Hello,_World!%22_program"
)
webview.start()
Here's the result:
hello2
We can also generate HTML and send it into the browser window.
#!/usr/bin/env python3
import webview
html="""
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
body {
margin: 0;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background-color: #444;
color: #fff;
min-height: 100vh;
}
</style>
</head>
<body>
<h1>Hello, World!</h1>
</body>
</html>
"""
window = webview.create_window(
"Hello, World!",
html=html
)
webview.start()
Here's the result:
hello3
Let's try another thing, loading from a file. Here's Python, HTML, and CSS parts.
Passing file:
URLs dooesn't seem to work, but passing file paths directly does.
#!/usr/bin/env python3
import webview
window = webview.create_window(
"Hello, World!",
"hello3.html"
)
webview.start()
The document:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="./hello3.css" />
</head>
<body>
<h1>Hello, World!</h1>
</body>
</html>
The styling:
body {
margin: 0;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background-color: #444;
color: #fff;
min-height: 100vh;
}
Here's the result, identical to what we had before:
Counter
Now that we went through the warm-up, let's write a click counter app.
We can create an API for the webapp and pass it as js_api
argument. It will be available on the frontend through window.pywebview.api
. It's important to note that it's completely async
so we need to await
all results.
#!/usr/bin/env python3
import webview
class App:
def __init__(self):
self.counter = 0
def click(self):
print("Clicked!")
self.counter += 1
def getCount(self):
return self.counter
app = App()
window = webview.create_window(
"Click Counter",
"counter.html",
js_api=App()
)
webview.start()
The document:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="./counter.css" />
</head>
<body>
<div>Click count: <span id="count">0</span></div>
<button>Click</button>
<script src="./counter.js"></script>
</body>
</html>
The styling:
body {
margin: 0;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background-color: #444;
color: #fff;
min-height: 100vh;
font-size: 300%;
}
button {
font-size: unset;
}
And finally the frontend code, notice all the await
s:
let button = document.querySelector("button")
let count = document.querySelector("#count")
button.addEventListener("click", async () => {
await window.pywebview.api.click()
count.innerText = await window.pywebview.api.getCount()
})
Here's the result:
Conclusions
Pywebview staples together a nice backend - fully powered Python, and a disastrous frontend without even a console.log
. It's something to consider if you have big existing Python codebase, you want to create a very simple frontend for it, and you know the systems it will run on, but it's grossly insufficient for anything requiring more complex frontends.
These are mostly technical limitations rather than anything fundamental, and with some effort pywebview could definitely be developed into a viable platform with minor changes (drop IE11, add dev tools, add reload etc.).
And even though I already concluded it's quite bad, in the next episode we'll do our traditional terminal app in pywebview anyway.
As usual, all the code for the episode is here.