Skip to content

Commit c143335

Browse files
authored
optimize rx.color to not use validate literal parameters (#5244)
* optimize rx.color to not use validate literal parameters * fstring issues
1 parent 65e9fed commit c143335

File tree

4 files changed

+87
-25
lines changed

4 files changed

+87
-25
lines changed

reflex/components/core/colors.py

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,22 @@
11
"""The colors used in Reflex are a wrapper around https://www.radix-ui.com/colors."""
22

3-
from reflex.constants.colors import Color, ColorType, ShadeType
4-
from reflex.utils.types import validate_parameter_literals
3+
from reflex.constants.base import REFLEX_VAR_OPENING_TAG
4+
from reflex.constants.colors import (
5+
COLORS,
6+
MAX_SHADE_VALUE,
7+
MIN_SHADE_VALUE,
8+
Color,
9+
ColorType,
10+
ShadeType,
11+
)
12+
from reflex.vars.base import Var
513

614

7-
@validate_parameter_literals
8-
def color(color: ColorType, shade: ShadeType = 7, alpha: bool = False) -> Color:
15+
def color(
16+
color: ColorType | Var[str],
17+
shade: ShadeType | Var[int] = 7,
18+
alpha: bool | Var[bool] = False,
19+
) -> Color:
920
"""Create a color object.
1021
1122
Args:
@@ -15,5 +26,25 @@ def color(color: ColorType, shade: ShadeType = 7, alpha: bool = False) -> Color:
1526
1627
Returns:
1728
The color object.
29+
30+
Raises:
31+
ValueError: If the color, shade, or alpha are not valid.
1832
"""
33+
if isinstance(color, str):
34+
if color not in COLORS and REFLEX_VAR_OPENING_TAG not in color:
35+
raise ValueError(f"Color must be one of {COLORS}, received {color}")
36+
elif not isinstance(color, Var):
37+
raise ValueError("Color must be a string or a Var")
38+
39+
if isinstance(shade, int):
40+
if shade < MIN_SHADE_VALUE or shade > MAX_SHADE_VALUE:
41+
raise ValueError(
42+
f"Shade must be between {MIN_SHADE_VALUE} and {MAX_SHADE_VALUE}"
43+
)
44+
elif not isinstance(shade, Var):
45+
raise ValueError("Shade must be an integer or a Var")
46+
47+
if not isinstance(alpha, (bool, Var)):
48+
raise ValueError("Alpha must be a boolean or a Var")
49+
1950
return Color(color, shade, alpha)

reflex/constants/colors.py

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
"""The colors used in Reflex are a wrapper around https://www.radix-ui.com/colors."""
22

3+
from __future__ import annotations
4+
35
from dataclasses import dataclass
4-
from typing import Literal
6+
from typing import TYPE_CHECKING, Literal, get_args
7+
8+
if TYPE_CHECKING:
9+
from reflex.vars import Var
510

611
ColorType = Literal[
712
"gray",
@@ -40,10 +45,16 @@
4045
"white",
4146
]
4247

48+
COLORS = frozenset(get_args(ColorType))
49+
4350
ShadeType = Literal[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
51+
MIN_SHADE_VALUE = 1
52+
MAX_SHADE_VALUE = 12
4453

4554

46-
def format_color(color: ColorType, shade: ShadeType, alpha: bool) -> str:
55+
def format_color(
56+
color: ColorType | Var[str], shade: ShadeType | Var[int], alpha: bool | Var[bool]
57+
) -> str:
4758
"""Format a color as a CSS color string.
4859
4960
Args:
@@ -54,21 +65,27 @@ def format_color(color: ColorType, shade: ShadeType, alpha: bool) -> str:
5465
Returns:
5566
The formatted color.
5667
"""
57-
return f"var(--{color}-{'a' if alpha else ''}{shade})"
68+
if isinstance(alpha, bool):
69+
return f"var(--{color}-{'a' if alpha else ''}{shade})"
70+
71+
from reflex.components.core import cond
72+
73+
alpha_var = cond(alpha, "a", "")
74+
return f"var(--{color}-{alpha_var}{shade})"
5875

5976

6077
@dataclass
6178
class Color:
6279
"""A color in the Reflex color palette."""
6380

6481
# The color palette to use
65-
color: ColorType
82+
color: ColorType | Var[str]
6683

6784
# The shade of the color to use
68-
shade: ShadeType = 7
85+
shade: ShadeType | Var[int] = 7
6986

7087
# Whether to use the alpha variant of the color
71-
alpha: bool = False
88+
alpha: bool | Var[bool] = False
7289

7390
def __format__(self, format_spec: str) -> str:
7491
"""Format the color as a CSS color string.

reflex/utils/types.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -909,12 +909,19 @@ def validate_parameter_literals(func: Callable):
909909
Returns:
910910
The wrapper function.
911911
"""
912+
console.deprecate(
913+
"validate_parameter_literals",
914+
reason="Use manual validation instead.",
915+
deprecation_version="0.7.11",
916+
removal_version="0.8.0",
917+
dedupe=True,
918+
)
919+
920+
func_params = list(inspect.signature(func).parameters.items())
921+
annotations = {param[0]: param[1].annotation for param in func_params}
912922

913923
@wraps(func)
914924
def wrapper(*args, **kwargs):
915-
func_params = list(inspect.signature(func).parameters.items())
916-
annotations = {param[0]: param[1].annotation for param in func_params}
917-
918925
# validate args
919926
for param, arg in zip(annotations, args, strict=False):
920927
if annotations[param] is inspect.Parameter.empty:

tests/units/components/core/test_colors.py

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@
99
class ColorState(rx.State):
1010
"""Test color state."""
1111

12-
color: str = "mint"
13-
color_part: str = "tom"
14-
shade: int = 4
15-
alpha: bool = False
12+
color: rx.Field[str] = rx.field("mint")
13+
color_part: rx.Field[str] = rx.field("tom")
14+
shade: rx.Field[int] = rx.field(4)
15+
alpha: rx.Field[bool] = rx.field(False)
1616

1717

1818
color_state_name = ColorState.get_full_name().replace(".", "__")
@@ -22,6 +22,12 @@ def create_color_var(color):
2222
return LiteralVar.create(color)
2323

2424

25+
color_with_fstring = rx.color(
26+
f"{ColorState.color}", # pyright: ignore [reportArgumentType]
27+
ColorState.shade,
28+
)
29+
30+
2531
@pytest.mark.parametrize(
2632
"color, expected, expected_type",
2733
[
@@ -41,26 +47,27 @@ def create_color_var(color):
4147
Color,
4248
),
4349
(
44-
create_color_var(rx.color(f"{ColorState.color}", f"{ColorState.shade}")),
45-
f'("var(--"+{color_state_name!s}.color+"-"+{color_state_name!s}.shade+")")',
50+
create_color_var(color_with_fstring),
51+
f'("var(--"+{color_state_name!s}.color+"-"+(((__to_string) => __to_string.toString())({color_state_name!s}.shade))+")")',
4652
Color,
4753
),
4854
(
4955
create_color_var(
50-
rx.color(f"{ColorState.color_part}ato", f"{ColorState.shade}")
56+
rx.color(
57+
f"{ColorState.color_part}ato", # pyright: ignore [reportArgumentType]
58+
ColorState.shade,
59+
)
5160
),
52-
f'("var(--"+({color_state_name!s}.color_part+"ato")+"-"+{color_state_name!s}.shade+")")',
61+
f'("var(--"+({color_state_name!s}.color_part+"ato")+"-"+(((__to_string) => __to_string.toString())({color_state_name!s}.shade))+")")',
5362
Color,
5463
),
5564
(
56-
create_color_var(f"{rx.color(ColorState.color, f'{ColorState.shade}')}"),
65+
create_color_var(f"{rx.color(ColorState.color, ColorState.shade)}"),
5766
f'("var(--"+{color_state_name!s}.color+"-"+{color_state_name!s}.shade+")")',
5867
str,
5968
),
6069
(
61-
create_color_var(
62-
f"{rx.color(f'{ColorState.color}', f'{ColorState.shade}')}"
63-
),
70+
create_color_var(f"{color_with_fstring}"),
6471
f'("var(--"+{color_state_name!s}.color+"-"+{color_state_name!s}.shade+")")',
6572
str,
6673
),

0 commit comments

Comments
 (0)