Repository: modin-project/modin (python)

Issue:
BUG: getting extension property that should raise AttributeError instead gets the property itself
### Modin version checks

- [x] I have checked that this issue has not already been reported.

- [x] I have confirmed this bug exists on the latest released version of Modin.

- [x] I have confirmed this bug exists on the main branch of Modin. (In order to do this you can follow [this guide](https://modin.readthedocs.io/en/stable/getting_started/installation.html#installing-from-the-github-main-branch).)


### Reproducible Example

```python
from modin.pandas.api.extensions import register_base_accessor
import modin.pandas as pd
import pytest

def _get_property(self):
    raise AttributeError


register_base_accessor('new_property')(property(_get_property))
df = pd.DataFrame()
df.new_property

with pytest.raises(AttributeError):
    df.new_property
```

### Issue Description

if getting an extension property should raise `AttributeError`, modin returns the `property` object itself instead of raise the same error.

I think the lack of a `__get__` call here is the reason: https://github.com/modin-project/modin/blob/837fb954b7e05555652546e8bd68b63ddcf65e09/modin/pandas/base.py#L4694

we should delete `_getattr__from_extension_impl`. `__getattribute__` can take care of getting extension properties.

### Expected Behavior

should raise `AttributeError`

### Error Logs

N/A

### Installed Versions

<details>

INSTALLED VERSIONS
------------------
commit                : 837fb954b7e05555652546e8bd68b63ddcf65e09
python                : 3.9.21
python-bits           : 64
OS                    : Darwin
OS-release            : 24.4.0
Version               : Darwin Kernel Version 24.4.0: Fri Apr 11 18:33:39 PDT 2025; root:xnu-11417.101.15~117/RELEASE_ARM64_T6020
machine               : arm64
processor             : arm
byteorder             : little
LC_ALL                : None
LANG                  : en_US.UTF-8
LOCALE                : en_US.UTF-8

Modin dependencies
------------------
modin                 : 0.32.0+58.g837fb954.dirty
ray                   : 2.44.1
dask                  : 2024.8.0
distributed           : 2024.8.0

pandas dependencies
-------------------
pandas                : 2.2.3
numpy                 : 2.0.2
pytz                  : 2025.2
dateutil              : 2.9.0.post0
pip                   : 24.2
Cython                : None
sphinx                : 7.4.7
IPython               : 8.18.1
adbc-driver-postgresql: None
adbc-driver-sqlite    : None
bs4                   : 4.13.4
blosc                 : None
bottleneck            : None
dataframe-api-compat  : None
fastparquet           : 2024.11.0
fsspec                : 2025.3.2
html5lib              : None
hypothesis            : None
gcsfs                 : None
jinja2                : 3.1.6
lxml.etree            : 5.3.2
matplotlib            : 3.9.4
numba                 : None
numexpr               : 2.10.2
odfpy                 : None
openpyxl              : 3.1.5
pandas_gbq            : 0.28.0
psycopg2              : 2.9.10
pymysql               : None
pyarrow               : 19.0.1
pyreadstat            : None
pytest                : 8.3.5
python-calamine       : None
pyxlsb                : None
s3fs                  : 2025.3.2
scipy                 : 1.13.1
sqlalchemy            : 2.0.40
tables                : N/A
tabulate              : 0.9.0
xarray                : 2024.7.0
xlrd                  : 2.0.1
xlsxwriter            : None
zstandard             : None
tzdata                : 2025.2
qtpy                  : None
pyqt5                 : None

</details>


Pull request:
FIX-#7562: Raise AttributeError for missing extension properties.
Stop falling back to `__getattr__` for missing extensions. The `_getattr__from_extension_impl` implementation didn't access properties correctly, but we don't need to fall back to `__getattr__` at all, and we can implement extension property access with just `__getattribute__`.

Resolves #7562


Patch:
diff --git a/modin/core/storage_formats/pandas/query_compiler_caster.py b/modin/core/storage_formats/pandas/query_compiler_caster.py
index 5ddd2802..96df688f 100644
--- a/modin/core/storage_formats/pandas/query_compiler_caster.py
+++ b/modin/core/storage_formats/pandas/query_compiler_caster.py
@@ -59,7 +59,6 @@ _NON_EXTENDABLE_ATTRIBUTES = {
     "__delattr__",
     "__getattr__",
     "_getattribute__from_extension_impl",
-    "_getattr__from_extension_impl",
     "get_backend",
     "move_to",
     "_update_inplace",
diff --git a/modin/pandas/base.py b/modin/pandas/base.py
index 29fe3aef..a7f9f8f8 100644
--- a/modin/pandas/base.py
+++ b/modin/pandas/base.py
@@ -110,7 +110,6 @@ _EXTENSION_NO_LOOKUP = {
     "_query_compiler",
     "get_backend",
     "_getattribute__from_extension_impl",
-    "_getattr__from_extension_impl",
     "_get_query_compiler",
     "set_backend",
     "_pinned",
@@ -4376,29 +4375,6 @@ class BasePandasDataset(QueryCompilerCaster, ClassLogger):
                 return default_handler
         return attr
 
-    @disable_logging
-    def __getattr__(self, item) -> Any:
-        """
-        Return attribute from this `BasePandasDataset`.
-
-        Parameters
-        ----------
-        item : str
-            Item to get.
-
-        Returns
-        -------
-        Any
-            The attribute from this `BasePandasDataset`.
-        """
-        # NOTE that to get an attribute, python calls __getattribute__() first and
-        # then falls back to __getattr__() if the former raises an AttributeError.
-        if item not in _EXTENSION_NO_LOOKUP:
-            extension = self._getattr__from_extension_impl(item, __class__._extensions)
-            if extension is not sentinel:
-                return extension
-        return object.__getattribute__(self, item)
-
     def __array_ufunc__(
         self, ufunc: np.ufunc, method: str, *inputs: Any, **kwargs: Any
     ) -> DataFrame | Series | Any:
@@ -4664,36 +4640,6 @@ class BasePandasDataset(QueryCompilerCaster, ClassLogger):
             )
         return sentinel
 
-    @disable_logging
-    def _getattr__from_extension_impl(self, item, extensions: EXTENSION_DICT_TYPE):
-        """
-        __getattr__() an extension with the given name from the given set of extensions.
-
-        Implement __getattr__() for extensions. python falls back to
-        __getattr__() if __getattribute__() raises an AttributeError.
-
-        Parameters
-        ----------
-        item : str
-            The name of the attribute to get.
-        extensions : EXTENSION_DICT_TYPE
-            The set of extensions.
-
-        Returns
-        -------
-        Any
-            The attribute from the extension, or `sentinel` if the attribute is
-            not found.
-        """
-        extension = self._get_extension(item, extensions)
-        if extension is not sentinel:
-            # We need to implement callable extensions before we fall back
-            # to __getattr__(), because they need to dispatch to the
-            # appropriate backend.
-            assert not callable(extension)
-            return extension
-        return sentinel
-
     @disable_logging
     @_inherit_docstrings(QueryCompilerCaster._get_query_compiler)
     def _get_query_compiler(self):
diff --git a/modin/pandas/dataframe.py b/modin/pandas/dataframe.py
index e9e39fe1..755ec266 100644
--- a/modin/pandas/dataframe.py
+++ b/modin/pandas/dataframe.py
@@ -2644,11 +2644,6 @@ class DataFrame(BasePandasDataset):
         """
         # NOTE that to get an attribute, python calls __getattribute__() first and
         # then falls back to __getattr__() if the former raises an AttributeError.
-
-        if key not in _EXTENSION_NO_LOOKUP:
-            extension = self._getattr__from_extension_impl(key, __class__._extensions)
-            if extension is not sentinel:
-                return extension
         try:
             return super().__getattr__(key)
         except AttributeError as err:
diff --git a/modin/pandas/series.py b/modin/pandas/series.py
index 139f3f08..c5d08df9 100644
--- a/modin/pandas/series.py
+++ b/modin/pandas/series.py
@@ -387,10 +387,6 @@ class Series(BasePandasDataset):
         """
         # NOTE that to get an attribute, python calls __getattribute__() first and
         # then falls back to __getattr__() if the former raises an AttributeError.
-        if key not in _EXTENSION_NO_LOOKUP:
-            extension = self._getattr__from_extension_impl(key, __class__._extensions)
-            if extension is not sentinel:
-                return extension
         try:
             return super().__getattr__(key)
         except AttributeError as err:


Test patch:
diff --git a/modin/tests/pandas/extensions/test_base_extensions.py b/modin/tests/pandas/extensions/test_base_extensions.py
index e45438c8..50ec0c1f 100644
--- a/modin/tests/pandas/extensions/test_base_extensions.py
+++ b/modin/tests/pandas/extensions/test_base_extensions.py
@@ -217,13 +217,23 @@ class TestProperty:
         )
 
         modin_object = data_class({"a": [1, 2, 3], "b": [4, 5, 6]}).set_backend(backend)
-        assert hasattr(modin_object, public_property_name)
         setattr(modin_object, public_property_name, "value")
         assert getattr(modin_object, public_property_name) == "value"
         delattr(modin_object, public_property_name)
         # check that the deletion works.
         assert not hasattr(modin_object, private_property_name)
 
+    def test_get_property_that_raises_attribute_error_on_get_modin_issue_7562(
+        self, data_class
+    ):
+        def get_property(self):
+            raise AttributeError
+
+        register_base_accessor(name="extension_property")(property(fget=get_property))
+        modin_object = data_class()
+        with pytest.raises(AttributeError):
+            getattr(modin_object, "extension_property")
+
     def test_non_settable_extension_property(self, Backend1, data_class):
         modin_object = data_class([0])
         property_name = "property_name"