m2m模型翻译
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

296 lines
9.1 KiB

6 months ago
  1. import errno
  2. import itertools
  3. import logging
  4. import os.path
  5. import tempfile
  6. import traceback
  7. from contextlib import ExitStack, contextmanager
  8. from pathlib import Path
  9. from typing import (
  10. Any,
  11. Callable,
  12. Dict,
  13. Generator,
  14. List,
  15. Optional,
  16. TypeVar,
  17. Union,
  18. )
  19. from pip._internal.utils.misc import enum, rmtree
  20. logger = logging.getLogger(__name__)
  21. _T = TypeVar("_T", bound="TempDirectory")
  22. # Kinds of temporary directories. Only needed for ones that are
  23. # globally-managed.
  24. tempdir_kinds = enum(
  25. BUILD_ENV="build-env",
  26. EPHEM_WHEEL_CACHE="ephem-wheel-cache",
  27. REQ_BUILD="req-build",
  28. )
  29. _tempdir_manager: Optional[ExitStack] = None
  30. @contextmanager
  31. def global_tempdir_manager() -> Generator[None, None, None]:
  32. global _tempdir_manager
  33. with ExitStack() as stack:
  34. old_tempdir_manager, _tempdir_manager = _tempdir_manager, stack
  35. try:
  36. yield
  37. finally:
  38. _tempdir_manager = old_tempdir_manager
  39. class TempDirectoryTypeRegistry:
  40. """Manages temp directory behavior"""
  41. def __init__(self) -> None:
  42. self._should_delete: Dict[str, bool] = {}
  43. def set_delete(self, kind: str, value: bool) -> None:
  44. """Indicate whether a TempDirectory of the given kind should be
  45. auto-deleted.
  46. """
  47. self._should_delete[kind] = value
  48. def get_delete(self, kind: str) -> bool:
  49. """Get configured auto-delete flag for a given TempDirectory type,
  50. default True.
  51. """
  52. return self._should_delete.get(kind, True)
  53. _tempdir_registry: Optional[TempDirectoryTypeRegistry] = None
  54. @contextmanager
  55. def tempdir_registry() -> Generator[TempDirectoryTypeRegistry, None, None]:
  56. """Provides a scoped global tempdir registry that can be used to dictate
  57. whether directories should be deleted.
  58. """
  59. global _tempdir_registry
  60. old_tempdir_registry = _tempdir_registry
  61. _tempdir_registry = TempDirectoryTypeRegistry()
  62. try:
  63. yield _tempdir_registry
  64. finally:
  65. _tempdir_registry = old_tempdir_registry
  66. class _Default:
  67. pass
  68. _default = _Default()
  69. class TempDirectory:
  70. """Helper class that owns and cleans up a temporary directory.
  71. This class can be used as a context manager or as an OO representation of a
  72. temporary directory.
  73. Attributes:
  74. path
  75. Location to the created temporary directory
  76. delete
  77. Whether the directory should be deleted when exiting
  78. (when used as a contextmanager)
  79. Methods:
  80. cleanup()
  81. Deletes the temporary directory
  82. When used as a context manager, if the delete attribute is True, on
  83. exiting the context the temporary directory is deleted.
  84. """
  85. def __init__(
  86. self,
  87. path: Optional[str] = None,
  88. delete: Union[bool, None, _Default] = _default,
  89. kind: str = "temp",
  90. globally_managed: bool = False,
  91. ignore_cleanup_errors: bool = True,
  92. ):
  93. super().__init__()
  94. if delete is _default:
  95. if path is not None:
  96. # If we were given an explicit directory, resolve delete option
  97. # now.
  98. delete = False
  99. else:
  100. # Otherwise, we wait until cleanup and see what
  101. # tempdir_registry says.
  102. delete = None
  103. # The only time we specify path is in for editables where it
  104. # is the value of the --src option.
  105. if path is None:
  106. path = self._create(kind)
  107. self._path = path
  108. self._deleted = False
  109. self.delete = delete
  110. self.kind = kind
  111. self.ignore_cleanup_errors = ignore_cleanup_errors
  112. if globally_managed:
  113. assert _tempdir_manager is not None
  114. _tempdir_manager.enter_context(self)
  115. @property
  116. def path(self) -> str:
  117. assert not self._deleted, f"Attempted to access deleted path: {self._path}"
  118. return self._path
  119. def __repr__(self) -> str:
  120. return f"<{self.__class__.__name__} {self.path!r}>"
  121. def __enter__(self: _T) -> _T:
  122. return self
  123. def __exit__(self, exc: Any, value: Any, tb: Any) -> None:
  124. if self.delete is not None:
  125. delete = self.delete
  126. elif _tempdir_registry:
  127. delete = _tempdir_registry.get_delete(self.kind)
  128. else:
  129. delete = True
  130. if delete:
  131. self.cleanup()
  132. def _create(self, kind: str) -> str:
  133. """Create a temporary directory and store its path in self.path"""
  134. # We realpath here because some systems have their default tmpdir
  135. # symlinked to another directory. This tends to confuse build
  136. # scripts, so we canonicalize the path by traversing potential
  137. # symlinks here.
  138. path = os.path.realpath(tempfile.mkdtemp(prefix=f"pip-{kind}-"))
  139. logger.debug("Created temporary directory: %s", path)
  140. return path
  141. def cleanup(self) -> None:
  142. """Remove the temporary directory created and reset state"""
  143. self._deleted = True
  144. if not os.path.exists(self._path):
  145. return
  146. errors: List[BaseException] = []
  147. def onerror(
  148. func: Callable[..., Any],
  149. path: Path,
  150. exc_val: BaseException,
  151. ) -> None:
  152. """Log a warning for a `rmtree` error and continue"""
  153. formatted_exc = "\n".join(
  154. traceback.format_exception_only(type(exc_val), exc_val)
  155. )
  156. formatted_exc = formatted_exc.rstrip() # remove trailing new line
  157. if func in (os.unlink, os.remove, os.rmdir):
  158. logger.debug(
  159. "Failed to remove a temporary file '%s' due to %s.\n",
  160. path,
  161. formatted_exc,
  162. )
  163. else:
  164. logger.debug("%s failed with %s.", func.__qualname__, formatted_exc)
  165. errors.append(exc_val)
  166. if self.ignore_cleanup_errors:
  167. try:
  168. # first try with tenacity; retrying to handle ephemeral errors
  169. rmtree(self._path, ignore_errors=False)
  170. except OSError:
  171. # last pass ignore/log all errors
  172. rmtree(self._path, onexc=onerror)
  173. if errors:
  174. logger.warning(
  175. "Failed to remove contents in a temporary directory '%s'.\n"
  176. "You can safely remove it manually.",
  177. self._path,
  178. )
  179. else:
  180. rmtree(self._path)
  181. class AdjacentTempDirectory(TempDirectory):
  182. """Helper class that creates a temporary directory adjacent to a real one.
  183. Attributes:
  184. original
  185. The original directory to create a temp directory for.
  186. path
  187. After calling create() or entering, contains the full
  188. path to the temporary directory.
  189. delete
  190. Whether the directory should be deleted when exiting
  191. (when used as a contextmanager)
  192. """
  193. # The characters that may be used to name the temp directory
  194. # We always prepend a ~ and then rotate through these until
  195. # a usable name is found.
  196. # pkg_resources raises a different error for .dist-info folder
  197. # with leading '-' and invalid metadata
  198. LEADING_CHARS = "-~.=%0123456789"
  199. def __init__(self, original: str, delete: Optional[bool] = None) -> None:
  200. self.original = original.rstrip("/\\")
  201. super().__init__(delete=delete)
  202. @classmethod
  203. def _generate_names(cls, name: str) -> Generator[str, None, None]:
  204. """Generates a series of temporary names.
  205. The algorithm replaces the leading characters in the name
  206. with ones that are valid filesystem characters, but are not
  207. valid package names (for both Python and pip definitions of
  208. package).
  209. """
  210. for i in range(1, len(name)):
  211. for candidate in itertools.combinations_with_replacement(
  212. cls.LEADING_CHARS, i - 1
  213. ):
  214. new_name = "~" + "".join(candidate) + name[i:]
  215. if new_name != name:
  216. yield new_name
  217. # If we make it this far, we will have to make a longer name
  218. for i in range(len(cls.LEADING_CHARS)):
  219. for candidate in itertools.combinations_with_replacement(
  220. cls.LEADING_CHARS, i
  221. ):
  222. new_name = "~" + "".join(candidate) + name
  223. if new_name != name:
  224. yield new_name
  225. def _create(self, kind: str) -> str:
  226. root, name = os.path.split(self.original)
  227. for candidate in self._generate_names(name):
  228. path = os.path.join(root, candidate)
  229. try:
  230. os.mkdir(path)
  231. except OSError as ex:
  232. # Continue if the name exists already
  233. if ex.errno != errno.EEXIST:
  234. raise
  235. else:
  236. path = os.path.realpath(path)
  237. break
  238. else:
  239. # Final fallback on the default behavior.
  240. path = os.path.realpath(tempfile.mkdtemp(prefix=f"pip-{kind}-"))
  241. logger.debug("Created temporary directory: %s", path)
  242. return path