Brython
December 20, 2024I already mentioned in a previous post that web development has its own Three Musketeers, with HTML, CSS, JS and PHP taking the main roles. It turns out that I'd like to introduce another character. While I could stretch the analogy further by referencing Raoul de Bragelonne, a figure who enters the story 20 years later, I’ll resist the temptation to overdo it.
This new addition has a unique quality. It is a combination of two things: Browser and Python. I’ve never considered myself a big fan of portmanteau words, but Brython, is one I can get behind. Especially considering the fact that it enables writing code in Python to manage tasks that would typically require JS. For greater clarity, and to avoid any confusion, Brython actually takes a Python code and transpiles it into JS, allowing it to run directly in the browser
Getting started is easy. There’s no installation or setup beyond including the Brython script in your HTML script:
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/brython@3.10.5/brython.min.js"></script>
From there, you can use the following lines to write your Python code directly:
<body onload="brython()">
<script type="text/python">
# write your Python code here
</script>
</body>
As easy as 1-2-3.
So I wanted to give it a try.
Even though a tutorial and some demos are provided on their website, I wanted to first replicate the clock that is visible on their home page. I took a bit of time to understand which libraries were available, but I managed to get the following example:
This clock, slightly inspired by Mondaine watches, is actually created by the following Python code:
from browser import document, window
# canvas
canvas = document["clock"]
ctx = canvas.getContext("2d")
# functions to make things easier
pi = window.Math.PI
def cos(val):
return window.Math.cos(val)
def sin(val):
return window.Math.sin(val)
# draw clock
def draw_clock():
# get time and date
now = window.Date.new()
day = now.getDate()
hours = now.getHours()
mins = now.getMinutes()
secs = now.getSeconds()
# canvas
ctx.clearRect(0, 0, canvas.width, canvas.height)
cx = canvas.width / 2
cy = canvas.height / 2
radius = 120
# draw clock
ctx.beginPath()
ctx.arc(cx, cy, radius, 0, 2 * pi)
ctx.fillStyle = "#fff"
ctx.fill()
ctx.strokeStyle = "#000"
ctx.lineWidth = 8
ctx.stroke()
# draw small clock ticks
ctx.strokeStyle = "#000"
ctx.lineWidth = 2
for i in range(60):
angle = (pi / 30) * i
x1 = cx + (radius * 0.9 * cos(angle))
y1 = cy + (radius * 0.9 * sin(angle))
x2 = cx + (radius * 0.95 * cos(angle))
y2 = cy + (radius * 0.95 * sin(angle))
ctx.beginPath()
ctx.moveTo(x1, y1)
ctx.lineTo(x2, y2)
ctx.stroke()
# draw clock ticks
ctx.strokeStyle = "#000"
ctx.lineWidth = 5
for i in range(12):
angle = (pi / 6) * i
x1 = cx + (radius * 0.8 * cos(angle))
y1 = cy + (radius * 0.8 * sin(angle))
x2 = cx + (radius * 0.95 * cos(angle))
y2 = cy + (radius * 0.95 * sin(angle))
ctx.beginPath()
ctx.moveTo(x1, y1)
ctx.lineTo(x2, y2)
ctx.stroke()
# place day with frame
text_angle = 0
text_x = cx + (radius * 0.6 * cos(text_angle))
text_y = cy + (radius * 0.6 * sin(text_angle))
ctx.font = "20px Arial"
ctx.fillStyle = "black"
ctx.textAlign = "center"
ctx.textBaseline = "middle"
ctx.fillText(str(day), text_x, text_y)
date_width = ctx.measureText(str(day)).width
frame_padding = 10
ctx.strokeStyle = "black"
ctx.lineWidth = 2
ctx.strokeRect(
text_x - (date_width / 2) - (frame_padding / 2),
text_y - 8 - (frame_padding / 2),
date_width + frame_padding,
15 + frame_padding
)
# draw hours
hour_angle = (pi / 6) * (hours % 12 + mins / 60) - (pi / 2)
hour_length = radius * 0.7
ctx.lineWidth = 8
ctx.beginPath()
ctx.moveTo(cx, cy)
ctx.lineTo(cx + hour_length * cos(hour_angle),
cy + hour_length * sin(hour_angle))
ctx.stroke()
# draw minutes
min_angle = (pi / 30) * (mins + secs / 60) - (pi / 2)
min_length = radius * 0.85
ctx.lineWidth = 6
ctx.beginPath()
ctx.moveTo(cx, cy)
ctx.lineTo(cx + min_length * cos(min_angle),
cy + min_length * sin(min_angle))
ctx.stroke()
# draw seconds
sec_angle = (pi / 30) * secs - (pi / 2)
sec_length = radius * 0.8
ctx.strokeStyle = "red"
ctx.lineWidth = 2
ctx.beginPath()
ctx.moveTo(cx, cy)
ctx.lineTo(cx + sec_length * cos(sec_angle),
cy + sec_length * sin(sec_angle))
ctx.stroke()
# draw a dot in the middle
ctx.beginPath()
ctx.arc(cx, cy, 8, 0, 2 * pi)
ctx.fillStyle = "black"
ctx.fill()
# update clock
def update_clock():
draw_clock()
window.setTimeout(update_clock, 1000)
# initialize clock
update_clock()
That’s not to say Brython is perfect. It’s likely not as fast as native JavaScript, and challenges might emerge when integrating popular JavaScript libraries or creating more complex applications. However, for many cases, it’s already more than sufficient. Moreover, efforts are underway increase further its scope of application.
It is true that in a JS-dominated world, Brython doesn’t need to compete directly. But it offers something different, and I think this alternative will become more and more popular.