Introduce bindable dataclass fields#3987
Conversation
falkoschindler
left a comment
There was a problem hiding this comment.
Thank you for the pull request, @balex89!
@rodja and I thought about the API quite a bit. Requiring the @bindable_dataclass decorator and the field wrapper feels a bit verbose. In #3957 you wrote "One might want to be able to pick specific attributes only". But let's question this requirement. Do we really need to handle this use case? I know, the bindable_field function was kind of my idea. But if 99% of our users want to mark all fields as bindable anyway, your initial approach of converting all fields would be much simpler and nicer to use. What do you think?
Apart from that we think we should merge @dataclass and @bindable_dataclass so that the user doesn't have to write both decorators and remember the right order. The new decorator would simply forward all keyword arguments to the dataclass decorator.
@falkoschindler, I do agree on making that a default behavior. And yet, shouldn't we leave an option to specify fields explicitly for those 1%? For instance: @bindable_dataclass
class A:
x: int # bindable
y: int # bindable
z: int # bindable
@bindable_dataclass(bindable_fields=['x', 'y'])
class B:
x: int # bindable
y: int # bindable
z: int # not bindableThat doesn't seem to complicate implementation or interface much while providing full control on attribute picking, if ever needed. As was said above, we forward additional |
|
@balex89 You're right. Even though we could add such an additional attribute later on demand, we can as well add it right now. 👍 |
2b22bd9 to
d7c3418
Compare
|
API is simplified according to comments above. |
|
Thanks for the update, @balex89! I just tested with the following code: @binding.bindable_dataclass # version 1
# @binding.bindable_dataclass(bindable_fields=['x']) # version 2
# @binding.bindable_dataclass(slots=True) # version 3
class A:
x: float
y: float
a = A(x=1, y=2) # a should be of type A
x = a.x # x should be of type float
with ui.row():
ui.number('X').bind_value(a, 'x')
ui.slider(min=0, max=10).bind_value(a, 'x')
with ui.row():
ui.number('Y').bind_value(a, 'y')
ui.slider(min=0, max=10).bind_value(a, 'y')It mostly works as expected, but there are still some subtle issues:
|
|
@falkoschindler, I pushed some improvements. Futher down your list:
|
|
Hi @balex89, I finally had another look into this PR. Regarding point 3: You're totally right. Updating a bindable property triggers immediate propagation, even if active links are involved. To reproduce the delay we need to update the non-bindable property directly. The pull request is almost ready to merge. But I'm still seeing two mypy errors which would break the build chain when merged into main: I don't understand why |
|
Hi @falkoschindler! Turns out I initially misused |
falkoschindler
left a comment
There was a problem hiding this comment.
Awesome! Thanks again, @balex89!
All linters and type checkers seem happy now. Let's merge!
|
I am very interested in this feature. Since I need a simpl way to create models with many parameters I can bind without creating "active links". Not sure if my solution is robust. I changed the init method of the binding.BindableProperty() descriptor to accept an optinal name. With this i can create a bindable model like this from typing import Any, Callable, Optional
from nicegui import binding, ui
def __init__mod(self, on_change: Optional[Callable[..., Any]] = None, name="") -> None:
self._change_handler = on_change
self.name = name
print("--> __init__", self.name)
binding.BindableProperty.__init__ = __init__mod
def create_model(attr_names):
class Model:
pass
for name in attr_names:
setattr(Model, name, binding.BindableProperty(name=name))
model = Model()
for name in attr_names:
setattr(model, name, None)
return model
model = create_model(["a", "b", "c"])
print(model.a, model.b, model.c)
ui.run(reload=False) |
I think the hints still show |
|
@MateoSaez It works for me. Maybe your IDE or Pylance extension is not up to date? |

This PR follows up an idea #3957 to introduce bindable
dataclasses.dataclassfields.The main challenge is to marry
bindablePropertywithdataclasses.field, and by that preserve native dataclass features.The proposed idea is to use a wrapper around dataclasses.field that updates passed
metadata, adding nicegui-specific options (for now just a "bindable" flag). Then use it to addbindablePropertiesretroactively on dataclass type postprocessing (much like it is done in dataclasses_json.config)Usage example
Known tradeoffs
MyClass.x) is lost.