uv: `uv` fails to install `scikits.odes` on Python 3.11 due to Cython dependency resolution mismatch, works on Python 3.10 and earlier (cannot find `longintrepr.h`?)

Description

Hi there! I’m opening this issue because I’m unable to install a package with uv, but the installation works with pip – it’s called scikits.odes. The installation instructions mention the requirement of a Fortran compiler and a SUNDIALS installation present before installing off of the source distribution available on PyPI (no wheels are available).

How to reproduce

Here is a reproducer on an M-series macOS machine (compiling scikits.odes is much easier on Unix-like platforms such as GNU/Linux and macOS, rather than on Windows).

[!NOTE] Installing SUNDIALS via Homebrew usually works, but it doesn’t work at the time of writing because the formula was updated to SUNDIALS v7.0.0, which is not supported (we regularly test in CI against v6.5.0). I have provided another method of installing SUNDIALS v6.5.0 for the purpose of this MWE.

# install Python 3.11 with Homebrew
brew install python@3.11
# Clone repository (we provide a helper script to install SUNDIALS
git clone https://github.com/pybamm-team/PyBaMM.git --depth 1
# Create a virtual environment in venv/
python3.11 -m venv venv
# Activate it
source venv/bin/activate
# Install SUNDIALS v6.5.0 via helper script, to ~/.local/
pip install nox
nox -s pybamm-requires
# Install uv into this virtual environment via pip
pip install uv
# Point to the SUNDIALS installation directory before installation
export SUNDIALS_INST="$HOME/.local/"
# Install it
uv pip install scikits.odes --verbose --no-cache-dir

Expected behaviour

The expected behaviour would be that I can install scikits.odes with uv on both Python 3.10 and Python 3.11 (or between Python 3.8–3.11 based on the provided official support for the package). I can currently install scikits.odes with pip on all Python versions from 3.8–3.11.

Additional context

I see that https://github.com/astral-sh/uv/issues/1946 faced this issue of not being able to find longintrepr.h earlier, I don’t know if that’s related – I am not used to working with Cython or Fortran codebases, but can help debug a bit with pybind11 linkage. However, I’m noticing with a deeper dive with the logs that pip’s resolver chooses to point to cython==0.29.37, while uv chooses cython==3.0a7, which might be causing the trouble?

I would be happy to provide additional reproducers or logs as necessary.

xref: pybamm-team/PyBaMM#3825, aio-libs/aiohttp#6600

Specifications

uv 0.1.24 (a5cae0292 2024-03-22) installed via pip from PyPI macOS M-series, running Mac OS X Sonoma v14.3 (arm64)

About this issue

  • Original URL
  • State: closed
  • Created 3 months ago
  • Comments: 24 (7 by maintainers)

Most upvoted comments

This is a bug in packaging.

I’ve created an issue on packaging side, let’s see if there’s any feedback there: https://github.com/pypa/packaging/issues/788

Sorry for doing a second u-turn on this conversation, I really should have waited until I had time to carefully read through everything before posting, I will try and learn from this.

But I remember why I linked the packse issue now, it linked to an important comment that was in a conversation I was involved in on this: https://github.com/pypa/packaging/issues/776#issuecomment-1900515985

In particular from @dstufft

I think this makes general sense, !=1.0a1 is explicitly saying you don’t want that version, so it not requesting prereleases, while ==1.0a1 is explicitly saying you do want that version, so it is requesting prereleases.

The > and < operators kind of sit in a weird place though, >1.0a1 is not requesting 1.0a1, it’s requesting anything of a higher version than that, so it currently does not trigger the “user has requested prereleases” logic, but that creates a weird asymmetry with >=1.0a1. To make matters worse, It’s not possible to get a prerelease version that is less than 1.0a0.dev0, so does <1.0a0.dev0 include or exclude prereleases?

So it is intentional that packaging does not include 3.0.0a7 here:

>>> from packaging.specifiers import SpecifierSet
>>> SpecifierSet('<3.0.0a8').contains("3.0.0a7")
False
>>> SpecifierSet('<=3.0.0a8').contains("3.0.0a7")
True

And this is where pip inherits it’s behavior from, therefore at least according to the person who wrote the spec, uv would not be following it.

I’m going to close as “working as intended”.

Okay I’m starting to get (a bit) lost here – when asking the cython<3.0.0a8 question, doesn’t this imply that the user does not want 3.0.0a8? And therefore, it’s not explicitly “requested” per se, but more like explicitly “excluded” to be downloaded/installed? If I were to ask for cython<=3.0.0a8, I’m surely going to get 3.0.0a8, as expected.

Yes, 3.0.0a8 should absolutely be excluded given cython<3.0.0a8. What’s being discussed here is whether 3.0.0a7 should be included, given cython<3.0.0a8. The PEP says: yes, it should be included in the specifier.

Might be worth looking at: https://discuss.python.org/t/handling-of-pre-releases-when-backtracking/40505/22

Being the person who opened that thread, I strongly came away with the opinion of “the spec is ambiguous” in a lot of these prerelease cases 😔, and pip has not reviewed if it is closely following it where it’s not ambiguous (there’s several open issues on pip side, both before this thread and after it that I’ve created).

I’m a little confused – uv pip install "cython<3.0.0a8" returning cython-3.0a7 is right, per the spec, isn’t it?

The exclusive ordered comparison <V MUST NOT allow a pre-release of the specified version unless the specified version is itself a pre-release.

Pip doesn’t include prereleases when using exclusive ordered comparison, e.g. <.

It does include prereleases when using inclusive ordered comparison, e.g. <=.

I beleive pip is following the spec here and uv is probably wrong, but the spec is open to some interpretation. I wrote about this issue here: https://github.com/astral-sh/packse/issues/161, but hadn’t got round to making a specific uv issue yet.

I think you could add a constraints file:

cython<3

And then pass it when you install, like: uv pip install -r requirements.txt -c constraints.txt