Skip to content

Commit

Permalink
refactor: Separate remove_empty_keys and dict_to_camel_case behavior (d…
Browse files Browse the repository at this point in the history
…eephaven#971)

The `dict_to_camel_case` function was doing a bit too much based on its
name. This separates the utils a bit so they are better named and
`dict_to_camel_case` is true camel case without our special keys and
removing Nones.
  • Loading branch information
mattrunyon authored Oct 29, 2024
1 parent cf03ff0 commit 6a99461
Show file tree
Hide file tree
Showing 5 changed files with 72 additions and 27 deletions.
1 change: 1 addition & 0 deletions plugins/ui/src/deephaven/ui/_internal/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
get_component_qualname,
to_camel_case,
dict_to_camel_case,
dict_to_react_props,
remove_empty_keys,
wrap_callable,
)
48 changes: 35 additions & 13 deletions plugins/ui/src/deephaven/ui/_internal/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,28 +101,50 @@ def to_react_prop_case(snake_case_text: str) -> str:
return to_camel_case(snake_case_text)


def convert_dict_keys(
dict: dict[str, Any], convert_key: Callable[[str], str]
) -> dict[str, Any]:
"""
Convert the keys of a dict using a function.
Args:
dict: The dict to convert the keys of.
convert_key: The function to convert the keys.
Returns:
The dict with the converted keys.
"""
return {convert_key(k): v for k, v in dict.items()}


def dict_to_camel_case(
snake_case_dict: dict[str, Any],
omit_none: bool = True,
convert_key: Callable[[str], str] = to_react_prop_case,
dict: dict[str, Any],
) -> dict[str, Any]:
"""
Convert a dict with snake_case keys to a dict with camelCase keys.
Convert a dict to a dict with camelCase keys.
Args:
snake_case_dict: The snake_case dict to convert.
omit_none: Whether to omit keys with a value of None.
convert_key: The function to convert the keys. Can be used to customize the conversion behaviour
dict: The dict to convert.
Returns:
The camelCase dict.
"""
camel_case_dict: dict[str, Any] = {}
for key, value in snake_case_dict.items():
if omit_none and value is None:
continue
camel_case_dict[convert_key(key)] = value
return camel_case_dict
return convert_dict_keys(dict, to_camel_case)


def dict_to_react_props(dict: dict[str, Any]) -> dict[str, Any]:
"""
Convert a dict to React-style prop names ready for the web.
Converts snake_case to camelCase with the exception of special props like `UNSAFE_` or `aria_` props.
Removes empty keys.
Args:
dict: The dict to convert.
Returns:
The React props dict.
"""
return convert_dict_keys(remove_empty_keys(dict), to_react_prop_case)


def remove_empty_keys(dict: dict[str, Any]) -> dict[str, Any]:
Expand Down
4 changes: 2 additions & 2 deletions plugins/ui/src/deephaven/ui/elements/BaseElement.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from typing import Any
from .Element import Element
from .._internal import dict_to_camel_case, RenderContext
from .._internal import dict_to_react_props, RenderContext


class BaseElement(Element):
Expand All @@ -27,7 +27,7 @@ def __init__(
# If there's only one child, we pass it as a single child, not a list
# There are many React elements that expect only a single child, and will fail if they get a list (even if it only has one element)
props["children"] = children[0]
self._props = dict_to_camel_case(props)
self._props = dict_to_react_props(props)

@property
def name(self) -> str:
Expand Down
4 changes: 2 additions & 2 deletions plugins/ui/src/deephaven/ui/elements/UITable.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
RowPressCallback,
StringSortDirection,
)
from .._internal import dict_to_camel_case, RenderContext
from .._internal import dict_to_react_props, RenderContext

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -192,7 +192,7 @@ def _with_dict_prop(self, key: str, value: dict[str, Any]) -> "UITable":

def render(self, context: RenderContext) -> dict[str, Any]:
logger.debug("Returning props %s", self._props)
return dict_to_camel_case({**self._props})
return dict_to_react_props({**self._props})

def aggregations(
self,
Expand Down
42 changes: 32 additions & 10 deletions plugins/ui/test/deephaven/ui/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,17 @@ def test_to_react_prop_case(self):
self.assertEqual(to_react_prop_case("aria_expanded"), "aria-expanded")
self.assertEqual(to_react_prop_case("aria_labelledby"), "aria-labelledby")

def test_convert_dict_keys(self):
from deephaven.ui._internal.utils import convert_dict_keys

# Test with a function reversing the keys
self.assertDictEqual(
convert_dict_keys(
{"foo": "fiz", "bar": "biz"}, convert_key=lambda x: x[::-1]
),
{"oof": "fiz", "rab": "biz"},
)

def test_dict_to_camel_case(self):
from deephaven.ui._internal.utils import dict_to_camel_case

Expand All @@ -60,23 +71,34 @@ def test_dict_to_camel_case(self):
{"alreadyCamelCase": "foo", "alignItems": "bar"},
)
self.assertDictEqual(
dict_to_camel_case({"foo": None, "bar": "biz"}),
{"bar": "biz"},
dict_to_camel_case({"foo": "bar"}),
{"foo": "bar"},
)
self.assertDictEqual(
dict_to_camel_case({"foo": None, "bar": "biz"}, omit_none=False),
{"foo": None, "bar": "biz"},
dict_to_camel_case({"bar": "biz", "UNSAFE_class_name": "harry"}),
{"bar": "biz", "UNSAFEClassName": "harry"},
)

def test_dict_to_react_props(self):
from deephaven.ui._internal.utils import dict_to_react_props

self.assertDictEqual(
dict_to_camel_case({"bar": "biz", "UNSAFE_class_name": "harry"}),
{"bar": "biz", "UNSAFE_className": "harry"},
dict_to_react_props({"test_string": "foo", "test_string_2": "bar_biz"}),
{"testString": "foo", "testString2": "bar_biz"},
)
# Test with a function reversing the keys
self.assertDictEqual(
dict_to_camel_case(
{"foo": "fiz", "bar": "biz"}, convert_key=lambda x: x[::-1]
dict_to_react_props({"alreadyCamelCase": "foo", "align_items": "bar"}),
{"alreadyCamelCase": "foo", "alignItems": "bar"},
)
self.assertDictEqual(
dict_to_react_props({"foo": None, "bar": "biz"}),
{"bar": "biz"},
)
self.assertDictEqual(
dict_to_react_props(
{"bar": "biz", "UNSAFE_class_name": "harry", "aria_label": "ron"}
),
{"oof": "fiz", "rab": "biz"},
{"bar": "biz", "UNSAFE_className": "harry", "aria-label": "ron"},
)

def test_remove_empty_keys(self):
Expand Down

0 comments on commit 6a99461

Please sign in to comment.