diff --git a/pip_api/_installed_distributions.py b/pip_api/_installed_distributions.py index 6c6b17d..92bd58a 100644 --- a/pip_api/_installed_distributions.py +++ b/pip_api/_installed_distributions.py @@ -11,17 +11,31 @@ class Distribution: - def __init__(self, name: str, version: str, location: Optional[str] = None): + def __init__( + self, + name: str, + version: str, + location: Optional[str] = None, + editable_project_location: Optional[str] = None, + ): self.name = name self.version = parse(version) self.location = location - self.editable = bool(self.location) + self.editable_project_location = editable_project_location + + if pip_api.PIP_VERSION >= parse("21.3"): + self.editable = bool(self.editable_project_location) + else: + self.editable = bool(self.location) def __repr__(self): - return "".format( + return "".format( self.name, self.version, ", location='{}'".format(self.location) if self.location else "", + ", editable_project_location='{}'".format(self.editable_project_location) + if self.editable_project_location + else "", ) @@ -71,10 +85,14 @@ def _new_installed_distributions(local: bool, paths: List[os.PathLike]): # The returned JSON is an array of objects, each of which looks like this: # { "name": "some-package", "version": "0.0.1", "location": "/path/", ... } # The location key was introduced with pip 10.0.0b1, so we don't assume its - # presence. + # presence. The editable_project_location key was introduced with pip 21.3, + # so we also don't assume its presence. for raw_dist in json.loads(result): dist = Distribution( - raw_dist["name"], raw_dist["version"], raw_dist.get("location") + raw_dist["name"], + raw_dist["version"], + raw_dist.get("location"), + raw_dist.get("editable_project_location"), ) ret[dist.name] = dist diff --git a/tests/conftest.py b/tests/conftest.py index 264d5f6..6f35993 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -18,6 +18,17 @@ def some_distribution(data): version=Version("0.0.1"), location=None, filename=data.join("dummyproject-0.0.1-py3-none-any.whl"), + editable=False, + ) + + +@pytest.fixture +def some_editable_distribution(data): + return pretend.stub( + name="dummyproject", + version=Version("0.0.1"), + location=None, + filename=data.join("dummyproject"), editable=True, ) diff --git a/tests/data/dummyproject/setup.py b/tests/data/dummyproject/setup.py new file mode 100644 index 0000000..2624880 --- /dev/null +++ b/tests/data/dummyproject/setup.py @@ -0,0 +1,7 @@ +from distutils.core import setup + +setup( + name="dummyproject", + version="1.0", + py_modules=["dummyproject"], +) diff --git a/tests/test_installed_distributions.py b/tests/test_installed_distributions.py index 064cdda..e52f859 100644 --- a/tests/test_installed_distributions.py +++ b/tests/test_installed_distributions.py @@ -27,11 +27,15 @@ def test_installed_distributions(pip, some_distribution): assert distribution.version == some_distribution.version # Various versions of `pip` have different behavior here: + # * `pip` 21.3 and newer include the `editable_project_location` key in the JSON output # * `pip` 10.0.0b1 and newer include the `location` key in the JSON output # * `pip` 9.0.0 through 10.0.0b0 support JSON, but don't include `location` # * `pip` versions before 9.0.0 don't support JSON and don't include # any location information in the textual output that we parse - if pip_api.PIP_VERSION >= parse("10.0.0b0"): + if pip_api.PIP_VERSION >= parse("21.3"): + assert os.path.exists(distribution.location) + assert not distribution.editable + elif pip_api.PIP_VERSION >= parse("10.0.0b0"): # We don't know exactly where the distribution has been installed, # but we know it exists and therefore is editable. assert os.path.exists(distribution.location) @@ -41,6 +45,20 @@ def test_installed_distributions(pip, some_distribution): assert not distribution.editable +def test_installed_distributions_editable(pip, some_editable_distribution): + # `pip` 21.3 and newer include the `editable_project_location` key in the JSON output + if pip_api.PIP_VERSION >= parse("21.3"): + pip.run("install", "--editable", some_editable_distribution.filename) + + distributions = pip_api.installed_distributions() + assert some_editable_distribution.name in distributions + distribution = distributions[some_editable_distribution.name] + + assert distribution.editable_project_location is not None + assert os.path.exists(distribution.editable_project_location) + assert distribution.editable + + def test_installed_distributions_legacy_version(pip, data): distributions = pip_api.installed_distributions()