Skip to content

Commit 33effe6

Browse files
DaltheCowsjmonsonjaredoconnell
committed
add custom dict camelize logic (#246)
This removes reliance on the library pyhumps converting dicts keys from snake_case to camelCase for use in the UI --------- Signed-off-by: dalthecow <[email protected]> Signed-off-by: Samuel Monson <[email protected]> Signed-off-by: Jared O'Connell <[email protected]> Co-authored-by: Samuel Monson <[email protected]> Co-authored-by: Jared O'Connell <[email protected]>
1 parent 54556ae commit 33effe6

File tree

6 files changed

+125
-6
lines changed

6 files changed

+125
-6
lines changed

src/guidellm/benchmark/output.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import math
66
from abc import ABC, abstractmethod
77
from collections import OrderedDict
8+
from copy import deepcopy
89
from datetime import datetime
910
from pathlib import Path
1011
from typing import Any, ClassVar
@@ -36,6 +37,8 @@
3637
safe_format_timestamp,
3738
split_text_list_by_length,
3839
)
40+
from guidellm.utils.dict import recursive_key_update
41+
from guidellm.utils.text import camelize_str
3942

4043
__all__ = [
4144
"GenerativeBenchmarkerCSV",
@@ -711,22 +714,20 @@ async def finalize(self, report: GenerativeBenchmarksReport) -> Path:
711714
:param report: The completed benchmark report.
712715
:return: Path to the saved HTML file.
713716
"""
714-
import humps
715-
716717
output_path = self.output_path
717718
if output_path.is_dir():
718719
output_path = output_path / GenerativeBenchmarkerHTML.DEFAULT_FILE
719720
output_path.parent.mkdir(parents=True, exist_ok=True)
720721

721722
data_builder = UIDataBuilder(report.benchmarks)
722723
data = data_builder.to_dict()
723-
camel_data = humps.camelize(data)
724+
camel_data = recursive_key_update(deepcopy(data), camelize_str)
724725

725726
ui_api_data = {}
726-
for key, value in camel_data.items():
727-
placeholder_key = f"window.{key} = {{}};"
727+
for k, v in camel_data.items():
728+
placeholder_key = f"window.{k} = {{}};"
728729
replacement_value = (
729-
f"window.{key} = {json.dumps(value, indent=2)};\n"
730+
f"window.{k} = {json.dumps(v, indent=2)};\n"
730731
)
731732
ui_api_data[placeholder_key] = replacement_value
732733

src/guidellm/utils/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from .auto_importer import AutoImporterMixin
22
from .console import Colors, Console, ConsoleUpdateStep, StatusIcons, StatusStyles
33
from .default_group import DefaultGroupHandler
4+
from .dict import recursive_key_update
45
from .encoding import (
56
Encoder,
67
EncodingTypesAlias,
@@ -55,6 +56,7 @@
5556
)
5657
from .text import (
5758
EndlessTextCreator,
59+
camelize_str,
5860
clean_text,
5961
filter_text,
6062
format_value_display,
@@ -79,6 +81,7 @@
7981
"EndlessTextCreator",
8082
"InfoMixin",
8183
"IntegerRangeSampler",
84+
"camelize_str",
8285
"InterProcessMessaging",
8386
"InterProcessMessagingManagerQueue",
8487
"InterProcessMessagingPipe",
@@ -110,6 +113,7 @@
110113
"format_value_display",
111114
"get_literal_vals",
112115
"is_punctuation",
116+
"recursive_key_update",
113117
"load_text",
114118
"safe_add",
115119
"safe_divide",

src/guidellm/utils/dict.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
def recursive_key_update(d, key_update_func):
2+
if not isinstance(d, dict) and not isinstance(d, list):
3+
return d
4+
5+
if isinstance(d, list):
6+
for item in d:
7+
recursive_key_update(item, key_update_func)
8+
return d
9+
10+
updated_key_pairs = []
11+
for key, _ in d.items():
12+
updated_key = key_update_func(key)
13+
if key != updated_key:
14+
updated_key_pairs.append((key, updated_key))
15+
16+
for key_pair in updated_key_pairs:
17+
old_key, updated_key = key_pair
18+
d[updated_key] = d[old_key]
19+
del d[old_key]
20+
21+
for _, value in d.items():
22+
recursive_key_update(value, key_update_func)
23+
return d

src/guidellm/utils/text.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
__all__ = [
2929
"MAX_PATH_LENGTH",
3030
"EndlessTextCreator",
31+
"camelize_str",
3132
"clean_text",
3233
"filter_text",
3334
"format_value_display",
@@ -281,6 +282,12 @@ def is_punctuation(text: str) -> bool:
281282
return len(text) == 1 and not text.isalnum() and not text.isspace()
282283

283284

285+
def camelize_str(snake_case_string: str) -> str:
286+
return (words := snake_case_string.split("_"))[0].lower() + "".join(
287+
word.capitalize() for word in words[1:]
288+
)
289+
290+
284291
class EndlessTextCreator:
285292
"""
286293
Infinite text generator for load testing and content creation operations.

tests/unit/utils/dict.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import pytest
2+
3+
from guidellm.utils.dict import recursive_key_update
4+
5+
6+
def update_str(string):
7+
return string + "_updated"
8+
9+
10+
@pytest.mark.smoke
11+
def test_recursive_key_update_updates_keys():
12+
my_dict = {
13+
"my_key": {
14+
"my_nested_key": {"my_double_nested_key": "someValue"},
15+
"my_other_nested_key": "someValue",
16+
},
17+
"my_other_key": "value",
18+
}
19+
my_updated_dict = {
20+
"my_key_updated": {
21+
"my_nested_key_updated": {"my_double_nested_key_updated": "someValue"},
22+
"my_other_nested_key_updated": "someValue",
23+
},
24+
"my_other_key_updated": "value",
25+
}
26+
recursive_key_update(my_dict, update_str)
27+
assert my_dict == my_updated_dict
28+
29+
30+
def truncate_str_to_ten(string):
31+
return string[:10]
32+
33+
34+
@pytest.mark.smoke
35+
def test_recursive_key_update_leaves_unchanged_keys():
36+
my_dict = {
37+
"my_key": {
38+
"my_nested_key": {"my_double_nested_key": "someValue"},
39+
"my_other_nested_key": "someValue",
40+
},
41+
"my_other_key": "value",
42+
}
43+
my_updated_dict = {
44+
"my_key": {
45+
"my_nested_": {"my_double_": "someValue"},
46+
"my_other_n": "someValue",
47+
},
48+
"my_other_k": "value",
49+
}
50+
recursive_key_update(my_dict, truncate_str_to_ten)
51+
assert my_dict == my_updated_dict
52+
53+
54+
@pytest.mark.smoke
55+
def test_recursive_key_update_updates_dicts_in_list():
56+
my_dict = {
57+
"my_key": [
58+
{"my_list_item_key_1": "someValue"},
59+
{"my_list_item_key_2": "someValue"},
60+
{"my_list_item_key_3": "someValue"},
61+
]
62+
}
63+
my_updated_dict = {
64+
"my_key_updated": [
65+
{"my_list_item_key_1_updated": "someValue"},
66+
{"my_list_item_key_2_updated": "someValue"},
67+
{"my_list_item_key_3_updated": "someValue"},
68+
]
69+
}
70+
recursive_key_update(my_dict, update_str)
71+
assert my_dict == my_updated_dict

tests/unit/utils/text.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import pytest
2+
3+
from guidellm.utils.text import camelize_str
4+
5+
6+
@pytest.mark.smoke
7+
def test_camelize_str_camelizes_string():
8+
assert camelize_str("no_longer_snake_case") == "noLongerSnakeCase"
9+
10+
11+
@pytest.mark.smoke
12+
def test_camelize_str_leaves_non_snake_case_text_untouched():
13+
assert camelize_str("notsnakecase") == "notsnakecase"

0 commit comments

Comments
 (0)