Skip to content

Commit e0139ef

Browse files
ENH: Support plugin DataFrame accessor via entry points (pandas-dev#29076)
Allows external libraries to register DataFrame accessors using the 'pandas_dataframe_accessor' entry point group. This enables plugins to be automatically used without explicit import. Co-authored-by: Afonso Antunes <[email protected]>
1 parent f496acf commit e0139ef

File tree

3 files changed

+60
-0
lines changed

3 files changed

+60
-0
lines changed

pandas/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,3 +346,7 @@
346346
"unique",
347347
"wide_to_long",
348348
]
349+
350+
from pandas.core.accessor import DataFrameAccessorLoader
351+
352+
DataFrameAccessorLoader.load()

pandas/core/accessor.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,14 @@
2626
from pandas.core.generic import NDFrame
2727

2828

29+
try:
30+
# Python >= 3.10
31+
from importlib.metadata import entry_points
32+
except ImportError:
33+
# fallback to early versions
34+
from importlib_metadata import entry_points
35+
36+
2937
class DirNamesMixin:
3038
_accessors: set[str] = set()
3139
_hidden_attrs: frozenset[str] = frozenset()
@@ -393,3 +401,26 @@ def register_index_accessor(name: str) -> Callable[[TypeT], TypeT]:
393401
from pandas import Index
394402

395403
return _register_accessor(name, Index)
404+
405+
406+
class DataFrameAccessorLoader:
407+
"""Loader class for registering DataFrame accessors via entry points."""
408+
409+
ENTRY_POINT_GROUP = "pandas_dataframe_accessor"
410+
411+
@classmethod
412+
def load(cls):
413+
"""loads and registers accessors defined by 'pandas_dataframe_accessor'."""
414+
eps = entry_points(group=cls.ENTRY_POINT_GROUP)
415+
416+
for ep in eps:
417+
name = ep.name
418+
419+
def make_property(ep):
420+
def accessor(self):
421+
cls_ = ep.load()
422+
return cls_(self)
423+
424+
return accessor
425+
426+
register_dataframe_accessor(name)(make_property(ep))
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import pandas as pd
2+
from unittest.mock import patch
3+
from pandas.core.accessor import DataFrameAccessorLoader
4+
5+
def test_load_dataframe_accessors():
6+
# GH29076
7+
# Mocked EntryPoint to simulate a plugin
8+
class MockEntryPoint:
9+
name = "test_accessor"
10+
def load(self):
11+
class TestAccessor:
12+
def __init__(self, df):
13+
self._df = df
14+
def test_method(self):
15+
return "success"
16+
return TestAccessor
17+
18+
# Patch the entry_points function to return the mocked plugin
19+
with patch("pandas.core.accessor.entry_points", return_value=[MockEntryPoint()]):
20+
DataFrameAccessorLoader.load()
21+
22+
# Create DataFrame and verify that the accessor was registered
23+
df = pd.DataFrame({"a": [1, 2, 3]})
24+
assert hasattr(df, "test_accessor")
25+
assert df.test_accessor.test_method() == "success"

0 commit comments

Comments
 (0)