Skip to content

Commit

Permalink
Merge pull request #55047 from s0undt3ch/features/time-based-deprecation
Browse files Browse the repository at this point in the history
[master] Allow deprecating by date
  • Loading branch information
dwoz authored Oct 19, 2019
2 parents 22d2390 + e5cd1a2 commit 340c0b5
Show file tree
Hide file tree
Showing 2 changed files with 175 additions and 20 deletions.
128 changes: 109 additions & 19 deletions salt/utils/versions.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
import numbers
import sys
import warnings
import datetime
import inspect
import contextlib
# pylint: disable=blacklisted-module,no-name-in-module
from distutils.version import StrictVersion as _StrictVersion
from distutils.version import LooseVersion as _LooseVersion
Expand Down Expand Up @@ -75,6 +78,28 @@ def _cmp(self, other):
return 1


def _format_warning(message, category, filename, lineno, line=None):
'''
Replacement for warnings.formatwarning that disables the echoing of
the 'line' parameter.
'''
return '{}:{}: {}: {}\n'.format(
filename, lineno, category.__name__, message
)


@contextlib.contextmanager
def _patched_format_warning():
if six.PY2:
saved = warnings.formatwarning
warnings.formatwarning = _format_warning
yield
warnings.formatwarning = saved
else:
# Under Py3 we no longer have to patch warnings.formatwarning
yield


