diff --git a/reflex/utils/format.py b/reflex/utils/format.py index 24eec9a83..902d9610c 100644 --- a/reflex/utils/format.py +++ b/reflex/utils/format.py @@ -513,6 +513,10 @@ def format_state(value: Any) -> Any: "data": format_dataframe_values(value), } + # Convert datetime objects to str. + if types.is_datetime(type(value)): + return str(value) + # Convert Image objects to base64. if types.is_image(type(value)): return format_image_data(value) # type: ignore diff --git a/reflex/utils/types.py b/reflex/utils/types.py index 3d51b2cd2..98703039d 100644 --- a/reflex/utils/types.py +++ b/reflex/utils/types.py @@ -4,6 +4,7 @@ from __future__ import annotations import contextlib import typing +from datetime import date, datetime, time, timedelta from typing import Any, Callable, Type, Union, _GenericAlias # type: ignore from reflex.base import Base @@ -168,6 +169,18 @@ def is_figure(value: Type) -> bool: return value.__name__ == "Figure" +def is_datetime(value: Type) -> bool: + """Check if the given value is a datetime object. + + Args: + value: The value to check. + + Returns: + Whether the value is a date, datetime, time, or timedelta. + """ + return issubclass(value, (date, datetime, time, timedelta)) + + def is_valid_var_type(var: Type) -> bool: """Check if the given value is a valid prop type. @@ -182,6 +195,7 @@ def is_valid_var_type(var: Type) -> bool: or is_dataframe(var) or is_figure(var) or is_image(var) + or is_datetime(var) ) diff --git a/tests/test_state.py b/tests/test_state.py index 2c60e377d..a783e89f8 100644 --- a/tests/test_state.py +++ b/tests/test_state.py @@ -1,5 +1,6 @@ from __future__ import annotations +import datetime import functools from typing import Dict, List @@ -37,6 +38,7 @@ class TestState(State): obj: Object = Object() complex: Dict[int, Object] = {1: Object(), 2: Object()} fig: Figure = Figure() + dt: datetime.datetime = datetime.datetime.fromisoformat("1989-11-09T18:53:00+01:00") @ComputedVar def sum(self) -> float: @@ -202,6 +204,7 @@ def test_class_vars(test_state): "sum", "upper", "fig", + "dt", } @@ -267,6 +270,58 @@ def test_dict(test_state): ) +def test_format_state(test_state): + """Test that the format state is correct. + + Args: + test_state: A state. + """ + formatted_state = format.format_state(test_state.dict()) + exp_formatted_state = { + "array": [1, 2, 3.14], + "child_state": {"count": 23, "grandchild_state": {"value2": ""}, "value": ""}, + "child_state2": {"value": ""}, + "complex": { + 1: {"prop1": 42, "prop2": "hello"}, + 2: {"prop1": 42, "prop2": "hello"}, + }, + "dt": "1989-11-09 18:53:00+01:00", + "fig": [], + "is_hydrated": False, + "key": "", + "map_key": "a", + "mapping": {"a": [1, 2, 3], "b": [4, 5, 6]}, + "num1": 0, + "num2": 3.14, + "obj": {"prop1": 42, "prop2": "hello"}, + "sum": 3.14, + "upper": "", + } + assert formatted_state == exp_formatted_state + + +def test_format_state_datetime(): + """Test that the format state is correct for datetime classes.""" + + class DateTimeState(State): + d: datetime.date = datetime.date.fromisoformat("1989-11-09") + dt: datetime.datetime = datetime.datetime.fromisoformat( + "1989-11-09T18:53:00+01:00" + ) + t: datetime.time = datetime.time.fromisoformat("18:53:00+01:00") + td: datetime.timedelta = datetime.timedelta(days=11, minutes=11) + + formatted_state = format.format_state(DateTimeState().dict()) + exp_formatted_state = { + "d": "1989-11-09", + "dt": "1989-11-09 18:53:00+01:00", + "is_hydrated": False, + "t": "18:53:00+01:00", + "td": "11 days, 0:11:00", + } + assert formatted_state == exp_formatted_state + + def test_default_setters(test_state): """Test that we can set default values. @@ -575,6 +630,7 @@ def test_reset(test_state, child_state): "array", "map_key", "mapping", + "dt", } # The dirty vars should be reset.