base.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. import logging
  2. import re
  3. from typing import Container, Iterator, List, Optional, Union
  4. from pip._vendor.packaging.version import LegacyVersion, Version
  5. from pip._internal.utils.misc import stdlib_pkgs # TODO: Move definition here.
  6. DistributionVersion = Union[LegacyVersion, Version]
  7. logger = logging.getLogger(__name__)
  8. class BaseDistribution:
  9. @property
  10. def location(self):
  11. # type: () -> Optional[str]
  12. """Where the distribution is loaded from.
  13. A string value is not necessarily a filesystem path, since distributions
  14. can be loaded from other sources, e.g. arbitrary zip archives. ``None``
  15. means the distribution is created in-memory.
  16. """
  17. raise NotImplementedError()
  18. @property
  19. def metadata_version(self):
  20. # type: () -> Optional[str]
  21. """Value of "Metadata-Version:" in the distribution, if available."""
  22. raise NotImplementedError()
  23. @property
  24. def canonical_name(self):
  25. # type: () -> str
  26. raise NotImplementedError()
  27. @property
  28. def version(self):
  29. # type: () -> DistributionVersion
  30. raise NotImplementedError()
  31. @property
  32. def installer(self):
  33. # type: () -> str
  34. raise NotImplementedError()
  35. @property
  36. def editable(self):
  37. # type: () -> bool
  38. raise NotImplementedError()
  39. @property
  40. def local(self):
  41. # type: () -> bool
  42. raise NotImplementedError()
  43. @property
  44. def in_usersite(self):
  45. # type: () -> bool
  46. raise NotImplementedError()
  47. class BaseEnvironment:
  48. """An environment containing distributions to introspect."""
  49. @classmethod
  50. def default(cls):
  51. # type: () -> BaseEnvironment
  52. raise NotImplementedError()
  53. @classmethod
  54. def from_paths(cls, paths):
  55. # type: (Optional[List[str]]) -> BaseEnvironment
  56. raise NotImplementedError()
  57. def get_distribution(self, name):
  58. # type: (str) -> Optional[BaseDistribution]
  59. """Given a requirement name, return the installed distributions."""
  60. raise NotImplementedError()
  61. def _iter_distributions(self):
  62. # type: () -> Iterator[BaseDistribution]
  63. """Iterate through installed distributions.
  64. This function should be implemented by subclass, but never called
  65. directly. Use the public ``iter_distribution()`` instead, which
  66. implements additional logic to make sure the distributions are valid.
  67. """
  68. raise NotImplementedError()
  69. def iter_distributions(self):
  70. # type: () -> Iterator[BaseDistribution]
  71. """Iterate through installed distributions."""
  72. for dist in self._iter_distributions():
  73. # Make sure the distribution actually comes from a valid Python
  74. # packaging distribution. Pip's AdjacentTempDirectory leaves folders
  75. # e.g. ``~atplotlib.dist-info`` if cleanup was interrupted. The
  76. # valid project name pattern is taken from PEP 508.
  77. project_name_valid = re.match(
  78. r"^([A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9])$",
  79. dist.canonical_name,
  80. flags=re.IGNORECASE,
  81. )
  82. if not project_name_valid:
  83. logger.warning(
  84. "Ignoring invalid distribution %s (%s)",
  85. dist.canonical_name,
  86. dist.location,
  87. )
  88. continue
  89. yield dist
  90. def iter_installed_distributions(
  91. self,
  92. local_only=True, # type: bool
  93. skip=stdlib_pkgs, # type: Container[str]
  94. include_editables=True, # type: bool
  95. editables_only=False, # type: bool
  96. user_only=False, # type: bool
  97. ):
  98. # type: (...) -> Iterator[BaseDistribution]
  99. """Return a list of installed distributions.
  100. :param local_only: If True (default), only return installations
  101. local to the current virtualenv, if in a virtualenv.
  102. :param skip: An iterable of canonicalized project names to ignore;
  103. defaults to ``stdlib_pkgs``.
  104. :param include_editables: If False, don't report editables.
  105. :param editables_only: If True, only report editables.
  106. :param user_only: If True, only report installations in the user
  107. site directory.
  108. """
  109. it = self.iter_distributions()
  110. if local_only:
  111. it = (d for d in it if d.local)
  112. if not include_editables:
  113. it = (d for d in it if not d.editable)
  114. if editables_only:
  115. it = (d for d in it if d.editable)
  116. if user_only:
  117. it = (d for d in it if d.in_usersite)
  118. return (d for d in it if d.canonical_name not in skip)