-
Notifications
You must be signed in to change notification settings - Fork 10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Idea] allow repeated attribute extension! #6
Comments
Interesting! Attributes would be updated/overridden and classes would be merged with a space then. Are there other attributes except for class that would need special treatment (I can't think of any of the top of my head)? Can you share more details on the UI framework that you are trying to build? |
I can't think of any either but to be fair I'm dusting off my frontend skills after quite a few years of backend focus right now. As for the framework, I might have oversold it. Basically I'm a long time Rails guy toying with this new era of type hinted Python web tooling (FastAPI, SQLModel, and now Htpy & Ludic on the views end of things). I am pretty sold on the productivity of Htmx and Tailwind, but I believe it needs the right templating system. The problem I see is that in order to be productive while adopting "locality of behavior" (ref1 ref2) practices these tools embrace, you really need a templating system that makes rendering components as low friction as rendering primitive DOM elements. To me that's the thing React nailed, and I was trying to get to that level with your lovely system. On reflection though, I don't think just my earlier suggestion is really going to cut it. Another idea I had was finding a way to make your @component # This modifies the call signature of the function to bootstrap_modal(*, attrs)[children]
def bootstrap_modal(*, children: Node) -> Element:
return div(".modal", tabindex="-1", role="dialog")[
div(".modal-dialog", role="document")[div(".modal-content")[children]]
]
@component
def bootstrap_header(*, closeable: bool, children: Node) -> Element:
rest = []
if closeable:
rest = button(
".close",
type="button",
data_dismiss="modal",
aria_label="Close",
)[span(aria_hidden="true")[Markup("×")]]
return div(".modal-header")[div(".modal-title")[children, *rest]]
@component
def bootstrap_body(*, children: Node) -> Element:
return div(".modal-body")[children]
@component
def bootstrap_footer(*, children: Node) -> Element:
return div(".modal-footer")[children]
bootstrap_modal[
bootstrap_header(closeable=true)["Hello, World!"],
bootstrap_body["Lorem ipsum"],
bootstrap_footer["Etc!"],
] |
I smashed together a working POC of this decorator function. No type hinting (yet). I'm at the limits of my knowledge of Python already to be honest 😅 |
I gave it a shot and implemented a type safe from __future__ import annotations
import typing as t
from collections.abc import Callable
from dataclasses import dataclass
import htpy as h
P = t.ParamSpec("P")
R = t.TypeVar("R")
C = t.TypeVar("C")
@dataclass
class _ChildrenWrapper(t.Generic[C, R]):
_component_func: t.Any
_args: t.Any
_kwargs: t.Any
def __getitem__(self, children: C) -> R:
return self._component_func(children, *self._args, **self._kwargs) # type: ignore
def with_children(
component_func: Callable[t.Concatenate[C, P], R],
) -> Callable[P, _ChildrenWrapper[C, R]]:
def func(*args: P.args, **kwargs: P.kwargs) -> _ChildrenWrapper[C, R]:
return _ChildrenWrapper(component_func, args, kwargs)
return func
@with_children
def bs_button(children: str, style: t.Literal["success", "danger"]) -> h.Element:
return h.button(class_=["btn", f"btn-{style}"])[children]
@with_children
def article_section(children: h.Node, title: str) -> h.Node:
return [h.h1[title], children]
print(bs_button("danger")["Delete my account"])
print(h.div[article_section("htpy")[h.p["Write HTML in Python!"]]])
if t.TYPE_CHECKING:
reveal_type(bs_button("danger")["Delete my account"])
reveal_type(article_section("htpy")[h.p["Write HTML in Python!"]]) Result:
It also gives the correct return type so that you can also return lists/fragments. Generally I dont think htpy should be too opinionated about different ways to structure components libraries. It should just focus on generating the HTML. Python is powerful enough to implement any kind of component structure. Writing docs and figuring out useful and best practices and practical patterns is key to use htpy in a good way. We are wrapping "children" in our own code in some components but we have been happy enough with just passing children as a key word argument. :) I think this snippet could be added to the "common patterns" docs! |
(Btw, very much agree about LoB. We are using Django(with types)+htpy+htmx+Alpine.js and find it very productive combination. Have been doing AngularJS/React+APIs for years. We ran into limitations of django templates with this new approach, that's how htpy was started.) |
Wow, I didn't expect it to be possible to build that decorator without reaching into Happy to make that docs PR if you'd like. |
Thanks, that would be very nice! :) |
Closing this since there is no clear way forward. Feel free to discuss ideas on how to structure htpy components / code in the Discussions and/or open a new issue with more concrete ideas! |
Hey guys, saw your pip package in an HN thread and it really scratched an itch for me. I've been playing around with it and Tailwind to see if I can build a Python native UI framework.
This script:
Outputs: (I'm adding newlines for readability)
Where as I would have expected successive
Element()
calls to accumulate attributes, and intelligently merge accumulated classes, e.g.For what its worth, with just one line change I was able to get the attribute accumulation by prepending
**self._attrs,
here.Thoughts? I appreciate this can be worked around with the UI Component pattern you mention, but applying that at such a low level makes composition quite painful. Presumably you would end up almost never being able to use the clever
element()[]
syntax since its all wrapped in method calls.The text was updated successfully, but these errors were encountered: