Skip to content

Commit 9e11f9d

Browse files
author
Brandon Carpenter
committed
Add support for PEP-610 editable packages (#12313)
Find paths to editable packages by looking at direct_url.json files -- installed by pip when the -e/--editable flag is used -- and add to the package search paths.
1 parent 1636a05 commit 9e11f9d

File tree

8 files changed

+78
-3
lines changed

8 files changed

+78
-3
lines changed

mypy/modulefinder.py

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
import ast
77
import collections
88
import functools
9+
import glob
10+
import json
911
import os
1012
import re
1113
import subprocess
@@ -752,7 +754,42 @@ def get_search_dirs(python_executable: Optional[str]) -> List[str]:
752754
raise CompileError(
753755
[f"mypy: Invalid python executable '{python_executable}': {reason}"]
754756
) from err
755-
return sys_path
757+
return sys_path + find_editable_dirs(sys_path)
758+
759+
760+
def find_editable_dirs(search_paths: List[str]) -> List[str]:
761+
"""Find directories of editable packages."""
762+
editable_paths = []
763+
for search_path in search_paths:
764+
for meta_path in glob.glob('*.dist-info/direct_url.json', root_dir=search_path):
765+
path = _parse_direct_url_file(os.path.join(search_path, meta_path))
766+
if path and path not in search_path and path not in editable_paths:
767+
editable_paths.append(path)
768+
return editable_paths
769+
770+
771+
def _parse_direct_url_file(path: str) -> Optional[str]:
772+
"""Get the path of an editable package using direct_url.json.
773+
774+
See https://www.python.org/dev/peps/pep-0610/
775+
"""
776+
try:
777+
file = open(path, encoding="utf-8")
778+
except OSError:
779+
return
780+
with file:
781+
try:
782+
direct_url = json.load(file)
783+
except (ValueError, IOError):
784+
return
785+
try:
786+
url = str(direct_url["url"]) if direct_url["dir_info"]["editable"] else ""
787+
except (LookupError, TypeError):
788+
return
789+
if url.startswith("file://"):
790+
path = url[7:]
791+
# Path will already be absolute, except for those in the unit tests
792+
return os.path.normpath(path)
756793

757794

758795
def add_py2_mypypath_entries(mypypath: List[str]) -> List[str]:

mypy/test/testmodulefinder.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
FindModuleCache,
66
SearchPaths,
77
ModuleNotFoundReason,
8+
find_editable_dirs,
89
)
910

1011
from mypy.test.helpers import Suite, assert_equal
@@ -153,12 +154,12 @@ def setUp(self) -> None:
153154
os.path.join(self.package_dir, "..", "not-a-directory"),
154155
os.path.join(self.package_dir, "..", "modulefinder-src"),
155156
self.package_dir,
156-
)
157+
) + tuple(find_editable_dirs([self.package_dir]))
157158

158159
self.search_paths = SearchPaths(
159160
python_path=(),
160161
mypy_path=(os.path.join(data_path, "pkg1"),),
161-
package_path=tuple(package_paths),
162+
package_path=package_paths,
162163
typeshed_path=(),
163164
)
164165
options = Options()
@@ -172,6 +173,14 @@ def setUp(self) -> None:
172173
def path(self, *parts: str) -> str:
173174
return os.path.join(self.package_dir, *parts)
174175

176+
def test__find_editable_dirs(self):
177+
found_dirs = find_editable_dirs([self.package_dir])
178+
found_dirs.sort()
179+
expected = [os.path.join(*p)
180+
for p in [("test-data", "packages", "editable_pkg_typed-src"),
181+
("test-data", "packages", "editable_pkg_untyped-src")]]
182+
assert_equal(expected, found_dirs)
183+
175184
def test__packages_with_ns(self) -> None:
176185
cases = [
177186
# Namespace package with py.typed
@@ -213,6 +222,11 @@ def test__packages_with_ns(self) -> None:
213222
("standalone", ModuleNotFoundReason.FOUND_WITHOUT_TYPE_HINTS),
214223
("standalone.standalone_var", ModuleNotFoundReason.FOUND_WITHOUT_TYPE_HINTS),
215224

225+
# Packages found by following direct_url.json files
226+
("editable_pkg_typed", os.path.join("test-data", "packages", "editable_pkg_typed-src",
227+
"editable_pkg_typed", "__init__.py")),
228+
("editable_pkg_untyped", ModuleNotFoundReason.FOUND_WITHOUT_TYPE_HINTS),
229+
216230
# Packages found by following .pth files
217231
("baz_pkg", self.path("baz", "baz_pkg", "__init__.py")),
218232
("ns_baz_pkg.a", self.path("baz", "ns_baz_pkg", "a.py")),
@@ -275,6 +289,11 @@ def test__packages_without_ns(self) -> None:
275289
("standalone", ModuleNotFoundReason.FOUND_WITHOUT_TYPE_HINTS),
276290
("standalone.standalone_var", ModuleNotFoundReason.FOUND_WITHOUT_TYPE_HINTS),
277291

292+
# Packages found by following direct_url.json files
293+
("editable_pkg_typed", os.path.join("test-data", "packages", "editable_pkg_typed-src",
294+
"editable_pkg_typed", "__init__.py")),
295+
("editable_pkg_untyped", ModuleNotFoundReason.FOUND_WITHOUT_TYPE_HINTS),
296+
278297
# Packages found by following .pth files
279298
("baz_pkg", self.path("baz", "baz_pkg", "__init__.py")),
280299
("ns_baz_pkg.a", ModuleNotFoundReason.NOT_FOUND),

test-data/packages/editable_pkg_typed-src/editable_pkg_typed/__init__.py

Whitespace-only changes.

test-data/packages/editable_pkg_typed-src/editable_pkg_typed/py.typed

Whitespace-only changes.

test-data/packages/editable_pkg_untyped-src/editable_pkg_untyped/__init__.py

Whitespace-only changes.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"url": "file://test-data/packages/editable_pkg_typed-src",
3+
"dir_info": {
4+
"editable": true
5+
}
6+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"url": "file://test-data/packages/editable_pkg_untyped-src",
3+
"dir_info": {
4+
"editable": true
5+
}
6+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"comment": "This file is in place only to ensure editable features don't break on non-editable packages.",
3+
"url": "https://packages.example.com/foo/0/foo-0.tar.gz",
4+
"archive_info": {
5+
"hash": "sha256=2dc6b5a470a1bde68946f263f1af1515a2574a150a30d6ce02c6ff742fcc0db8"
6+
}
7+
}

0 commit comments

Comments
 (0)