Repository: python-poetry/poetry

Issue:
Poetry 1.1.0a2 includes python code in the version string
To reproduce:

1. `poetry init` with default choices; no dependencies.
1. `poetry add pytest-cov`
1. `poetry install`

The last command prints:
```
Installing dependencies from lock file

Package operations: 0 installs, 1 update, 0 removals

- Updating pytest-cov (2.10.0 import os, sys;exec('if \'COV_CORE_SOURCE\' in os.environ:\n try:\n  from pytest_cov.embed import init\n  init()\n except Exception as exc:\n  sys.stderr.write(\n   "pytest-cov: Failed to setup subprocess coverage. "\n   "Environ: {0!r} "\n   "Exception: {1!r}\\n".format(\n    dict((k, v) for k, v in os.environ.items() if k.startswith(\'COV_CORE\')),\n    exc\n   )\n  )\n') -> 2.10.0) 
```


Discussion:
This means that `pytest-cov` is always updated on install.
The root cause of the issue is the content of the `pytest-cov.pth` file.
```py
import os, sys;exec('if \'COV_CORE_SOURCE\' in os.environ:\n try:\n  from pytest_cov.embed import init\n  init()\n except Exception as exc:\n  sys.stderr.write(\n   "pytest-cov: Failed to setup subprocess coverage. "\n   "Environ: {0!r} "\n   "Exception: {1!r}\\n".format(\n    dict((k, v) for k, v in os.environ.items() if k.startswith(\'COV_CORE\')),\n    exc\n   )\n  )\n')
```

