Skip to content

Commit e1cdb64

Browse files
timsaucerclaude
andcommitted
refactor: keep bundle protocols out of datafusion.context
datafusion.context imported QueryPlannerExportable, SessionComponentsExportable and SessionPlannerExportable at runtime, so they were reachable as datafusion.context.* despite datafusion.extensions being their one home. Move them under TYPE_CHECKING and route the runtime isinstance checks through a private _extensions module alias. The protocols are new in 55.0.0, so no released import path is dropped. Add a test pinning that all four capsule-getter protocols live only in datafusion.extensions, and list PhysicalOptimizerRuleExportable in llms.txt. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 5266516 commit e1cdb64

3 files changed

Lines changed: 38 additions & 15 deletions

File tree

‎docs/source/llms.txt‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
- [`datafusion.expr`](https://datafusion.apache.org/python/autoapi/datafusion/expr/index.html): expression tree nodes (`Expr`, `Window`, `WindowFrame`, `GroupingSet`).
3232
- [`datafusion.functions`](https://datafusion.apache.org/python/autoapi/datafusion/functions/index.html): 290+ scalar, aggregate, and window functions.
3333
- [`datafusion.context.SessionContext`](https://datafusion.apache.org/python/autoapi/datafusion/context/index.html): session entry point, data loading, SQL execution.
34-
- [`datafusion.extensions`](https://datafusion.apache.org/python/autoapi/datafusion/extensions/index.html): `SessionExtensionComponents`, `SessionComponentsExportable`, `SessionPlannerExportable`, `QueryPlannerExportable` — the extension-bundle protocol.
34+
- [`datafusion.extensions`](https://datafusion.apache.org/python/autoapi/datafusion/extensions/index.html): `SessionExtensionComponents`, `SessionComponentsExportable`, `SessionPlannerExportable`, `QueryPlannerExportable`, `PhysicalOptimizerRuleExportable` — the extension-bundle and capsule-getter protocols.
3535
- [`datafusion.ipc`](https://datafusion.apache.org/python/autoapi/datafusion/ipc/index.html): worker and sender context slots for shipping expressions between processes.
3636

3737
## Examples

‎python/datafusion/context.py‎

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858

5959
import pyarrow as pa
6060

61+
from datafusion import extensions as _extensions
6162
from datafusion.catalog import (
6263
Catalog,
6364
CatalogList,
@@ -69,12 +70,6 @@
6970
)
7071
from datafusion.dataframe import DataFrame
7172
from datafusion.expr import sort_list_to_raw_sort_list
72-
from datafusion.extensions import (
73-
QueryPlannerExportable,
74-
SessionComponentsExportable,
75-
SessionExtensionComponents,
76-
SessionPlannerExportable,
77-
)
7873
from datafusion.options import (
7974
DEFAULT_MAX_INFER_SCHEMA,
8075
CsvReadOptions,
@@ -101,10 +96,16 @@
10196
from datafusion.expr import Expr, SortKey
10297

10398
# Type-only on purpose. `datafusion.extensions` is the one home for the
104-
# capsule-getter protocols; importing this at runtime would restore
105-
# `datafusion.context.PhysicalOptimizerRuleExportable`, the 54.0.0 path
106-
# that 55.0.0 drops.
107-
from datafusion.extensions import PhysicalOptimizerRuleExportable
99+
# capsule-getter protocols; importing these at runtime would make them
100+
# reachable as `datafusion.context.*`, and for
101+
# `PhysicalOptimizerRuleExportable` would restore the 54.0.0 path that
102+
# 55.0.0 drops. Runtime checks go through the private `_extensions` alias.
103+
from datafusion.extensions import (
104+
PhysicalOptimizerRuleExportable,
105+
QueryPlannerExportable,
106+
SessionComponentsExportable,
107+
SessionPlannerExportable,
108+
)
108109
from datafusion.plan import ExecutionPlan, LogicalPlan
109110
from datafusion.user_defined import (
110111
AggregateUDF,
@@ -1975,7 +1976,11 @@ def with_extensions(
19751976
"""
19761977
for extension in extensions:
19771978
if not isinstance(
1978-
extension, (SessionComponentsExportable, SessionPlannerExportable)
1979+
extension,
1980+
(
1981+
_extensions.SessionComponentsExportable,
1982+
_extensions.SessionPlannerExportable,
1983+
),
19791984
):
19801985
msg = (
19811986
"Extension implements neither "
@@ -1991,10 +1996,10 @@ def with_extensions(
19911996
logical_codecs: list[LogicalExtensionCodecExportable] = []
19921997
physical_codecs: list[PhysicalExtensionCodecExportable] = []
19931998
for extension in extensions:
1994-
if not isinstance(extension, SessionComponentsExportable):
1999+
if not isinstance(extension, _extensions.SessionComponentsExportable):
19952000
continue
19962001
components = extension.__datafusion_session_components__(self)
1997-
if not isinstance(components, SessionExtensionComponents):
2002+
if not isinstance(components, _extensions.SessionExtensionComponents):
19982003
msg = (
19992004
"__datafusion_session_components__ must return "
20002005
"SessionExtensionComponents, got "
@@ -2016,7 +2021,7 @@ def with_extensions(
20162021
# rather than wrapping the session's default in an FFI hop.
20172022
planner: _PyCapsule | None = None
20182023
for extension in extensions:
2019-
if not isinstance(extension, SessionPlannerExportable):
2024+
if not isinstance(extension, _extensions.SessionPlannerExportable):
20202025
continue
20212026
fallback = (
20222027
planner

‎python/tests/test_imports.py‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,24 @@ def test_import_from_functions_submodule():
192192
from datafusion.functions import foobar # noqa: F401
193193

194194

195+
@pytest.mark.parametrize(
196+
"name",
197+
[
198+
"PhysicalOptimizerRuleExportable",
199+
"QueryPlannerExportable",
200+
"SessionComponentsExportable",
201+
"SessionPlannerExportable",
202+
],
203+
)
204+
def test_extension_protocols_live_only_in_extensions(name):
205+
import datafusion.context
206+
import datafusion.extensions
207+
208+
assert getattr(datafusion.extensions, name).__module__ == "datafusion.extensions"
209+
assert not hasattr(datafusion.context, name)
210+
assert not hasattr(datafusion, name)
211+
212+
195213
def test_classes_are_inheritable():
196214
class MyExecContext(SessionContext):
197215
pass

0 commit comments

Comments
 (0)