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.

278 lines
9.8 KiB

6 months ago
  1. import email.message
  2. import email.parser
  3. import logging
  4. import os
  5. import zipfile
  6. from typing import Collection, Iterable, Iterator, List, Mapping, NamedTuple, Optional
  7. from pip._vendor import pkg_resources
  8. from pip._vendor.packaging.requirements import Requirement
  9. from pip._vendor.packaging.utils import NormalizedName, canonicalize_name
  10. from pip._vendor.packaging.version import parse as parse_version
  11. from pip._internal.exceptions import InvalidWheel, NoneMetadataError, UnsupportedWheel
  12. from pip._internal.utils.egg_link import egg_link_path_from_location
  13. from pip._internal.utils.misc import display_path, normalize_path
  14. from pip._internal.utils.wheel import parse_wheel, read_wheel_metadata_file
  15. from .base import (
  16. BaseDistribution,
  17. BaseEntryPoint,
  18. BaseEnvironment,
  19. DistributionVersion,
  20. InfoPath,
  21. Wheel,
  22. )
  23. __all__ = ["NAME", "Distribution", "Environment"]
  24. logger = logging.getLogger(__name__)
  25. NAME = "pkg_resources"
  26. class EntryPoint(NamedTuple):
  27. name: str
  28. value: str
  29. group: str
  30. class InMemoryMetadata:
  31. """IMetadataProvider that reads metadata files from a dictionary.
  32. This also maps metadata decoding exceptions to our internal exception type.
  33. """
  34. def __init__(self, metadata: Mapping[str, bytes], wheel_name: str) -> None:
  35. self._metadata = metadata
  36. self._wheel_name = wheel_name
  37. def has_metadata(self, name: str) -> bool:
  38. return name in self._metadata
  39. def get_metadata(self, name: str) -> str:
  40. try:
  41. return self._metadata[name].decode()
  42. except UnicodeDecodeError as e:
  43. # Augment the default error with the origin of the file.
  44. raise UnsupportedWheel(
  45. f"Error decoding metadata for {self._wheel_name}: {e} in {name} file"
  46. )
  47. def get_metadata_lines(self, name: str) -> Iterable[str]:
  48. return pkg_resources.yield_lines(self.get_metadata(name))
  49. def metadata_isdir(self, name: str) -> bool:
  50. return False
  51. def metadata_listdir(self, name: str) -> List[str]:
  52. return []
  53. def run_script(self, script_name: str, namespace: str) -> None:
  54. pass
  55. class Distribution(BaseDistribution):
  56. def __init__(self, dist: pkg_resources.Distribution) -> None:
  57. self._dist = dist
  58. @classmethod
  59. def from_directory(cls, directory: str) -> BaseDistribution:
  60. dist_dir = directory.rstrip(os.sep)
  61. # Build a PathMetadata object, from path to metadata. :wink:
  62. base_dir, dist_dir_name = os.path.split(dist_dir)
  63. metadata = pkg_resources.PathMetadata(base_dir, dist_dir)
  64. # Determine the correct Distribution object type.
  65. if dist_dir.endswith(".egg-info"):
  66. dist_cls = pkg_resources.Distribution
  67. dist_name = os.path.splitext(dist_dir_name)[0]
  68. else:
  69. assert dist_dir.endswith(".dist-info")
  70. dist_cls = pkg_resources.DistInfoDistribution
  71. dist_name = os.path.splitext(dist_dir_name)[0].split("-")[0]
  72. dist = dist_cls(base_dir, project_name=dist_name, metadata=metadata)
  73. return cls(dist)
  74. @classmethod
  75. def from_metadata_file_contents(
  76. cls,
  77. metadata_contents: bytes,
  78. filename: str,
  79. project_name: str,
  80. ) -> BaseDistribution:
  81. metadata_dict = {
  82. "METADATA": metadata_contents,
  83. }
  84. dist = pkg_resources.DistInfoDistribution(
  85. location=filename,
  86. metadata=InMemoryMetadata(metadata_dict, filename),
  87. project_name=project_name,
  88. )
  89. return cls(dist)
  90. @classmethod
  91. def from_wheel(cls, wheel: Wheel, name: str) -> BaseDistribution:
  92. try:
  93. with wheel.as_zipfile() as zf:
  94. info_dir, _ = parse_wheel(zf, name)
  95. metadata_dict = {
  96. path.split("/", 1)[-1]: read_wheel_metadata_file(zf, path)
  97. for path in zf.namelist()
  98. if path.startswith(f"{info_dir}/")
  99. }
  100. except zipfile.BadZipFile as e:
  101. raise InvalidWheel(wheel.location, name) from e
  102. except UnsupportedWheel as e:
  103. raise UnsupportedWheel(f"{name} has an invalid wheel, {e}")
  104. dist = pkg_resources.DistInfoDistribution(
  105. location=wheel.location,
  106. metadata=InMemoryMetadata(metadata_dict, wheel.location),
  107. project_name=name,
  108. )
  109. return cls(dist)
  110. @property
  111. def location(self) -> Optional[str]:
  112. return self._dist.location
  113. @property
  114. def installed_location(self) -> Optional[str]:
  115. egg_link = egg_link_path_from_location(self.raw_name)
  116. if egg_link:
  117. location = egg_link
  118. elif self.location:
  119. location = self.location
  120. else:
  121. return None
  122. return normalize_path(location)
  123. @property
  124. def info_location(self) -> Optional[str]:
  125. return self._dist.egg_info
  126. @property
  127. def installed_by_distutils(self) -> bool:
  128. # A distutils-installed distribution is provided by FileMetadata. This
  129. # provider has a "path" attribute not present anywhere else. Not the
  130. # best introspection logic, but pip has been doing this for a long time.
  131. try:
  132. return bool(self._dist._provider.path)
  133. except AttributeError:
  134. return False
  135. @property
  136. def canonical_name(self) -> NormalizedName:
  137. return canonicalize_name(self._dist.project_name)
  138. @property
  139. def version(self) -> DistributionVersion:
  140. return parse_version(self._dist.version)
  141. def is_file(self, path: InfoPath) -> bool:
  142. return self._dist.has_metadata(str(path))
  143. def iter_distutils_script_names(self) -> Iterator[str]:
  144. yield from self._dist.metadata_listdir("scripts")
  145. def read_text(self, path: InfoPath) -> str:
  146. name = str(path)
  147. if not self._dist.has_metadata(name):
  148. raise FileNotFoundError(name)
  149. content = self._dist.get_metadata(name)
  150. if content is None:
  151. raise NoneMetadataError(self, name)
  152. return content
  153. def iter_entry_points(self) -> Iterable[BaseEntryPoint]:
  154. for group, entries in self._dist.get_entry_map().items():
  155. for name, entry_point in entries.items():
  156. name, _, value = str(entry_point).partition("=")
  157. yield EntryPoint(name=name.strip(), value=value.strip(), group=group)
  158. def _metadata_impl(self) -> email.message.Message:
  159. """
  160. :raises NoneMetadataError: if the distribution reports `has_metadata()`
  161. True but `get_metadata()` returns None.
  162. """
  163. if isinstance(self._dist, pkg_resources.DistInfoDistribution):
  164. metadata_name = "METADATA"
  165. else:
  166. metadata_name = "PKG-INFO"
  167. try:
  168. metadata = self.read_text(metadata_name)
  169. except FileNotFoundError:
  170. if self.location:
  171. displaying_path = display_path(self.location)
  172. else:
  173. displaying_path = repr(self.location)
  174. logger.warning("No metadata found in %s", displaying_path)
  175. metadata = ""
  176. feed_parser = email.parser.FeedParser()
  177. feed_parser.feed(metadata)
  178. return feed_parser.close()
  179. def iter_dependencies(self, extras: Collection[str] = ()) -> Iterable[Requirement]:
  180. if extras: # pkg_resources raises on invalid extras, so we sanitize.
  181. extras = frozenset(pkg_resources.safe_extra(e) for e in extras)
  182. extras = extras.intersection(self._dist.extras)
  183. return self._dist.requires(extras)
  184. def iter_provided_extras(self) -> Iterable[str]:
  185. return self._dist.extras
  186. def is_extra_provided(self, extra: str) -> bool:
  187. return pkg_resources.safe_extra(extra) in self._dist.extras
  188. class Environment(BaseEnvironment):
  189. def __init__(self, ws: pkg_resources.WorkingSet) -> None:
  190. self._ws = ws
  191. @classmethod
  192. def default(cls) -> BaseEnvironment:
  193. return cls(pkg_resources.working_set)
  194. @classmethod
  195. def from_paths(cls, paths: Optional[List[str]]) -> BaseEnvironment:
  196. return cls(pkg_resources.WorkingSet(paths))
  197. def _iter_distributions(self) -> Iterator[BaseDistribution]:
  198. for dist in self._ws:
  199. yield Distribution(dist)
  200. def _search_distribution(self, name: str) -> Optional[BaseDistribution]:
  201. """Find a distribution matching the ``name`` in the environment.
  202. This searches from *all* distributions available in the environment, to
  203. match the behavior of ``pkg_resources.get_distribution()``.
  204. """
  205. canonical_name = canonicalize_name(name)
  206. for dist in self.iter_all_distributions():
  207. if dist.canonical_name == canonical_name:
  208. return dist
  209. return None
  210. def get_distribution(self, name: str) -> Optional[BaseDistribution]:
  211. # Search the distribution by looking through the working set.
  212. dist = self._search_distribution(name)
  213. if dist:
  214. return dist
  215. # If distribution could not be found, call working_set.require to
  216. # update the working set, and try to find the distribution again.
  217. # This might happen for e.g. when you install a package twice, once
  218. # using setup.py develop and again using setup.py install. Now when
  219. # running pip uninstall twice, the package gets removed from the
  220. # working set in the first uninstall, so we have to populate the
  221. # working set again so that pip knows about it and the packages gets
  222. # picked up and is successfully uninstalled the second time too.
  223. try:
  224. # We didn't pass in any version specifiers, so this can never
  225. # raise pkg_resources.VersionConflict.
  226. self._ws.require(name)
  227. except pkg_resources.DistributionNotFound:
  228. return None
  229. return self._search_distribution(name)