I've been working on a lightweight framework called Ludic that focuses on web development using Python, htmx.org, and a somewhat React-like component approach. It leverages Starlette for performance and the latest Python 3.12 typing features.
I think it is somewhat similar to React, for example, you can create a Link component like you can see in the README.md
Now you can use it in f-strings, and well, other components.
The idea from the beginning was to integrate it with htmx.org, so to me, it feels kind of similar when you are writing endpoints using these "components".
But I understand why you are raising this question, I also didn't know how to name it, and React is a completely different framework. I didn't know how to describe the framework in just a few sentences but wanted to somehow. I might change the description at some point.
I still don't know how useful the framework is, I am playing around with it and I like it. Who knows what other people think, though.
FWIW, my reaction to the Link example was pretty much "Why do I have to build this, why isn't such a basic thing that any project would want not batteries-included?". Not the greatest first impression.
But `a` is included. You don't have to build a `Link` component. If you want to stylize your links in consistently across your page, you can build your own Link.
The example does nothing other than renaming `href` to `to` and adding a style.
How should the framework know which styles you want to apply to a link?
I think the example explains the component-driven design of the library well.
Interestingly, the second example on the official Ruby "About" page is almost exactly that: it defines .plus to be an alias of .+, and demonstrates how it might be used.
As a web developer (and therefore, I imagine, part of the target demographic for this sort of tool), I personally find this component example very useful. Components are very powerful, but most existing Python templating languages make it difficult or overly verbose to use them to their full extent. This is a really good demonstration of how I can write simple presentational components and use them with server-rendered HTMX. That shows off one of the main things I would want to do with this sort of framework.
So from that perspective, this is very much the perfect sort of example.
That's a totally different presentation though. The ludic home page never mentions that there is an existing function/data type! It just goes straight into the definition of Link without giving any context to someone who doesn't know what htmx is. There is nothing to tell a naive reader that this isn't how you'd do this in a production app.
Thank you for the feedback, I need to improve the home page a lot. I thought I should make it as brief as possible so that people don't need to read long essays to quickly understand what it is about. But at the same time, presenting all the necessary information in a short text seems hard.
Well, I would like it if the solution supported standard Python typing. Here I can create a "component" that expects a specific type of the first child and a specific type of the second child. I would probably have to use a separate tool for some kind of type-checking the SXML or something.
BTW in Rust, you can create macros, that is something I like a lot as you can see in yew framework - https://yew.rs/docs/getting-started/build-a-sample-app#updat... - you can write HTML which probably are typed. Python doesn't have anything like that, I don't know about any other way to do this.
Whether compile time guarantees or not, even having HTML elements as objects would be better than putting them into the f-strings. With objects we could probably pattern match on then and traverse the tree, instead of having to rely on other tooling or even regex to search for things inside an f-string.
Nice release! This looks good, docs look really nice too. I'd use HTMX anywhere I can to improve a basic HTML site.
One word of warning for people considering HTMX though – it's not a framework, it's an appliance.
In my experience with it, I ran into problems integrating with HTMX very quickly. Drop it into your project, use the HTML attributes, and it'll be great. However if you need custom Javascript, HTMX provides little in the way of an integration path (the lifecycle events are basic), and nothing in the way of structure, and can even get in the way in places where its lifecycle causes issues in the lifecycle of whatever you're adding. To add your own Javascript you'll almost certainly want Jquery or React/Svelte/etc, and with the latter you'll get interactions between their lifecycles.
If you're using it entirely via the HTML API, and you're happy to accept that sometimes it won't do something, you'll be fine. If you're looking to add units of more complex client-side functionality in places, try prototyping with it to make sure it works well, it didn't for me.
I think those are all good points, but do cringe a little at the idea that "to add your own JavaScript you'll want React". Seems like a lot to bring in if you're really just wanting to add some JavaScript.
Ha, fair point. I don't mean so much that for any JS you'd want to bring in React, but more that something like that will likely be needed to manage a codebase of any scale.
My context here is a mostly server-side application using HTMX, where I wanted to add a few small bits of additional client-side code. I tried to do it without any libraries but rapidly realised that Jquery would simplify things a lot, and managed to do ok with that, but was still fighting HTMX throughout, and I ended up with a lot of poorly managed local state quite quickly. It doesn't take much complexity to need some better form of state management, and something like React provides some direction there, but of course there are other options, my point was only really to say that HTMX will not do any of this for you, and may get in the way depending on what you need to do.
Yeah, it is insane how complex and wonky attempting to manage state becomes without a coherent model.
I kind of see HTMX as discouraging the traditional SPA-ish state management approach though. Or, it could be that the idea just gives me peace of mind. But, the idea being that you manage state less on the client because you're updating the UI with DOM content generated on the server versus rendering from local state on the client.
In practice, of course, avoiding client-state can be tough, especially as you go from sprinkling in limited dynamic interaction to a full-on SPA experience. I'm a little different though, in that I'm not sure SPAs were ever a good idea—at least without shifting completely away from building directly on Web idioms like DOM, HTML and CSS.
Yeah this is exactly it, HTMX does discourage this. And this is why my conclusion from my experience was that you either need to only use HTMX, or to do frontend "properly" with some sort of state management, because being somewhere in the middle is a pretty bad experience.
React can be added by rendering into a dom node that's not necessarily the root of your app.
Yes, it can be used to render the entire UI app with rich state management across all pages, routing, etc.
However it can just as simply be used to render 1 complex part of an app that has an isolated but possibly complex state. In this scenario I think sprinkling react is reasonable.
I'm admittedly out of touch with current React best practices, but "sprinkling react" isn't a phrase I'd have expected to hear based on my last contact with it. Sprinkling JavaScript or Alpine.js or HTMX, yes. But React seems to come with baggage, no?
Yeah, it seems to me if you're not buying into the entire app being based on React, including JSX with Router/Layout/etc. there's not much point to even using it library.
At that point, you can just write your own Javascript functions / classes for rendering, and handle your own state management
HTMX lends itself more to "sprinkling". Its basically just a very compact version of fetch(), combined with trigger logic (e.g. setTimeout(), onclick, etc.
True, I can recommend hotwire turbo as an alternate similar to htmx, it has a js framework called Stimulus that integrates nicely for those instances where you need some custom interactivity.
Wish GVR updates pyxl4(used by Mixt) and includes it into Python. It could be used for html, xml, yaml and its config language friends and declarative UIs like React. IMO if and for are better implemented in Jinja or JSX better than Python's ternary and list comprehension. Comparison at bottom.
Most of the frameworks here are calling side effects upon shared objects in the guise of `with` statements. Not to mention the extreme verbosity.
I second this. kotlinx-html is poorly documented and has a couple quirks, but is fantastic. As I've been building one of my current projects, I've also been building a framework based on jooq + kotlinx.html + javalin + htmx + flowbite (+kotlinjs for a few fancy bits, e.g. the charting library). Aside from tailwind, you end up with a fully typed and compiler-checked application end to end, i.e. no magic strings to be seen. I'm exclusively using HX-Retarget, so the backend is in charge of where things land, meaning that the frontend can do things like:
div {
onClick(loadItems)
}
I don't know whether I'll ever actually get it in good enough shape to release, but I do plan to blog about how some of the tricks I've found it fitting it together. One thing I definitely need to do before anybody looks at it is clean up how routes and targets are managed.
Thanks! It's http://smaller.fish. I have a subscribe form at the bottom which I only use for new post announcements (which, as you can see, are pretty infrequent :)).
Call me traumatized by the Web framework wars, but this seems like the path back to some of the issues that HTMX is trying to solve.
There is a strong developer instinct to "simplify through complexity". That is, to stuff down so much functionality and saw off all edges in an effort to hide complexity. "Let's expose a simple interface that will allow devs to do everything they want to do."
It starts with good intentions and initially provides some improvements in productivity. But it continues to evolve to the point of untenability: the complexity starts to ooze out and become a thing to manage, but with abstractions that make it more difficult; the framework becomes so opinionated that it begins to stifle; performance issues are introduced as we drift farther from the metal.
We continue to try to solve with newer constructs and abstractions, frequently introducing more issues. Eventually, it comes full circle ("hey, let's add SSR!"), and we're again looking for another solution. One that's simpler.
Then the cycle starts over again.
I think there is an inflection point with all of these things, wherein whatever perceived productivity gains are had by pushing more functionality into the framework quietly begins the path to regression. I don't exactly know where it is, but I would bet that it happens far sooner than we suspect.
So, I kind of like HTMX where it is, in spirit and implementation. Whatever additional productivity I can pick up via helper functionality and reuse is something I believe there is value in devs doing on their own, for their individual use cases and preferences. There's a tendency to think "well why reinvent the wheel? Let's make it a framework". I've come to believe that impulse is somewhat pernicious in its effect. Ultimately we're going to have to write our own code at some point. I'd suggest that we're better off doing it much sooner than frameworks encourage.
Spot on, in my opinion we need at least a 10 years ban on the creation of any new web framework of any kind. Hope that AI coders will make frameworks obsolete, but maybe i'm crazy :-).
- Type safe HTML: you get an exception instead of malformed HTML
- Truly reusable components: any Python web framework, component packages are truly reusable in any other projects. I never seen this with template engines.
- Huge productivity boost: not even close! No hunting for templates, no jumping between files, everything is in Python functions.
- Composable elements: you can nest things as much as you want, refactor very easily. Doing this with templates is not even possible, or such a pain I never did it.
- Storybook: There are a couple ones for Django, but they are clunky and not reusable at all.
I felt the same for a long time, but started thinking about a nice API. It took me months, but I finally got it: https://github.com/kissgyorgy/compone
Yes, I quite like the idea of being able to run pyflakes/flake8/black/pytest/mypy over my "html templates", and benefiting from all the power that entails.
It may hurt your eyes at first sight, for sure... But similarly to technologies like Tailwind CSS, it's mostly a matter of getting used to it - and after a while it end ups feeling very natural to use :-)
I imagine a lot of this comes down to personal preference. In the early days of Mountaineer [^1] (gee, almost two months ago at this point), I played around with the idea of embedding html into python instead of needing a JS layer. Eventually my consensus was:
- The most flexible shim approaches typically end up wrapping JS/React components anyway (like Reflex/Pinecone)
- We really need better IDE support for html strings that are within python strings. The editing problem is a big setback.
The ergonomics of Python + JS in separate code files won out and the user experience has been better than forcing them both into a common language would be.
This has the benefit of leveraging whatever the two languages are best at, in native code, so you have access to all the native APIs without having to learn a shim on top of it. Way more longevity to that approach too. Context switching between two languages isn't that bad if you minimize the glue layer that you have to write between them.
I agree and I don't fully understand the why of it.
I remember coding PL/SQL to emmit HTML in Oracle around 1999 or 2000 and using functions to code the various elements.
That got old and repetitive very quickly - for instance, everytime I had to correct a spelling error, I had to recompile the code.
To get around it I used one or two tables to hold html snippets to decouple the business/backend logic from the frontend, and stopped using the PL/SQL functions completely.
My speed of developmet skyrocketed, and separating and abstracting the frontend from the backend made so much sense.
A few years later, I was doing web developemt with Python using the Zope framework (not many people know about it tiday, I think).
It uses a specialised serverside templating language called TAL (Template Attribute Language)[1] that basically builds the front end dynamically, and then you feed it data from the backend.
Very neat and allowed me to build reusable compoments as well as collections of a schema definition (basically a dict), html template(s) and the code to validate that the input matched the schema and could be rendered.
Or something like that - its been 2 employers and almost 20 years since I worked with that :)
I did build a small extension for Wordpress using a PHP implementation[2] of TAL a few years ago, and TAL still works like a charm :)
My point is that I still believe there is value in keeping python out of the html-templating, and in keeping the front end logic apart from the backend logic.
There is something I am not understanding about the renewal of mixing HTML/GUI template with code, but I haven't fully found it yet.
Yeah, I understand that. Now that Python 3.12 has better support for f-strings, I thought there might be a way to make it possible. But I am still not sure it will work. There is a pretty weird hack to make f-strings work while avoiding the possibility of rendering unsafe user input.
It's nice for small hacks, but for anything longer than a couple dozen characters you're probably still better off making jinja2 (or similar) templates as painless as possible.
In general, this usually results in front-end logic being very tightly coupled with back-end logic. In some of the examples given, you even have database access in the same line that is generating the HTML document.
> In some of the examples given, you even have database access in the same line that is generating the HTML document.
Python template engines have the exact same problem, just way less obvious.
It doesn't have to be that way. Make all the queries up-front and pass the result the same way as you would pass context to templates. This way, all your components are pure. The difference is explicitness. Much easier to spot where side-effects happen than in templates.
I think the issue is orthogonal. I'm not a huge fan of react but it's an example of an architecture where the structure that is (imposed/encouraged) helps avoid the problem you're talking about.
I don't think the issue is "markup expressed in another language" - I think it's "poor application architecture". I don't dispute there might be a correlation between libraries and frameworks that do poorly on each - but that doesn't mean it's intrinsic.
I also started working on a Python Component library, but it's fully independent of any web framework, and you can generate XML/RSS with it too. I'm using it for months now, I will release the first version soon: https://github.com/kissgyorgy/compone
I’ve been burned by ASGI and asynchronous python so much in the past, I wouldn’t touch it again even if someone paid me for it. It’s prone to bugs, hard to debug and for little benefit.
Myself I built an RSS reader using aiohttp and then forked the code to make an image sorter.
I had a lot of people tell me I was crazy to write aiohttp servers because of the problems the previous poster mentioned but I had a long run of it working pretty well. On the other hand I’ve frequently had the experience of writing something in Python that falls apart like a tower of Jenga blocks the moment somebody else touches it. (Quite different from my experience with Java, Javascript, C#, PHP, …)
Once I started serving images along with the text and using the app over a 2Mbps link it seemed to me the aiohttp server was getting a little unreliable.
Most basically, an aiohttp server can only use one CPU thread at a time with the consequences that if one request holds the CPU for 5 seconds the server stops processing requests completely.. Although the aiohttp server could be reliable if you are almost entirely waiting for data to get back from the database, the residual amount of CPU load is going to put an upper bound on what the server can handle in the best case. If a coder is not so careful you get burned. My RSS reader has an AI component which runs completely in batch jobs, I don’t do any inference during requests which limits my flexibility and makes the application less “real-time” than it could be.
You can imagine systems where one aio server hands tasks off the another aio server but the blocking situation doesn’t get any better and if you want to make the system scalable there has to be at least one multiprocess or multithreaded server somewhere. At some point I am like “I have a 16 core machine but I am only using 1 core like my laptop in the 1990s” It didn’t help that my aio database drivers aren’t in conda which is another build hassle.
So last week I tore up the image sorter and rewrote it in Flask (my aio framework looks like Flask so it’s not too hard) and I run it out of gunicorn, I put a reverse proxy in front to deal with some Tailscale problems so I have IIS serving the images.
> Once I started serving images along with the text and using the app over a 2Mbps link it seemed to me the aiohttp server was getting a little unreliable.
Does that imply that it was due to images being heavier, and thus if you were to stream something like video from aiohttp it would not go so well?
For video I think I'd use nginx or something like that. The aiohttp server is meant for serving dynamic content, nginx is using async io too but it is written in C and optimized in every way.
Currently for my case the worst problem is head-of-line blocking, the system could be downloading 50 large images but to put a tag on a gallery it needs to load a small HTML page for a modal dialog, then it has to populate a dropdown box after a tag category is selected, then process the form request.
Since all these images are queued to download, the system has trouble prioritizing the HTML requests. The best answer to this for me is to build that thumbnailer to get the image size down, but I am also thinking of putting images on an entirely different port.
I've written about 200 lines of Python async code in total, so to run into a bug like that so soon was not encouraging.
I suspect it's just not a very popular feature and so it doesn't get a lot of use and debugging. And it's Python so it really needs a lot of ton of real world use to detect bugs.
Anyway I'm not going to waste my time debugging Python internals so I just switched to Deno.
It’s pretty easy to make a mistake like that with any concurrency framework. I can tell you stories about all the cases where in Java it was “use ExecutorService and… done” but it is so easy to get it wrong there.
I wish someone would sponsor development of HTMX support via NginX or Apache2 modules. That way you don't have to go to python, php, java, etc... to get HTMX support. You would just enable the module, configure a few things and let it rip. Kinda like server side includes in Apache2.
Can you explain what you mean? htmx is client-side, so you don't need server-side includes. Just host HTML snippets on your web server and let htmx stitch them together in the browser. I'm not sure where you want the web server to play a role?
HTMX is a client side JS file which parses the dom and then interprets it in a certain way and then provides some functionality. However the choice whether someone creates the initial html in a SPA context or a full page refresh is up to the developer. Maybe I want to generate the HTML snippets HTMX processes from the server side in PHP/Python and then have HTMX run it's magic on page load.
Or maybe if the same HTMX logic is put into server side code equivalent which generates , the server side code can generate that extra functionality and only generate the js that is needed eliminating the step of including HTMX on the client side.
nginx/apache work well as secure reverse-proxies, load-balancers/etc. to the application servers.
Complicating them with hooks for a bunch of random APIs is asking for trouble. Let the application servers sit behind them so they don't have to worry about those issues, and nginx/apache don't have to worry about application server issues
Whoa, a creative (in a good way IMO) use of __ getitem__() dunder method to depict attributes and children. No action at a distance using with blocks, static and strongly typing instead of stringly typing, no need to extend Python, composable from fragments.
Have you tried to "bring up front" if and for?
For example
> if_(cond, tag1(), tag2()) # without eagerly execute both branches
Instead of
> tag1() if cond else tag2()
And
for_(cars, lambda car: car_details(car)) # while still being type checked
Python's inline if's still read backwards to me and it would be nice to switch the order. But we are kind of stuck with it. I also don't want to introduce new control structures in this lib. Just want to use whatever Python has to offer in terms of control structures/syntax to make it more approachable.
htpy supports generators/callables as children (https://htpy.dev/streaming/). As long as you are careful to wrap things in generators/lambdas everything is lazy. Implementing if_ and for_ is straightforward and anyone can build constructs like that to use. But will not be lazy unless all arguments are wrapped in lambdas:
from htpy import div, li, ul
def if_(cond, a, b):
return a() if cond() else b()
def for_(items, func):
return (func(item) for item in items())
print(div[if_(lambda: True, lambda: "True!", lambda: "False!")])
print(div[if_(lambda: False, lambda: "True!", lambda: "False!")])
print(ul[for_(lambda: ["a", "b", "c"], lambda x: li[x])])
I think these kinds of tools are awesome, although I normally prefer not having to rely on getting the functions/classes/types defined for me, I usually find it easier to just use lxml[0] to build HTML components from composable Python functions[1], similar to React.
The idea of nested function calls to build HTML is not new. Back in the hey-day of JS frameworks, this was a common vdom pattern. I kinda miss [MithrilJS](https://mithril.js.org/#dom-elements)
It is a bit funny. When I started with Python ~18 years ago, the team lead on that project liked this framework "Nevow". It wad plugged into Twisted. Completely async, IIRC JS could have been built in, too. Some ideas are really not new under the sun.
I've been diving into Python/FastAPI + HTMX development and while I love both separately, I felt like the combined tooling could be a lot better. Jinja2 doesn't really satisfy me. At least not in this tech stack.
I'm still a bit sceptical if this is the solution but I'm definitely going to look into it as it fixes a few things I'm annoyed with.
Agreed. As someone who primarily develops in python, I recently tried this exact same stack as a way of avoiding the inevitable (i.e. learning typescript). After dealing with the complexity of interleaving templates, overly complex information flows, etc. I copied some of the html from the jinja templates I created into a svelte project, added openapi-typescript to glue it all together, and moved on. While I am by no means a fan of typescript and modern web development, it is much easier build things given the extensive ecosystem. And if nothing else, the “frontend” is separate all of the core logic, which makes things easier to understand.
The core implementation is only 85 lines of Python, and has no imports at all, so works nicely on minimal/alternative Pythons like Micropython.
I'm using it on a medium-sized side project and it's shockingly productive compared to string templates. I don't think anyone is using it except me, though.
This looks really neat. One suggestion is that in your readme, show the webpage output with your examples. It’s hard to image exactly what the page will look like just looking at the hotmetal python code
Nice work! There are a few expressions of this idea. There's a reason people keep coming back to it: it's actually really nice to write markup this way.
I've been looking for something exactly like this for a while — a small Python library for generating HTML that isn't prescriptive about framework and doesn't introduce any particually weird syntax. Jinja2 macros are... not great when you want to turn your HTML into lots of smaller functions.
Cool idea. I had a similar one a few weeks ago, with this additional idea of turning this into a "real" templating language (e.g. by adding a special construct for conditionals, looping, variables, macro expansion...).
This looks like a similar model to how Elm handles HTML. Except Elm provides functions for each tag, so it also does type checking to ensure that the passed arguments correspond to the actual attributes of the tag.
Interested in feedback!
* Docs: https://ludic.readthedocs.io/ * Code: https://github.com/paveldedik/ludic/ * Examples: https://github.com/paveldedik/ludic/tree/main/examples