Skip to content

Commit 1de8de7

Browse files
committed
pep610: handle mixed case dist-info names
In certain cases, eg: PyYAML, the package metadata is installed at `PyYAML-<version>.dist-info`. This change ensures that we detect the `direct_url.json` files correctly in these cases.
1 parent 4835c59 commit 1de8de7

File tree

2 files changed

+97
-25
lines changed

2 files changed

+97
-25
lines changed

Diff for: poetry/installation/executor.py

+6-2
Original file line numberDiff line numberDiff line change
@@ -743,7 +743,9 @@ def _save_url_reference(self, operation: "OperationTypes") -> None:
743743
# That's not what we want so we remove the direct_url.json file,
744744
# if it exists.
745745
for direct_url in self._env.site_packages.find(
746-
self._direct_url_json_path(package), True
746+
self._direct_url_json_path(package),
747+
writable_only=True,
748+
case_insensitive=True,
747749
):
748750
direct_url.unlink()
749751

@@ -762,7 +764,9 @@ def _save_url_reference(self, operation: "OperationTypes") -> None:
762764

763765
if url_reference:
764766
for path in self._env.site_packages.find(
765-
self._package_dist_info_path(package), writable_only=True
767+
self._package_dist_info_path(package),
768+
writable_only=True,
769+
case_insensitive=True,
766770
):
767771
self._env.site_packages.write_text(
768772
path / "direct_url.json",

Diff for: poetry/utils/env.py

+91-23
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,9 @@ def writable_candidates(self) -> List[Path]:
198198

199199
return self._writable_candidates
200200

201-
def make_candidates(self, path: Path, writable_only: bool = False) -> List[Path]:
201+
def make_candidates(
202+
self, path: Path, writable_only: bool = False, strict: bool = False
203+
) -> List[Path]:
202204
candidates = self._candidates if not writable_only else self.writable_candidates
203205
if path.is_absolute():
204206
for candidate in candidates:
@@ -214,7 +216,71 @@ def make_candidates(self, path: Path, writable_only: bool = False) -> List[Path]
214216
)
215217
)
216218

217-
return [candidate / path for candidate in candidates if candidate]
219+
results = [candidate / path for candidate in candidates if candidate]
220+
221+
if not results and strict:
222+
raise RuntimeError(
223+
'Unable to find a suitable destination for "{}" in {}'.format(
224+
str(path), paths_csv(self._candidates)
225+
)
226+
)
227+
228+
return results
229+
230+
def _exists_case_insensitive(
231+
self,
232+
path: Union[str, Path],
233+
return_first: bool = True,
234+
writable_only: bool = False,
235+
) -> Union[Tuple[Path, Any], List[Tuple[Path, Any]]]:
236+
if isinstance(path, str):
237+
path = Path(path)
238+
239+
candidates = self._candidates if not writable_only else self.writable_candidates
240+
241+
if path.is_absolute():
242+
for candidate in candidates:
243+
try:
244+
# we set the path to be relative here
245+
path = path.relative_to(candidate)
246+
# we consider only one candidate as the path is relative to it
247+
candidates = [candidate]
248+
break
249+
except ValueError:
250+
pass
251+
else:
252+
raise ValueError(
253+
"{} is not relative to any discovered {}sites".format(
254+
path, "writable " if writable_only else ""
255+
)
256+
)
257+
258+
results = []
259+
260+
for candidate in candidates:
261+
try:
262+
# because this is a case insensitive search, we use posix lower form
263+
path_posix_lower = candidate.joinpath(path).as_posix().lower()
264+
265+
for match in candidate.glob("**/*"):
266+
if path_posix_lower == match.as_posix().lower():
267+
# a case insensitive match was found, we return the matched path
268+
# (case sensitive) as this is used by the caller
269+
result = match, True
270+
if return_first:
271+
return result
272+
results.append(result)
273+
break
274+
else:
275+
results.append((candidate.joinpath(path), False))
276+
except OSError:
277+
# TODO: Replace with PermissionError
278+
pass
279+
280+
if results:
281+
return results
282+
283+
raise OSError("Unable to access any of {}".format(paths_csv(candidates)))
218284

219285
def _path_method_wrapper(
220286
self,
@@ -228,14 +294,9 @@ def _path_method_wrapper(
228294
if isinstance(path, str):
229295
path = Path(path)
230296

231-
candidates = self.make_candidates(path, writable_only=writable_only)
232-
233-
if not candidates:
234-
raise RuntimeError(
235-
'Unable to find a suitable destination for "{}" in {}'.format(
236-
str(path), paths_csv(self._candidates)
237-
)
238-
)
297+
candidates = self.make_candidates(
298+
path, writable_only=writable_only, strict=True
299+
)
239300

240301
results = []
241302

@@ -244,8 +305,7 @@ def _path_method_wrapper(
244305
result = candidate, getattr(candidate, method)(*args, **kwargs)
245306
if return_first:
246307
return result
247-
else:
248-
results.append(result)
308+
results.append(result)
249309
except OSError:
250310
# TODO: Replace with PermissionError
251311
pass
@@ -261,20 +321,28 @@ def write_text(self, path: Union[str, Path], *args: Any, **kwargs: Any) -> Path:
261321
def mkdir(self, path: Union[str, Path], *args: Any, **kwargs: Any) -> Path:
262322
return self._path_method_wrapper(path, "mkdir", *args, **kwargs)[0]
263323

264-
def exists(self, path: Union[str, Path]) -> bool:
265-
return any(
266-
value[-1]
267-
for value in self._path_method_wrapper(path, "exists", return_first=False)
268-
)
324+
def exists(self, path: Union[str, Path], case_insensitive: bool = False) -> bool:
325+
if case_insensitive:
326+
results = self._exists_case_insensitive(path, return_first=False)
327+
else:
328+
results = self._path_method_wrapper(path, "exists", return_first=False)
329+
return any(value[-1] for value in results)
269330

270-
def find(self, path: Union[str, Path], writable_only: bool = False) -> List[Path]:
271-
return [
272-
value[0]
273-
for value in self._path_method_wrapper(
331+
def find(
332+
self,
333+
path: Union[str, Path],
334+
writable_only: bool = False,
335+
case_insensitive: bool = False,
336+
) -> List[Path]:
337+
if case_insensitive:
338+
results = self._exists_case_insensitive(
339+
path, return_first=False, writable_only=writable_only
340+
)
341+
else:
342+
results = self._path_method_wrapper(
274343
path, "exists", return_first=False, writable_only=writable_only
275344
)
276-
if value[-1] is True
277-
]
345+
return [value[0] for value in results if value[-1] is True]
278346

279347
def __getattr__(self, item: str) -> Any:
280348
try:

0 commit comments

Comments
 (0)