Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions sdk/python/examples/apps/custom_controls/custom_buttons.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
from dataclasses import dataclass, field
from typing import Any

import flet as ft


def main(page: ft.Page):
@ft.control
class MyButton(ft.Button):
expand: int = field(default_factory=lambda: 1)
style: ft.ButtonStyle = field(
default_factory=lambda: ft.ButtonStyle(
shape=ft.RoundedRectangleBorder(radius=10)
)
)
bgcolor: ft.Colors = ft.Colors.BLUE_ACCENT
icon: Any = ft.Icons.HEADPHONES

@dataclass
class MyButton2(ft.Button):
expand: Any = 1
bgcolor: ft.Colors = ft.Colors.GREEN_ACCENT
style: ft.ButtonStyle = field(
default_factory=lambda: ft.ButtonStyle(
shape=ft.RoundedRectangleBorder(radius=20)
)
)
icon: ft.IconDataOrControl = ft.Icons.HEADPHONES

@ft.control
class MyButton3(ft.Button):
def init(self):
self.expand = 1
self.bgcolor = ft.Colors.RED_ACCENT
self.style = ft.ButtonStyle(shape=ft.RoundedRectangleBorder(radius=30))
self.icon = ft.Icons.HEADPHONES

page.add(
ft.Row([MyButton(content="1")]),
ft.Row([MyButton2(content="2")]),
ft.Row([MyButton3(content="3")]),
)


ft.run(main)
30 changes: 26 additions & 4 deletions sdk/python/packages/flet/src/flet/messaging/protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,20 @@
from flet.controls.duration import Duration


def _get_root_dataclass_field(cls, field_name):
"""
Returns the field definition from the earliest dataclass in the MRO
that declares `field_name`. This lets us recover defaults configured
on base controls before subclasses override them.
"""

for base in reversed(cls.__mro__):
dataclass_fields = getattr(base, "__dataclass_fields__", None)
if dataclass_fields and field_name in dataclass_fields:
return dataclass_fields[field_name]
return None


def configure_encode_object_for_msgpack(control_cls):
def encode_object_for_msgpack(obj):
if is_dataclass(obj):
Expand Down Expand Up @@ -36,10 +50,18 @@ def encode_object_for_msgpack(obj):
elif is_dataclass(v):
r[field.name] = v
prev_classes[field.name] = v
elif v is not None and (
v != field.default or not isinstance(obj, control_cls)
):
r[field.name] = v
else:
default_value = field.default
if isinstance(obj, control_cls):
root_field = _get_root_dataclass_field(
obj.__class__, field.name
)
if root_field is not None:
default_value = root_field.default
if v is not None and (
v != default_value or not isinstance(obj, control_cls)
):
r[field.name] = v

if not hasattr(obj, "_frozen"):
setattr(obj, "__prev_lists", prev_lists)
Expand Down
17 changes: 16 additions & 1 deletion sdk/python/packages/flet/tests/test_patch_dataclass.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import msgpack

from flet.controls.base_control import BaseControl
from flet.controls.base_control import BaseControl, control
from flet.controls.base_page import PageMediaData
from flet.controls.object_patch import ObjectPatch
from flet.controls.padding import Padding
Expand Down Expand Up @@ -37,6 +37,21 @@ class AppSettings:
assert settings.config.timeout == 2.5


def test_encode_emits_overridden_defaults():
@control("BaseTestControl")
class BaseTestControl(BaseControl):
foo: int = 0

@control("ChildTestControl")
class ChildTestControl(BaseTestControl):
foo: int = 5

encoder = configure_encode_object_for_msgpack(BaseControl)
encoded = encoder(ChildTestControl())

assert encoded["foo"] == 5


def test_page_patch_dataclass():
conn = Connection()
conn.pubsubhub = PubSubHub()
Expand Down
Loading