Repository: modin-project/modin (python)

Issue:
BUG: Casting and extensions should exclude move_to and _update_inplace()
move_to shouldn't need to to do casting itself, and it's an alias of set_backend(), which we use to implement the extension system.

We use _update_inplace to implement set_backend(inplace=True), so sometimes set_backend(inplace=True) will call _update_inplace, which will cast its arguments with set_backend(), so we end up transferring the data twice.

Pull request description:
FIX-#7490: Exclude move_to and _update_inplace from casting.
<!--
Thank you for your contribution!
Please review the contributing docs: https://modin.readthedocs.io/en/latest/development/contributing.html
if you have questions about contributing.
-->

## What do these changes do?

<!-- Please give a short brief about these changes. -->

- [x] first commit message and PR title follow format outlined [here](https://modin.readthedocs.io/en/latest/development/contributing.html#commit-message-formatting)
  > **_NOTE:_**  If you edit the PR title to match this format, you need to add another commit (even if it's empty) or amend your last commit for the CI job that checks the PR title to pick up the new PR title.
- [x] passes `flake8 modin/ asv_bench/benchmarks scripts/doc_checker.py`
- [x] passes `black --check modin/ asv_bench/benchmarks scripts/doc_checker.py`
- [x] signed commit with `git commit -s` <!-- you can amend your commit with a signature via `git commit -amend -s` -->
- [x] Resolves #7490
- [x] tests added and passing
- [x] module layout described at `docs/development/architecture.rst` is up-to-date <!-- if you have added, renamed or removed files or directories please update the documentation accordingly -->


Patch:
diff --git a/modin/core/storage_formats/pandas/query_compiler_caster.py b/modin/core/storage_formats/pandas/query_compiler_caster.py
index 7ef0a96e..372c6c3e 100644
--- a/modin/core/storage_formats/pandas/query_compiler_caster.py
+++ b/modin/core/storage_formats/pandas/query_compiler_caster.py
@@ -49,6 +49,8 @@ _NON_EXTENDABLE_ATTRIBUTES = {
     "_getattribute__from_extension_impl",
     "_getattr__from_extension_impl",
     "get_backend",
+    "move_to",
+    "_update_inplace",
     "set_backend",
     "_get_extension",
     "_query_compiler",


Test patch:
diff --git a/modin/tests/pandas/native_df_interoperability/test_compiler_caster.py b/modin/tests/pandas/native_df_interoperability/test_compiler_caster.py
index fed31e6c..3af66cf8 100644
--- a/modin/tests/pandas/native_df_interoperability/test_compiler_caster.py
+++ b/modin/tests/pandas/native_df_interoperability/test_compiler_caster.py
@@ -11,6 +11,8 @@
 # ANY KIND, either express or implied. See the License for the specific language
 # governing permissions and limitations under the License.
 
+from unittest import mock
+
 import pandas
 import pytest
 
@@ -252,6 +254,17 @@ def test_cast_to_second_backend_with_concat(pico_df, cluster_df):
     assert df3.get_backend() == "Cluster"  # result should be on cluster
 
 
+def test_moving_pico_to_cluster_in_place_calls_set_backend_only_once_github_issue_7490(
+    pico_df, cluster_df
+):
+    with mock.patch.object(
+        pd.DataFrame, "set_backend", wraps=pico_df.set_backend
+    ) as mock_set_backend:
+        pico_df.set_backend(cluster_df.get_backend(), inplace=True)
+    assert pico_df.get_backend() == "Cluster"
+    mock_set_backend.assert_called_once_with("Cluster", inplace=True)
+
+
 def test_cast_to_second_backend_with___init__(pico_df, cluster_df):
     df3 = pd.DataFrame({"pico": pico_df.iloc[:, 0], "cluster": cluster_df.iloc[:, 0]})
     assert pico_df.get_backend() == "Pico"