Repository: streamlit/streamlit

Issue:
dataframe styling breaks on pandas >= 1.1.0
Pandas just released v1.1.0, and our styling code (which uses private pandas methods) is incompatible with it.

(As a temporary workaround, we're pinning pandas to < 1.1.0.)

When this is fixed, we should remove the restriction on pandas < 1.1.0 from the Streamlit `Pipfile`
dataframe styling breaks on pandas >= 1.1.0
Pandas just released v1.1.0, and our styling code (which uses private pandas methods) is incompatible with it.

(As a temporary workaround, we're pinning pandas to < 1.1.0.)

When this is fixed, we should remove the restriction on pandas < 1.1.0 from the Streamlit `Pipfile`


Discussion:
> I have discovered a truly remarkable solution which this comment is too narrow to contain.

On a serious note, I'll try to send out a PR by the end of the week.


> I have discovered a truly remarkable solution which this comment is too narrow to contain.

On a serious note, I'll try to send out a PR by the end of the week.



Resolving patch:
diff --git a/lib/streamlit/elements/arrow_table.py b/lib/streamlit/elements/arrow_table.py
--- a/lib/streamlit/elements/arrow_table.py
+++ b/lib/streamlit/elements/arrow_table.py
@@ -25,7 +25,7 @@ def marshall(proto, data, default_uuid=None):
     ----------
     proto : proto.ArrowTable
         Output. The protobuf for a Streamlit ArrowTable proto.
-    
+
     data : pandas.DataFrame, pandas.Styler, numpy.ndarray, Iterable, dict, or None
         Something that is or can be converted to a dataframe.
 
@@ -127,14 +127,16 @@ def _marshall_styles(proto, styler, styles):
         for style in table_styles:
             # NB: styles in "table_styles" have a space
             # between the UUID and the selector.
-            rule = _pandas_style_to_css(style, styler.uuid, separator=" ")
+            rule = _pandas_style_to_css(
+                "table_styles", style, styler.uuid, separator=" "
+            )
             css_rules.append(rule)
 
     if "cellstyle" in styles:
         cellstyle = styles["cellstyle"]
         cellstyle = _trim_pandas_styles(cellstyle)
         for style in cellstyle:
-            rule = _pandas_style_to_css(style, styler.uuid)
+            rule = _pandas_style_to_css("cell_style", style, styler.uuid)
             css_rules.append(rule)
 
     if len(css_rules) > 0:
@@ -155,7 +157,7 @@ def _trim_pandas_styles(styles):
     return [x for x in styles if any(any(y) for y in x["props"])]
 
 
-def _pandas_style_to_css(style, uuid, separator=""):
+def _pandas_style_to_css(style_type, style, uuid, separator=""):
     """Convert pandas.Styler translated styles entry to CSS.
 
     Parameters
@@ -176,8 +178,19 @@ def _pandas_style_to_css(style, uuid, separator=""):
         declarations.append(declaration)
 
     table_selector = "#T_" + str(uuid)
-    cell_selector = style["selector"]
-    selector = table_selector + separator + cell_selector
+
+    if style_type == "table_styles" or (
+        style_type == "cell_style" and type_util.is_old_pandas_version()
+    ):
+        cell_selectors = [style["selector"]]
+    else:
+        cell_selectors = style["selectors"]
+
+    selectors = []
+    for cell_selector in cell_selectors:
+        selectors.append(table_selector + separator + cell_selector)
+    selector = ", ".join(selectors)
+
     declaration_block = "; ".join(declarations)
     rule_set = selector + " { " + declaration_block + " }"
 
diff --git a/lib/streamlit/elements/data_frame_proto.py b/lib/streamlit/elements/data_frame_proto.py
--- a/lib/streamlit/elements/data_frame_proto.py
+++ b/lib/streamlit/elements/data_frame_proto.py
@@ -187,12 +187,22 @@ def _get_css_styles(translated_style):
     """Parses pandas.Styler style dictionary into a
     {(row, col): [CSSStyle]} dictionary
     """
-    # Create {(row, col): [CSSStyle]} from translated_style['cellstyle']
-    # translated_style['cellstyle'] has the shape:
+    # In pandas < 1.1.0
+    # translated_style["cellstyle"] has the following shape:
     # [
     #   {
-    #       'props': [['color', ' black'], ['background-color', 'orange'], ['', '']],
-    #       'selector': 'row0_col0'
+    #       "props": [["color", " black"], ["background-color", "orange"], ["", ""]],
+    #       "selector": "row0_col0"
+    #   }
+    #   ...
+    # ]
+    #
+    # In pandas >= 1.1.0
+    # translated_style["cellstyle"] has the following shape:
+    # [
+    #   {
+    #       "props": [("color", " black"), ("background-color", "orange"), ("", "")],
+    #       "selectors": ["row0_col0"]
     #   }
     #   ...
     # ]
@@ -201,25 +211,29 @@ def _get_css_styles(translated_style):
 
     css_styles = {}
     for cell_style in translated_style["cellstyle"]:
-        cell_selector = cell_style["selector"]  # a string of the form 'row0_col0'
-        match = cell_selector_regex.match(cell_selector)
-        if not match:
-            raise RuntimeError(
-                'Failed to parse cellstyle selector "%s"' % cell_selector
-            )
-        row = int(match.group(1))
-        col = int(match.group(2))
-        css_declarations = []
-        props = cell_style["props"]
-        for prop in props:
-            if not isinstance(prop, list) or len(prop) != 2:
-                raise RuntimeError('Unexpected cellstyle props "%s"' % prop)
-            name = str(prop[0]).strip()
-            value = str(prop[1]).strip()
-            if name and value:
-                css_declarations.append(CSSStyle(property=name, value=value))
-
-        css_styles[(row, col)] = css_declarations
+        if type_util.is_old_pandas_version():
+            cell_selectors = [cell_style["selector"]]
+        else:
+            cell_selectors = cell_style["selectors"]
+
+        for cell_selector in cell_selectors:
+            match = cell_selector_regex.match(cell_selector)
+            if not match:
+                raise RuntimeError(
+                    'Failed to parse cellstyle selector "%s"' % cell_selector
+                )
+            row = int(match.group(1))
+            col = int(match.group(2))
+            css_declarations = []
+            props = cell_style["props"]
+            for prop in props:
+                if not isinstance(prop, (tuple, list)) or len(prop) != 2:
+                    raise RuntimeError('Unexpected cellstyle props "%s"' % prop)
+                name = str(prop[0]).strip()
+                value = str(prop[1]).strip()
+                if name and value:
+                    css_declarations.append(CSSStyle(property=name, value=value))
+            css_styles[(row, col)] = css_declarations
 
     return css_styles
 
diff --git a/lib/streamlit/type_util.py b/lib/streamlit/type_util.py
--- a/lib/streamlit/type_util.py
+++ b/lib/streamlit/type_util.py
@@ -267,3 +267,11 @@ def ensure_iterable(obj):
         return obj
     except:
         raise
+
+
+def is_old_pandas_version():
+    """Return True if `pandas` version is < `1.1.0`."""
+    import pandas as pd
+    from packaging import version
+
+    return version.parse(pd.__version__) < version.parse("1.1.0")


Test patch: