Skip to content

Commit 678b2dc

Browse files
feat: Warning for duplicated packages
- Added tests - created doc .rst file Co-authored-by: Afonso Antunes <[email protected]>
1 parent fcfa155 commit 678b2dc

File tree

3 files changed

+193
-0
lines changed

3 files changed

+193
-0
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
TODO

pandas/core/accessor.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,10 +407,23 @@ class DataFrameAccessorLoader:
407407
def load(cls) -> None:
408408
"""loads and registers accessors defined by 'pandas_dataframe_accessor'."""
409409
eps = entry_points(group=cls.ENTRY_POINT_GROUP)
410+
names: set[str] = set()
410411

411412
for ep in eps:
412413
name: str = ep.name
413414

415+
if name in names: # Verifies duplicated package names
416+
warnings.warn(
417+
f"Warning: you have two packages with the same name: '{name}'. "
418+
"Uninstall the package you don't want to use "
419+
"in order to remove this warning.\n",
420+
UserWarning,
421+
stacklevel=2,
422+
)
423+
424+
else:
425+
names.add(name)
426+
414427
def make_property(ep):
415428
def accessor(self) -> Any:
416429
cls_ = ep.load()

pandas/tests/test_plugis_entrypoint_loader.py

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import pandas as pd
2+
from pandas._testing._warnings import assert_produces_warning
23
from pandas.core.accessor import DataFrameAccessorLoader
34

45

@@ -33,3 +34,181 @@ def mock_entry_points(*, group):
3334
df = pd.DataFrame({"a": [1, 2, 3]})
3435
assert hasattr(df, "test_accessor")
3536
assert df.test_accessor.test_method() == "success"
37+
38+
39+
def test_duplicate_accessor_names(monkeypatch):
40+
# GH29076
41+
# Create plugin
42+
class MockEntryPoint1:
43+
name = "duplicate_accessor"
44+
45+
def load(self):
46+
class Accessor1:
47+
def __init__(self, df):
48+
self._df = df
49+
50+
def which(self):
51+
return "Accessor1"
52+
53+
return Accessor1
54+
55+
# Create plugin
56+
class MockEntryPoint2:
57+
name = "duplicate_accessor"
58+
59+
def load(self):
60+
class Accessor2:
61+
def __init__(self, df):
62+
self._df = df
63+
64+
def which(self):
65+
return "Accessor2"
66+
67+
return Accessor2
68+
69+
def mock_entry_points(*, group):
70+
if group == DataFrameAccessorLoader.ENTRY_POINT_GROUP:
71+
return [MockEntryPoint1(), MockEntryPoint2()]
72+
return []
73+
74+
monkeypatch.setattr("pandas.core.accessor.entry_points", mock_entry_points)
75+
76+
# Check that the UserWarning is raised
77+
with assert_produces_warning(UserWarning, match="duplicate_accessor") as record:
78+
DataFrameAccessorLoader.load()
79+
80+
messages = [str(w.message) for w in record]
81+
assert any("two packages with the same name" in msg for msg in messages)
82+
83+
df = pd.DataFrame({"x": [1, 2, 3]})
84+
assert hasattr(df, "duplicate_accessor")
85+
assert df.duplicate_accessor.which() in {"Accessor1", "Accessor2"}
86+
87+
88+
def test_unique_accessor_names(monkeypatch):
89+
# GH29076
90+
# Create plugin
91+
class MockEntryPoint1:
92+
name = "accessor1"
93+
94+
def load(self):
95+
class Accessor1:
96+
def __init__(self, df):
97+
self._df = df
98+
99+
def which(self):
100+
return "Accessor1"
101+
102+
return Accessor1
103+
104+
# Create plugin
105+
class MockEntryPoint2:
106+
name = "accessor2"
107+
108+
def load(self):
109+
class Accessor2:
110+
def __init__(self, df):
111+
self._df = df
112+
113+
def which(self):
114+
return "Accessor2"
115+
116+
return Accessor2
117+
118+
def mock_entry_points(*, group):
119+
if group == DataFrameAccessorLoader.ENTRY_POINT_GROUP:
120+
return [MockEntryPoint1(), MockEntryPoint2()]
121+
return []
122+
123+
monkeypatch.setattr("pandas.core.accessor.entry_points", mock_entry_points)
124+
125+
# Check that no UserWarning is raised
126+
with assert_produces_warning(None, check_stacklevel=False):
127+
DataFrameAccessorLoader.load()
128+
129+
df = pd.DataFrame({"x": [1, 2, 3]})
130+
assert hasattr(df, "accessor1"), "Accessor1 not registered"
131+
assert hasattr(df, "accessor2"), "Accessor2 not registered"
132+
assert df.accessor1.which() == "Accessor1", "Accessor1 method incorrect"
133+
assert df.accessor2.which() == "Accessor2", "Accessor2 method incorrect"
134+
135+
136+
def test_duplicate_and_unique_accessor_names(monkeypatch):
137+
# GH29076
138+
# Create plugin
139+
class MockEntryPoint1:
140+
name = "duplicate_accessor"
141+
142+
def load(self):
143+
class Accessor1:
144+
def __init__(self, df):
145+
self._df = df
146+
147+
def which(self):
148+
return "Accessor1"
149+
150+
return Accessor1
151+
152+
# Create plugin
153+
class MockEntryPoint2:
154+
name = "duplicate_accessor"
155+
156+
def load(self):
157+
class Accessor2:
158+
def __init__(self, df):
159+
self._df = df
160+
161+
def which(self):
162+
return "Accessor2"
163+
164+
return Accessor2
165+
166+
# Create plugin
167+
class MockEntryPoint3:
168+
name = "unique_accessor"
169+
170+
def load(self):
171+
class Accessor3:
172+
def __init__(self, df):
173+
self._df = df
174+
175+
def which(self):
176+
return "Accessor3"
177+
178+
return Accessor3
179+
180+
def mock_entry_points(*, group):
181+
if group == DataFrameAccessorLoader.ENTRY_POINT_GROUP:
182+
return [MockEntryPoint1(), MockEntryPoint2(), MockEntryPoint3()]
183+
return []
184+
185+
monkeypatch.setattr("pandas.core.accessor.entry_points", mock_entry_points)
186+
187+
# Capture warnings
188+
with assert_produces_warning(UserWarning, match="duplicate_accessor") as record:
189+
DataFrameAccessorLoader.load()
190+
191+
messages = [str(w.message) for w in record]
192+
193+
# Filter warnings for the specific message about duplicate packages
194+
duplicate_package_warnings = [
195+
msg
196+
for msg in messages
197+
if "you have two packages with the same name: 'duplicate_accessor'" in msg
198+
]
199+
200+
# Assert one warning about duplicate packages
201+
assert len(duplicate_package_warnings) == 1, (
202+
f"Expected exactly one warning about duplicate packages, "
203+
f"got {len(duplicate_package_warnings)}: {duplicate_package_warnings}"
204+
)
205+
206+
df = pd.DataFrame({"x": [1, 2, 3]})
207+
assert hasattr(df, "duplicate_accessor"), "duplicate_accessor not registered"
208+
209+
assert hasattr(df, "unique_accessor"), "unique_accessor not registered"
210+
211+
assert df.duplicate_accessor.which() in {"Accessor1", "Accessor2"}, (
212+
"duplicate_accessor method incorrect"
213+
)
214+
assert df.unique_accessor.which() == "Accessor3", "unique_accessor method incorrect"

0 commit comments

Comments
 (0)