search_scope.py 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. import itertools
  2. import logging
  3. import os
  4. import posixpath
  5. import urllib.parse
  6. from dataclasses import dataclass
  7. from typing import List
  8. from pip._vendor.packaging.utils import canonicalize_name
  9. from pip._internal.models.index import PyPI
  10. from pip._internal.utils.compat import has_tls
  11. from pip._internal.utils.misc import normalize_path, redact_auth_from_url
  12. logger = logging.getLogger(__name__)
  13. @dataclass(frozen=True)
  14. class SearchScope:
  15. """
  16. Encapsulates the locations that pip is configured to search.
  17. """
  18. __slots__ = ["find_links", "index_urls", "no_index"]
  19. find_links: List[str]
  20. index_urls: List[str]
  21. no_index: bool
  22. @classmethod
  23. def create(
  24. cls,
  25. find_links: List[str],
  26. index_urls: List[str],
  27. no_index: bool,
  28. ) -> "SearchScope":
  29. """
  30. Create a SearchScope object after normalizing the `find_links`.
  31. """
  32. # Build find_links. If an argument starts with ~, it may be
  33. # a local file relative to a home directory. So try normalizing
  34. # it and if it exists, use the normalized version.
  35. # This is deliberately conservative - it might be fine just to
  36. # blindly normalize anything starting with a ~...
  37. built_find_links: List[str] = []
  38. for link in find_links:
  39. if link.startswith("~"):
  40. new_link = normalize_path(link)
  41. if os.path.exists(new_link):
  42. link = new_link
  43. built_find_links.append(link)
  44. # If we don't have TLS enabled, then WARN if anyplace we're looking
  45. # relies on TLS.
  46. if not has_tls():
  47. for link in itertools.chain(index_urls, built_find_links):
  48. parsed = urllib.parse.urlparse(link)
  49. if parsed.scheme == "https":
  50. logger.warning(
  51. "pip is configured with locations that require "
  52. "TLS/SSL, however the ssl module in Python is not "
  53. "available."
  54. )
  55. break
  56. return cls(
  57. find_links=built_find_links,
  58. index_urls=index_urls,
  59. no_index=no_index,
  60. )
  61. def get_formatted_locations(self) -> str:
  62. lines = []
  63. redacted_index_urls = []
  64. if self.index_urls and self.index_urls != [PyPI.simple_url]:
  65. for url in self.index_urls:
  66. redacted_index_url = redact_auth_from_url(url)
  67. # Parse the URL
  68. purl = urllib.parse.urlsplit(redacted_index_url)
  69. # URL is generally invalid if scheme and netloc is missing
  70. # there are issues with Python and URL parsing, so this test
  71. # is a bit crude. See bpo-20271, bpo-23505. Python doesn't
  72. # always parse invalid URLs correctly - it should raise
  73. # exceptions for malformed URLs
  74. if not purl.scheme and not purl.netloc:
  75. logger.warning(
  76. 'The index url "%s" seems invalid, please provide a scheme.',
  77. redacted_index_url,
  78. )
  79. redacted_index_urls.append(redacted_index_url)
  80. lines.append(
  81. "Looking in indexes: {}".format(", ".join(redacted_index_urls))
  82. )
  83. if self.find_links:
  84. lines.append(
  85. "Looking in links: {}".format(
  86. ", ".join(redact_auth_from_url(url) for url in self.find_links)
  87. )
  88. )
  89. return "\n".join(lines)
  90. def get_index_urls_locations(self, project_name: str) -> List[str]:
  91. """Returns the locations found via self.index_urls
  92. Checks the url_name on the main (first in the list) index and
  93. use this url_name to produce all locations
  94. """
  95. def mkurl_pypi_url(url: str) -> str:
  96. loc = posixpath.join(
  97. url, urllib.parse.quote(canonicalize_name(project_name))
  98. )
  99. # For maximum compatibility with easy_install, ensure the path
  100. # ends in a trailing slash. Although this isn't in the spec
  101. # (and PyPI can handle it without the slash) some other index
  102. # implementations might break if they relied on easy_install's
  103. # behavior.
  104. if not loc.endswith("/"):
  105. loc = loc + "/"
  106. return loc
  107. return [mkurl_pypi_url(url) for url in self.index_urls]