Skip to content

fix(python): correct the asof and reader input annotations - #8955

Open
jonasdedden wants to merge 2 commits into
lance-format:mainfrom
jonasdedden:fix/python-structural-optional-types
Open

fix(python): correct the asof and reader input annotations#8955
jonasdedden wants to merge 2 commits into
lance-format:mainfrom
jonasdedden:fix/python-structural-optional-types

Conversation

@jonasdedden

@jonasdedden jonasdedden commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Two annotation bugs on the optional-dependency inputs. Each was hiding a runtime bug.

asof

ts_types was Union[datetime, pd.Timestamp, str], and was defined twice. The stubs pandas ships type Timestamp.__new__ as Self | NaTType, so this failed under mypy --strict and basedpyright:

lance.dataset(uri, asof=pd.Timestamp("2026-01-01"))
  • Put NaTType in the union and drop pd.Timestamp, which datetime already covers. One definition, in lance.util.
  • NaT is a datetime subclass, so it used to walk straight through sanitize_ts and then compare false against every version. You got "earlier than the first version". Now it is rejected up front.
  • The pandas branch for strings was guarded by _check_for_pandas(ts), which reads the argument's MRO and so is always false for a str. It never ran. asof="2026-01-01" told you to install pandas when pandas was installed. Now guarded by _PANDAS_AVAILABLE.

Reader inputs

ReaderLike had pd.Timestamp where a dataframe belongs, and reached Arrow through pa.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.

  • List what _coerce_reader actually 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.
  • Dispatch Polars with _check_for_polars and a real isinstance instead of matching on __module__, so the branch narrows.
  • Import LanceDataset inside the function. lance.dataset is both a module and a function, and the module-level form resolves to the function.
  • Update the write_dataset docstring, and add the dict and row-dict cases to test_input_data.

Checks

uv run make lint-python is clean and 247 tests pass. Downstream with pandas-stubs and pyarrow-stubs, all 12 input shapes are accepted and object() is rejected for both data_obj and asof. asof is also checked against the stubs pandas ships, which disagree with pandas-stubs here.

The new typing test skips pytest, like test_fragment_typing.py, because the lint job installs pyright without the test dependencies.

Left for later

types.py is still not in the pyright target. Without pyarrow-stubs, isinstance(x, pa.Table) does not narrow and every branch of _coerce_reader errors. Adding the stubs and clearing what they turn up is its own PR. Until then CI cannot check ReaderLike, and only the asof cases 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.

@github-actions github-actions Bot added bug Something isn't working A-python Python bindings labels Sep 2, 2026
lance-gatekeeper[bot]

This comment was marked as outdated.

@lance-gatekeeper lance-gatekeeper Bot added the K-changes Latest Gatekeeper recommendation requests changes. label Sep 2, 2026
@github-actions github-actions Bot added the A-deps Dependency updates label Sep 2, 2026
@lance-gatekeeper lance-gatekeeper Bot removed the K-changes Latest Gatekeeper recommendation requests changes. label Sep 2, 2026
lance-gatekeeper[bot]

This comment was marked as outdated.

@lance-gatekeeper lance-gatekeeper Bot added K-approved Latest Gatekeeper recommendation permits acceptance. K-risk Latest Gatekeeper recommendation includes a non-blocking risk. labels Sep 2, 2026
@jonasdedden
jonasdedden force-pushed the fix/python-structural-optional-types branch from 8490a06 to 3ccef5f Compare September 2, 2026 20:57
@jonasdedden jonasdedden changed the title fix(python): model optional inputs with protocols fix(python): correct the asof and reader input annotations Sep 2, 2026
@lance-gatekeeper lance-gatekeeper Bot removed K-approved Latest Gatekeeper recommendation permits acceptance. K-risk Latest Gatekeeper recommendation includes a non-blocking risk. labels Sep 2, 2026
lance-gatekeeper[bot]

This comment was marked as outdated.

@lance-gatekeeper lance-gatekeeper Bot added K-approved Latest Gatekeeper recommendation permits acceptance. K-risk Latest Gatekeeper recommendation includes a non-blocking risk. labels Sep 2, 2026
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
jonasdedden force-pushed the fix/python-structural-optional-types branch from 3ccef5f to 9775b51 Compare September 2, 2026 21:46
@lance-gatekeeper lance-gatekeeper Bot removed K-approved Latest Gatekeeper recommendation permits acceptance. K-risk Latest Gatekeeper recommendation includes a non-blocking risk. labels Sep 2, 2026

@lance-gatekeeper lance-gatekeeper Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Gate recommendation: approve with a non-blocking risk.

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.

@lance-gatekeeper lance-gatekeeper Bot added K-approved Latest Gatekeeper recommendation permits acceptance. K-risk Latest Gatekeeper recommendation includes a non-blocking risk. labels Sep 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-deps Dependency updates A-python Python bindings bug Something isn't working K-approved Latest Gatekeeper recommendation permits acceptance. K-risk Latest Gatekeeper recommendation includes a non-blocking risk.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant