Replies: 3 comments 3 replies
-
Thanks a lot for writing this down, it is really useful to me and hopefully others too! Will reply in more detail, I think there are good solutions to a lot of the problems you describe or that they can be documented/worked around :) |
Beta Was this translation helpful? Give feedback.
-
Thanks for taking the time to share this. It is very valuable feedback!
Thanks for sharing this. Django has class based views, I personally use function based views in 95% of the cases. To me, following the inheritance flow of CBV and multiple levels of inheritance is very hard. I know a lot of people like class based views. Personally, I find combining functions much easier to follow. We have built quite sophisticated UIs in our code base with just functions. That said, one of the primary reason to use htpy is to leverage the full power of Python. Using functions, classes, generators, modules, the dynamic nature of Python is totally fine and is what makes htpy so powerful. Just like the same arguments of functions vs classes/inheritance can be made for any code, the same applies here. Different projects/backgrounds/ideas are certainly possible and there are no single "right way" to structure this code. I would be open to adding a class based approach to components to the docs. Defining
I would like to avoid another way of naming/defining the elements to stay consistent and not give multiple ways of doing the same thing. It would be annoying to have a mixture of div and Div. I like it lower case since that is the way html elements are typically written these days. :)
I have started using Btw, React also does lowercase for the HTML elements. JSX uses lower or upper case of the first character of a JSX element to determine whether it is a HTML element or component. When React moved on from class components to functions/hooks, you still had to define your function components with an upper case letter.
I agree, I had problems with this too. I will open issues/fix this. Currently the validation of the attributes and children happens at rendering time, not when the elements are constructed. Not all cases can be caught if using streaming with callables or generators but most common cases could raise an error directly and give a normal/nice stack trace.
Again, thanks for taking the time to write this, I am glad htpy is useful to you too! :) |
Beta Was this translation helpful? Give feedback.
-
Hey @pelme, I spent quite some time playing with your proposal and I'm very happy with the result. So my htpy wrapper looks like this import re
import htpy
def __getattr__(name: str) -> htpy.Element:
words = filter(None, re.sub(r"([A-Z])", r"_\1", name).split("_"))
snake_case = "_".join(words).lower()
return getattr(htpy, snake_case) Yup, fairly simple. And it does everything that I need from it, here's my associated unit tests for reference: import htpy as h
import app.views.tags as t
def test_div() -> None:
assert str(h.div) == str(t.div) == str(t.Div)
def test_img() -> None:
assert str(h.img) == str(t.img) == str(t.Img)
def test_p() -> None:
assert str(h.p) == str(t.p) == str(t.P)
def test_my_component() -> None:
assert (
str(h.my_component)
== str(t.my_component)
== str(t.MyComponent)
== str(t.My_Component)
)
def test_a_component() -> None:
assert (
str(h.a_component)
== str(t.a_component)
== str(t.AComponent)
== str(t.A_Component)
) |
Beta Was this translation helpful? Give feedback.
-
Hi everyone!
I just spent the last month migrating an entire project to HTPY and I wanted to share this journey with you, probably it can be useful to others but more than that, it's a way for me to show my appreciation to the creators and the maintainers of this wonderful project.
The project and the motivation
My project is a side-project that I've been maintaining alone for years. Stack is:
So why move to HTPY in the first place ? It all began with the announcement of the new FastHtml framework and one of the pillars for this framework is HTML generated by Python code through their own FastTags system (HTPY equivalent). My first reaction was WTF, why would you do that instead of using good old templates? Then I wondered if I was truly happy with my templates and soon realized that in fact they were pretty limiting:
I decided to go with HTPY in particular for multiple reasons:
The migration process
So I decided to migrate my ~60 templates to HTPY. 2 weeks "full time" work during my summer holidays.
I won't lie, it's been painful, even though using the
html2htpy
utility made things a lot easier. Despite that, the process was still cumbersome and involved a lot of manual and repetitive tasks:html2htpy
for a given template{% if ... %}
{% for ... %}
{{ _("...") }}
All in all, migrating a template would take me between 30 minutes and 3 hours depending on the complexity, but I can tell you it was well worth it.
The component management
The HTPY website recommends managing components with functions. This was my initial approach and even though it was okay in the beginning, I soon realized it would be limiting to re-implement the same kind of inheritance behaviour I was liking in Jinja2.
So I ended up taking a class based approach. Starting with creating a base
View
class that all my templates would inherit from:Then I could create a basic template like so where
title
andcontent
are fragments that can be overriden :For a specific page, I will then subclass a given template:
Now in my controller, when I want to render a full page, I will do:
Or for a specific component or fragment:
If for some reason I need to render multiple components that are not part of a single Node, I will do the following (can be useful in some situations with HTMX):
I find this class system very flexible, readable and easy to maintain, highly recommend HTPY users to take a similar approach instead of the function based approach.
The naming conventions
One annoyance I had while migrating to HTPY is the naming convention for tags. They are in lower case words for standard elements and lower snake_case for custom elements. The things is that it makes it hard to read and most of all creates some naming overlap with some of your variables if you don't pay attention.
For example, all my WTForms instances were assigned to a variable named
form
. No luck that there is also an HTML element namedform
. It took me hours to solve this issue. The problem can happen (and I'm sure will happen again) for a lot of elements likea
,label
that I often use as variable names; the same goes forinput
that conflicts with the native Pythoninput()
function.My proposal here would be to allow (and not replace because I realize it would be a big breaking change) for having tags in PascalCase.
So
div
could be aliased toDiv
,my_element
could be aliased toMyElement
(and utimately translated tomy-element
when rendered as HTML).Multiple reasons for that proposal:
The datatypes
In the process of migrating, one error I soon encountered was when trying to display a badge of unread messages. The count was an
int
which is not a valid datatype a child Node. So you cant dospan[count]
butspan[str(count)]
instead. I also found myself trying to render other data types that obviously couldn't be handled by HTPY such as myWTForms
fields or mydatetime
attributes.My proposal here would be to introduce some kind of custom serializer to avoid having some
str()
all over the place.It could work like the native Python
json
module; thejson.dumps()
function accepts acls
parameter that you can initialize with a custom subclass ofJSONEncoder
to allow serialization of complex Python objects to json.The existing
htpy.render_node()
function could benefit from having a similar pattern to allow for complex serialization that would otherwise end up being repeated in several places. Just an idea, probably there are other ways to achieve this or is not part of the project's vision.The error management
This leads me to a final complaint about errors that are very cryptic when htpy cannot properly render your code for whatever reason, making it very hard to troubleshoot what the problem is and where the problem is. So I often ended up commenting code until it works and then reactivating until I find the issue, not very efficient.
Conclusion
Sorry for the long rant, but I would like to express to the community and in particular to the creators and maintainers how valuable the HTPY project has become to me. It made my own project much cleaner and modular than it used to be.
Yes I have a few complaints but nothing is a blocker for me at this point and I'm aware that the project is still very young and has lots of room for improvement still.
So thanks for what you have done with HTPY!
Beta Was this translation helpful? Give feedback.
All reactions