Repository: pypa/pip

Issue:
Allow customized gone_in sentence for deprecation messages
**What's the problem this feature will solve?**
The current `pip._internal.utils.deprecation:deprecated` function, in at least one case (#10128), leads to an awkward and confusing deprecation message. I would like to make the message more customizable so that awkward wording is reduced.

**Describe the solution you'd like**
See https://github.com/pypa/pip/pull/10129#issuecomment-875980520. Namely, I would like to introduce a new kwarg `gone_in_message` with default value `DEPRECATION_GONE_IN_MESSAGE = "pip {} will remove support for this functionality."`. I would also like to be able to set `gone_in_message=""` to omit the automatic generation of such a message.

This would allow me to achieve my desired wording:

> pip currently copies the source tree into a temporary directory before building it. pip 21.3 will build packages in-place within the original source tree ("in-tree build"). This new behavior can be tested with the --use-feature=in-tree-build flag. Future versions of pip will remove support for out-of-tree builds, and for this testing flag. You can find discussion regarding this at {link}.

**Alternative Solutions**
I have tried to reword things to work with the builtin message phrasing, but it forced me to use awkward sentences like "Regarding...". See #10129 for further discussion.

Discussion:
maresb: Thanks @uranusjr so much for the review! I really appreciate it. I'm on the road at the moment but I will try to address everything soon.
maresb: Hello @uranusjr, I have attempted to address all of the points you raised. I am ready for the next round of review. I'm happy to further elaborate on my motivation, or to rebase/squash my commits. Thank you!
pradyunsg: I am a -1 on this PR as it stands -- more context at https://github.com/pypa/pip/issues/10165#issuecomment-883233747.
pypa-bot: Hello!

I am an automated bot and I have noticed that this pull request is not currently able to be merged. If you are able to either merge the ``master`` branch into this pull request or rebase this pull request against ``master`` then it will eligible for code review and hopefully merging!
maresb: Closes #10223
maresb: @pradyunsg and @uranusjr, do the latest commits satisfactorily address all your concerns?
maresb: Oops, I need to adjust the unit tests now...

Patch:
diff --git a/news/10165.trivial.rst b/news/10165.trivial.rst
new file mode 100644
index 000000000..45d40200c
--- /dev/null
+++ b/news/10165.trivial.rst
@@ -0,0 +1,4 @@
+Add a ``feature_flag`` optional kwarg to the ``deprecated()`` function
+``pip._internal.utils.deprecation:deprecated``. Also formulate a corresponding canned
+message which suggests using the ``--use-feature={feature_flag}`` to test upcoming
+behavior.
diff --git a/src/pip/_internal/utils/deprecation.py b/src/pip/_internal/utils/deprecation.py
index 57dbdbdca..a163395c4 100644
--- a/src/pip/_internal/utils/deprecation.py
+++ b/src/pip/_internal/utils/deprecation.py
@@ -11,6 +11,15 @@ from pip._vendor.packaging.version import parse
 from pip import __version__ as current_version
 
 DEPRECATION_MSG_PREFIX = "DEPRECATION: "
+DEPRECATION_MESSAGE = DEPRECATION_MSG_PREFIX + "{reason}"
+GONE_IN_MESSAGE_FUTURE = "pip {gone_in} will enforce this behavior change."
+GONE_IN_MESSAGE_PAST = "This behavior change has been enforced since pip {gone_in}."
+REPLACEMENT_MESSAGE = "A possible replacement is {replacement}."
+FEATURE_FLAG_MESSAGE = (
+    "You can temporarily use the flag --use-feature={feature_flag} "
+    "to test the upcoming behavior."
+)
+ISSUE_MESSAGE = "Discussion can be found at https://github.com/pypa/pip/issues/{issue}."
 
 
 class PipDeprecationWarning(Warning):
@@ -56,20 +65,24 @@ def deprecated(
     reason: str,
     replacement: Optional[str],
     gone_in: Optional[str],
+    feature_flag: Optional[str] = None,
     issue: Optional[int] = None,
 ) -> None:
     """Helper to deprecate existing functionality.
 
     reason:
         Textual reason shown to the user about why this functionality has
-        been deprecated.
+        been deprecated. Should be a complete sentence.
     replacement:
         Textual suggestion shown to the user about what alternative
         functionality they can use.
     gone_in:
         The version of pip does this functionality should get removed in.
-        Raises errors if pip's current version is greater than or equal to
+        Raises an error if pip's current version is greater than or equal to
         this.
+    feature_flag:
+        Command-line flag of the form --use-feature={feature_flag} for testing
+        upcoming functionality.
     issue:
         Issue number on the tracker that would serve as a useful place for
         users to find related discussion and provide feedback.
@@ -77,28 +90,38 @@ def deprecated(
     Always pass replacement, gone_in and issue as keyword arguments for clarity
     at the call site.
     """
-
+    # Determine whether or not the feature is already gone in this version.
+    is_gone = gone_in is not None and parse(current_version) >= parse(gone_in)
+    # Allow variable substitutions within the "reason" variable.
+    formatted_reason = reason.format(gone_in=gone_in)
     # Construct a nice message.
     #   This is eagerly formatted as we want it to get logged as if someone
     #   typed this entire message out.
+    formatted_deprecation_message = DEPRECATION_MESSAGE.format(reason=formatted_reason)
+    gone_in_message = GONE_IN_MESSAGE_PAST if is_gone else GONE_IN_MESSAGE_FUTURE
+    formatted_gone_in_message = (
+        gone_in_message.format(gone_in=gone_in) if gone_in else None
+    )
+    formatted_replacement_message = (
+        REPLACEMENT_MESSAGE.format(replacement=replacement) if replacement else None
+    )
+    formatted_feature_flag_message = (
+        None
+        if is_gone or not feature_flag
+        else FEATURE_FLAG_MESSAGE.format(feature_flag=feature_flag)
+    )
+    formatted_issue_message = ISSUE_MESSAGE.format(issue=issue) if issue else None
     sentences = [
-        (reason, DEPRECATION_MSG_PREFIX + "{}"),
-        (gone_in, "pip {} will remove support for this functionality."),
-        (replacement, "A possible replacement is {}."),
-        (
-            issue,
-            (
-                "You can find discussion regarding this at "
-                "https://github.com/pypa/pip/issues/{}."
-            ),
-        ),
+        formatted_deprecation_message,
+        formatted_gone_in_message,
+        formatted_replacement_message,
+        formatted_feature_flag_message,
+        formatted_issue_message,
     ]
-    message = " ".join(
-        template.format(val) for val, template in sentences if val is not None
-    )
+    message = " ".join(sentence for sentence in sentences if sentence)
 
-    # Raise as an error if it has to be removed.
-    if gone_in is not None and parse(current_version) >= parse(gone_in):
+    # Raise as an error if the functionality is gone.
+    if is_gone:
         raise PipDeprecationWarning(message)
-
-    warnings.warn(message, category=PipDeprecationWarning, stacklevel=2)
+    else:
+        warnings.warn(message, category=PipDeprecationWarning, stacklevel=2)


Test patch:
diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py
index ebccd6660..4e5bba2e1 100644
--- a/tests/unit/test_utils.py
+++ b/tests/unit/test_utils.py
@@ -934,9 +934,9 @@ def test_deprecated_message_reads_well():
 
     assert message == (
         "DEPRECATION: Stop doing this! "
-        "pip 1.0 will remove support for this functionality. "
+        "This behavior change has been enforced since pip 1.0. "
         "A possible replacement is to be nicer. "
-        "You can find discussion regarding this at "
+        "Discussion can be found at "
         "https://github.com/pypa/pip/issues/100000."
     )