Repository navigation
Revert: Replace Pandas with Polars #15395
Description
Activity
- addedenhancementThis PR modified some existing filesThis PR modified some existing filesdependenciesPull requests that update a dependency filePull requests that update a dependency file
on Sep 21, 2026 @priya-sundaram-dev Adding Polars (#15397) has substantially slowed our build process.
Do we really need to build Polars from scratch on every pull request?
Is there some way to use uv cache to speed up the installation process?
Should wecargo install polarsinstead of installing it with uv?priya-sundaram-dev commented
on Sep 22, 2026 ContributorMore actionsGood catch — and the cause is specific. Polars does ship prebuilt wheels, so normally there's no Rust compile at all. The problem is our interpreter.
As of 1.44.2 the binary lives in
polars-runtime-32, and every wheel it publishes is taggedcp310-abi3(see theuv.lockentries #15397 added).abi3/ stable-ABI wheels are not loadable under free-threaded CPython — the free-threaded build has its own ABI and pip/uv won't match anabi3tag against a3.14tinterpreter. Since our.python-versionis3.14t, uv finds no compatible wheel for polars and falls back to building the sdist, i.e. compiling Rust from scratch on every run. That's the slowdown.So, to your three questions:
- Build from scratch every PR? Only because of
3.14t. On a standard (GIL) CPython thecp310-abi3wheel installs in seconds. cargo install polars? That won't help —polarsisn't a standalone cargo binary crate; it's a Rust library plus Python bindings.cargo installhas nothing to install here.- uv cache? Worth doing regardless (
enable-cache: trueonastral-sh/setup-uv, oractions/cacheon~/.cache/uv), but it only caches the result of the source build — the first run per lock change still pays full compile time.
Cleanest fix: run the Polars-using check on a plain-GIL interpreter (a dedicated matrix leg pinned to
3.14, not3.14t) so it uses the prebuilt wheel, and keep the free-threaded leg for everything else. Whenpolars-runtime-32starts publishingcp314tfree-threaded wheels upstream, we can drop the split. I can open a PR that adds the pinned leg + enables the uv cache if you'd like.- Build from scratch every PR? Only because of
Please create a PR that only enables the uv cache. We do not break the uv lock very often.
Once we have merged or closed all of our open PRs, we can discuss the two test runs (with and without the GIL).
priya-sundaram-dev commented
on Sep 22, 2026 ContributorMore actionsQuick heads-up before I open anything: the uv cache is already enabled on
masterand is hitting. Theastral-sh/setup-uv@v7step already has:- uses: astral-sh/setup-uv@v7 with: enable-cache: true cache-dependency-glob: uv.lock
and the latest build run confirms it's live, not cold:
Cache hit for: setup-uv-2-...-pruned-c5f50d16... Cache restored successfullyAlso worth noting: #15397 was closed, not merged, so Polars is no longer in
uv.lock— nothing is currently building it from source. The slowdown you saw was the one-time cost on that PR itself: adding Polars changeduv.lock, which invalidated the cache key (it's keyed on the lock hash), so that single run had to build thecp310-abi3sdist under3.14twith a cold cache. Since it's closed, that cost is gone.So there's nothing to enable — it's already on. I'd rather not send a no-op PR.
The only thing a PR could add here is eliminating even that one-time cold-build cost on the rare lock change, by giving the cache a prefix
restore-keysfallback so a lock bump restores the previous cache (old built wheels intact) and only re-resolves the delta.setup-uvdoesn't exposerestore-keys, so that means a small hand-rolledactions/cacheblock instead of the built-in — which is the bigger change you said you'd rather avoid, and you noted the lock rarely changes anyway. Happy to do it if you decide it's worth it, but as-is I don't think it's needed. Your call.- chore: uv sync --upgrade + relock; make scripts/pr_file_map.py executable #15398 was merged as the pull request that broke the uv link. Am I misunderstanding the process?
Lines 1199 to 1225 in 6a2ca68
[[package]] name = "polars" version = "1.44.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "polars-runtime-32" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a4/15/e8541eefc22fbc7ca89bcb5112298a153729f73cfbc0cf6a668e509f975c/polars-1.44.2.tar.gz", hash = "sha256:86c8e26b6c2de8c8d344bb910b74dfc47b118ac3fe0f19b44909467990a0b281", size = 766272, upload-time = "2026-09-09T07:42:08.859Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/51/6d/3014112c7f717d1253223faa13b6db3ac3a64ed00ab2a3bc1b942bc9cdd4/polars-1.44.2-py3-none-any.whl", hash = "sha256:1bb331f17a40d9d931101533dcd33637b66edc61eb377b07020dac16a0f0377b", size = 865768, upload-time = "2026-09-09T07:40:12.053Z" }, ] [[package]] name = "polars-runtime-32" version = "1.44.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/d4/a1/a7eace6587b56f22cf2a21ab4d5e695db372dc23fd96accb68b1ec12660b/polars_runtime_32-1.44.2.tar.gz", hash = "sha256:b84842f7d621aaca7a52e165e19a24f89db45f8aa13744941430218419a14a67", size = 3172205, upload-time = "2026-09-09T07:42:10.851Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/99/5b/a5215f82c3dd443dc5d6911b0d3e937f97056e0ef7753f7e123422481a18/polars_runtime_32-1.44.2-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:1fd536720668ba203a16a20b08cd6b23057e407a0279cf36b2f35f879d6e3208", size = 48334014, upload-time = "2026-09-09T07:40:16.43Z" }, { url = "https://files.pythonhosted.org/packages/c2/e0/f3dc93fce4b4e99370db6a89001a1b8d3c606e3560d0d91dda809d6c6324/polars_runtime_32-1.44.2-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:e0fd43720c8222ae39919c8ff891636d53b352706087120e62f83544dd3ff782", size = 43331099, upload-time = "2026-09-09T07:40:21.029Z" }, { url = "https://files.pythonhosted.org/packages/4e/4f/076626ce93ddd622203c4b27be2a96d034cf5b24110c52e96e6029f0ea33/polars_runtime_32-1.44.2-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbf9b45040291dc1c6c588c837019c33557bde25ec536562a9cca9e1f6dfcc45", size = 46590853, upload-time = "2026-09-09T07:40:24.934Z" }, { url = "https://files.pythonhosted.org/packages/e9/24/ed9982657c446dd5491b089370eea196725673570cfc61f7225a9fdd7ef0/polars_runtime_32-1.44.2-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1bafb441e99199a62c63bf1bbdc0ea09ee9776dbac2bf31452b5000fb1df2f7", size = 49912258, upload-time = "2026-09-09T07:40:29.238Z" }, { url = "https://files.pythonhosted.org/packages/71/42/5490ab360aa2406119825ad82203a5e2ff27a3a5893ca8e0b93c053a59a3/polars_runtime_32-1.44.2-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:10c0c695a418407617b5159db7d9a21074a733e4c6d61275b6762f25cb31ca99", size = 46737310, upload-time = "2026-09-09T07:40:33.143Z" }, { url = "https://files.pythonhosted.org/packages/06/8f/d741afb1dcd1848161189e017d27972e7e78556d8dce66b94d4235093706/polars_runtime_32-1.44.2-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:c4a09fb14aad711526346efc0cb2015c2fd0555ce4118b6524e5debbaea65ff5", size = 49887654, upload-time = "2026-09-09T07:40:37.455Z" }, { url = "https://files.pythonhosted.org/packages/ba/e7/c61c1c7eea37705920fe7c1302d1dd80d1165db2b928f0da3eae6d1ebb75/polars_runtime_32-1.44.2-cp310-abi3-win_amd64.whl", hash = "sha256:8598e7a20efba70bb74978c7df7af7c606ff4d79b9b48fdd808250b189bc9a13", size = 51297967, upload-time = "2026-09-09T07:40:41.993Z" }, { url = "https://files.pythonhosted.org/packages/e7/a0/d0dd0d2ec95fa328dd47055905fae53ba3cd79f11c8973326ebe75a49e4c/polars_runtime_32-1.44.2-cp310-abi3-win_arm64.whl", hash = "sha256:d51040d3ab40157f6db3c62be59cab5b80fb3c8d158924769c4982a1c8eef730", size = 44303345, upload-time = "2026-09-09T07:40:47.081Z" }, ] priya-sundaram-dev commented
on Sep 22, 2026 ContributorMore actionsYou're not misunderstanding it at all — you're right, and I was wrong on one fact. I conflated this with #15397. Polars is now a permanent dependency (
polars>=1.44.2inpyproject.tomlfrom the Pandas→Polars migration), so it's genuinely inuv.lockand stays there — it was never removed. Apologies for the confusion.So your read of #15398 is exactly correct:
uv sync --upgrade + relockrewroteuv.lock, and since the cache key is the lock hash (cache-dependency-glob: uv.lock), that relock invalidated the cache and the next build hit a cold cache.The reason a cold cache is expensive here specifically is the free-threaded runner.
.python-versionis3.14t, andpolars-runtime-32only shipscp310-abi3wheels. abi3 wheels aren't usable under free-threading, so on3.14tuv can't grab a wheel and builds the ~3 MB Rust sdist from source. On a warm cache that built artifact is restored and it's fast; every lock change pays the Rust build once.So the cache is already on and already doing its job — there's no "enable the cache" PR to send, which is what I was trying (clumsily) to say. The only thing left is whether to soften that once-per-relock cold build. Two honest options:
- Leave it. Relocks are rare, the cost is bounded to one run, and the built-in
setup-uvcache handles the common case. Simplest. - Add a
restore-keysprefix fallback so a lock bump restores the previous cache (Rust build intact) and only re-resolves the delta instead of rebuilding from scratch.setup-uvdoesn't exposerestore-keys, so this means a small hand-rolledactions/cacheblock — the bigger change you'd rather avoid.
Given how seldom the lock changes, my vote is option 1, but I'm happy to send option 2 if you'd prefer the belt-and-suspenders version. And once a
cp314twheel for polars ships, the source build disappears on its own.- Leave it. Relocks are rare, the cost is bounded to one run, and the built-in
- Upload abi3.abi3t wheels pola-rs/polars#27955 makes it clear that the Polars team is not yet ready to release wheels for free-threaded Python.
The long Polars build times are debilitating, so we will close this issue and revert the migration to Polars until that project delivers free-threaded wheels.
@priya-sundaram-dev Please create a pull request that removes Polars from
pyproject.tomland then runs:
%uv sync --upgrade && uv lockpriya-sundaram-dev commented
on Sep 22, 2026 ContributorMore actionsDone — #15404 removes
polars>=1.44.2frompyproject.tomland rerunsuv sync --upgrade && uv lock. All 30 polars-related lines drop out ofuv.lockwith no other version changes, and no source file imports polars, so the revert is clean. That takes the Rust-from-source build off the critical path until upstream ships free-threaded (cp314t) wheels.- changed the title
[-]Replace Pandas with Polars[/-][+]Revert: Replace Pandas with Polars[/+]on Sep 22, 2026
Feature description
Please create a pull request that removes Pandas and replaces it with Polars?
https://docs.pola.rs/user-guide/migration/pandas
To find at least 8 algorithms that will need to be fixed, use:
%
git grep "import pandas" && git grep "pandas import"uv sync --upgrade && uv lock#15397pyproject.toml.