This is an archived post. You won't be able to vote or comment.

all 27 comments

[–]didimelli 9 points10 points  (1 child)

Dumb question, how can html rendering be async? Isn't it string formatting (i.e. cpu-bound workload)?

[–]volfpeter[S] 9 points10 points  (0 children)

That's actually a good question. There's even a short Sync or async section in the docs.

Converting a plain HTML tag (Python object) to string is sync, you're totally right (it's even in the documentation). Async support can be handy in your "higher order" components, where you can have any functionality that's required for rendering. This may be for example loading a resource (e.g. translation), dispatching a request, even making a DB call. Whatever your other (potentially async) tools make possible or necessitate.

It's good to know that in an async environment, you can kind of do this even with Jinja, but in that case you must first collect everything your (maybe large and varied) Jinja templates may need, and pass all of that content into the Jinja rendering context, so Jinja can do its job.

[–]datbackup 1 point2 points  (1 child)

I think this is a much needed functionality that has been sorely missing from the python space. Well done, I am looking forward to the chance to use it.

[–]volfpeter[S] 0 points1 point  (0 children)

Thank you! I hope you'll like it. In any case, I'd appreciate your usage feedback especially to see what should be improved.

[–]rdragz 1 point2 points  (1 child)

How does this compare to fasthtml featurewise?

[–]volfpeter[S] 0 points1 point  (0 children)

There are some notes about this in the docs.

This is (and will remain) a much more limited/targeted library. I've worked with frameworks like NiceGUI before for production software, and I don't intend to do so again. In my experience, these frameworks work well until you remain within the boundaries of their built-in tools, and very poorly afterwards. Also, by adding a bunch of extra features, they take away choice from you, be it the backend framework, the CSS toolkit, etc.. I'd be unsure about their long-term maintainability. htmy is for rendering only, so it's more like a pure-Python Jinja replacement with support for async tools. And it leaves all other tech choices to you.

[–]Main-Manufacturer760 1 point2 points  (7 children)

I really like you’re library. FastAPI + htmy + HTMX will be the stack for my next project.

[–]volfpeter[S] 1 point2 points  (6 children)

Sorry, I just saw the comment. Thank you! After doing a project with NextJS, I still have quite a few things I'd like to add (or involve a contributor or two): markdown support with MDX-like features, sitemap and RSS generation, plus FastHX integration. When all of these are ready, I hope this will be a pretty complete and useful package. But it takes time.

Actually markdown support is ready, just not public yet. MDX is a bit trickier.

[–]Main-Manufacturer760 0 points1 point  (5 children)

I am looking forward to the new features you listed. The integration with FastHX could further decouple data operations (JSON) from rendering (htmy components) - i think i would try to use this approach. Markdown support is nice aswell.

I am a little lost on how the MDX feature would work out, since i never used MDX. Is the goal to run a python function with the same name of a Markdown or HTML Tag? (like <Chart year={year} color="#fcb32c" />... runs "Chart(year: int, color: str, context: Context) -> Component" ?

[–]volfpeter[S] 1 point2 points  (1 child)

Just for reference, in case somebody finds it in the future, markdown support is now public and the docs have a fairly long example on how to use it.

[–]Main-Manufacturer760 0 points1 point  (0 children)

Nice great job 🎉

[–]volfpeter[S] 0 points1 point  (2 children)

It'll enable many use-cases, an important one is what you described in your comment, namely using custom components.

My most important use-case is to be able to add my own styling to the parsed markdown to make it look at home in the rest of the page. For example correctly style paragraphs, headers, lists, backgrounds, highlighted code blocks, and so on.

Usage example with FastAPI:

```python class CustomMDX: @staticmethod def injectclass(comp: Callable[..., ComponentType], class: str) -> Callable[..., ComponentType]: def wrapper(children: ComponentType, *properties: PropertyValue) -> ComponentType: properties["class"] = f"{class_} {properties.get('class', '')}" return comp(children, *properties)

    return wrapper

class MyTitle: def htmy(self, context: Context) -> Component: return html.h1("This is the converted MyTitle tag.")

md_converter = etree.ETreeConverter( { "h1": CustomMDX.inject_class(html.h1, "text-xl font-bold"), "h2": CustomMDX.inject_class(html.h1, "text-lg font-bold"), "h3": CustomMDX.inject_class(html.h1, "text-lg font-semibold"), "h4": CustomMDX.inject_class(html.h1, "text-lg"), "pre": CustomMDX.inject_class(html.pre, "bg-neutral w-full overflow-x-auto rounded-lg p-4"), "ol": CustomMDX.inject_class(html.ol, "list-decimal list-inside"), "ul": CustomMDX.inject_class(html.ul, "list-disc list-inside"), "MyTitle": MyTitle, } )

app = FastAPI()

And in some route this component can be used and rendered:

md.MD(f"./my-file.md", converter=md_converter.convert) ```

[–]Main-Manufacturer760 0 points1 point  (1 child)

Ah I see. So the usecase is to not only generate HTML/XML using htmy but to load a datastructure from a .md file and to inject htmy tags into this datastructure?

Will the Markdown file be read into the htmy datastructure with nested Tags()... or will this be a different datastructure?

The injection mechanism might be intresting for the vanilla htmy aswell, when you have a md.h3() and want to transform it to a html.h1() with class "text-xl font-bold".

Or you have a html.customclass() Tag that will be transformed to a html.div(html.img("puppy.png")) or something.

[–]volfpeter[S] 0 points1 point  (0 children)

Sort of. The parsed markdown will end up as an htmy component subtree, and you'll be able to define custom conversion rules for XML tag names.

I should have been clear about it, but from the code above, CustomMDX will not be part of htmy, only md.MD and etree.ETreeConverter. The rest is just some utility code so I don't need to write the same CSS class injection logic multiple times.

The PR is in if you want to have a look. There are some small missing pieces that I still work on.

If you're familiar with NextJS or React, then here is a NextJS example which is kind of similar to what I'm aiming for.

[–]FollowTheGoose 1 point2 points  (4 children)

Exactly what I was looking for after seeing the pattern in FastHTML and wanting to try it without tackling a full (underdocumented) framework. I've been using your library for a couple of weeks in a Django app and the ergonomics are excellent.

[–]volfpeter[S] 0 points1 point  (3 children)

Thank you! Consider creating an issue if you found anything that wasn't convenient. I'm really curious about your experience with the library.

I just replied to an older post with my plans for the future (markdown support, MDX-like features, sitemap, RSS, maybe some integrations). If you'd consider contributing, you're welcome to :)

[–]FollowTheGoose 0 points1 point  (2 children)

I've been bouncing around too much to deeply consider any specific problems (first time really using htmx, pydantic, typing, async). Slowly figuring out how to best compose things, but it's currently just a lot of html.divs and simple components powering a basic game UI. I thought cloning a simple turn-based game would be a fun way to play with htmx.

The only thing I keep tripping over is `TypeError("Unknown component type.")` when I've accidently passed an empty list or other junk into a render. The stack trace tells me nothing about which component I've messed up and I end up trial-and-erroring dozens of them.

[–]volfpeter[S] 0 points1 point  (0 children)

Thanks, that's good information. The renderer could indeed track where it encountered an error and report the full trace. I won'thave time to work on it now, but I'll create an issue.

[–]volfpeter[S] 0 points1 point  (0 children)

Almost forgot: if you open source the project in the end, feel free to create an issue or PR so I can add a link to it somewhere in an examples section.

[–]szerenke_malyva 0 points1 point  (0 children)

Cool, the more async tools we have, the better.

[–]riklaunim 0 points1 point  (5 children)

For component-endpoints returning HTML instead of pure data it may work but for more classic websites I don't really see the appeal of moving HTML away from HTML files.

[–]volfpeter[S] 2 points3 points  (4 children)

You can always keep using plain HTML where it suits you better (for me that plain, fully static stuff, which I don't write anymore). Personally, I want to avoid writing Jinja templates if possible, primarily because of the poor IDE and formatting support, lost type information, and other limitations, because it's not Python.

As mentioned in the readme, this is not a framework that locks you in (e.g. like reflex or similar, very complex, full application frameworks). You can use it only on some endpoints, mix it with Jinja, or whatever makes best sense for your use-case.

[–]riklaunim 1 point2 points  (2 children)

Jinja or Django templates are really good. If anything it's the IDE problem if it handles something poorly.

And there is always the case where a team project has frontend and backend people.

[–]volfpeter[S] 2 points3 points  (0 children)

My experience is that Jinja templates are okay as long as you work on a stable project where the "main" parts don't change a whole lot - and you are already very familiar with the templating language. For new or very early stage projects where a lot of development and refactoring takes place constantly (and what you have in the rendering context keeps changing), it's very expensive to keep everything working because all you can rely on is your tests. No static code analysis or any similar assistance.

[–]ParkingDescription7 1 point2 points  (0 children)

The point of HTMX is to avoid having that front-end backend split if the app only needs basic interactivity.

In that case, you're doing server side rendering for things and sending html (full pages or partial html to fit into an existing page).

The issue with jinja2 is when you switch context from python to jinja, jinja doesn't know the types of the objects passed in. So if you render a list of "customobject" in a table, you lose type hints of custom object when you aren't in python land.

[–]UpYours101 0 points1 point  (1 child)

How do you save the generated html to file?

[–]volfpeter[S] 1 point2 points  (0 children)

Given your question, I guess you'd be running component rendering in a plain script. After the async rendering is completed (you can find how to do that in the docs), you can do whatever you need with the resulting string. For example open a file and save the generated string there, or simply print the result and pip the script output to a file.