Skip to content

Commit 6da87f6

Browse files
timsaucerclaude
andcommitted
test: pin which check decides a planner hook is a hook
9baf121 fixed a bundle setting __datafusion_session_planner__ to None -- a base class spelling out that its subclasses contribute no planner -- being admitted as a components bundle and then having its None called: "'NoneType' object is not callable", naming neither the extension nor the hook. isinstance against SessionPlannerExportable rejects an attribute bound to None; hasattr does not. Nothing pinned it. The fix narrows the list in Python, and _commit_extensions still carries a Rust-side hasattr as a backstop, so two checks exist and only one decides. Handing _commit_extensions the unfiltered arguments again brings the failure straight back, and every other test in the file passes while it does. Verified by reverting the filter: this test is the one that fails. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 97e6315 commit 6da87f6

1 file changed

Lines changed: 26 additions & 0 deletions

File tree

python/tests/test_context.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1191,6 +1191,32 @@ def __datafusion_session_planner__(self, ctx, fallback):
11911191
assert batches[0].column(0) == pa.array([1])
11921192

11931193

1194+
def test_with_extensions_ignores_a_planner_attribute_set_to_none(ctx):
1195+
"""Regression test: ``__datafusion_session_planner__ = None`` is not a hook.
1196+
1197+
A base class spelling out that its subclasses contribute no planner binds
1198+
the attribute to ``None`` rather than omitting it. ``isinstance`` against
1199+
:py:class:`~datafusion.extensions.SessionPlannerExportable` rejects that,
1200+
but a ``hasattr`` does not, so a host that validated with one and dispatched
1201+
with the other called the ``None``: ``'NoneType' object is not callable``,
1202+
naming neither the extension nor the hook.
1203+
1204+
``_commit_extensions`` still carries a Rust-side ``hasattr``, and the
1205+
Python caller now hands it a list already narrowed by ``isinstance``. Two
1206+
checks means one of them decides, and this pins which: a refactor that
1207+
passes the unfiltered arguments through reintroduces the failure, and
1208+
nothing else in this file notices.
1209+
"""
1210+
1211+
class NoPlannerExtension(_CodecOnlyExtension):
1212+
__datafusion_session_planner__ = None
1213+
1214+
result = ctx.with_extensions(NoPlannerExtension())
1215+
1216+
assert result.logical_extension_codec_ids() == ["my_library.logical"]
1217+
assert result.physical_extension_codec_ids() == ["my_library.physical"]
1218+
1219+
11941220
def test_with_extensions_rejects_bad_codec_capsule(ctx):
11951221
"""A correctly shaped object still has to return the right capsule."""
11961222

0 commit comments

Comments
 (0)