diff --git a/plugins/ui/src/deephaven/ui/_internal/utils.py b/plugins/ui/src/deephaven/ui/_internal/utils.py index 1cd6b66e3..e29ce21d3 100644 --- a/plugins/ui/src/deephaven/ui/_internal/utils.py +++ b/plugins/ui/src/deephaven/ui/_internal/utils.py @@ -4,11 +4,27 @@ from inspect import signature import sys from functools import partial +from deephaven.dtypes import Instant, ZonedDateTime, LocalDate +from deephaven.time import to_j_instant, to_j_zdt, to_j_local_date + +from ..types import Date + +DateConverter = ( + Callable[[Date], Instant] # type: ignore + | Callable[[Date], ZonedDateTime] # type: ignore + | Callable[[Date], LocalDate] # type: ignore +) _UNSAFE_PREFIX = "UNSAFE_" _ARIA_PREFIX = "aria_" _ARIA_PREFIX_REPLACEMENT = "aria-" +_CONVERTERS = { + "java.time.Instant": to_j_instant, + "java.time.ZonedDateTime": to_j_zdt, + "java.time.LocalDate": to_j_local_date, +} + def get_component_name(component: Any) -> str: """ @@ -197,3 +213,136 @@ def create_props(args: dict[str, Any]) -> tuple[tuple[Any], dict[str, Any]]: children, props = args.pop("children"), args.pop("props") props.update(args) return children, props + + +def _convert_to_java_date( + date: Date, +) -> Instant | ZonedDateTime | LocalDate: # type: ignore + """ + Convert a Date to a Java date type. + In order of preference, tries to convert to Instant, ZonedDateTime, and LocalDate. + If none of these work, raises a TypeError. + + Args: + date: The date to convert to a Java date type. + + Returns: + The Java date type. + """ + try: + return to_j_instant(date) + except Exception: + # ignore, try next + pass + + try: + return to_j_zdt(date) + except Exception: + # ignore, try next + pass + + try: + return to_j_local_date(date) + except Exception: + raise TypeError( + f"Could not convert {date} to one of Instant, ZonedDateTime, or LocalDate." + ) + + +def _jclass_converter( + value: Instant | ZonedDateTime | LocalDate, # type: ignore +) -> Callable[[Date], Any]: + """ + Get the converter for the Java date type. + + Args: + value: The Java date type to get the converter for. + + Returns: + The converter for the Java date type. + """ + return _CONVERTERS[str(value.jclass)[6:]] + + +def _wrap_date_callable( + date_callable: Callable[[Date], None], + converter: Callable[[Date], Any], +) -> Callable[[Date], None]: + """ + Wrap a callable to convert the Date argument to a Java date type. + This maintains the original callable signature so that the Date argument can be dropped. + + Args: + date_callable: The callable to wrap. + converter: The date converter to use. + + Returns: + The wrapped callable. + """ + return lambda date: wrap_callable(date_callable)(converter(date)) + + +def prioritized_callable_date_converter( + props: dict[str, Any], + priority: Sequence[str], + default_converter: Callable[[Date], Any], +) -> DateConverter: + """ + Get a callable date converter based on the priority of the props. + If none of the priority props are present, uses the default converter. + + Args: + props: The props passed to the component. + priority: The priority of the props to check. + default_converter: The default converter to use if none of the priority props are present. + + Returns: + The callable date converter. + """ + + for prop in priority: + if props.get(prop): + return _jclass_converter(props[prop]) + + return default_converter + + +def convert_date_props( + props: dict[str, Any], + simple_date_props: set[str], + list_date_props: set[str], + callable_date_props: set[str], + callable_date_converter: Callable[[dict[str, Any]], Callable[[Date], Any]], +) -> None: + """ + Convert date props to Java date types in place. + + Args: + props: The props passed to the component. + simple_date_props: A set of simple date keys to convert. The prop value should be a single Date. + list_date_props: A set of list date keys to convert. The prop value should be a list of Dates. + callable_date_props: A set of callable date keys to convert. + The prop value should be a callable that takes a Date. + callable_date_converter: A callable that takes the props and + returns a date converter to be used for the callable date props. + + Returns: + The converted props. + """ + for key in simple_date_props: + if props.get(key) is not None: + props[key] = _convert_to_java_date(props[key]) + + for key in list_date_props: + if props.get(key) is not None: + if not isinstance(props[key], list): + raise TypeError(f"{key} must be a list of Dates") + props[key] = [_convert_to_java_date(date) for date in props[key]] + + converter = callable_date_converter(props) + + for key in callable_date_props: + if props.get(key) is not None: + if not callable(props[key]): + raise TypeError(f"{key} must be a callable") + props[key] = _wrap_date_callable(props[key], converter) diff --git a/plugins/ui/src/deephaven/ui/components/__init__.py b/plugins/ui/src/deephaven/ui/components/__init__.py index f0678f892..d18981f10 100644 --- a/plugins/ui/src/deephaven/ui/components/__init__.py +++ b/plugins/ui/src/deephaven/ui/components/__init__.py @@ -14,6 +14,7 @@ from .list_view import list_view from .list_action_group import list_action_group from .list_action_menu import list_action_menu +from .date_picker import date_picker from . import html @@ -28,6 +29,7 @@ "content", "contextual_help", "dashboard", + "date_picker", "flex", "form", "fragment", diff --git a/plugins/ui/src/deephaven/ui/components/date_picker.py b/plugins/ui/src/deephaven/ui/components/date_picker.py index ed57713f7..926616d57 100644 --- a/plugins/ui/src/deephaven/ui/components/date_picker.py +++ b/plugins/ui/src/deephaven/ui/components/date_picker.py @@ -1,139 +1,33 @@ from __future__ import annotations from typing import Any, Sequence, Callable - -from deephaven.dtypes import Instant, ZonedDateTime, LocalDate -from deephaven.time import to_j_instant, to_j_zdt, to_j_local_date - -from ..elements import Element -from .._internal.utils import create_props, wrap_callable +from deephaven.time import to_j_instant +from ..elements import Element, BaseElement +from .._internal.utils import ( + create_props, + convert_date_props, + prioritized_callable_date_converter, +) from ..types import Date, Granularity DatePickerElement = Element +# All the props that can be date types +_SIMPLE_DATE_PROPS = { + "placeholder_value", + "value", + "default_value", + "min_value", + "max_value", +} +_LIST_DATE_PROPS = {"unavailable_values"} +_CALLABLE_DATE_PROPS = {"on_change"} -def convert_to_java_date( - date: Date, -) -> Instant | ZonedDateTime | LocalDate: # type: ignore - """ - Convert a Date to a Java date type. - In order of preference, tries to convert to Instant, ZonedDateTime, and LocalDate. - If none of these work, raises a TypeError. - - Args: - date: The date to convert to a Java date type. - - Returns: - The Java date type. - """ - try: - return to_j_instant(date) - except TypeError: - # ignore, try next - pass - - try: - return to_j_zdt(date) - except TypeError: - # ignore, try next - pass - - try: - return to_j_local_date(date) - except TypeError: - raise TypeError( - f"Could not convert {date} to one of Instant, ZonedDateTime, or LocalDate." - ) - - -def date_converter( - props: dict[str, Any], -) -> Callable[[Date], Instant | ZonedDateTime | LocalDate]: # type: ignore - """ - Go through "value", "default_value", and "placeholder_value" in props to - determine the type of date converter to use. - If none of these are present, defaults to Instant. - This is used to convert callback arguments to Java date types. - - Args: - props: The props to check for date types. - - Returns: - The date converter. - """ - converters = { - Instant: to_j_instant, - ZonedDateTime: to_j_zdt, - LocalDate: to_j_local_date, - } - - if "value" in props: - return converters[type(props["value"])] - elif "default_value" in props: - return converters[type(props["default_value"])] - elif "placeholder_value" in props: - return converters[type(props["placeholder_value"])] - - return to_j_instant - - -def wrap_date_callable( - date_callable: Callable[[Date], None], - converter: Callable[[Date], Any], -) -> Callable[[Date], None]: - """ - Wrap a callable to convert the Date argument to a Java date type. - This maintains the original callable signature so that the Date argument can be dropped. - - Args: - date_callable: The callable to wrap. - converter: The date converter to use. - - Returns: - The wrapped callable. - """ - return lambda date: wrap_callable(date_callable)(converter(date)) - - -def convert_date_props( - props: dict[str, Any], - simple_date_props: set[str], - list_date_props: set[str], - callable_date_props: set[str], - converter: Callable[[Date], Any], -) -> None: - """ - Convert date props to Java date types in place. - - Args: - props: The props passed to the component. - simple_date_props: A set of simple date keys to convert. The prop value should be a single Date. - list_date_props: A set of list date keys to convert. The prop value should be a list of Dates. - callable_date_props: A set of callable date keys to convert. - The prop value should be a callable that takes a Date. - converter: The date converter to use. - - Returns: - The converted props. - """ - for key in simple_date_props: - if key in props: - props[key] = convert_to_java_date(props[key]) - - for key in list_date_props: - if key in props: - if not isinstance(props[key], list): - raise TypeError(f"{key} must be a list of Dates") - props[key] = [convert_to_java_date(date) for date in props[key]] +# The priority of the date props to determine the format of the date passed to the callable date props +_DATE_PROPS_PRIORITY = ["value", "default_value", "placeholder_value"] - for key in callable_date_props: - if key in props: - if not callable(props[key]): - raise TypeError(f"{key} must be a callable") - props[key] = wrap_date_callable(props[key], converter) - -def convert_date_picker_props( +def _convert_date_picker_props( props: dict[str, Any], ) -> dict[str, Any]: """ @@ -145,20 +39,17 @@ def convert_date_picker_props( Returns: The converted props. """ - simple_date_props = { - "placeholder_value", - "value", - "default_value", - "min_value", - "max_value", - } - list_date_props = {"unavailable_values"} - callable_date_props = {"on_change"} - - converter = date_converter(props) + + callable_date_converter = prioritized_callable_date_converter( + props, _DATE_PROPS_PRIORITY, to_j_instant + ) convert_date_props( - props, simple_date_props, list_date_props, callable_date_props, converter + props, + _SIMPLE_DATE_PROPS, + _LIST_DATE_PROPS, + _CALLABLE_DATE_PROPS, + callable_date_converter, ) return props @@ -174,7 +65,7 @@ def date_picker( granularity: Granularity | None = None, on_change: Callable[[Date], None] | None = None, **props: Any, -) -> DatePickerElement | None: +) -> DatePickerElement: """ @@ -185,8 +76,6 @@ def date_picker( """ _, props = create_props(locals()) - convert_date_picker_props(props) - - print(props) + _convert_date_picker_props(props) - return None # BaseElement("deephaven.ui.components.DatePicker", **props) + return BaseElement("deephaven.ui.components.DatePicker", **props)