def warn_until(version,
message,
category=DeprecationWarning,
Expand Down Expand Up @@ -125,7 +150,6 @@ def warn_until(version,
_version_ = salt.version.SaltStackVersion(*_version_info_)

if _version_ >= version:
import inspect
caller = inspect.getframeinfo(sys._getframe(stacklevel - 1))
raise RuntimeError(
'The warning triggered on filename \'{filename}\', line number '
Expand All @@ -140,26 +164,92 @@ def warn_until(version,
)

if _dont_call_warnings is False:
def _formatwarning(message,
category,
filename,
lineno,
line=None): # pylint: disable=W0613
'''
Replacement for warnings.formatwarning that disables the echoing of
the 'line' parameter.
'''
return '{0}:{1}: {2}: {3}\n'.format(
filename, lineno, category.__name__, message
with _patched_format_warning():
warnings.warn(
message.format(version=version.formatted_version),
category,
stacklevel=stacklevel
)


def _get_utcnow_date():
'''
This function exists because we can't easily patch builtin objects when mocking
'''
return datetime.datetime.utcnow().date()


def warn_until_date(date,
message,
category=DeprecationWarning,
stacklevel=None,
_dont_call_warnings=False):
'''
Helper function to raise a warning, by default, a ``DeprecationWarning``,
until the provided ``date``, after which, a ``RuntimeError`` will
be raised to remind the developers to remove the warning because the
target date has been reached.
:param date: A ``datetime.date`` or ``datetime.datetime`` instance.
:param message: The warning message to be displayed.
:param category: The warning class to be thrown, by default
``DeprecationWarning``
:param stacklevel: There should be no need to set the value of
``stacklevel``. Salt should be able to do the right thing.
:param _dont_call_warnings: This parameter is used just to get the
functionality until the actual error is to be
issued. When we're only after the date
checks to raise a ``RuntimeError``.
'''
_strptime_fmt = '%Y%m%d'
if not isinstance(date, (six.string_types, datetime.date, datetime.datetime)):
raise RuntimeError(
'The \'date\' argument should be passed as a \'datetime.date()\' or '
'\'datetime.datetime()\' objects or as string parserable by '
'\'datetime.datetime.strptime()\' with the following format \'{}\'.'.format(
_strptime_fmt
)
saved = warnings.formatwarning
warnings.formatwarning = _formatwarning
warnings.warn(
message.format(version=version.formatted_version),
category,
stacklevel=stacklevel
)
warnings.formatwarning = saved
elif isinstance(date, six.text_type):
date = datetime.datetime.strptime(date, _strptime_fmt)

# We're really not interested in the time
if isinstance(date, datetime.datetime):
date = date.date()

if stacklevel is None:
# Attribute the warning to the calling function, not to warn_until_date()
stacklevel = 2

today = _get_utcnow_date()
if today >= date:
caller = inspect.getframeinfo(sys._getframe(stacklevel - 1))
raise RuntimeError(
'{message} This warning(now exception) triggered on '
'filename \'{filename}\', line number {lineno}, is '
'supposed to be shown until {date}. Today is {today}. '
'Please remove the warning.'.format(
message=message.format(
date=date.isoformat(),
today=today.isoformat()
),
filename=caller.filename,
lineno=caller.lineno,
date=date.isoformat(),
today=today.isoformat(),
),
)

if _dont_call_warnings is False:
with _patched_format_warning():
warnings.warn(
message.format(
date=date.isoformat(),
today=today.isoformat()
),
category,
stacklevel=stacklevel
)


def kwargs_warn_until(kwargs,
Expand Down
67 changes: 66 additions & 1 deletion tests/unit/utils/test_versions.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from __future__ import absolute_import, print_function, unicode_literals
import os
import sys
import datetime
import warnings

# Import Salt Testing libs
Expand Down Expand Up @@ -106,7 +107,7 @@ def test_spelling_version_name(self):
names in the salt.utils.versions.warn_until call
'''
salt_dir = integration.CODE_DIR
query = 'salt.utils.versions.warn_until'
query = 'salt.utils.versions.warn_until('
names = salt.version.SaltStackVersion.NAMES

salt_dir += '/salt/'
Expand Down Expand Up @@ -291,3 +292,67 @@ def raise_warning(**kwargs):
r'0.17.0 is released. Current version is now 0.17.0. '
r'Please remove the warning.'):
raise_warning(bar='baz', qux='quux', _version_info_=(0, 17)) # some kwargs

def test_warn_until_date_warning_raised(self):
# We *always* want *all* warnings thrown on this module
warnings.filterwarnings('always', '', DeprecationWarning, __name__)

fake_utcnow = datetime.date(2000, 1, 1)

with patch('salt.utils.versions._get_utcnow_date', return_value=fake_utcnow):

# Test warning with datetime.date instance
with warnings.catch_warnings(record=True) as recorded_warnings:
salt.utils.versions.warn_until_date(datetime.date(2000, 1, 2), 'Deprecation Message!')
self.assertEqual(
'Deprecation Message!', six.text_type(recorded_warnings[0].message)
)

# Test warning with datetime.datetime instance
with warnings.catch_warnings(record=True) as recorded_warnings:
salt.utils.versions.warn_until_date(datetime.datetime(2000, 1, 2), 'Deprecation Message!')
self.assertEqual(
'Deprecation Message!', six.text_type(recorded_warnings[0].message)
)

# Test warning with date as a string
with warnings.catch_warnings(record=True) as recorded_warnings:
salt.utils.versions.warn_until_date('20000102', 'Deprecation Message!')
self.assertEqual(
'Deprecation Message!', six.text_type(recorded_warnings[0].message)
)

# the deprecation warning is not issued because we passed
# _dont_call_warning
with warnings.catch_warnings(record=True) as recorded_warnings:
salt.utils.versions.warn_until_date('20000102', 'Deprecation Message!', _dont_call_warnings=True)
self.assertEqual(0, len(recorded_warnings))

# Let's test for RuntimeError raise
with self.assertRaisesRegex(
RuntimeError,
r'Deprecation Message! This warning\(now exception\) triggered on '
r'filename \'(.*)test_versions.py\', line number ([\d]+), is '
r'supposed to be shown until ([\d-]+). Today is ([\d-]+). '
r'Please remove the warning.'):
salt.utils.versions.warn_until_date('20000101', 'Deprecation Message!')

# Even though we're calling warn_until_date, we pass _dont_call_warnings
# because we're only after the RuntimeError
with self.assertRaisesRegex(
RuntimeError,
r'Deprecation Message! This warning\(now exception\) triggered on '
r'filename \'(.*)test_versions.py\', line number ([\d]+), is '
r'supposed to be shown until ([\d-]+). Today is ([\d-]+). '
r'Please remove the warning.'):
salt.utils.versions.warn_until_date('20000101', 'Deprecation Message!', _dont_call_warnings=True)

def test_warn_until_date_bad_strptime_format(self):
# We *always* want *all* warnings thrown on this module
warnings.filterwarnings('always', '', DeprecationWarning, __name__)

# Let's test for RuntimeError raise
with self.assertRaisesRegex(
ValueError,
'time data \'0022\' does not match format \'%Y%m%d\''):
salt.utils.versions.warn_until_date('0022', 'Deprecation Message!')

0 comments on commit 340c0b5

Please sign in to comment.