Skip to content

Cannot install a direct reference dependency as an "editable" dependency #10216

Open
@AustinTSchaffer

Description

@AustinTSchaffer

Description

It seems that you can't install a package in editable mode, while simultaneously installing a package that depends on that package via a direct reference.

I came across this issue when splitting a project into 2 packages that both live in the same repository. Package A now depends on Package B via a direct reference. Since both are in the same repository, I tried installing both in editable mode, but pip came back with:

# pip install --use-feature=in-tree-build -e package_a/ -e package_b/
Obtaining file:///home/austin/workspace/pypa/test_project/package_a
Obtaining file:///home/austin/workspace/pypa/test_project/package_b
Processing ./package_b
ERROR: Cannot install package-a==1.0.0 and package-b 1.0.0 (from /home/austin/workspace/pypa/test_project/package_b) because these package versions have conflicting dependencies.

The conflict is caused by:
    The user requested package-b 1.0.0 (from /home/austin/workspace/pypa/test_project/package_b)
    package-a 1.0.0 depends on package-b 1.0.0 (from /home/austin/workspace/pypa/test_project/package_b)

To fix this you could try to:
1. loosen the range of package versions you've specified
2. remove package versions to allow pip attempt to solve the dependency conflict

ERROR: ResolutionImpossible: for help visit https://pip.pypa.io/en/latest/user_guide/#fixing-conflicting-dependencies

It seems that this action should be allowed taking the wording of the error message at face value.

I did some debugging and found that Resolution._add_to_criteria from src/pip/_vendor/resolvelib/resolvers.py raises a RequirementsConflicted exception. The resolver cannot resolve the 2 requirements below. The resolver fails to generate any candidates apparently because both requirements are instances of ExplicitRequirement.

RequirementInformation(requirement=ExplicitRequirement(EditableCandidate('file:///home/austin/workspace/pypa/test_project/project_b')), parent=None)
RequirementInformation(requirement=ExplicitRequirement(LinkCandidate('file:///home/austin/workspace/pypa/test_project/project_b')), parent=EditableCandidate('file:///home/austin/workspace/pypa/test_project/project_a'))

We have 2 workarounds for this:

  1. Remove the direct reference from package_a, and just have package_a depend on any package named package_b. This is an issue because Package A won't know where to get Package B unless you install them both at the same time with pip install -e ./package_a/ -e ./package_b/. More worrying is if someone registers a package on PyPI that matches the name of our "Package B".
  2. Install "Package A" first, then install "Package B" later. This is the solution we went with, since Pip now takes previously installed packages into consideration when solving an environment, and we keep the versions of dependencies of both packages relatively in sync. This does feel like it could cause problems down the road.

Expected behavior

I'm not sure what should be expected in this situation? It seems like one of these should be true:

  1. Pip should allow pip install -e A -e B when A depends on B via a direct reference
  2. Pip's error message for Point 1 should indicate why you can't do that
  3. Setuptools should allow you to install a direct-reference package in editable mode

Point 3 doesn't feel right, but just wanted to throw that out there.

pip version

21.3.dev0 (commit: a53f888)

Python version

3.9.5

OS

Ubuntu 21.04 hirsute

How to Reproduce

Create a directory named "package_b". Create a setup.py in that directory with the contents:

from setuptools import setup
setup(name="package_b", version="1.0.0")

Create a directory named "package_a" in the same directory that contains the "package_b" directory. Create a setup.py in that directory with the contents:

from setuptools import setup
import os
package_b_dir = os.path.dirname(__file__).replace("package_a", "package_b")
setup(
    name="package_a",
    version="1.0.0",
    install_requires=[
        "package_b @ file://%s" % package_b_dir,
    ],
)

You should now have:

.
├── package_a
│   └── setup.py
└── package_b
    └── setup.py

Now run pip install -e ./package_a/ -e ./package_b/.

Output

The output is above in the description section, but pasting it here as well:


# pip install --use-feature=in-tree-build -e package_a/ -e package_b/
Obtaining file:///home/austin/workspace/pypa/test_project/package_a
Obtaining file:///home/austin/workspace/pypa/test_project/package_b
Processing ./package_b
ERROR: Cannot install package-a==1.0.0 and package-b 1.0.0 (from /home/austin/workspace/pypa/test_project/package_b) because these package versions have conflicting dependencies.

The conflict is caused by:
    The user requested package-b 1.0.0 (from /home/austin/workspace/pypa/test_project/package_b)
    package-a 1.0.0 depends on package-b 1.0.0 (from /home/austin/workspace/pypa/test_project/package_b)

To fix this you could try to:
1. loosen the range of package versions you've specified
2. remove package versions to allow pip attempt to solve the dependency conflict

ERROR: ResolutionImpossible: for help visit https://pip.pypa.io/en/latest/user_guide/#fixing-conflicting-dependencies

Code of Conduct

Metadata

Metadata

Assignees

No one assigned

    Labels

    C: dependency resolutionAbout choosing which dependencies to installtype: bugA confirmed bug or unintended behavior

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions