Skip to content

chore(python): python 3.14 support#7728

Open
mocobeta wants to merge 10 commits into
lance-format:mainfrom
mocobeta:chore/support-python-3.14
Open

chore(python): python 3.14 support#7728
mocobeta wants to merge 10 commits into
lance-format:mainfrom
mocobeta:chore/support-python-3.14

Conversation

@mocobeta

@mocobeta mocobeta commented Jul 10, 2026

Copy link
Copy Markdown

This PR adds Python 3.14 support.

All tests passed with some warnings.

❯ python -V
Python 3.14.5

❯ uv run make test

===================================================================================== warnings summary ======================================================================================
python/tests/test_dataset.py::test_sharded_iterator_fragments
python/tests/test_dataset.py::test_sharded_iterator_batches
python/tests/test_dataset.py::test_sharded_iterator_non_full_batch
  /home/moco/repo/mocobeta/worktrees/lance/calm-warbler/lance/python/python/lance/_dataset/sharded_batch_iterator.py:69: UserWarning: ShardedBatchIterator is deprecated, use :class:`Sampler` instead
    warnings.warn(

python/tests/test_fork.py::test_table_roundtrip
  /home/moco/repo/mocobeta/worktrees/lance/calm-warbler/lance/python/python/lance/__init__.py:353: UserWarning: lance is not fork-safe. If you are using multiprocessing, use spawn or forkserver instead.
    warnings.warn(

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
========================================================= 1101 passed, 594 skipped, 1 deselected, 4 warnings in 1396.62s (0:23:16) ==========================================================

Benchmark result

Summary by CodeRabbit

Summary by CodeRabbit

  • Chores

    • Updated CI workflows and compatibility testing to use Python 3.14, including the Linux x86_64 wheel matrix and ARM job setup.
    • Adjusted wheel upload gating and refreshed test dependency constraints (datasets/duckdb), along with static analysis settings to target Python 3.14.
  • Bug Fixes

    • Improved background event pumping behavior during buffered-drain completion: pump errors are now propagated on Python 3.14+ (while older versions continue to log warnings without failing).

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The changes update Python CI and tooling from 3.13 to 3.14, refresh test dependency constraints, and make background executor pump errors version-dependent.

Changes

Python 3.14 validation updates

Layer / File(s) Summary
Python 3.14 CI and tooling alignment
.github/workflows/python.yml, python/pyproject.toml
CI matrices and compatibility jobs target Python 3.14; wheel artifact conditions, test dependencies, and Pyright configuration are updated.

Executor pump error propagation

Layer / File(s) Summary
Propagate pump errors
python/src/executor.rs
block_on_pumping propagates pump() errors for Python 3.14+ and retains warning-based handling for Python 3.13 and earlier.

Estimated code review effort: 2 (Simple) | ~10 minutes

Suggested reviewers: westonpace

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: adding Python 3.14 support across workflow, dependencies, and executor behavior.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added A-python Python bindings A-deps Dependency updates A-ci CI / build workflows chore labels Jul 10, 2026
@github-actions

Copy link
Copy Markdown
Contributor

ACTION NEEDED
Lance follows the Conventional Commits specification for release automation.

The PR title and description are used as the merge commit message. Please update your PR title and description to match the specification.

For details on the error please inspect the "PR Title Check" action.

@mocobeta mocobeta changed the title chore: Python 3.14 support chore: python 3.14 support Jul 10, 2026
@mocobeta mocobeta marked this pull request as ready for review July 10, 2026 20:28

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@python/pyproject.toml`:
- Line 106: Align the Pyright target with the project’s supported baseline by
changing the pythonVersion setting in pyproject.toml to 3.10, matching
requires-python >=3.10; alternatively, add a separate check that type-checks
against the minimum supported Python version.
- Around line 75-76: Align the compatibility job with the pinned test
dependencies by either adding a shared exported constraints/lockfile and
installing it in the workflow’s pylance[tests,ray] job, or updating
[project.optional-dependencies].tests to mirror the datasets==4.4.0 and
duckdb>=1.5.0,<1.6.0 constraints shown in pyproject.toml.

In `@python/src/executor.rs`:
- Around line 239-243: The public API rustdoc for block_on_pumping must document
that completion-time draining of buffered progress events can return a PyErr
even after the future succeeds. Update its documentation with an executable
example demonstrating final pump/drain behavior and PyErr propagation, linking
to the relevant executor methods and types.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: cf85f057-17e8-412f-9e37-6218e023ad9d

📥 Commits

Reviewing files that changed from the base of the PR and between 1aec146 and 879bedd.

⛔ Files ignored due to path filters (1)
  • python/uv.lock is excluded by !**/*.lock
📒 Files selected for processing (3)
  • .github/workflows/python.yml
  • python/pyproject.toml
  • python/src/executor.rs

Comment thread python/pyproject.toml
Comment thread python/pyproject.toml

[tool.pyright]
pythonVersion = "3.13"
pythonVersion = "3.14"

@coderabbitai coderabbitai Bot Jul 10, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

set -euo pipefail
printf '\n== python/pyproject.toml (around line 106) ==\n'
nl -ba python/pyproject.toml | sed -n '1,180p'

printf '\n== Search for pyright config and requires-python ==\n'
rg -n --hidden -S 'pythonVersion|requires-python|pyright' python . -g '!**/.git/**'

Repository: lance-format/lance

Length of output: 243


🏁 Script executed:

set -euo pipefail

printf '\n== python/pyproject.toml (excerpt) ==\n'
awk 'NR>=1 && NR<=180 { printf "%4d  %s\n", NR, $0 }' python/pyproject.toml

printf '\n== Search for pyright/min-version settings ==\n'
rg -n --hidden -S 'pythonVersion|requires-python|pyright|minimum version|min-version|3\.14|3\.10' python .

Repository: lance-format/lance

Length of output: 50375


🏁 Script executed:

set -euo pipefail

printf '\n== python/pyproject.toml (lines 90-120) ==\n'
sed -n '90,120p' python/pyproject.toml | awk '{ printf "%4d  %s\n", NR+89, $0 }'

printf '\n== config files mentioning pythonVersion/requires-python ==\n'
fd -HI -t f 'pyproject.toml|pyrightconfig.json|*.json|*.toml|*.yaml|*.yml' python . | head -n 50

printf '\n== exact matches in python/ directory ==\n'
rg -n --hidden -S '^(pythonVersion|requires-python)\s*=|pythonVersion|requires-python' python/pyproject.toml python/pyrightconfig.json python/.github python/.circleci python/.pre-commit-config.yaml python/.pre-commit-config.yml python/pyrightconfig.json

Repository: lance-format/lance

Length of output: 1551


🏁 Script executed:

set -euo pipefail

printf '\n== python/pyproject.toml (lines 90-120) ==\n'
awk 'NR>=90 && NR<=120 { printf "%4d  %s\n", NR, $0 }' python/pyproject.toml

printf '\n== likely config files under python/ ==\n'
find python -maxdepth 2 \( -name 'pyproject.toml' -o -name 'pyrightconfig.json' -o -name '*.toml' -o -name '*.json' -o -name '*.yml' -o -name '*.yaml' \) | sort

printf '\n== exact matches in python config files ==\n'
rg -n --hidden -S 'pythonVersion|requires-python' python/pyproject.toml python/pyrightconfig.json python/.pre-commit-config.yaml python/.pre-commit-config.yml python/.github python/.circleci 2>/dev/null || true

Repository: lance-format/lance

Length of output: 1395


🏁 Script executed:

set -euo pipefail

printf '\n== python/pyproject.toml (lines 90-120) ==\n'
awk 'NR>=90 && NR<=120 { printf "%4d  %s\n", NR, $0 }' python/pyproject.toml

printf '\n== matches for pythonVersion/requires-python under python/ ==\n'
rg -n --hidden -S 'pythonVersion|requires-python' python

Repository: lance-format/lance

Length of output: 1324


Align Pyright with the supported baseline python/pyproject.toml:105-106

requires-python is still >=3.10, but pythonVersion = "3.14" makes Pyright type-check against 3.14 syntax/stubs, so 3.10–3.13 compatibility issues can slip through. Set this to 3.10 or add a separate minimum-version check.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@python/pyproject.toml` at line 106, Align the Pyright target with the
project’s supported baseline by changing the pythonVersion setting in
pyproject.toml to 3.10, matching requires-python >=3.10; alternatively, add a
separate check that type-checks against the minimum supported Python version.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I think this is a valid point. We should probably set pythonVersion to 3.10 for linting. I would keep this as is for consistency for now.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@mocobeta Understood, thanks for confirming. I'll leave this as-is for now since you're intentionally prioritizing consistency with the CI baseline. Feel free to revisit this when you're ready to formalize the linting target at the minimum supported version (3.10).


✏️ Learnings added
Learnt from: mocobeta
Repo: lance-format/lance PR: 7728
File: python/pyproject.toml:106-106
Timestamp: 2026-07-11T16:30:06.317Z
Learning: In `python/pyproject.toml`, the `[tool.pyright]` `pythonVersion` setting is intentionally kept aligned with the latest supported Python version (currently 3.14) for consistency, even though `requires-python` allows a lower minimum (`>=3.10`). This is a known/accepted inconsistency; don't flag it again unless asked.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Comment thread python/src/executor.rs Outdated
Comment on lines +239 to +243
// when the index build finishes so fast that no pump cycles
// occurred during execution (common on Python 3.14 due to
// GIL/async scheduling changes), all events sit in the
// channel buffer and get drained after completion
pump()?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Document the completion-time pump error contract.

pump()? now turns a successful future into an error when draining buffered progress events fails, but the block_on_pumping rustdoc does not describe this behavior or provide an executable example. Update the public API documentation to show final buffered-event draining and PyErr propagation, with links to the relevant methods.

As per coding guidelines, **/*.{rs,md,py} requires all public APIs to have documentation with examples, and documentation should link to relevant structs and methods.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@python/src/executor.rs` around lines 239 - 243, The public API rustdoc for
block_on_pumping must document that completion-time draining of buffered
progress events can return a PyErr even after the future succeeds. Update its
documentation with an executable example demonstrating final pump/drain behavior
and PyErr propagation, linking to the relevant executor methods and types.

Source: Coding guidelines

Comment thread .github/workflows/python.yml Outdated
strategy:
matrix:
python-minor-version: ["10", "13"]
python-minor-version: ["10", "14"]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We should probably have at least one CI job that also runs on 3.13 in addition to 3.10 and 3.14 to verify we don't break support for it? Fortunately, 3.10 will be EOL here pretty soon.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Python 13 was re-added to the matrix:
fce76db

Comment thread python/src/executor.rs Outdated
Comment on lines +244 to +243
}
// when the index build finishes so fast that no pump cycles
// occurred during execution (common on Python 3.14 due to
// GIL/async scheduling changes), all events sit in the
// channel buffer and get drained after completion
pump()?;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'm sort of confused by this change. Why is propagating this error related to GIL changes?

@mocobeta mocobeta Jul 11, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Sorry I forgot to add the context of this change in the PR description.

I found that this test failed with Python 3.14. The expected RuntimeError was not thrown.

Honestly, I am not an expert in GIL-free Python 3.14. My coding agent detected the possible cause of the test failure. It says the expected error in the test was silently logged in the block instead of being properly propagated.

I'm copy-pasting the coding agent's report.


If Python 3.14 changed async scheduling such that all progress events now arrive after the index build future completes (rather than interleaved during execution), errors would be silently logged instead of propagated. This could be caused by:

  1. GIL scheduling changes in Python 3.14 affecting when Python::try_attach can re-attach to the Python interpreter
  2. Tokio event loop timing differences — the index build may now complete too quickly for pump cycles to interleave with stage events
  3. A race condition where the channel fills but drain() only runs after completion

This is likely a real lance code bug exposed by Python 3.14's threading/timing changes, not just a test issue. The fix should be on line ~248 of python/src/executor.rs — errors from post-completion pump cycles should propagate, not be logged as warnings (at least for non-complete events like "start:train_ivf").


@mocobeta mocobeta Jul 13, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I did some additional research with Qwen3.6. The subtle timing change seems to occur with both GIL-enabled and disabled Python 3.14. (It seems the code was affected by an implementation-level change in CPython 3.14, but I myself cannot provide the rationale.)


Root cause

In block_on_pumping, progress events are drained via pump() at two points:

  1. During execution (pump()?;) — errors propagate correctly
  2. After completion (post-completion drain) — the GIL-enabled branch swallows errors with log::warn!

On Python 3.14, async scheduling changes cause the index build to complete faster than before. No events exist in the channel during the first pump cycle, so they all get drained post-completion where the old code silently drops them:

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

The test failure only happens with 3.14. I introduced a fallback to the current behavior when running on 3.13 or older.

3d1738d

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
python/pyproject.toml (1)

106-106: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Run Pyright against the supported minimum version too.

requires-python still supports Python 3.10, but pythonVersion = "3.14" makes this check model only the 3.14 environment. Add a separate pyright --pythonversion 3.10 check (while retaining the 3.14 check), or target 3.10 here. Pyright documents pythonVersion as controlling version-specific analysis. (github.com)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@python/pyproject.toml` at line 106, Update the Pyright configuration’s
pythonVersion setting to ensure analysis also runs against the supported minimum
Python 3.10 version, while retaining coverage for Python 3.14 through a separate
check or equivalent configuration.

Source: MCP tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@python/pyproject.toml`:
- Line 106: Update the Pyright configuration’s pythonVersion setting to ensure
analysis also runs against the supported minimum Python 3.10 version, while
retaining coverage for Python 3.14 through a separate check or equivalent
configuration.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: f2f3bb68-b43b-40a9-804d-697fbc516c6f

📥 Commits

Reviewing files that changed from the base of the PR and between fce76db and e8c6f9b.

📒 Files selected for processing (1)
  • python/pyproject.toml

@mocobeta mocobeta changed the title chore: python 3.14 support chore(python): python 3.14 support Jul 11, 2026
@mocobeta

mocobeta commented Jul 12, 2026

Copy link
Copy Markdown
Author

Hi @westonpace, when I tried to run the benchmark scripts following DEVELOPMENT.md I encountered a possible issue with it. I opened #7742.

@mocobeta

mocobeta commented Jul 12, 2026

Copy link
Copy Markdown
Author

Benchmark result

I ran the benchmark (#7744 patch applied) on my PC both with Python 3.13 and 3.14.

Machine Spec:

  • Core i7-1365U
  • 12 logical processors
  • 32GiB RAM

Commands:

# run benchmarks with python 3.13.13
uv run pytest python/benchmarks/ -m "not slow" --ignore python/benchmarks/test_random_access.py --benchmark-save=py3.13

# run benchmarks with python 3.14.5
uv run pytest python/benchmarks/ -m "not slow" --ignore python/benchmarks/test_random_access.py --benchmark-save=py3.14

# compare the results by mean
pytest-benchmark compare --sort mean --group-by name py3.13 py3.14 > compare.txt

Result:

compare.txt

In the run, I observe slight performance degradation in 3.14, although my PC is not the latest 🤔

Just to note, performance-related interests are out of scope of this change. My intention here is to ensure the benchmark runs successfully with Python 3.14.

@mocobeta mocobeta requested a review from westonpace July 13, 2026 10:07

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@python/src/executor.rs`:
- Around line 22-24: Update is_python314_or_later so a None argument first
checks the version of the currently attachable interpreter, returning whether it
is Python 3.14 or later; only use the existing fallback when no interpreter can
be attached. Preserve the direct version check for Some(py).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 3ad9259d-f8b8-4610-93d2-4fa921b485ba

📥 Commits

Reviewing files that changed from the base of the PR and between e8c6f9b and 3d1738d.

⛔ Files ignored due to path filters (1)
  • python/uv.lock is excluded by !**/*.lock
📒 Files selected for processing (1)
  • python/src/executor.rs

Comment thread python/src/executor.rs
Comment on lines +22 to +24
fn is_python314_or_later(py: Option<Python<'_>>) -> bool {
py.map_or(true, |py| py.version_info() >= (3, 14))
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map the relevant Rust files first
ast-grep outline python/src/executor.rs --view expanded
ast-grep outline python/src/dataset.rs --view expanded

# Locate the helper and its call sites
rg -n "is_python314_or_later|block_on_pumping\(" python/src/executor.rs python/src/dataset.rs

# Read the relevant slices with line numbers
sed -n '1,120p' python/src/executor.rs
printf '\n==== dataset slice ====\n'
sed -n '4320,4405p' python/src/dataset.rs

Repository: lance-format/lance

Length of output: 23019


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Read the pumping implementation and nearby call sites
sed -n '191,285p' python/src/executor.rs
printf '\n==== all block_on_pumping call sites ====\n'
rg -n "block_on_pumping\(" -S python/src

Repository: lance-format/lance

Length of output: 3966


Handle None using the attachable interpreter version

python/src/dataset.rs calls block_on_pumping(None, ...), so this path currently treats every run as 3.14+ and propagates post-completion callback errors even on 3.13 and older. When py is None, check the currently attachable interpreter before choosing the branch, and only fall back when no interpreter is available.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@python/src/executor.rs` around lines 22 - 24, Update is_python314_or_later so
a None argument first checks the version of the currently attachable
interpreter, returning whether it is Python 3.14 or later; only use the existing
fallback when no interpreter can be attached. Preserve the direct version check
for Some(py).

@mocobeta

Copy link
Copy Markdown
Author

I fixed the clippy warning.
I'll look into this compat test failure. I could reproduce it on my PC, but still don't know the root cause.
https://github.com/lance-format/lance/actions/runs/29257054304/job/86927411461?pr=7728

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-ci CI / build workflows A-deps Dependency updates A-python Python bindings chore

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants