You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@datafusion.apache.org by ti...@apache.org on 2026/10/08 17:44:33 UTC
(datafusion-python) branch main updated: ci: add ty type checker and fix existing type errors (#1786)
This is an automated email from the ASF dual-hosted git repository.
timsaucer pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/datafusion-python.git
The following commit(s) were added to refs/heads/main by this push:
new 5febeb36 ci: add ty type checker and fix existing type errors (#1786)
5febeb36 is described below
commit 5febeb36e2cac4ad15aab51fe1671957a3ff7dbe
Author: Tim Saucer <ti...@gmail.com>
AuthorDate: Thu Oct 8 13:44:29 2026 -0400
ci: add ty type checker and fix existing type errors (#1786)
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) <no...@anthropic.com>
---
.github/workflows/build.yml | 4 ++
.pre-commit-config.yaml | 7 +++
pyproject.toml | 12 +++++
python/datafusion/__init__.py | 6 +--
python/datafusion/catalog.py | 9 ++--
python/datafusion/context.py | 14 +++---
python/datafusion/dataframe.py | 24 +++++-----
python/datafusion/expr.py | 39 +++++++++++++----
python/datafusion/functions/__init__.py | 24 +++++-----
python/datafusion/functions/spark.py | 6 +--
python/datafusion/plan.py | 2 +-
python/datafusion/user_defined.py | 77 ++++++++++++++++++++++++++-------
uv.lock | 27 ++++++++++++
13 files changed, 184 insertions(+), 67 deletions(-)
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index e3733344..7b926a9e 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -81,6 +81,10 @@ jobs:
uv run --no-project ruff check --output-format=github python/
uv run --no-project ruff format --check python/
+ - name: Run ty
+ run: |
+ uv run --no-project ty check --output-format github
+
- name: Run codespell
run: |
uv run --no-project codespell --toml pyproject.toml
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 0a212480..9419fc3c 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -30,6 +30,13 @@ repos:
- id: ruff-format
- repo: local
hooks:
+ - id: ty
+ name: ty
+ description: Type check python/datafusion with ty.
+ entry: uv run --no-project ty check
+ pass_filenames: false
+ types: [file, python]
+ language: system
- id: rust-fmt
name: Rust fmt
description: Run cargo fmt on files included in the commit. rustfmt should be installed before-hand.
diff --git a/pyproject.toml b/pyproject.toml
index 604d3f84..b115b119 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -181,6 +181,16 @@ extend-allowed-calls = ["datafusion.lit", "lit"]
# CI and pre-commit invoke codespell with different paths, so we have a little
# redundancy here, and we intentionally drop python in the path.
+[tool.ty.src]
+# Only the published package is type checked for now. Tests and examples
+# can be added once the package itself is clean.
+include = ["python/datafusion"]
+
+[tool.ty.analysis]
+# `_internal` is the compiled PyO3 extension and ships no `.pyi` stubs.
+# pandas and polars are optional and only imported under TYPE_CHECKING.
+allowed-unresolved-imports = ["datafusion._internal", "pandas", "polars"]
+
[tool.codespell]
skip = [
"*/tests/test_functions.py",
@@ -219,6 +229,8 @@ dev = [
"pyyaml>=6.0.3",
"ruff>=0.15.1",
"toml>=0.10.2",
+ # Pinned exactly: ty is pre-1.0 and new releases can add diagnostics.
+ "ty==0.0.84",
]
# Release tooling only. Kept out of `dev` because pygithub pulls in
# cryptography, which ships no free-threaded wheel and fails to build
diff --git a/python/datafusion/__init__.py b/python/datafusion/__init__.py
index 76f64779..087b2767 100644
--- a/python/datafusion/__init__.py
+++ b/python/datafusion/__init__.py
@@ -57,13 +57,9 @@ https://github.com/apache/datafusion-python/blob/main/skills/datafusion_python/S
from __future__ import annotations
+import importlib.metadata as importlib_metadata
from typing import Any
-try:
- import importlib.metadata as importlib_metadata
-except ImportError:
- import importlib_metadata # type: ignore[import]
-
# Public submodules
from . import functions, ipc, object_store, substrait, unparser
diff --git a/python/datafusion/catalog.py b/python/datafusion/catalog.py
index d1d50117..9161c62f 100644
--- a/python/datafusion/catalog.py
+++ b/python/datafusion/catalog.py
@@ -19,6 +19,7 @@
from __future__ import annotations
+import sys
from abc import ABC, abstractmethod
from typing import TYPE_CHECKING, Any, Protocol
@@ -31,10 +32,10 @@ if TYPE_CHECKING:
from datafusion.context import TableProviderExportable
from datafusion.expr import CreateExternalTable
-try:
- from warnings import deprecated # Python 3.13+
-except ImportError:
- from typing_extensions import deprecated # Python 3.12
+if sys.version_info >= (3, 13):
+ from warnings import deprecated
+else:
+ from typing_extensions import deprecated
__all__ = [
diff --git a/python/datafusion/context.py b/python/datafusion/context.py
index c663dea2..08dbc440 100644
--- a/python/datafusion/context.py
+++ b/python/datafusion/context.py
@@ -44,14 +44,15 @@ execution model.
from __future__ import annotations
+import sys
import uuid
import warnings
from typing import TYPE_CHECKING, Any, Protocol
-try:
- from warnings import deprecated # Python 3.13+
-except ImportError:
- from typing_extensions import deprecated # Python 3.12
+if sys.version_info >= (3, 13):
+ from warnings import deprecated
+else:
+ from typing_extensions import deprecated
from urllib.parse import urlparse
@@ -85,7 +86,6 @@ from ._internal import expr as expr_internal
if TYPE_CHECKING:
import pathlib
- import sys
from collections.abc import Iterable, Sequence
import pandas as pd
@@ -1292,7 +1292,7 @@ class SessionContext:
delimiter=delimiter,
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 "",
)
)
@@ -2196,7 +2196,7 @@ class SessionContext:
schema_infer_max_records=schema_infer_max_records,
file_extension=file_extension,
table_partition_cols=table_partition_cols,
- file_compression_type=file_compression_type,
+ file_compression_type=file_compression_type or "",
)
)
diff --git a/python/datafusion/dataframe.py b/python/datafusion/dataframe.py
index 8a867a8c..12ab0a3c 100644
--- a/python/datafusion/dataframe.py
+++ b/python/datafusion/dataframe.py
@@ -44,19 +44,21 @@ overview of the execution model.
from __future__ import annotations
+import sys
import warnings
from collections.abc import AsyncIterator, Iterable, Iterator, Sequence
from typing import (
TYPE_CHECKING,
Any,
Literal,
+ cast,
overload,
)
-try:
- from warnings import deprecated # Python 3.13+
-except ImportError:
- from typing_extensions import deprecated # Python 3.12
+if sys.version_info >= (3, 13):
+ from warnings import deprecated
+else:
+ from typing_extensions import deprecated
from datafusion._internal import DataFrame as DataFrameInternal
from datafusion._internal import DataFrameWriteOptions as DataFrameWriteOptionsInternal
@@ -1158,8 +1160,8 @@ class DataFrame:
if left_on is not None or right_on is not None:
error_msg = "`left_on` or `right_on` should not provided with `on`"
raise ValueError(error_msg)
- left_on = on
- right_on = on
+ # The legacy ``(left, right)`` tuple form was consumed above.
+ left_on = right_on = cast("str | Sequence[str]", on)
elif left_on is not None or right_on is not None:
if left_on is None or right_on is None:
error_msg = "`left_on` and `right_on` should both be provided."
@@ -1344,10 +1346,11 @@ class DataFrame:
Returns:
Repartitioned DataFrame.
"""
- exprs = [self.parse_sql_expr(e) if isinstance(e, str) else e for e in exprs]
- exprs = expr_list_to_raw_expr_list(exprs)
+ raw_exprs = expr_list_to_raw_expr_list(
+ [self.parse_sql_expr(e) if isinstance(e, str) else e for e in exprs]
+ )
- return DataFrame(self.df.repartition_by_hash(*exprs, num=num))
+ return DataFrame(self.df.repartition_by_hash(*raw_exprs, num=num))
def union(self, other: DataFrame, distinct: bool = False) -> DataFrame:
"""Calculate the union of two :py:class:`DataFrame`.
@@ -1833,10 +1836,9 @@ class DataFrame:
>>> df.unnest_columns("a", recursions=[("a", "a", 1)]).to_pydict()
{'a': [1, 2, 3], 'b': ['x', 'x', 'y']}
"""
- columns = list(columns)
return DataFrame(
self.df.unnest_columns(
- columns, preserve_nulls=preserve_nulls, recursions=recursions
+ list(columns), preserve_nulls=preserve_nulls, recursions=recursions
)
)
diff --git a/python/datafusion/expr.py b/python/datafusion/expr.py
index bfa124ff..d6cf85bc 100644
--- a/python/datafusion/expr.py
+++ b/python/datafusion/expr.py
@@ -46,13 +46,14 @@ operators and helpers.
from __future__ import annotations
+import sys
from collections.abc import Callable, Iterable, Sequence
-from typing import TYPE_CHECKING, Any, ClassVar
+from typing import TYPE_CHECKING, Any, ClassVar, overload
-try:
- from warnings import deprecated # Python 3.13+
-except ImportError:
- from typing_extensions import deprecated # Python 3.12
+if sys.version_info >= (3, 13):
+ from warnings import deprecated
+else:
+ from typing_extensions import deprecated
import pyarrow as pa
@@ -114,7 +115,7 @@ def _create_external_table_location(self: Any) -> str:
return locations[0] if locations else ""
-CreateExternalTable.location = _create_external_table_location
+CreateExternalTable.location = _create_external_table_location # ty: ignore[deprecated]
CreateFunction = expr_internal.CreateFunction
CreateFunctionBody = expr_internal.CreateFunctionBody
@@ -410,8 +411,18 @@ def _to_raw_expr(value: Expr | str) -> expr_internal.Expr:
raise TypeError(error)
+@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]: ...
+
+
def expr_list_to_raw_expr_list(
- expr_list: list[Expr] | Expr | None,
+ expr_list: Sequence[Expr | str] | Expr | str | None,
) -> list[expr_internal.Expr] | None:
"""Convert a sequence of expressions or column names to raw expressions."""
if isinstance(expr_list, Expr | str):
@@ -428,6 +439,16 @@ def sort_or_default(e: Expr | SortExpr) -> expr_internal.SortExpr:
return SortExpr(e, ascending=True, nulls_first=False).raw_sort
+@overload
+def sort_list_to_raw_sort_list(sort_list: None) -> None: ...
+
+
+@overload
+def sort_list_to_raw_sort_list(
+ sort_list: Sequence[SortKey] | SortKey,
+) -> list[expr_internal.SortExpr]: ...
+
+
def sort_list_to_raw_sort_list(
sort_list: Sequence[SortKey] | SortKey | None,
) -> list[expr_internal.SortExpr] | None:
@@ -771,7 +792,7 @@ class Expr: # noqa: PLW1641
return Expr(functions_internal.array_slice(self.expr, start, stop, step))
return Expr(self.expr.__getitem__(key))
- def __eq__(self, rhs: object) -> Expr:
+ def __eq__(self, rhs: object) -> Expr: # ty: ignore[invalid-method-override]
"""Equal to.
Accepts either an expression or any valid PyArrow scalar literal value.
@@ -782,7 +803,7 @@ class Expr: # noqa: PLW1641
rhs = Expr.literal(rhs)
return Expr(self.expr.__eq__(rhs.expr))
- def __ne__(self, rhs: object) -> Expr:
+ def __ne__(self, rhs: object) -> Expr: # ty: ignore[invalid-method-override]
"""Not equal to.
Accepts either an expression or any valid PyArrow scalar literal value.
diff --git a/python/datafusion/functions/__init__.py b/python/datafusion/functions/__init__.py
index d5e146bd..c7d99f78 100644
--- a/python/datafusion/functions/__init__.py
+++ b/python/datafusion/functions/__init__.py
@@ -871,8 +871,8 @@ def concat(*args: Expr) -> Expr:
>>> result.collect_column("c")[0].as_py()
'hello world'
"""
- args = [arg.expr for arg in args]
- return Expr(f.concat(args))
+ raw_args = [arg.expr for arg in args]
+ return Expr(f.concat(raw_args))
def concat_ws(separator: str, *args: Expr) -> Expr:
@@ -888,8 +888,8 @@ def concat_ws(separator: str, *args: Expr) -> Expr:
>>> result.collect_column("c")[0].as_py()
'hello-world'
"""
- args = [arg.expr for arg in args]
- return Expr(f.concat_ws(separator, args))
+ raw_args = [arg.expr for arg in args]
+ return Expr(f.concat_ws(separator, raw_args))
def order_by(expr: Expr, ascending: bool = True, nulls_first: bool = False) -> SortExpr:
@@ -1272,8 +1272,8 @@ def coalesce(*args: Expr) -> Expr:
>>> result.collect_column("c")[0].as_py()
2
"""
- args = [arg.expr for arg in args]
- return Expr(f.coalesce(*args))
+ raw_args = [arg.expr for arg in args]
+ return Expr(f.coalesce(*raw_args))
def cos(arg: Expr) -> Expr:
@@ -3105,8 +3105,8 @@ def make_array(*args: Expr) -> Expr:
>>> result.collect_column("arr")[0].as_py()
[1, 2, 3]
"""
- args = [arg.expr for arg in args]
- return Expr(f.make_array(args))
+ raw_args = [arg.expr for arg in args]
+ return Expr(f.make_array(raw_args))
def make_list(*args: Expr) -> Expr:
@@ -3240,8 +3240,8 @@ def struct(*args: Expr) -> Expr:
>>> result.collect_column("s")[0].as_py() == {"c0": 1, "c1": 2}
True
"""
- args = [arg.expr for arg in args]
- return Expr(f.struct(*args))
+ raw_args = [arg.expr for arg in args]
+ return Expr(f.struct(*raw_args))
def named_struct(name_pairs: list[tuple[str, Expr]]) -> Expr:
@@ -3762,8 +3762,8 @@ def array_concat(*args: Expr) -> Expr:
>>> result.collect_column("result")[0].as_py()
[1, 2, 3, 4]
"""
- args = [arg.expr for arg in args]
- return Expr(f.array_concat(args))
+ raw_args = [arg.expr for arg in args]
+ return Expr(f.array_concat(raw_args))
def array_cat(*args: Expr) -> Expr:
diff --git a/python/datafusion/functions/spark.py b/python/datafusion/functions/spark.py
index c4002670..74315fb4 100644
--- a/python/datafusion/functions/spark.py
+++ b/python/datafusion/functions/spark.py
@@ -56,14 +56,14 @@ def _filter_raw(filter: Expr | None) -> Any:
return filter.expr if filter is not None else None
-def _coerce_i32(value: Expr | int | None) -> Expr | None:
- """Coerce a native ``int`` to an int32 literal, passing ``Expr``/``None`` through.
+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.
"""
- if value is None or isinstance(value, Expr):
+ if isinstance(value, Expr):
return value
return Expr.literal(pa.scalar(value, type=pa.int32()))
diff --git a/python/datafusion/plan.py b/python/datafusion/plan.py
index 8b61c097..ffad3862 100644
--- a/python/datafusion/plan.py
+++ b/python/datafusion/plan.py
@@ -143,7 +143,7 @@ class LogicalPlan: # noqa: PLW1641
)
return self.to_bytes()
- def __eq__(self, other: LogicalPlan) -> bool:
+ def __eq__(self, other: object) -> bool:
"""Test equality."""
if not isinstance(other, LogicalPlan):
return False
diff --git a/python/datafusion/user_defined.py b/python/datafusion/user_defined.py
index 1d8ccbe3..c2624f61 100644
--- a/python/datafusion/user_defined.py
+++ b/python/datafusion/user_defined.py
@@ -197,6 +197,7 @@ class ScalarUDF:
operating on a group of rows.
"""
+ @overload
def __init__(
self,
name: str,
@@ -204,6 +205,25 @@ class ScalarUDF:
input_fields: list[pa.Field],
return_field: pa.Field,
volatility: Volatility | str,
+ ) -> None: ...
+
+ @overload
+ def __init__(
+ self,
+ name: str,
+ func: ScalarUDFExportable,
+ input_fields: None = ...,
+ return_field: None = ...,
+ volatility: None = ...,
+ ) -> None: ...
+
+ def __init__(
+ self,
+ name: str,
+ func: Callable[..., _R] | ScalarUDFExportable,
+ input_fields: list[pa.Field] | None = None,
+ return_field: pa.Field | None = None,
+ volatility: Volatility | str | None = None,
) -> None:
"""Instantiate a scalar user-defined function (UDF).
@@ -212,6 +232,12 @@ class ScalarUDF:
if hasattr(func, "__datafusion_scalar_udf__"):
self._udf = df_internal.ScalarUDF.from_pycapsule(func)
return
+ if input_fields is None or return_field is None or volatility is None:
+ msg = (
+ "`input_fields`, `return_field`, and `volatility` "
+ "must be provided when `func` is callable."
+ )
+ raise TypeError(msg)
if isinstance(input_fields, pa.DataType):
input_fields = [input_fields]
self._udf = df_internal.ScalarUDF(
@@ -294,7 +320,7 @@ class ScalarUDF:
def udf(func: _PyCapsule) -> ScalarUDF: ...
@staticmethod
- def udf(*args: Any, **kwargs: Any): # noqa: D417, C901
+ def udf(*args: Any, **kwargs: Any): # noqa: D417
"""Create a new User-Defined Function (UDF).
This class can be used both as either a function or a decorator.
@@ -365,10 +391,7 @@ class ScalarUDF:
msg = "`func` argument must be callable"
raise TypeError(msg)
if name is None:
- if hasattr(func, "__qualname__"):
- name = func.__qualname__.lower()
- else:
- name = func.__class__.__name__.lower()
+ name = getattr(func, "__qualname__", func.__class__.__name__).lower()
input_fields = data_types_or_fields_to_field_list(input_fields)
return_field = data_type_or_field_to_field(return_field, "value")
return ScalarUDF(
@@ -391,7 +414,7 @@ class ScalarUDF:
)
@functools.wraps(func)
- def wrapper(*args: Any, **kwargs: Any) -> Callable:
+ def wrapper(*args: Any, **kwargs: Any) -> Expr:
return udf_caller(*args, **kwargs)
return wrapper
@@ -509,10 +532,10 @@ class AggregateUDF:
self,
name: str,
accumulator: Callable[[], Accumulator] | AggregateUDFExportable,
- input_types: list[pa.DataType] | None,
- return_type: pa.DataType | None,
- state_type: list[pa.DataType] | None,
- volatility: Volatility | str | None,
+ input_types: list[pa.DataType] | None = None,
+ return_type: pa.DataType | None = None,
+ state_type: list[pa.DataType] | None = None,
+ volatility: Volatility | str | None = None,
) -> None:
"""Instantiate a user-defined aggregate function (UDAF).
@@ -959,6 +982,7 @@ class WindowUDF:
also :py:class:`ScalarUDF` for operating on a row by row basis.
"""
+ @overload
def __init__(
self,
name: str,
@@ -966,6 +990,25 @@ class WindowUDF:
input_types: list[pa.DataType],
return_type: pa.DataType,
volatility: Volatility | str,
+ ) -> None: ...
+
+ @overload
+ def __init__(
+ self,
+ name: str,
+ func: WindowUDFExportable,
+ input_types: None = ...,
+ return_type: None = ...,
+ volatility: None = ...,
+ ) -> None: ...
+
+ def __init__(
+ self,
+ name: str,
+ func: Callable[[], WindowEvaluator] | WindowUDFExportable,
+ input_types: list[pa.DataType] | None = None,
+ return_type: pa.DataType | None = None,
+ volatility: Volatility | str | None = None,
) -> None:
"""Instantiate a user-defined window function (UDWF).
@@ -975,6 +1018,12 @@ class WindowUDF:
if hasattr(func, "__datafusion_window_udf__"):
self._udwf = df_internal.WindowUDF.from_pycapsule(func)
return
+ if input_types is None or return_type is None or volatility is None:
+ msg = (
+ "`input_types`, `return_type`, and `volatility` "
+ "must be provided when `func` is callable."
+ )
+ raise TypeError(msg)
self._udwf = df_internal.WindowUDF(
name, func, input_types, return_type, str(volatility)
)
@@ -1134,7 +1183,7 @@ class WindowUDF:
msg = "`func` must implement the abstract base class WindowEvaluator"
raise TypeError(msg)
- name = name or func.__qualname__.lower()
+ name = name or WindowUDF._get_default_name(func)
input_types = (
[input_types] if isinstance(input_types, pa.DataType) else input_types
)
@@ -1144,9 +1193,7 @@ class WindowUDF:
@staticmethod
def _get_default_name(func: Callable) -> str:
"""Get the default name for a function based on its attributes."""
- if hasattr(func, "__qualname__"):
- return func.__qualname__.lower()
- return func.__class__.__name__.lower()
+ return getattr(func, "__qualname__", func.__class__.__name__).lower()
@staticmethod
def _normalize_input_types(
@@ -1333,7 +1380,7 @@ class TableFunction:
@staticmethod
def _create_table_udf_decorator(
- name: str | None = None,
+ name: str,
*,
with_session: bool = False,
) -> Callable[[Callable[..., Any]], TableFunction]:
diff --git a/uv.lock b/uv.lock
index 49982971..b1bf3c5e 100644
--- a/uv.lock
+++ b/uv.lock
@@ -505,6 +505,7 @@ dev = [
{ name = "pyyaml" },
{ name = "ruff" },
{ name = "toml" },
+ { name = "ty" },
]
docs = [
{ name = "jinja2" },
@@ -549,6 +550,7 @@ dev = [
{ name = "pyyaml", specifier = ">=6.0.3" },
{ name = "ruff", specifier = ">=0.15.1" },
{ name = "toml", specifier = ">=0.10.2" },
+ { name = "ty", specifier = "==0.0.84" },
]
docs = [
{ name = "jinja2", specifier = ">=3.1.5" },
@@ -2937,6 +2939,31 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/96/8d/1080ee4c231f361b6ce4470d556c8c435b67c7e0753aaa641497ee92f88b/traitlets-5.15.1-py3-none-any.whl", hash = "sha256:770a53705f84b81ac107e83a1b3328ff2dae16094d8fc3cfc004e4b22dfd8e92", size = 85858, upload-time = "2026-06-03T12:26:04.395Z" },
]
+[[package]]
+name = "ty"
+version = "0.0.84"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/e1/f6/34f8869c45ea87fe6a931ffa824b1fe4428167173543d92988d631add355/ty-0.0.84.tar.gz", hash = "sha256:0cefdb0cd5d399418dbe83c349ba605047b7dff815ae6f460c80e8522b40be67", size = 7409537, upload-time = "2026-09-24T13:16:24.367Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/aa/0c/600648778e1c79133ce6b6a5c4c2531ae16b90c9b30f5041b38b574f61b8/ty-0.0.84-py3-none-linux_armv6l.whl", hash = "sha256:868438e2b9abfc835dd5b5b07ed693db95d8348ac40e3228252511d63ae87695", size = 13935333, upload-time = "2026-09-24T13:15:31.167Z" },
+ { url = "https://files.pythonhosted.org/packages/c5/3b/27f06f49945c36170765fad82076898039ae3873338e9865f876dd62f12c/ty-0.0.84-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b00f39b521f0b559cde0f6abf8dd47213b513b04f33d58efeb9b98486a16bb17", size = 13511328, upload-time = "2026-09-24T13:15:34.446Z" },
+ { url = "https://files.pythonhosted.org/packages/fc/b6/b12dd130e7f30141f0645614f454c925991cd389b1cc6c8284fd6bf32326/ty-0.0.84-py3-none-macosx_11_0_arm64.whl", hash = "sha256:8f78c2915567f2ee62ce0c24d86091bc76f81577677755e242d09951db588b9a", size = 13445445, upload-time = "2026-09-24T13:15:37.476Z" },
+ { url = "https://files.pythonhosted.org/packages/44/96/f012de4b3c1d9a4773326621380d768b4d48c47e620dc012775c5e01c7d7/ty-0.0.84-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:61155cad0921e890d86d1d911a40b3a0e924ef4bdc3b4f3865a6c89c7e3012dc", size = 13471235, upload-time = "2026-09-24T13:15:40.64Z" },
+ { url = "https://files.pythonhosted.org/packages/05/32/dca630f5f5353fa7bf67eebccf1549df99b16656d21071d0df6677c826a5/ty-0.0.84-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:417c3bf14947ed16b0b92976ab333c27915590354e96d4f581709068d3811355", size = 13680704, upload-time = "2026-09-24T13:15:43.817Z" },
+ { url = "https://files.pythonhosted.org/packages/44/8d/33702c8f62f05ad45b33644fb914d483de6a0a7bc8e51884e9b8773e6d69/ty-0.0.84-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e06ef117759f654f5379efe9ae79bda8a186016c016f1023d05c34f8e15c1546", size = 14518923, upload-time = "2026-09-24T13:15:46.802Z" },
+ { url = "https://files.pythonhosted.org/packages/c8/a1/da3246e6ef2ae0e842bca4058b25c54bee55ab058ffff4ee54e72517ec01/ty-0.0.84-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a05d1d53db8b64410ab38c6e32c5898510584869d1d5dd9c0605db81c9aef7df", size = 15027731, upload-time = "2026-09-24T13:15:49.755Z" },
+ { url = "https://files.pythonhosted.org/packages/20/17/b0099098a560aeb4f9fde6c912f9a81622c3e44e6555e1fbe322b368ec31/ty-0.0.84-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b92400494ab855cfb1204ac1101e3f53b2d09a8e21d171758b2cf9acdab1809e", size = 14748260, upload-time = "2026-09-24T13:15:52.713Z" },
+ { url = "https://files.pythonhosted.org/packages/60/d5/32cd67aedb271f006e716ad093806c96c5d7e2af86be8255469dbd3d76c8/ty-0.0.84-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af17eb391ae3027fed9be1780bea555c2b8a25ba68e184c627b4981b2c9eaab7", size = 14205858, upload-time = "2026-09-24T13:15:55.677Z" },
+ { url = "https://files.pythonhosted.org/packages/43/c4/638ac62ca2e8eca1b92e3e16273085744fada728b993500819b5c258ca15/ty-0.0.84-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:44cf06af0e7ec78ea6d8ff33a3450fc19627756a4fba681f7410d4e3ef63047a", size = 14573196, upload-time = "2026-09-24T13:15:59.165Z" },
+ { url = "https://files.pythonhosted.org/packages/a6/f6/9ee81c5d8921e5fd5f10889ae563c95cd3272b31df2ada528919ca1cef6b/ty-0.0.84-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c6e56b443ff9ba462e34bfbc1e582004c2719ed35f8f93a433744b0ba0afb2d9", size = 13480760, upload-time = "2026-09-24T13:16:02.313Z" },
+ { url = "https://files.pythonhosted.org/packages/f6/83/fbe5d1177667ae16d84c3242846b143809c9cc09568b333e5a9b4d5908f6/ty-0.0.84-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8b6562fcc48820c1030e2a4509ef2c14ef93043ca4860280074314ac3a825b95", size = 13696346, upload-time = "2026-09-24T13:16:05.391Z" },
+ { url = "https://files.pythonhosted.org/packages/86/9a/1b4ac9ddc34cc0ab4624bc219825d66af52f1ad87f4778ad08ad03fa06d5/ty-0.0.84-py3-none-musllinux_1_2_i686.whl", hash = "sha256:dee7421451efcbd70f03e95a8f2c8d9e5ec3d35edcd27b4095ee00ee062abfc4", size = 13959410, upload-time = "2026-09-24T13:16:08.661Z" },
+ { url = "https://files.pythonhosted.org/packages/c9/04/70e289437a67dfd686056ab4fd91880261a8233033a6a95d29393741fb97/ty-0.0.84-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:31e96307cf621babe969307dd657783d672e2d8bdbd06493710da635bf63fa07", size = 14350294, upload-time = "2026-09-24T13:16:11.865Z" },
+ { url = "https://files.pythonhosted.org/packages/9c/f4/ae7dbd118425c850981d23efb850f0d9d7f6b8d2b852bde2767434f668b4/ty-0.0.84-py3-none-win32.whl", hash = "sha256:4bdc85f91cbaae35825f2b17bb4ac5de0563367875cb0f42b2bf07a1bf7ff2ba", size = 13176439, upload-time = "2026-09-24T13:16:15.049Z" },
+ { url = "https://files.pythonhosted.org/packages/29/ec/994d87252f065dda5a500bc8afc726d18f6582d81d6f4ff1feb8a267c800/ty-0.0.84-py3-none-win_amd64.whl", hash = "sha256:71cc9aa7a7c89d4e12c6a814e9c0c3310ad5bee44efedbd6f5a42d91e9ac0f0d", size = 13962879, upload-time = "2026-09-24T13:16:18.191Z" },
+ { url = "https://files.pythonhosted.org/packages/6d/1c/e6e91852b72566b1ab9aca504712857dfb89324c12092a5bd61d42d34427/ty-0.0.84-py3-none-win_arm64.whl", hash = "sha256:6c9d3718c4a0d318bc9c92af9b2aa85c3294e7dff54cf1d69ad79568f56516da", size = 13656453, upload-time = "2026-09-24T13:16:21.51Z" },
+]
+
[[package]]
name = "typing-extensions"
version = "4.15.0"
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@datafusion.apache.org
For additional commands, e-mail: commits-help@datafusion.apache.org