Poetry, when detecting installed packages attempts to read the `pth` file associated with the package if one is available. However, we do not execute the code to detect dynamic path not doe we handle multiple directories. This could be done, but might open up a few other concerns. This part of the code needs to be revisited at somepoint anyway. As a temporary fix, we can simply ignore the file if it is not a valid directory. However, a proper fix will need to take into consideration that this file [can contain executable code as well as multiple entries](https://docs.python.org/3/library/site.html).

https://github.com/python-poetry/poetry/blob/687a897e85ce1668ffee746bf050cc9f0bdf0fb1/poetry/repositories/installed_repository.py#L52-L63

Resolving patch:
diff --git a/poetry/repositories/installed_repository.py b/poetry/repositories/installed_repository.py
--- a/poetry/repositories/installed_repository.py
+++ b/poetry/repositories/installed_repository.py
@@ -1,3 +1,5 @@
+from typing import Set
+
 from poetry.core.packages import Package
 from poetry.utils._compat import Path
 from poetry.utils._compat import metadata
@@ -10,6 +12,37 @@
 
 
 class InstalledRepository(Repository):
+    @classmethod
+    def get_package_paths(cls, sitedir, name):  # type: (Path, str) -> Set[Path]
+        """
+        Process a .pth file within the site-packages directory, and return any valid
+        paths. We skip executable .pth files as there is no reliable means to do this
+        without side-effects to current run-time. Mo check is made that the item refers
+        to a directory rather than a file, however, in order to maintain backwards
+        compatibility, we allow non-existing paths to be discovered. The latter
+        behaviour is different to how Python's site-specific hook configuration works.
+
+        Reference: https://docs.python.org/3.8/library/site.html
+
+        :param sitedir: The site-packages directory to search for .pth file.
+        :param name: The name of the package to search .pth file for.
+        :return: A `Set` of valid `Path` objects.
+        """
+        paths = set()
+
+        pth_file = sitedir.joinpath("{}.pth".format(name))
+        if pth_file.exists():
+            with pth_file.open() as f:
+                for line in f:
+                    line = line.strip()
+                    if line and not line.startswith(("#", "import ", "import\t")):
+                        path = Path(line)
+                        if not path.is_absolute():
+                            path = sitedir.joinpath(path)
+                        paths.add(path)
+
+        return paths
+
     @classmethod
     def load(cls, env):  # type: (Env) -> InstalledRepository
         """
@@ -49,19 +82,14 @@ def load(cls, env):  # type: (Env) -> InstalledRepository
                     is_standard_package = False
 
                 if is_standard_package:
-                    if (
-                        path.name.endswith(".dist-info")
-                        and env.site_packages.joinpath(
-                            "{}.pth".format(package.pretty_name)
-                        ).exists()
-                    ):
-                        with env.site_packages.joinpath(
-                            "{}.pth".format(package.pretty_name)
-                        ).open() as f:
-                            directory = Path(f.readline().strip())
+                    if path.name.endswith(".dist-info"):
+                        paths = cls.get_package_paths(
+                            sitedir=env.site_packages, name=package.pretty_name
+                        )
+                        if paths:
+                            # TODO: handle multiple source directories?
                             package.source_type = "directory"
-                            package.source_url = directory.as_posix()
-
+                            package.source_url = paths.pop().as_posix()
                     continue
 
                 src_path = env.path / "src"


Test patch:
diff --git a/tests/repositories/fixtures/installed/lib/python3.7/site-packages/editable-with-import-2.3.4.dist-info/METADATA b/tests/repositories/fixtures/installed/lib/python3.7/site-packages/editable-with-import-2.3.4.dist-info/METADATA
new file mode 100644
--- /dev/null
+++ b/tests/repositories/fixtures/installed/lib/python3.7/site-packages/editable-with-import-2.3.4.dist-info/METADATA
@@ -0,0 +1,22 @@
+Metadata-Version: 2.1
+Name: editable-with-import
+Version: 2.3.4
+Summary: Editable description.
+License: MIT
+Keywords: cli,commands
+Author: Foo Bar
+Author-email: foo@bar.com
+Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*
+Classifier: License :: OSI Approved :: MIT License
+Classifier: Programming Language :: Python :: 2
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.4
+Classifier: Programming Language :: Python :: 3.5
+Classifier: Programming Language :: Python :: 3.6
+Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
+Description-Content-Type: text/x-rst
+
+Editable
+####
diff --git a/tests/repositories/fixtures/installed/lib/python3.7/site-packages/editable-with-import.pth b/tests/repositories/fixtures/installed/lib/python3.7/site-packages/editable-with-import.pth
new file mode 100644
--- /dev/null
+++ b/tests/repositories/fixtures/installed/lib/python3.7/site-packages/editable-with-import.pth
@@ -0,0 +1 @@
+import os
diff --git a/tests/repositories/test_installed_repository.py b/tests/repositories/test_installed_repository.py
--- a/tests/repositories/test_installed_repository.py
+++ b/tests/repositories/test_installed_repository.py
@@ -1,8 +1,12 @@
+import pytest
+
 from poetry.repositories.installed_repository import InstalledRepository
+from poetry.utils._compat import PY36
 from poetry.utils._compat import Path
 from poetry.utils._compat import metadata
 from poetry.utils._compat import zipp
 from poetry.utils.env import MockEnv as BaseMockEnv
+from pytest_mock.plugin import MockFixture
 
 
 FIXTURES_DIR = Path(__file__).parent / "fixtures"
@@ -18,6 +22,7 @@
     ),
     metadata.PathDistribution(VENDOR_DIR / "attrs-19.3.0.dist-info"),
     metadata.PathDistribution(SITE_PACKAGES / "editable-2.3.4.dist-info"),
+    metadata.PathDistribution(SITE_PACKAGES / "editable-with-import-2.3.4.dist-info"),
 ]
 
 
@@ -27,7 +32,13 @@ def site_packages(self):  # type: () -> Path
         return SITE_PACKAGES
 
 
-def test_load(mocker):
+@pytest.fixture
+def env():  # type: () -> MockEnv
+    return MockEnv(path=ENV_DIR)
+
+
+@pytest.fixture
+def repository(mocker, env):  # type: (MockFixture, MockEnv) -> InstalledRepository
     mocker.patch(
         "poetry.utils._compat.metadata.Distribution.discover",
         return_value=INSTALLED_RESULTS,
@@ -44,10 +55,19 @@ def test_load(mocker):
         ],
     )
     mocker.patch("poetry.repositories.installed_repository._VENDORS", str(VENDOR_DIR))
-    repository = InstalledRepository.load(MockEnv(path=ENV_DIR))
+    return InstalledRepository.load(env)
+
+
+def test_load_successful(repository):
+    assert len(repository.packages) == 5
+
+
+def test_load_ensure_isolation(repository):
+    for pkg in repository.packages:
+        assert pkg.name != "attrs"
 
-    assert len(repository.packages) == 4
 
+def test_load_standard_package(repository):
     cleo = repository.packages[0]
     assert cleo.name == "cleo"
     assert cleo.version.text == "0.7.6"
@@ -56,11 +76,13 @@ def test_load(mocker):
         == "Cleo allows you to create beautiful and testable command-line interfaces."
     )
 
-    foo = repository.packages[2]
+    foo = repository.packages[3]
     assert foo.name == "foo"
     assert foo.version.text == "0.1.0"
 
-    pendulum = repository.packages[3]
+
+def test_load_git_package(repository):
+    pendulum = repository.packages[4]
     assert pendulum.name == "pendulum"
     assert pendulum.version.text == "2.0.5"
     assert pendulum.description == "Python datetimes made easy"
@@ -68,11 +90,26 @@ def test_load(mocker):
     assert pendulum.source_url == "https://github.com/sdispater/pendulum.git"
     assert pendulum.source_reference == "bb058f6b78b2d28ef5d9a5e759cfa179a1a713d6"
 
-    for pkg in repository.packages:
-        assert pkg.name != "attrs"
 
+@pytest.mark.skipif(
+    not PY36, reason="pathlib.resolve() does not support strict argument"
+)
+def test_load_editable_package(repository):
+    # test editable package with text .pth file
     editable = repository.packages[1]
-    assert "editable" == editable.name
-    assert "2.3.4" == editable.version.text
-    assert "directory" == editable.source_type
-    assert "/path/to/editable" == editable.source_url
+    assert editable.name == "editable"
+    assert editable.version.text == "2.3.4"
+    assert editable.source_type == "directory"
+    assert (
+        editable.source_url
+        == Path("/path/to/editable").resolve(strict=False).as_posix()
+    )
+
+
+def test_load_editable_with_import_package(repository):
+    # test editable package with executable .pth file
+    editable = repository.packages[2]
+    assert editable.name == "editable-with-import"
+    assert editable.version.text == "2.3.4"
+    assert editable.source_type == ""
+    assert editable.source_url == ""