Blog of a data person

Parsing Python web components

2025-04-13

I have added a fun party trick to my blog. I can now write stuff like this:

<terminal theme="dark">
web components (sortof) from Python!
</terminal>

And it will look like this:

terminal
web components (sortof) from Python!

Notice that theme? I can change it.

<terminal theme="light">
web components (sortof) from Python!
</terminal>

And when I do:

terminal
web components (sortof) from Python!

It's all thanks to a new feature I am trying out in mohtml. Under the hood there's a registry and a parser that understand that when it sees HTML like above that it should call a Python function with the following call:

terminal(
    "web components (sortof) from Python!", 
    theme="light"
)

This works via a registrar pattern. So ahead of time you would run something like this:

from mohtml.parser import CustomHTMLParser
from mohtml.components import terminal 

parser = CustomHTMLParser()

# You would normally use this as a decorator. May change this. 
parser.register("terminal")(terminal)

# Parser has components configured, can now post process rendered markdown
parser(blogpost_rendered)

The terminal Python function will effectively handle the rest. It can either use the functions in mohtml but it can also use Jinja2 under the hood. The really cool thing about the approach is that effectively I get some MDX-style features without writing any javascript what-so-ever.

This is super early days for this project, but so much fun!

Introducing mopaint more seriously

2025-04-12

I started mopaint as an April fools joke. It's a widget for notebooks that looks like MSPaint. It started out as a joke but I ended up liking it so much for demos that I added some extra features for it as well as some quality of life features.

Besides having a thin marker to draw with, you now also have a thick marker available in your Python notebooks to draw whatever you see fit!

CleanShot 2025-04-11 at 15.06.39.gif
Features!

And because this is an anywidget, you can also fetch the drawn image to pass along to another part of your (LLM) stack. Been having a lot of fun with this tool and Gemini recently.

If you want to see a demo, you can go to Github pages or play with the iframe below (made possible by marimo-snippets).

import marimo as mo
from mopaint import Paint
from mohtml import img
widget = mo.ui.anywidget(Paint(height=450))
widget
img(src=widget.value["base64"])  # Use base64 representation directly with mohtml

Pines UI

2025-04-11

I have been a fan of tailwindcss and alpine.js from the beginning and have been delighted to learn that there are many component libraries around now that combine the two. In particular, I stumbled apon pines ui the other day and it looks really interesting.

Here's one progress bar example that I grabbed from the docs.

What makes these components special? It's really just an alpine component that's declared directly in HTML but notice in the code below how the top x-data declaration allows you to pass through any configurations for the component?

<div x-data="{
        progress: 0,
        progressInterval: null,
    }"
    x-init="
        progressInterval = setInterval(() => {
            progress = progress + 1;
            if (progress >= 100) {
                clearInterval(progressInterval);
            }
        }, 100);
    "
    class="relative w-full h-3 overflow-hidden rounded-full bg-neutral-100">
    <span :style="'width:' + progress + '%'" class="absolute w-24 h-full duration-300 ease-linear bg-neutral-900" x-cloak></span>
</div>

Neat! I have yet to try and use this in an actual project, but it's nice to see the alpine stack getting re-usable component support.

ps. While exploring this tool I was also made aware of kutty and pinemix that feel like they offer a similar service.