deprecation.py 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. """
  2. A module that implements tooling to enable easy warnings about deprecations.
  3. """
  4. from __future__ import annotations
  5. import logging
  6. import warnings
  7. from typing import Any, TextIO
  8. from pip._vendor.packaging.version import parse
  9. from pip import __version__ as current_version # NOTE: tests patch this name.
  10. DEPRECATION_MSG_PREFIX = "DEPRECATION: "
  11. class PipDeprecationWarning(Warning):
  12. pass
  13. _original_showwarning: Any = None
  14. # Warnings <-> Logging Integration
  15. def _showwarning(
  16. message: Warning | str,
  17. category: type[Warning],
  18. filename: str,
  19. lineno: int,
  20. file: TextIO | None = None,
  21. line: str | None = None,
  22. ) -> None:
  23. if file is not None:
  24. if _original_showwarning is not None:
  25. _original_showwarning(message, category, filename, lineno, file, line)
  26. elif issubclass(category, PipDeprecationWarning):
  27. # We use a specially named logger which will handle all of the
  28. # deprecation messages for pip.
  29. logger = logging.getLogger("pip._internal.deprecations")
  30. logger.warning(message)
  31. else:
  32. _original_showwarning(message, category, filename, lineno, file, line)
  33. def install_warning_logger() -> None:
  34. # Enable our Deprecation Warnings
  35. warnings.simplefilter("default", PipDeprecationWarning, append=True)
  36. global _original_showwarning
  37. if _original_showwarning is None:
  38. _original_showwarning = warnings.showwarning
  39. warnings.showwarning = _showwarning
  40. def deprecated(
  41. *,
  42. reason: str,
  43. replacement: str | None,
  44. gone_in: str | None,
  45. feature_flag: str | None = None,
  46. issue: int | None = None,
  47. ) -> None:
  48. """Helper to deprecate existing functionality.
  49. reason:
  50. Textual reason shown to the user about why this functionality has
  51. been deprecated. Should be a complete sentence.
  52. replacement:
  53. Textual suggestion shown to the user about what alternative
  54. functionality they can use.
  55. gone_in:
  56. The version of pip does this functionality should get removed in.
  57. Raises an error if pip's current version is greater than or equal to
  58. this.
  59. feature_flag:
  60. Command-line flag of the form --use-feature={feature_flag} for testing
  61. upcoming functionality.
  62. issue:
  63. Issue number on the tracker that would serve as a useful place for
  64. users to find related discussion and provide feedback.
  65. """
  66. # Determine whether or not the feature is already gone in this version.
  67. is_gone = gone_in is not None and parse(current_version) >= parse(gone_in)
  68. message_parts = [
  69. (reason, f"{DEPRECATION_MSG_PREFIX}{{}}"),
  70. (
  71. gone_in,
  72. (
  73. "pip {} will enforce this behaviour change."
  74. if not is_gone
  75. else "Since pip {}, this is no longer supported."
  76. ),
  77. ),
  78. (
  79. replacement,
  80. "A possible replacement is {}.",
  81. ),
  82. (
  83. feature_flag,
  84. (
  85. "You can use the flag --use-feature={} to test the upcoming behaviour."
  86. if not is_gone
  87. else None
  88. ),
  89. ),
  90. (
  91. issue,
  92. "Discussion can be found at https://github.com/pypa/pip/issues/{}",
  93. ),
  94. ]
  95. message = " ".join(
  96. format_str.format(value)
  97. for value, format_str in message_parts
  98. if format_str is not None and value is not None
  99. )
  100. # Raise as an error if this behaviour is deprecated.
  101. if is_gone:
  102. raise PipDeprecationWarning(message)
  103. warnings.warn(message, category=PipDeprecationWarning, stacklevel=2)