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.

outdated.py 5.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. from __future__ import absolute_import
  2. import datetime
  3. import json
  4. import logging
  5. import os.path
  6. import sys
  7. from pip._vendor import lockfile, pkg_resources
  8. from pip._vendor.packaging import version as packaging_version
  9. from pip._internal.index import PackageFinder
  10. from pip._internal.utils.compat import WINDOWS
  11. from pip._internal.utils.filesystem import check_path_owner
  12. from pip._internal.utils.misc import ensure_dir, get_installed_version
  13. from pip._internal.utils.typing import MYPY_CHECK_RUNNING
  14. if MYPY_CHECK_RUNNING:
  15. import optparse # noqa: F401
  16. from typing import Any, Dict # noqa: F401
  17. from pip._internal.download import PipSession # noqa: F401
  18. SELFCHECK_DATE_FMT = "%Y-%m-%dT%H:%M:%SZ"
  19. logger = logging.getLogger(__name__)
  20. class SelfCheckState(object):
  21. def __init__(self, cache_dir):
  22. # type: (str) -> None
  23. self.state = {} # type: Dict[str, Any]
  24. self.statefile_path = None
  25. # Try to load the existing state
  26. if cache_dir:
  27. self.statefile_path = os.path.join(cache_dir, "selfcheck.json")
  28. try:
  29. with open(self.statefile_path) as statefile:
  30. self.state = json.load(statefile)[sys.prefix]
  31. except (IOError, ValueError, KeyError):
  32. # Explicitly suppressing exceptions, since we don't want to
  33. # error out if the cache file is invalid.
  34. pass
  35. def save(self, pypi_version, current_time):
  36. # type: (str, datetime.datetime) -> None
  37. # If we do not have a path to cache in, don't bother saving.
  38. if not self.statefile_path:
  39. return
  40. # Check to make sure that we own the directory
  41. if not check_path_owner(os.path.dirname(self.statefile_path)):
  42. return
  43. # Now that we've ensured the directory is owned by this user, we'll go
  44. # ahead and make sure that all our directories are created.
  45. ensure_dir(os.path.dirname(self.statefile_path))
  46. # Attempt to write out our version check file
  47. with lockfile.LockFile(self.statefile_path):
  48. if os.path.exists(self.statefile_path):
  49. with open(self.statefile_path) as statefile:
  50. state = json.load(statefile)
  51. else:
  52. state = {}
  53. state[sys.prefix] = {
  54. "last_check": current_time.strftime(SELFCHECK_DATE_FMT),
  55. "pypi_version": pypi_version,
  56. }
  57. with open(self.statefile_path, "w") as statefile:
  58. json.dump(state, statefile, sort_keys=True,
  59. separators=(",", ":"))
  60. def was_installed_by_pip(pkg):
  61. # type: (str) -> bool
  62. """Checks whether pkg was installed by pip
  63. This is used not to display the upgrade message when pip is in fact
  64. installed by system package manager, such as dnf on Fedora.
  65. """
  66. try:
  67. dist = pkg_resources.get_distribution(pkg)
  68. return (dist.has_metadata('INSTALLER') and
  69. 'pip' in dist.get_metadata_lines('INSTALLER'))
  70. except pkg_resources.DistributionNotFound:
  71. return False
  72. def pip_version_check(session, options):
  73. # type: (PipSession, optparse.Values) -> None
  74. """Check for an update for pip.
  75. Limit the frequency of checks to once per week. State is stored either in
  76. the active virtualenv or in the user's USER_CACHE_DIR keyed off the prefix
  77. of the pip script path.
  78. """
  79. installed_version = get_installed_version("pip")
  80. if not installed_version:
  81. return
  82. pip_version = packaging_version.parse(installed_version)
  83. pypi_version = None
  84. try:
  85. state = SelfCheckState(cache_dir=options.cache_dir)
  86. current_time = datetime.datetime.utcnow()
  87. # Determine if we need to refresh the state
  88. if "last_check" in state.state and "pypi_version" in state.state:
  89. last_check = datetime.datetime.strptime(
  90. state.state["last_check"],
  91. SELFCHECK_DATE_FMT
  92. )
  93. if (current_time - last_check).total_seconds() < 7 * 24 * 60 * 60:
  94. pypi_version = state.state["pypi_version"]
  95. # Refresh the version if we need to or just see if we need to warn
  96. if pypi_version is None:
  97. # Lets use PackageFinder to see what the latest pip version is
  98. finder = PackageFinder(
  99. find_links=options.find_links,
  100. index_urls=[options.index_url] + options.extra_index_urls,
  101. allow_all_prereleases=False, # Explicitly set to False
  102. trusted_hosts=options.trusted_hosts,
  103. session=session,
  104. )
  105. all_candidates = finder.find_all_candidates("pip")
  106. if not all_candidates:
  107. return
  108. pypi_version = str(
  109. max(all_candidates, key=lambda c: c.version).version
  110. )
  111. # save that we've performed a check
  112. state.save(pypi_version, current_time)
  113. remote_version = packaging_version.parse(pypi_version)
  114. # Determine if our pypi_version is older
  115. if (pip_version < remote_version and
  116. pip_version.base_version != remote_version.base_version and
  117. was_installed_by_pip('pip')):
  118. # Advise "python -m pip" on Windows to avoid issues
  119. # with overwriting pip.exe.
  120. if WINDOWS:
  121. pip_cmd = "python -m pip"
  122. else:
  123. pip_cmd = "pip"
  124. logger.warning(
  125. "You are using pip version %s, however version %s is "
  126. "available.\nYou should consider upgrading via the "
  127. "'%s install --upgrade pip' command.",
  128. pip_version, pypi_version, pip_cmd
  129. )
  130. except Exception:
  131. logger.debug(
  132. "There was an error checking the latest version of pip",
  133. exc_info=True,
  134. )