Skip to content

Commit 02f58aa

Browse files
authored
cast list of enums to string preemptively for polars (#7058)
## 📝 Summary <!-- Provide a concise summary of what this pull request is addressing. If this PR fixes any issues, list them here by number (e.g., Fixes #123). --> Fixes #7032 . Pre-emptively cast list of enums to strings, sometimes polars doesn't raise the error list of enums, and silently the output is incorrect. This fixes it. ## 🔍 Description of Changes <!-- Detail the specific changes made in this pull request. Explain the problem addressed and how it was resolved. If applicable, provide before and after comparisons, screenshots, or any relevant details to help reviewers understand the changes easily. --> ## 📋 Checklist - [x] I have read the [contributor guidelines](https://github.com/marimo-team/marimo/blob/main/CONTRIBUTING.md). - [ ] For large changes, or changes that affect the public API: this change was discussed or approved through an issue, on [Discord](https://marimo.io/discord?ref=pr), or the community [discussions](https://github.com/marimo-team/marimo/discussions) (Please provide a link if applicable). - [x] I have added tests for the changes made. - [x] I have run the code and verified that it works as expected.
1 parent c559090 commit 02f58aa

File tree

3 files changed

+81
-10
lines changed

3 files changed

+81
-10
lines changed

marimo/_plugins/ui/_impl/tables/polars_table.py

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,15 @@ def to_json_str(
118118
result = self._convert_time_to_string(
119119
result, column
120120
)
121+
# https://github.com/marimo-team/marimo/issues/7032
122+
# Polars issue with ordering and write_json for enums, so we convert to strings
123+
elif isinstance(dtype, pl.List) and isinstance(
124+
dtype.inner, (pl.Enum, pl.Categorical)
125+
):
126+
# Convert each element in the list to a string
127+
result = result.with_columns(
128+
pl.col(column.name).cast(pl.List(pl.String))
129+
)
121130
return sanitize_json_bigint(result.write_json())
122131
except (
123132
BaseException
@@ -154,16 +163,6 @@ def to_json_str(
154163
result, column
155164
)
156165
converted_columns.append(column.name)
157-
# https://github.com/marimo-team/marimo/issues/5562
158-
elif isinstance(dtype, pl.List) and isinstance(
159-
dtype.inner, (pl.Enum, pl.Categorical)
160-
):
161-
# Convert each element in the list to a string
162-
result = result.with_columns(
163-
pl.col(column.name).cast(pl.List(pl.String))
164-
)
165-
converted_columns.append(column.name)
166-
167166
if converted_columns:
168167
LOGGER.info(
169168
"Converted columns %s to safe values.",

tests/_plugins/ui/_impl/tables/test_polars_table.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import datetime
44
import json
55
import unittest
6+
from enum import Enum
67
from math import isnan
78
from typing import Any
89

@@ -995,3 +996,35 @@ def test_to_json_enum_list_supported(self) -> None:
995996

996997
data_list = pl.DataFrame(data, schema={"A": pl.List(pl.Categorical())})
997998
data_list.write_json()
999+
1000+
@pytest.mark.xfail(
1001+
reason="Polars does not properly order sliced data to json when enums in list"
1002+
)
1003+
def test_failing_enums_list(self) -> None:
1004+
import polars as pl
1005+
1006+
class MyEnum(Enum):
1007+
A = 1
1008+
B = 2
1009+
C = 3
1010+
D = 4
1011+
1012+
# Create 10 rows cycling through enum values B, C, D, A...
1013+
enum_names = [e.name for e in MyEnum]
1014+
rows = [
1015+
{"value": [enum_names[i % len(enum_names)]]} for i in range(1, 11)
1016+
]
1017+
1018+
expected_first_five = '[{"value":["B"]},{"value":["C"]},{"value":["D"]},{"value":["A"]},{"value":["B"]}]'
1019+
expected_second_five = '[{"value":["C"]},{"value":["D"]},{"value":["A"]},{"value":["B"]},{"value":["C"]}]'
1020+
1021+
# Test without schema - works fine
1022+
df = pl.DataFrame(rows)
1023+
assert df[0:5].write_json() == expected_first_five
1024+
assert df[5:10].write_json() == expected_second_five
1025+
1026+
# Test with schema - second slice fails
1027+
schema = {"value": pl.List(pl.Enum(enum_names))}
1028+
df_schema = pl.DataFrame(rows, schema=schema)
1029+
assert df_schema[0:5].write_json() == expected_first_five
1030+
assert df_schema[5:10].write_json() == expected_second_five # fails

tests/_plugins/ui/_impl/test_table.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
import json
55
from datetime import date
6+
from enum import Enum
67
from typing import TYPE_CHECKING, Any
78
from unittest.mock import patch
89

@@ -2399,3 +2400,41 @@ def hover_text(_row: str, _col: str, value: Any) -> str:
23992400
"4": {"column_0": "hover:carrots"},
24002401
"5": {"column_0": "hover:carrots"},
24012402
}
2403+
2404+
2405+
@pytest.mark.skipif(
2406+
not DependencyManager.polars.has(), reason="Polars not installed"
2407+
)
2408+
def test_polars_enums_in_list():
2409+
import polars as pl
2410+
2411+
class MyEnum(Enum):
2412+
A = 1
2413+
B = 2
2414+
C = 3
2415+
D = 4
2416+
2417+
# Create 10 rows cycling through enum values B, C, D, A...
2418+
enum_names = [e.name for e in MyEnum]
2419+
rows = [{"value": [enum_names[i % len(enum_names)]]} for i in range(1, 11)]
2420+
2421+
schema = {"value": pl.List(pl.Enum(enum_names))}
2422+
df = pl.DataFrame(rows, schema=schema)
2423+
2424+
table = ui.table(df, selection=None)
2425+
2426+
# First page
2427+
response = table._search(SearchTableArgs(page_size=5, page_number=0))
2428+
assert (
2429+
response.data
2430+
== '[{"value":["B"]},{"value":["C"]},{"value":["D"]},{"value":["A"]},{"value":["B"]}]'
2431+
)
2432+
2433+
# Second page
2434+
response_next_page = table._search(
2435+
SearchTableArgs(page_size=5, page_number=1)
2436+
)
2437+
assert (
2438+
response_next_page.data
2439+
== '[{"value":["C"]},{"value":["D"]},{"value":["A"]},{"value":["B"]},{"value":["C"]}]'
2440+
)

0 commit comments

Comments
 (0)