pixi icon indicating copy to clipboard operation
pixi copied to clipboard

Multiple dev packages with hard pins cannot be resolved without override

Open jacobtomlinson opened this issue 4 months ago • 6 comments

Checks

  • [x] I have checked that this issue has not already been reported.

  • [x] I have confirmed this bug exists on the latest version of pixi, using pixi --version.

Reproducible example

I am working on two Python packages which have a hard dependency on each other dask and distributed. These two packages are released in lock step and the pyproject.toml of each package is updated to depend on each other.

Currently the latest release is 2025.11.0, so dask depends on distributed==2025.11.0 and distributed depends on dask==2025.11.0. I usually develop these in a single conda environment with both packages installed via pip install -e .. After @lucascolley gave a neat EuroSciPy demo of using pixi to develop with multiple packages installed from source I've been meaning to give this a go.

However, with this hard pinning pixi doesn't seem to realise that both projects are installed from source, so it's failing to resolve. Both packages use setuptools-scm to dynamically set the version number from git, so once there are commits on main the versions look something like 2025.11.1.dev2+gc7d9c55e5.

mkdir pixidasktest && cd pixidasktest

git clone [email protected]:dask/dask.git
git clone [email protected]:dask/distributed.git

touch pixi.toml
# pixi.toml
[workspace]
channels = ["conda-forge"]
platforms = ["osx-arm64"]

[dependencies]
python = "==3.13"

[pypi-dependencies]
dask = { path = "./dask", editable = true }
distributed = { path = "./distributed", editable = true }
$ pixi shell
Error:   × failed to solve the pypi requirements of 'default' 'osx-arm64'
  ├─▶ failed to resolve pypi dependencies
  ╰─▶ Because there is no version of dask==2025.11.0 and distributed==2025.11.1.dev2+gc7d9c55e5 depends on dask==2025.11.0, we can conclude that
      distributed==2025.11.1.dev2+gc7d9c55e5 cannot be used.
      And because only distributed==2025.11.1.dev2+gc7d9c55e5 is available and you require distributed, we can conclude that your requirements
      are unsatisfiable.
$ pixi info                                                                                                                               took 3s
System
------------
       Pixi version: 0.59.0
           Platform: osx-arm64
   Virtual packages: __unix=0=0
                   : __osx=26.0.1=0
                   : __archspec=1=m1
          Cache dir: /Users/jtomlinson/Library/Caches/rattler/cache
       Auth storage: /Users/jtomlinson/.rattler/credentials.json
   Config locations: /Users/jtomlinson/.pixi/config.toml

Global
------------
            Bin dir: /Users/jtomlinson/.pixi/bin
    Environment dir: /Users/jtomlinson/.pixi/envs
       Manifest dir: /Users/jtomlinson/.pixi/manifests/pixi-global.toml

Workspace
------------
               Name: pixitest
      Manifest file: /Users/jtomlinson/Scratch/pixidasktest/pixi.toml

Environments
------------
        Environment: default
           Features: default
           Channels: conda-forge
   Dependency count: 1
       Dependencies: python
  PyPI Dependencies: dask, distributed
   Target platforms: osx-arm64
    Prefix location: /Users/jtomlinson/Scratch/pixidasktest/.pixi/envs/default

Is there a way of telling pixi that the packages can relax their hard pins on each other because they are both installed from source?

Issue description

No response

Expected behavior

I would expect pixi to see that dask depends on distributed==2025.11.0, however in pixi.toml there is already distributed editable installed from source so that takes precedence.

jacobtomlinson avatar Nov 13 '25 15:11 jacobtomlinson

cc @VeckoTheGecko I know you have been working with these packages in the xarray Pixi setup somewhat

lucascolley avatar Nov 13 '25 15:11 lucascolley

probably there is some way along the lines of https://github.com/pydata/xarray/pull/10888/commits/8abc993be1519571a570a023d993a1084a5078ae to get around this using https://pixi.sh/latest/advanced/override/

lucascolley avatar Nov 13 '25 15:11 lucascolley

probably there is some way along the lines of pydata/xarray@8abc993 to get around this using pixi.sh/latest/advanced/override

Exactly this - distributed pins the version of dask so you have to override it

VeckoTheGecko avatar Nov 13 '25 15:11 VeckoTheGecko

Thanks @VeckoTheGecko @lucascolley. I found that specifying the packages again as overrides gets things working. It's a shame this doesn't happen automatically for packages installed from source like this, that's what I would've expected to happen.

[workspace]
channels = ["conda-forge"]
platforms = ["osx-arm64"]

[dependencies]
python = "==3.13"

[pypi-dependencies]
dask = { path = "./dask", editable = true }
distributed = { path = "./distributed", editable = true }

[pypi-options.dependency-overrides]
dask = { path = "./dask", editable = true }
distributed = { path = "./distributed", editable = true }

jacobtomlinson avatar Nov 14 '25 10:11 jacobtomlinson

It's a shame this doesn't happen automatically for packages installed from source like this

Going out on a limb here - I disagree with this.

2025.11.1.dev2+gc7d9c55e5 should not satisfy the hard pin of ==2025.11.1 since its just not that version. It could, however, satisfy >=2025.11.1,<2025.11.2.

Explicitly overriding it is a feature I think - but if you want you can update your pyproject.toml to the above pin if you want to avoid explicitly specifying it (I haven't tested, but I think that should work)

Note that Pixi defers to UV for Pypi - so this is actually upstream.

UV reproducer https://github.com/pydata/xarray/pull/10888#issuecomment-3516452010

VeckoTheGecko avatar Nov 14 '25 10:11 VeckoTheGecko

2025.11.1.dev2+gc7d9c55e5 should not satisfy the hard pin of ==2025.11.1 since its just not that version. It could, however, satisfy >=2025.11.1,<2025.11.2.

I hear what you're saying, but I've already explicitly put dask = { path = "./dask", editable = true }. So it doesn't really matter if another package depends on a hard pinned version of dask (or any other constraint for that matter), by installing as editable I'm implicitly overriding everything else. I can't imagine a situation where you would want something else to happen.

I appreciate that Python says explicit > implicit, but this seems like one of those situations where there's only one possible course of action so implicit is fine.

jacobtomlinson avatar Nov 14 '25 11:11 jacobtomlinson

We follow uv in this case so I think we'll wait for a solution there. In the mean time the dependency overrides seem like a good solution that follows proper semantics.

baszalmstra avatar Nov 21 '25 12:11 baszalmstra