From bc8f92066d241166c89f1e21c2a64a1d3353461e Mon Sep 17 00:00:00 2001 From: Anatoly Myachev Date: Mon, 27 Nov 2023 22:31:23 +0100 Subject: [PATCH 1/3] Fix usage of 'column_names' that part of Interchange protocol Signed-off-by: Anatoly Myachev --- packages/python/plotly/plotly/express/_core.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/python/plotly/plotly/express/_core.py b/packages/python/plotly/plotly/express/_core.py index 037123a37e5..6f3d55dc5c9 100644 --- a/packages/python/plotly/plotly/express/_core.py +++ b/packages/python/plotly/plotly/express/_core.py @@ -1327,7 +1327,11 @@ def build_dataframe(args, constructor): df_not_pandas = args["data_frame"] args["data_frame"] = df_not_pandas.__dataframe__() - columns = args["data_frame"].column_names() + # According interchange protocol: `def column_names(self) -> Iterable[str]:` + # so this function can return for example a generator. + # The easiest way is to convert `columns` to `pandas.Index` so that the + # type is similar to the types in other code branches. + columns = pd.Index(args["data_frame"].column_names()) needs_interchanging = True elif hasattr(args["data_frame"], "to_pandas"): args["data_frame"] = args["data_frame"].to_pandas() From 44039726bc2a25191aee89632161b006be140509 Mon Sep 17 00:00:00 2001 From: Anatoly Myachev Date: Tue, 28 Nov 2023 22:33:14 +0100 Subject: [PATCH 2/3] add changelog entry and test case Signed-off-by: Anatoly Myachev --- CHANGELOG.md | 1 + .../tests/test_optional/test_px/test_px_input.py | 15 ++++++++++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 596e0418461..6df6cde3b59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Fix issue with creating dendrogram in subplots [[#4411](https://github.com/plotly/plotly.py/pull/4411)], - Fix issue with px.line not accepting "spline" line shape [[#2812](https://github.com/plotly/plotly.py/issues/2812)] - Fix KeyError when using column of `pd.Categorical` dtype with unobserved categories [[#4437](https://github.com/plotly/plotly.py/pull/4437)] +- Fix dataframe interchange in case `column_names` returns an unmaterialized object: generator, iterator, etc. [[#4442]](https://github.com/plotly/plotly.py/pull/4442) ## [5.18.0] - 2023-10-25 diff --git a/packages/python/plotly/plotly/tests/test_optional/test_px/test_px_input.py b/packages/python/plotly/plotly/tests/test_optional/test_px/test_px_input.py index 7c9ad6ac1ea..b0068a3409f 100644 --- a/packages/python/plotly/plotly/tests/test_optional/test_px/test_px_input.py +++ b/packages/python/plotly/plotly/tests/test_optional/test_px/test_px_input.py @@ -250,15 +250,24 @@ def test_build_df_with_index(): assert_frame_equal(tips.reset_index()[out["data_frame"].columns], out["data_frame"]) +@pytest.mark.parametrize("column_names_as_generator", [False, True]) def test_build_df_using_interchange_protocol_mock( - add_interchange_module_for_old_pandas, + add_interchange_module_for_old_pandas, column_names_as_generator ): class InterchangeDataFrame: def __init__(self, columns): self._columns = columns - def column_names(self): - return self._columns + if column_names_as_generator: + + def column_names(self): + for col in self._columns: + yield col + + else: + + def column_names(self): + return self._columns interchange_dataframe = InterchangeDataFrame( ["petal_width", "sepal_length", "sepal_width"] From e7bfc7c85451eb6547c9ad9b404e97d9b2369ae0 Mon Sep 17 00:00:00 2001 From: Anatoly Myachev Date: Tue, 28 Nov 2023 23:38:31 +0100 Subject: [PATCH 3/3] fix Signed-off-by: Anatoly Myachev --- .../tests/test_optional/test_px/test_px_input.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/python/plotly/plotly/tests/test_optional/test_px/test_px_input.py b/packages/python/plotly/plotly/tests/test_optional/test_px/test_px_input.py index b0068a3409f..94382cb9e01 100644 --- a/packages/python/plotly/plotly/tests/test_optional/test_px/test_px_input.py +++ b/packages/python/plotly/plotly/tests/test_optional/test_px/test_px_input.py @@ -14,12 +14,12 @@ @pytest.fixture def add_interchange_module_for_old_pandas(): if not hasattr(pd.api, "interchange"): - pd.api.interchange = mock.MagicMock() - # to make the following import work: `import pandas.api.interchange` - with mock.patch.dict( - "sys.modules", {"pandas.api.interchange": pd.api.interchange} - ): - yield + with mock.patch.object(pd.api, "interchange", mock.MagicMock(), create=True): + # to make the following import work: `import pandas.api.interchange` + with mock.patch.dict( + "sys.modules", {"pandas.api.interchange": pd.api.interchange} + ): + yield else: yield