Skip to content

Commit

Permalink
Simplify API for validated properties (beeware#3115)
Browse files Browse the repository at this point in the history
Inlines the definition of valid constants in Pack declarations, and removes the need for an explicit Choices class when declaring a style property.
  • Loading branch information
HalfWhitt authored Jan 28, 2025
1 parent 58249c5 commit 6e4b36a
Show file tree
Hide file tree
Showing 6 changed files with 90 additions and 101 deletions.
1 change: 1 addition & 0 deletions changes/3115.misc.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Travertino's new dataclass-based syntax for creating validated properties now directly accepts the arguments to pass along to its internal Choices object.
94 changes: 27 additions & 67 deletions core/src/toga/style/pack.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,7 @@
TRANSPARENT,
VISIBLE,
)
from travertino.declaration import (
BaseStyle,
Choices,
directional_property,
validated_property,
)
from travertino.declaration import BaseStyle, directional_property, validated_property
from travertino.layout import BaseBox
from travertino.size import BaseIntrinsicSize

Expand All @@ -56,45 +51,12 @@
# Make sure deprecation warnings are shown by default
warnings.filterwarnings("default", category=DeprecationWarning)

######################################################################
# Display
######################################################################

PACK = "pack"

######################################################################
# Declaration choices
######################################################################

# Used in backwards compatibility section below
ALIGNMENT = "alignment"
ALIGN_ITEMS = "align_items"

DISPLAY_CHOICES = Choices(PACK, NONE)
VISIBILITY_CHOICES = Choices(VISIBLE, HIDDEN)
DIRECTION_CHOICES = Choices(ROW, COLUMN)
ALIGN_ITEMS_CHOICES = Choices(START, CENTER, END)
ALIGNMENT_CHOICES = Choices(LEFT, RIGHT, TOP, BOTTOM, CENTER) # Deprecated
JUSTIFY_CONTENT_CHOICES = Choices(START, CENTER, END)
GAP_CHOICES = Choices(integer=True)

SIZE_CHOICES = Choices(NONE, integer=True)
FLEX_CHOICES = Choices(number=True)

MARGIN_CHOICES = Choices(integer=True)

TEXT_ALIGN_CHOICES = Choices(LEFT, RIGHT, CENTER, JUSTIFY)
TEXT_DIRECTION_CHOICES = Choices(RTL, LTR)

COLOR_CHOICES = Choices(color=True)
BACKGROUND_COLOR_CHOICES = Choices(TRANSPARENT, color=True)

FONT_FAMILY_CHOICES = Choices(*SYSTEM_DEFAULT_FONTS, string=True)
FONT_STYLE_CHOICES = Choices(*FONT_STYLES)
FONT_VARIANT_CHOICES = Choices(*FONT_VARIANTS)
FONT_WEIGHT_CHOICES = Choices(*FONT_WEIGHTS)
FONT_SIZE_CHOICES = Choices(integer=True)


class Pack(BaseStyle):
_doc_link = ":doc:`style properties </reference/style/pack>`"
Expand All @@ -107,43 +69,41 @@ class IntrinsicSize(BaseIntrinsicSize):

_depth = -1

display: str = validated_property(choices=DISPLAY_CHOICES, initial=PACK)
visibility: str = validated_property(choices=VISIBILITY_CHOICES, initial=VISIBLE)
direction: str = validated_property(choices=DIRECTION_CHOICES, initial=ROW)
align_items: str | None = validated_property(choices=ALIGN_ITEMS_CHOICES)
alignment: str | None = validated_property(choices=ALIGNMENT_CHOICES) # Deprecated
justify_content: str | None = validated_property(
choices=JUSTIFY_CONTENT_CHOICES, initial=START
)
gap: int = validated_property(choices=GAP_CHOICES, initial=0)
display: str = validated_property(PACK, NONE, initial=PACK)
visibility: str = validated_property(VISIBLE, HIDDEN, initial=VISIBLE)
direction: str = validated_property(ROW, COLUMN, initial=ROW)
align_items: str | None = validated_property(START, CENTER, END)
alignment: str | None = validated_property(
LEFT, RIGHT, TOP, BOTTOM, CENTER
) # Deprecated
justify_content: str | None = validated_property(START, CENTER, END, initial=START)
gap: int = validated_property(integer=True, initial=0)

width: str | int = validated_property(choices=SIZE_CHOICES, initial=NONE)
height: str | int = validated_property(choices=SIZE_CHOICES, initial=NONE)
flex: float = validated_property(choices=FLEX_CHOICES, initial=0)
width: str | int = validated_property(NONE, integer=True, initial=NONE)
height: str | int = validated_property(NONE, integer=True, initial=NONE)
flex: float = validated_property(number=True, initial=0)

margin: int | tuple[int] = directional_property("margin{}")
margin_top: int = validated_property(choices=MARGIN_CHOICES, initial=0)
margin_right: int = validated_property(choices=MARGIN_CHOICES, initial=0)
margin_bottom: int = validated_property(choices=MARGIN_CHOICES, initial=0)
margin_left: int = validated_property(choices=MARGIN_CHOICES, initial=0)
margin_top: int = validated_property(integer=True, initial=0)
margin_right: int = validated_property(integer=True, initial=0)
margin_bottom: int = validated_property(integer=True, initial=0)
margin_left: int = validated_property(integer=True, initial=0)

color: rgb | hsl | str | None = validated_property(choices=COLOR_CHOICES)
color: rgb | hsl | str | None = validated_property(color=True)
background_color: rgb | hsl | str | None = validated_property(
choices=BACKGROUND_COLOR_CHOICES
TRANSPARENT, color=True
)

text_align: str | None = validated_property(choices=TEXT_ALIGN_CHOICES)
text_direction: str | None = validated_property(
choices=TEXT_DIRECTION_CHOICES, initial=LTR
)
text_align: str | None = validated_property(LEFT, RIGHT, CENTER, JUSTIFY)
text_direction: str | None = validated_property(RTL, LTR, initial=LTR)

font_family: str = validated_property(choices=FONT_FAMILY_CHOICES, initial=SYSTEM)
font_style: str = validated_property(choices=FONT_STYLE_CHOICES, initial=NORMAL)
font_variant: str = validated_property(choices=FONT_VARIANT_CHOICES, initial=NORMAL)
font_weight: str = validated_property(choices=FONT_WEIGHT_CHOICES, initial=NORMAL)
font_size: int = validated_property(
choices=FONT_SIZE_CHOICES, initial=SYSTEM_DEFAULT_FONT_SIZE
font_family: str = validated_property(
*SYSTEM_DEFAULT_FONTS, string=True, initial=SYSTEM
)
font_style: str = validated_property(*FONT_STYLES, initial=NORMAL)
font_variant: str = validated_property(*FONT_VARIANTS, initial=NORMAL)
font_weight: str = validated_property(*FONT_WEIGHTS, initial=NORMAL)
font_size: int = validated_property(integer=True, initial=SYSTEM_DEFAULT_FONT_SIZE)

@classmethod
def _debug(cls, *args: str) -> None: # pragma: no cover
Expand Down
31 changes: 26 additions & 5 deletions travertino/src/travertino/declaration.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,13 +101,27 @@ def __str__(self):


class validated_property:
def __init__(self, choices, initial=None):
def __init__(
self,
*constants,
string=False,
integer=False,
number=False,
color=False,
initial=None,
):
"""Define a simple validated property attribute.
:param choices: The available choices.
:param constants: Explicitly allowable values.
:param string: Are strings allowed as values?
:param integer: Are integers allowed as values?
:param number: Are numbers allowed as values?
:param color: Are colors allowed as values?
:param initial: The initial value for the property.
"""
self.choices = choices
self.choices = Choices(
*constants, string=string, integer=integer, number=number, color=color
)
self.initial = None

try:
Expand All @@ -119,7 +133,7 @@ def __init__(self, choices, initial=None):
# Unfortunately, __set_name__ hasn't been called yet, so we don't know the
# property's name.
raise ValueError(
f"Invalid initial value {initial!r}. Available choices: {choices}"
f"Invalid initial value {initial!r}. Available choices: {self.choices}"
)

def __set_name__(self, owner, name):
Expand Down Expand Up @@ -444,7 +458,14 @@ def validated_property(cls, name, choices, initial=None):
DeprecationWarning,
stacklevel=2,
)
prop = validated_property(choices, initial)
prop = validated_property(
*choices.constants,
string=choices.string,
integer=choices.integer,
number=choices.number,
color=choices.color,
initial=initial,
)
setattr(cls, name, prop)
prop.__set_name__(cls, name)

Expand Down
24 changes: 12 additions & 12 deletions travertino/tests/test_choices.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,21 @@

@prep_style_class
class Style(BaseStyle):
none: str = validated_property(choices=Choices(NONE, REBECCAPURPLE), initial=NONE)
allow_string: str = validated_property(
choices=Choices(string=True), initial="start"
)
allow_integer: int = validated_property(choices=Choices(integer=True), initial=0)
allow_number: float = validated_property(choices=Choices(number=True), initial=0)
allow_color: str = validated_property(
choices=Choices(color=True), initial="goldenrod"
)
values: str = validated_property(choices=Choices("a", "b", NONE), initial="a")
none: str = validated_property(NONE, REBECCAPURPLE, initial=NONE)
allow_string: str = validated_property(string=True, initial="start")
allow_integer: int = validated_property(integer=True, initial=0)
allow_number: float = validated_property(number=True, initial=0)
allow_color: str = validated_property(color=True, initial="goldenrod")
values: str = validated_property("a", "b", NONE, initial="a")
multiple_choices: str | float = validated_property(
choices=Choices("a", "b", NONE, number=True, color=True),
"a",
"b",
NONE,
number=True,
color=True,
initial=None,
)
string_symbol: str = validated_property(choices=Choices(TOP, NONE))
string_symbol: str = validated_property(TOP, NONE)


with catch_warnings():
Expand Down
37 changes: 22 additions & 15 deletions travertino/tests/test_declaration.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,35 +19,38 @@
VALUE1 = "value1"
VALUE2 = "value2"
VALUE3 = "value3"
VALUE_CHOICES = Choices(VALUE1, VALUE2, VALUE3, None, integer=True)
DEFAULT_VALUE_CHOICES = Choices(VALUE1, VALUE2, VALUE3, integer=True)
VALUES = [VALUE1, VALUE2, VALUE3, None]


@prep_style_class
class Style(BaseStyle):
# Some properties with explicit initial values
explicit_const: str | int = validated_property(
choices=VALUE_CHOICES, initial=VALUE1
*VALUES, integer=True, initial=VALUE1
)
explicit_value: str | int = validated_property(choices=VALUE_CHOICES, initial=0)
explicit_value: str | int = validated_property(*VALUES, integer=True, initial=0)
explicit_none: str | int | None = validated_property(
choices=VALUE_CHOICES, initial=None
*VALUES, integer=True, initial=None
)

# A property with an implicit default value.
# This usually means the default is platform specific.
implicit: str | int | None = validated_property(choices=DEFAULT_VALUE_CHOICES)
implicit: str | int | None = validated_property(
VALUE1, VALUE2, VALUE3, integer=True
)

# A set of directional properties
thing: tuple[str | int] | str | int = directional_property("thing{}")
thing_top: str | int = validated_property(choices=VALUE_CHOICES, initial=0)
thing_right: str | int = validated_property(choices=VALUE_CHOICES, initial=0)
thing_bottom: str | int = validated_property(choices=VALUE_CHOICES, initial=0)
thing_left: str | int = validated_property(choices=VALUE_CHOICES, initial=0)
thing_top: str | int = validated_property(*VALUES, integer=True, initial=0)
thing_right: str | int = validated_property(*VALUES, integer=True, initial=0)
thing_bottom: str | int = validated_property(*VALUES, integer=True, initial=0)
thing_left: str | int = validated_property(*VALUES, integer=True, initial=0)

# Doesn't need to be tested in deprecated API:
list_prop: list[str] = list_property(choices=VALUE_CHOICES, initial=(VALUE2,))
list_prop: list[str] = list_property(*VALUES, integer=True, initial=(VALUE2,))


VALUE_CHOICES = Choices(*VALUES, integer=True)

with catch_warnings():
filterwarnings("ignore", category=DeprecationWarning)
Expand All @@ -69,7 +72,9 @@ class DeprecatedStyle(BaseStyle):

# A property with an implicit default value.
# This usually means the default is platform specific.
DeprecatedStyle.validated_property("implicit", choices=DEFAULT_VALUE_CHOICES)
DeprecatedStyle.validated_property(
"implicit", choices=Choices(VALUE1, VALUE2, VALUE3, integer=True)
)

# A set of directional properties
DeprecatedStyle.validated_property("thing_top", choices=VALUE_CHOICES, initial=0)
Expand Down Expand Up @@ -100,11 +105,11 @@ class MockedReapplyStyle(BaseStyle):
def test_invalid_style():
with pytest.raises(ValueError):
# Define an invalid initial value on a validated property
validated_property(choices=VALUE_CHOICES, initial="something")
validated_property(*VALUES, integer=True, initial="something")

with pytest.raises(ValueError):
# Same for list property
list_property(choices=VALUE_CHOICES, initial=["something"])
list_property(*VALUES, integer=True, initial=["something"])


@pytest.mark.parametrize("StyleClass", [Style, DeprecatedStyle])
Expand Down Expand Up @@ -831,7 +836,9 @@ class OldStyle(BaseStyle):
pass

with pytest.warns(DeprecationWarning):
OldStyle.validated_property("implicit", choices=DEFAULT_VALUE_CHOICES)
OldStyle.validated_property(
"implicit", Choices(*VALUES, integer=True), initial=VALUE1
)

with pytest.warns(DeprecationWarning):
OldStyle.directional_property("thing%s")
4 changes: 2 additions & 2 deletions travertino/tests/test_node.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import pytest

from travertino.declaration import BaseStyle, Choices, validated_property
from travertino.declaration import BaseStyle, validated_property
from travertino.layout import BaseBox, Viewport
from travertino.node import Node
from travertino.size import BaseIntrinsicSize
Expand All @@ -14,7 +14,7 @@
@prep_style_class
@mock_attr("reapply")
class Style(BaseStyle):
int_prop: int = validated_property(Choices(integer=True))
int_prop: int = validated_property(integer=True)

class IntrinsicSize(BaseIntrinsicSize):
pass
Expand Down

0 comments on commit 6e4b36a

Please sign in to comment.