1- from typing import Any , List , Optional
1+ import uuid
2+ from typing import TYPE_CHECKING , Any , List , Optional
23
34import pandas as pd
45
56import deepnote_toolkit .ocelots as oc
67
8+ if TYPE_CHECKING :
9+ import polars as pl
10+
711
812def sanitize_dataframe_for_chart (pd_df : pd .DataFrame ):
913 sanitized_dataframe = pd_df .copy ()
1014
1115 oc .pandas .utils .deduplicate_columns (sanitized_dataframe )
1216 _convert_timedelta_columns_to_seconds (sanitized_dataframe )
17+ _convert_uuid_columns_to_string (sanitized_dataframe )
1318 _convert_column_names_to_string (sanitized_dataframe )
1419
1520 return sanitized_dataframe
1621
1722
23+ def sanitize_polars_dataframe_for_chart (pl_df : "pl.DataFrame" ) -> "pl.DataFrame" :
24+ """
25+ Coerce polars columns that VegaFusion cannot serialize into chart-friendly
26+ types, returning a new DataFrame.
27+
28+ polars stores values it has no native type for (e.g. ``uuid.UUID`` objects)
29+ in an ``Object`` column, which converts to an opaque Arrow ``FixedSizeBinary``
30+ that VegaFusion cannot serialize to JSON. Such columns are not meaningfully
31+ chartable as-is, so we stringify them -- the polars analogue of the UUID
32+ handling in :func:`sanitize_dataframe_for_chart` for the pandas path.
33+ """
34+ import polars as pl
35+
36+ object_columns = [
37+ name for name , dtype in zip (pl_df .columns , pl_df .dtypes ) if dtype == pl .Object
38+ ]
39+ if not object_columns :
40+ return pl_df
41+
42+ return pl_df .with_columns (
43+ pl .col (name ).map_elements (str , return_dtype = pl .String )
44+ for name in object_columns
45+ )
46+
47+
1848def _convert_column_names_to_string (pd_df : pd .DataFrame ):
1949 """
2050 Converts dataframe column names to strings.
@@ -24,6 +54,32 @@ def _convert_column_names_to_string(pd_df: pd.DataFrame):
2454 pd_df .columns = pd_df .columns .astype (str )
2555
2656
57+ def _convert_uuid_columns_to_string (pd_df : pd .DataFrame ):
58+ """
59+ Converts columns of ``uuid.UUID`` objects to strings.
60+
61+ Starting with pyarrow 24.0.0, Arrow conversion infers the canonical
62+ ``arrow.uuid`` extension type (backed by ``FixedSizeBinary(16)``) for object
63+ columns holding ``uuid.UUID`` values; pyarrow <= 23 produced a serializable
64+ result for the same data. VegaFusion's Arrow runtime cannot serialize
65+ ``FixedSizeBinary(16)`` to JSON (``Unsupported datatype for JSON
66+ serialization: FixedSizeBinary(16)``), so we stringify such columns to keep
67+ charting working across pyarrow versions.
68+
69+ WARNING: This function modifies the DataFrame in-place.
70+ """
71+ for column in pd_df .columns :
72+ col = pd_df [column ]
73+ if not pd .api .types .is_object_dtype (col .dtype ):
74+ continue
75+ non_null = col .dropna ()
76+ if non_null .empty or not isinstance (non_null .iloc [0 ], uuid .UUID ):
77+ continue
78+ pd_df [column ] = col .map (
79+ lambda value : str (value ) if isinstance (value , uuid .UUID ) else value
80+ )
81+
82+
2783def _convert_timedelta_columns_to_seconds (pd_sanitized_df : pd .DataFrame ):
2884 """
2985 Converts timedelta columns to seconds.
0 commit comments