mirror of https://github.com/pypa/pip
Merge pull request #5542 from pradyunsg/dev-ops/deprecation-utilities
Introduce a helper function for deprecation
This commit is contained in:
commit
169ca95666
|
@ -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
|
||||||
===============
|
===============
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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:' %
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue