|
7 | 7 | import shutil
|
8 | 8 | import sys
|
9 | 9 | import sysconfig
|
| 10 | +import tempfile |
10 | 11 | import textwrap
|
11 | 12 |
|
12 | 13 | from contextlib import contextmanager
|
|
39 | 40 | from poetry.utils._compat import encode
|
40 | 41 | from poetry.utils._compat import list_to_shell_command
|
41 | 42 | from poetry.utils._compat import subprocess
|
| 43 | +from poetry.utils.helpers import paths_csv |
42 | 44 |
|
43 | 45 |
|
44 | 46 | GET_ENVIRONMENT_INFO = """\
|
@@ -143,6 +145,125 @@ def _version_nodot(version):
|
143 | 145 | """
|
144 | 146 |
|
145 | 147 |
|
| 148 | +class SitePackages: |
| 149 | + def __init__( |
| 150 | + self, path, fallbacks=None, skip_write_checks=False |
| 151 | + ): # type: (Path, List[Path], bool) -> None |
| 152 | + self._path = path |
| 153 | + self._fallbacks = fallbacks or [] |
| 154 | + self._skip_write_checks = skip_write_checks |
| 155 | + self._candidates = [self._path] + self._fallbacks |
| 156 | + self._writable_candidates = None if not skip_write_checks else self._candidates |
| 157 | + |
| 158 | + @property |
| 159 | + def path(self): # type: () -> Path |
| 160 | + return self._path |
| 161 | + |
| 162 | + @property |
| 163 | + def candidates(self): # type: () -> List[Path] |
| 164 | + return self._candidates |
| 165 | + |
| 166 | + @property |
| 167 | + def writable_candidates(self): # type: () -> List[Path] |
| 168 | + if self._writable_candidates is not None: |
| 169 | + return self._writable_candidates |
| 170 | + |
| 171 | + self._writable_candidates = [] |
| 172 | + for candidate in self._candidates: |
| 173 | + try: |
| 174 | + if not candidate.exists(): |
| 175 | + continue |
| 176 | + |
| 177 | + with tempfile.TemporaryFile(dir=str(candidate)): |
| 178 | + self._writable_candidates.append(candidate) |
| 179 | + except (IOError, OSError): |
| 180 | + pass |
| 181 | + |
| 182 | + return self._writable_candidates |
| 183 | + |
| 184 | + def make_candidates( |
| 185 | + self, path, writable_only=False |
| 186 | + ): # type: (Path, bool) -> List[Path] |
| 187 | + candidates = self._candidates if not writable_only else self.writable_candidates |
| 188 | + if path.is_absolute(): |
| 189 | + for candidate in candidates: |
| 190 | + try: |
| 191 | + path.relative_to(candidate) |
| 192 | + return [path] |
| 193 | + except ValueError: |
| 194 | + pass |
| 195 | + else: |
| 196 | + raise ValueError( |
| 197 | + "{} is not relative to any discovered {}sites".format( |
| 198 | + path, "writable " if writable_only else "" |
| 199 | + ) |
| 200 | + ) |
| 201 | + |
| 202 | + return [candidate / path for candidate in candidates if candidate] |
| 203 | + |
| 204 | + def _path_method_wrapper( |
| 205 | + self, path, method, *args, **kwargs |
| 206 | + ): # type: (Path, str, *Any, **Any) -> Union[Tuple[Path, Any], List[Tuple[Path, Any]]] |
| 207 | + |
| 208 | + # TODO: Move to parameters after dropping Python 2.7 |
| 209 | + return_first = kwargs.pop("return_first", True) |
| 210 | + writable_only = kwargs.pop("writable_only", False) |
| 211 | + |
| 212 | + candidates = self.make_candidates(path, writable_only=writable_only) |
| 213 | + |
| 214 | + if not candidates: |
| 215 | + raise RuntimeError( |
| 216 | + 'Unable to find a suitable destination for "{}" in {}'.format( |
| 217 | + str(path), paths_csv(self._candidates) |
| 218 | + ) |
| 219 | + ) |
| 220 | + |
| 221 | + results = [] |
| 222 | + |
| 223 | + for candidate in candidates: |
| 224 | + try: |
| 225 | + result = candidate, getattr(candidate, method)(*args, **kwargs) |
| 226 | + if return_first: |
| 227 | + return result |
| 228 | + else: |
| 229 | + results.append(result) |
| 230 | + except (IOError, OSError): |
| 231 | + # TODO: Replace with PermissionError |
| 232 | + pass |
| 233 | + |
| 234 | + if results: |
| 235 | + return results |
| 236 | + |
| 237 | + raise OSError("Unable to access any of {}".format(paths_csv(candidates))) |
| 238 | + |
| 239 | + def write_text(self, path, *args, **kwargs): # type: (Path, *Any, **Any) -> Path |
| 240 | + return self._path_method_wrapper(path, "write_text", *args, **kwargs)[0] |
| 241 | + |
| 242 | + def mkdir(self, path, *args, **kwargs): # type: (Path, *Any, **Any) -> Path |
| 243 | + return self._path_method_wrapper(path, "mkdir", *args, **kwargs)[0] |
| 244 | + |
| 245 | + def exists(self, path): # type: (Path) -> bool |
| 246 | + return any( |
| 247 | + value[-1] |
| 248 | + for value in self._path_method_wrapper(path, "exists", return_first=False) |
| 249 | + ) |
| 250 | + |
| 251 | + def find(self, path, writable_only=False): # type: (Path, bool) -> List[Path] |
| 252 | + return [ |
| 253 | + value[0] |
| 254 | + for value in self._path_method_wrapper( |
| 255 | + path, "exists", return_first=False, writable_only=writable_only |
| 256 | + ) |
| 257 | + if value[-1] is True |
| 258 | + ] |
| 259 | + |
| 260 | + def __getattr__(self, item): |
| 261 | + try: |
| 262 | + return super(SitePackages, self).__getattribute__(item) |
| 263 | + except AttributeError: |
| 264 | + return getattr(self.path, item) |
| 265 | + |
| 266 | + |
146 | 267 | class EnvError(Exception):
|
147 | 268 |
|
148 | 269 | pass
|
@@ -810,9 +931,13 @@ def pip_version(self):
|
810 | 931 | return self._pip_version
|
811 | 932 |
|
812 | 933 | @property
|
813 |
| - def site_packages(self): # type: () -> Path |
| 934 | + def site_packages(self): # type: () -> SitePackages |
814 | 935 | if self._site_packages is None:
|
815 |
| - self._site_packages = self.purelib |
| 936 | + # we disable write checks if no user site exist |
| 937 | + fallbacks = [self.usersite] if self.usersite else [] |
| 938 | + self._site_packages = SitePackages( |
| 939 | + self.purelib, fallbacks, skip_write_checks=False if fallbacks else True |
| 940 | + ) |
816 | 941 | return self._site_packages
|
817 | 942 |
|
818 | 943 | @property
|
|
0 commit comments