fix(python): correct the asof and reader input annotations - #8955
Open
jonasdedden wants to merge 2 commits into
Open
fix(python): correct the asof and reader input annotations#8955jonasdedden wants to merge 2 commits into
jonasdedden wants to merge 2 commits into
Conversation
jonasdedden
force-pushed
the
fix/python-structural-optional-types
branch
from
September 2, 2026 20:57
8490a06 to
3ccef5f
Compare
The stubs bundled with pandas declare `Timestamp.__new__` as returning `Self | NaTType`, so `lance.dataset(asof=pd.Timestamp(...))` was rejected by strict type checkers. Name `NaTType` in `ts_types`; `pd.Timestamp` needs no member of its own because it subclasses `datetime`. `NaT` subclasses `datetime` too, so it previously passed through `sanitize_ts` untouched and then compared false against every version timestamp, surfacing as a misleading "earlier than the first version" error. Reject it at the boundary instead. The pandas string-parsing branch was guarded by `_check_for_pandas(ts)`, which inspects the argument's MRO and is therefore always false for a `str`. That branch had never run: `asof="2026-01-01"` raised "Try installing Pandas" on machines that had pandas installed. Guard on `_PANDAS_AVAILABLE` instead. The new typing regression file joins the pyright target and pins the accepted and rejected `asof` types in both directions, so the annotation also fails the type check if it becomes too permissive.
`ReaderLike` listed `pd.Timestamp` rather than a dataframe type, and
reached the Arrow classes through `pa.dataset.Dataset`, which type
checkers cannot resolve because `pyarrow` does not expose `dataset` as an
attribute. The unresolved member made the whole union accept anything, so
neither defect surfaced and the union drifted out of step with
`_coerce_reader`: pandas and Polars dataframes, HuggingFace datasets,
column dicts, row dicts and Pydantic model instances are all coerced at
runtime but were absent from the annotation.
List every input `_coerce_reader` handles, import the Arrow classes
directly, and dispatch on the optional dependencies the way the rest of
the codebase does: `_check_for_polars` plus a real `isinstance` replaces
the duplicated `__module__.startswith("polars")` string matching, which
also lets a type checker narrow the branch. `LanceDataset` moves to a
function-local import because `lance.dataset` names both this module and
a function on the package.
`types.py` cannot join the pyright target yet: without `pyarrow-stubs`,
`isinstance(x, pa.Table)` does not narrow and every branch reports an
error, so that is left for a follow-up along with the stub dependency.
The union members are pinned in the typing regression file instead.
jonasdedden
force-pushed
the
fix/python-structural-optional-types
branch
from
September 2, 2026 21:46
3ccef5f to
9775b51
Compare
Contributor
There was a problem hiding this comment.
The timestamp contract and expanded reader union remain sound after the rebase. Relocating the runtime rejection assertions into the dataset suite keeps the typing target independent of pytest while preserving the exercised behavior.
A bounded typing risk remains: the repository's Pyright environment lacks PyArrow stubs, so the positive ReaderLike cases cannot reject invalid inputs, and Mapping/Sequence are broader than the concrete dict/list branches. Adding resolvable Arrow stubs and tightening those container shapes would make the regression contract enforceable.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two annotation bugs on the optional-dependency inputs. Each was hiding a runtime bug.
asofts_typeswasUnion[datetime, pd.Timestamp, str], and was defined twice. The stubs pandas ships typeTimestamp.__new__asSelf | NaTType, so this failed undermypy --strictand basedpyright:NaTTypein the union and droppd.Timestamp, whichdatetimealready covers. One definition, inlance.util.NaTis adatetimesubclass, so it used to walk straight throughsanitize_tsand then compare false against every version. You got "earlier than the first version". Now it is rejected up front._check_for_pandas(ts), which reads the argument's MRO and so is always false for astr. It never ran.asof="2026-01-01"told you to install pandas when pandas was installed. Now guarded by_PANDAS_AVAILABLE.Reader inputs
ReaderLikehadpd.Timestampwhere a dataframe belongs, and reached Arrow throughpa.dataset.Dataset. Type checkers cannot resolve that, and a union with an unresolved member accepts anything, so nobody noticed since #3021. The union had also fallen behind_coerce_reader._coerce_readeractually takes: pandas and Polars frames, Arrow table, batch, reader, dataset and scanner, HuggingFace datasets, column dicts, row dicts, Pydantic models, batch iterables. Import the Arrow classes directly._check_for_polarsand a realisinstanceinstead of matching on__module__, so the branch narrows.LanceDatasetinside the function.lance.datasetis both a module and a function, and the module-level form resolves to the function.write_datasetdocstring, and add the dict and row-dict cases totest_input_data.Checks
uv run make lint-pythonis clean and 247 tests pass. Downstream with pandas-stubs and pyarrow-stubs, all 12 input shapes are accepted andobject()is rejected for bothdata_objandasof.asofis also checked against the stubs pandas ships, which disagree with pandas-stubs here.The new typing test skips
pytest, liketest_fragment_typing.py, because the lint job installs pyright without the test dependencies.Left for later
types.pyis still not in the pyright target. Without pyarrow-stubs,isinstance(x, pa.Table)does not narrow and every branch of_coerce_readererrors. Adding the stubs and clearing what they turn up is its own PR. Until then CI cannot checkReaderLike, and only theasofcases are pinned in both directions.A union that names optional dependencies only checks anything when the checker can resolve all of them. Miss one and it goes back to accepting anything. No false errors, but no checking. That is already the case for reader inputs today.