Merge pull request #5542 from pradyunsg/dev-ops/deprecation-utilities

Introduce a helper function for deprecation
This commit is contained in:
Pradyun Gedam 2018-07-20 00:37:53 +05:30 committed by GitHub
commit 169ca95666
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 88 additions and 62 deletions

View File

@ -211,6 +211,12 @@ document existing behavior with the intention of covering that behavior with
the above deprecation process are always acceptable, and will be considered on the above deprecation process are always acceptable, and will be considered on
their merits. their merits.
.. note::
pip has a helper function for making deprecation easier for pip maintainers.
The supporting documentation can be found in the source code of
``pip._internal.utils.deprecation.deprecated``. The function is not a part of
pip's public API.
Release Process Release Process
=============== ===============

View File

@ -9,7 +9,6 @@ import os
import posixpath import posixpath
import re import re
import sys import sys
import warnings
from collections import namedtuple from collections import namedtuple
from pip._vendor import html5lib, requests, six from pip._vendor import html5lib, requests, six
@ -29,7 +28,7 @@ from pip._internal.exceptions import (
) )
from pip._internal.models.index import PyPI from pip._internal.models.index import PyPI
from pip._internal.pep425tags import get_supported from pip._internal.pep425tags import get_supported
from pip._internal.utils.deprecation import RemovedInPip12Warning from pip._internal.utils.deprecation import deprecated
from pip._internal.utils.logging import indent_log from pip._internal.utils.logging import indent_log
from pip._internal.utils.misc import ( from pip._internal.utils.misc import (
ARCHIVE_EXTENSIONS, SUPPORTED_EXTENSIONS, cached_property, normalize_path, ARCHIVE_EXTENSIONS, SUPPORTED_EXTENSIONS, cached_property, normalize_path,
@ -212,10 +211,12 @@ class PackageFinder(object):
# # dependency_links value # # dependency_links value
# # FIXME: also, we should track comes_from (i.e., use Link) # # FIXME: also, we should track comes_from (i.e., use Link)
if self.process_dependency_links: if self.process_dependency_links:
warnings.warn( deprecated(
"Dependency Links processing has been deprecated and will be " "Dependency Links processing has been deprecated and will be "
"removed in a future release.", "removed in a future release.",
RemovedInPip12Warning, replacement=None,
gone_in="18.2",
issue=4187,
) )
self.dependency_links.extend(links) self.dependency_links.extend(links)

View File

@ -4,7 +4,6 @@ import collections
import logging import logging
import os import os
import re import re
import warnings
from pip._vendor import pkg_resources, six from pip._vendor import pkg_resources, six
from pip._vendor.packaging.utils import canonicalize_name from pip._vendor.packaging.utils import canonicalize_name
@ -13,7 +12,7 @@ from pip._vendor.pkg_resources import RequirementParseError
from pip._internal.exceptions import InstallationError from pip._internal.exceptions import InstallationError
from pip._internal.req import InstallRequirement from pip._internal.req import InstallRequirement
from pip._internal.req.req_file import COMMENT_RE from pip._internal.req.req_file import COMMENT_RE
from pip._internal.utils.deprecation import RemovedInPip12Warning from pip._internal.utils.deprecation import deprecated
from pip._internal.utils.misc import ( from pip._internal.utils.misc import (
dist_is_editable, get_installed_distributions, dist_is_editable, get_installed_distributions,
) )
@ -216,10 +215,12 @@ class FrozenRequirement(object):
'for this package:' 'for this package:'
) )
else: else:
warnings.warn( deprecated(
"SVN editable detection based on dependency links " "SVN editable detection based on dependency links "
"will be dropped in the future.", "will be dropped in the future.",
RemovedInPip12Warning, replacement=None,
gone_in="18.2",
issue=4187,
) )
comments.append( comments.append(
'# Installing as editable to satisfy requirement %s:' % '# Installing as editable to satisfy requirement %s:' %

View File

@ -8,7 +8,6 @@ import shutil
import sys import sys
import sysconfig import sysconfig
import traceback import traceback
import warnings
import zipfile import zipfile
from distutils.util import change_root from distutils.util import change_root
from email.parser import FeedParser # type: ignore from email.parser import FeedParser # type: ignore
@ -33,7 +32,7 @@ from pip._internal.locations import (
PIP_DELETE_MARKER_FILENAME, running_under_virtualenv, PIP_DELETE_MARKER_FILENAME, running_under_virtualenv,
) )
from pip._internal.req.req_uninstall import UninstallPathSet from pip._internal.req.req_uninstall import UninstallPathSet
from pip._internal.utils.deprecation import RemovedInPip12Warning from pip._internal.utils.deprecation import deprecated
from pip._internal.utils.hashes import Hashes from pip._internal.utils.hashes import Hashes
from pip._internal.utils.logging import indent_log from pip._internal.utils.logging import indent_log
from pip._internal.utils.misc import ( from pip._internal.utils.misc import (
@ -582,11 +581,13 @@ class InstallRequirement(object):
if requires is None: if requires is None:
logging.warn(template, self, "it is missing.") logging.warn(template, self, "it is missing.")
warnings.warn( deprecated(
"Future versions of pip may reject packages with " "Future versions of pip may reject packages with "
"pyproject.toml files that do not contain the [build-system]" "pyproject.toml files that do not contain the [build-system]"
"table and the requires key, as specified in PEP 518.", "table and the requires key, as specified in PEP 518.",
RemovedInPip12Warning, replacement=None,
gone_in="18.2",
issue=5416,
) )
# Currently, we're isolating the build based on the presence of the # Currently, we're isolating the build based on the presence of the

View File

@ -6,68 +6,84 @@ from __future__ import absolute_import
import logging import logging
import warnings import warnings
from pip._vendor.packaging.version import parse
from pip import __version__ as current_version
from pip._internal.utils.typing import MYPY_CHECK_RUNNING from pip._internal.utils.typing import MYPY_CHECK_RUNNING
if MYPY_CHECK_RUNNING: if MYPY_CHECK_RUNNING:
from typing import Any # noqa: F401 from typing import Any, Optional # noqa: F401
class PipDeprecationWarning(Warning): class PipDeprecationWarning(Warning):
pass pass
class Pending(object): _original_showwarning = None # type: Any
pass
class RemovedInPip12Warning(PipDeprecationWarning, Pending):
pass
# Warnings <-> Logging Integration # Warnings <-> Logging Integration
_warnings_showwarning = None # type: Any
def _showwarning(message, category, filename, lineno, file=None, line=None): def _showwarning(message, category, filename, lineno, file=None, line=None):
if file is not None: if file is not None:
if _warnings_showwarning is not None: if _original_showwarning is not None:
_warnings_showwarning( _original_showwarning(
message, category, filename, lineno, file, line, message, category, filename, lineno, file, line,
) )
elif issubclass(category, PipDeprecationWarning):
# We use a specially named logger which will handle all of the
# deprecation messages for pip.
logger = logging.getLogger("pip._internal.deprecations")
logger.warning(message)
else: else:
if issubclass(category, PipDeprecationWarning): _original_showwarning(
# We use a specially named logger which will handle all of the message, category, filename, lineno, file, line,
# deprecation messages for pip. )
logger = logging.getLogger("pip._internal.deprecations")
# This is purposely using the % formatter here instead of letting
# the logging module handle the interpolation. This is because we
# want it to appear as if someone typed this entire message out.
log_message = "DEPRECATION: %s" % message
# PipDeprecationWarnings that are Pending still have at least 2
# versions to go until they are removed so they can just be
# warnings. Otherwise, they will be removed in the very next
# version of pip. We want these to be more obvious so we use the
# ERROR logging level.
if issubclass(category, Pending):
logger.warning(log_message)
else:
logger.error(log_message)
else:
_warnings_showwarning(
message, category, filename, lineno, file, line,
)
def install_warning_logger(): def install_warning_logger():
# Enable our Deprecation Warnings # Enable our Deprecation Warnings
warnings.simplefilter("default", PipDeprecationWarning, append=True) warnings.simplefilter("default", PipDeprecationWarning, append=True)
global _warnings_showwarning global _original_showwarning
if _warnings_showwarning is None: if _original_showwarning is None:
_warnings_showwarning = warnings.showwarning _original_showwarning = warnings.showwarning
warnings.showwarning = _showwarning warnings.showwarning = _showwarning
def deprecated(reason, replacement, gone_in, issue=None):
# type: (str, Optional[str], Optional[str], Optional[int]) -> None
"""Helper to deprecate existing functionality.
reason:
Textual reason shown to the user about why this functionality has
been deprecated.
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
this.
issue:
Issue number on the tracker that would serve as a useful place for
users to find related discussion and provide feedback.
Always pass replacement, gone_in and issue as keyword arguments for clarity
at the call site.
"""
# Construct a nice message.
# This is purposely eagerly formatted as we want it to appear as if someone
# typed this entire message out.
message = "DEPRECATION: " + reason
if replacement is not None:
message += " A possible replacement is {}.".format(replacement)
if issue is not None:
url = "https://github.com/pypa/pip/issues/" + str(issue)
message += " You can find discussion regarding this at {}.".format(url)
# Raise as an error if it has to be removed.
if gone_in is not None and parse(current_version) >= parse(gone_in):
raise PipDeprecationWarning(message)
warnings.warn(message, category=PipDeprecationWarning, stacklevel=2)

View File

@ -1,21 +1,22 @@
import textwrap
def test_environ(script, tmpdir): def test_environ(script, tmpdir):
"""$PYTHONWARNINGS was added in python2.7""" """$PYTHONWARNINGS was added in python2.7"""
demo = tmpdir.join('warnings_demo.py') demo = tmpdir.join('warnings_demo.py')
demo.write(''' demo.write(textwrap.dedent('''
from pip._internal.utils import deprecation from logging import basicConfig
deprecation.install_warning_logger() from pip._internal.utils import deprecation
from logging import basicConfig deprecation.install_warning_logger()
basicConfig() basicConfig()
from warnings import warn deprecation.deprecated("deprecated!", replacement=None, gone_in=None)
warn("deprecated!", deprecation.PipDeprecationWarning) '''))
''')
result = script.run('python', demo, expect_stderr=True) result = script.run('python', demo, expect_stderr=True)
assert result.stderr == \ expected = 'WARNING:pip._internal.deprecations:DEPRECATION: deprecated!\n'
'ERROR:pip._internal.deprecations:DEPRECATION: deprecated!\n' assert result.stderr == expected
script.environ['PYTHONWARNINGS'] = 'ignore' script.environ['PYTHONWARNINGS'] = 'ignore'
result = script.run('python', demo) result = script.run('python', demo)