Repository: modin-project/modin (python)

Issue:
FEAT: Support pinning objects to a backend
When automatic backend switching is enabled, users should be able to opt out via a `pin_backend` function on inidvidual objects.

Pull request description:
FIX-#7521: Fix wrong extension being used when backend is pinned
<!--
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?

This is a follow-up to #7522, which accidentally broke extension dispatch when an object is pinned. In the previous PR, the default method was being used rather than an extension, while the result backend was correctly preserved.

- [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 #7521 <!-- issue must be created for each patch -->
- [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 88477d37..4f9cc212 100644
--- a/modin/core/storage_formats/pandas/query_compiler_caster.py
+++ b/modin/core/storage_formats/pandas/query_compiler_caster.py
@@ -789,7 +789,18 @@ def wrap_function_in_argument_caster(
         if not AutoSwitchBackend.get() or (
             len(input_query_compilers) < 2 and pin_target_backend is not None
         ):
-            result = f(*args, **kwargs)
+            f_to_apply = _get_extension_for_method(
+                name=name,
+                extensions=extensions,
+                backend=(
+                    pin_target_backend
+                    if pin_target_backend is not None
+                    else Backend.get()
+                ),
+                args=args,
+                wrapping_function_type=wrapping_function_type,
+            )
+            result = f_to_apply(*args, **kwargs)
             if isinstance(result, QueryCompilerCaster):
                 result._set_backend_pinned(True, inplace=True)
             return result


Test patch:
diff --git a/modin/tests/pandas/extensions/test_dataframe_extensions.py b/modin/tests/pandas/extensions/test_dataframe_extensions.py
index 24a43faa..0b72f3a8 100644
--- a/modin/tests/pandas/extensions/test_dataframe_extensions.py
+++ b/modin/tests/pandas/extensions/test_dataframe_extensions.py
@@ -14,6 +14,7 @@
 import re
 from unittest import mock
 
+import pandas
 import pytest
 
 import modin.pandas as pd
@@ -311,3 +312,22 @@ def test_disallowed_extensions(Backend1, non_extendable_attribute_name):
         register_dataframe_accessor(
             name=non_extendable_attribute_name, backend=Backend1
         )("unused_value")
+
+
+def test_correct_backend_with_pin(Backend1):
+    # Ensures that the correct implementation is used when dispatching an operation on a pinned
+    # frame, as an earlier implementation used the wrong extension method while preserving the
+    # correct backend.
+
+    @register_dataframe_accessor(name="__repr__", backend=Backend1)
+    def my_repr(self):
+        return "fake_repr"
+
+    with config_context(AutoSwitchBackend=False, Backend="Python_Test"):
+        df = pd.DataFrame([1])
+        assert df.get_backend() == "Python_Test"
+        assert repr(df) == repr(pandas.DataFrame([1]))
+        df.set_backend(Backend1, inplace=True)
+        df.pin_backend(inplace=True)
+        assert df.get_backend() == Backend1
+        assert repr(df) == "fake_repr"