Repository navigation
Conversation
Add ty (pinned to 0.0.84, since it is pre-1.0) to the dev dependency group, configure it in pyproject.toml to check python/datafusion, and run it in the lint-python CI job and as a local pre-commit hook. datafusion._internal ships no stubs, and pandas/polars are optional TYPE_CHECKING-only imports, so they are allowed to stay unresolved. Fix the diagnostics ty reported: - Make the internal udtf decorator helper require `name`, matching the public overloads. `@udtf()` previously passed None into Rust and failed with "'None' is not an instance of 'str'". - Give AggregateUDF.__init__ defaults matching its FFI overload, and add the same FFI overload to ScalarUDF and WindowUDF. - Add None/non-None overloads to expr_list_to_raw_expr_list and sort_list_to_raw_sort_list so callers that unpack the result type check. - Stop rebinding typed *args and parameters to values of other types. - Import warnings.deprecated behind a sys.version_info check and drop the unreachable importlib_metadata fallback. - Fix smaller annotation mismatches (LogicalPlan.__eq__, spark._coerce_i32, CSV file_compression_type). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
| left_on = on | ||
| right_on = on | ||
| # The legacy ``(left, right)`` tuple form was consumed above. | ||
| left_on = right_on = cast("str | Sequence[str]", on) |
There was a problem hiding this comment.
This is a no-op at runtime but will make it so type checkers recognize that we've already verified the type.
| @overload | ||
| def expr_list_to_raw_expr_list(expr_list: None) -> None: ... | ||
|
|
||
|
|
||
| @overload | ||
| def expr_list_to_raw_expr_list( | ||
| expr_list: Sequence[Expr | str] | Expr | str, | ||
| ) -> list[expr_internal.Expr]: ... |
There was a problem hiding this comment.
These overloads make it so type checkers understand that if a None if fed in you get a None out, otherwise you get a valid list of Expr
There was a problem hiding this comment.
Warning
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
Copilot review overview
6 open findings
Makingnamerequired here is an API-breaking change for decorator usage patterns that previously… · New This invocation likely bypasses the pinnedty==0.0.84declared inpyproject.toml(because… · New ReplacingNonewith an empty string changes behavior and can introduce hard-to-debug failures if… · New ReplacingNonewith an empty string changes behavior and can introduce hard-to-debug failures if… · New This change removes support forNone(previously accepted and passed through). If any Spark… · New This change removes support forNone(previously accepted and passed through). If any Spark… · New
What changed in this PR
Adds Astral’s ty type checker to improve static type safety for the published python/datafusion package, and updates Python APIs/CI tooling to satisfy the newly enforced checks.
Changes:
- Add
tyconfiguration + a pinnedty==0.0.84dev dependency, and runty checkin CI and pre-commit. - Tighten/clarify Python API typing via overloads and minor refactors (e.g., expression/sort list helpers).
- Fix a few runtime/type issues discovered by
ty(e.g., UDF/UDWF constructor validation, join typing).
| File | Description |
|---|---|
| python/datafusion/user_defined.py | Adds overloads + runtime validation for UDF/UDWF construction; refines decorator typings; adjusts table UDF decorator signature. |
| python/datafusion/plan.py | Fixes __eq__ signature to accept object (typing-correct equality). |
| python/datafusion/functions/spark.py | Changes _coerce_i32 to disallow None and always return Expr. |
| python/datafusion/functions/__init__.py | Renames intermediate variables (raw_args) for clearer typing and intent. |
| python/datafusion/expr.py | Adds overloads for list conversion helpers; adjusts deprecated import logic; adds ty ignores where intended. |
| python/datafusion/dataframe.py | Improves join typing with cast; refactors repartition expr conversion; minor cleanup. |
| python/datafusion/context.py | Normalizes file_compression_type to a string for internal calls; updates deprecated import logic. |
| python/datafusion/catalog.py | Updates deprecated import logic for Python 3.13+ compatibility. |
| python/datafusion/__init__.py | Simplifies metadata import now that stdlib importlib.metadata is assumed available. |
| pyproject.toml | Adds ty config + allowed-unresolved-imports; pins ty==0.0.84 in dev. |
| .pre-commit-config.yaml | Adds a local ty pre-commit hook. |
| .github/workflows/build.yml | Runs ty check in the lint-python job. |
🧠 Review effort: Lite
Give feedback about Copilot approvals in this survey to enter a drawing for a $150 gift card.
| *, | ||
| with_session: bool = False, | ||
| ) -> Callable[[Callable[..., Any]], TableFunction]: |
|
|
||
| - name: Run ty | ||
| run: | | ||
| uv run --no-project ty check --output-format github |
| schema_infer_max_records=schema_infer_max_records, | ||
| file_extension=file_extension, | ||
| file_compression_type=file_compression_type, | ||
| file_compression_type=file_compression_type or "", |
| file_extension=file_extension, | ||
| table_partition_cols=table_partition_cols, | ||
| file_compression_type=file_compression_type, | ||
| file_compression_type=file_compression_type or "", |
| def _coerce_i32(value: Expr | int) -> Expr: | ||
| """Coerce a native ``int`` to an int32 literal, passing ``Expr`` through. | ||
|
|
||
| Several Spark datetime and interval builders require 32-bit integer | ||
| inputs, so a bare ``int`` must become an int32 literal rather than the | ||
| int64 default that :meth:`Expr.literal` would produce. | ||
| """ |
| def _coerce_i32(value: Expr | int) -> Expr: | ||
| """Coerce a native ``int`` to an int32 literal, passing ``Expr`` through. | ||
|
|
||
| Several Spark datetime and interval builders require 32-bit integer | ||
| inputs, so a bare ``int`` must become an int32 literal rather than the | ||
| int64 default that :meth:`Expr.literal` would produce. | ||
| """ |


Which issue does this PR close?
No issue filed yet; happy to open one if preferred.
Rationale for this change
This PR improves our type checking on all public facing Python APIs by running the ty type checker, both in CI and as part of
pre-commit. Ruff'sANNrules only require annotations to exist. Runningtyagainstpython/datafusionreported 82 diagnostics onmain; after configuring imports, about 50 were real annotation drift, and one was a runtime bug (@udtf()).ty is from Astral, like the ruff and uv tooling already used here, and is fast enough to run as a pre-commit hook.
What changes are included in this PR?
Tooling:
ty==0.0.84to thedevdependency group.ty checkin thelint-pythonCI job and as a local pre-commit hook. Demonstration of it working in CI: https://github.com/apache/datafusion-python/actions/runs/37644274243/job/112870854503?pr=1786Additionally we made fixes for the reported findings.
Are there any user-facing changes?
No API changes. Contributors need
tyin their environment (uv sync --dev) for the new pre-commit hook.