Add a removal decorator
This commit adds a new decorator to mark a function/class as deprecated in preparation for it's removal. Change-Id: I4c3cf3ff9abdd7faefeae907a8b73608123fb09d
This commit is contained in:
parent
b078a94a5b
commit
497c1b47be
94
debtcollector/removals.py
Normal file
94
debtcollector/removals.py
Normal file
@ -0,0 +1,94 @@
|
||||
# Copyright 2014 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import functools
|
||||
import inspect
|
||||
|
||||
from oslo_utils import reflection
|
||||
import wrapt
|
||||
|
||||
from debtcollector import _utils
|
||||
|
||||
|
||||
def remove(f=None, message=None, version=None, removal_version=None,
|
||||
stacklevel=3):
|
||||
"""Decorates a function, method, or class to emit a deprecation warning
|
||||
|
||||
:param str message: A message to include in the deprecation warning
|
||||
:param str version: Specify what version the removed function is present in
|
||||
:param str removal_version: What version the function will be removed. If
|
||||
'?' is used this implies an undefined future
|
||||
version
|
||||
:param int stacklevel: How many entries deep in the call stack before
|
||||
ignoring
|
||||
"""
|
||||
if f is None:
|
||||
return functools.partial(remove, message=message,
|
||||
version=version,
|
||||
removal_version=removal_version,
|
||||
stacklevel=stacklevel)
|
||||
|
||||
@wrapt.decorator
|
||||
def wrapper(f, instance, args, kwargs):
|
||||
try:
|
||||
# Prefer the py3.x name (if we can get at it...)
|
||||
f_name = f.__qualname__
|
||||
qualified = True
|
||||
if inspect.isclass(f):
|
||||
_prefix_pre = "Using class"
|
||||
else:
|
||||
_prefix_pre = "Using function/method"
|
||||
except AttributeError:
|
||||
f_name = f.__name__
|
||||
qualified = False
|
||||
|
||||
if not qualified:
|
||||
_prefix_pre = "Using function/method"
|
||||
if instance is None:
|
||||
# Decorator was used on a class
|
||||
if inspect.isclass(f):
|
||||
_prefix_pre = "Using class"
|
||||
module_name = inspect.getmodule(f).__name__
|
||||
if module_name == '__main__':
|
||||
f_name = reflection.get_class_name(
|
||||
f, fully_qualified=False)
|
||||
else:
|
||||
f_name = reflection.get_class_name(
|
||||
f, fully_qualified=True)
|
||||
base_name = None
|
||||
# Decorator was a used on a function
|
||||
else:
|
||||
module_name = inspect.getmodule(f).__name__
|
||||
if module_name != '__main__':
|
||||
f_name = reflection.get_callable_name(f)
|
||||
base_name = None
|
||||
# Decorator was used on a classmethod or instancemethod
|
||||
else:
|
||||
base_name = reflection.get_class_name(instance,
|
||||
fully_qualified=False)
|
||||
if base_name:
|
||||
function_name = ".".join([base_name, f_name])
|
||||
else:
|
||||
function_name = f_name
|
||||
else:
|
||||
function_name = f_name
|
||||
_prefix = _prefix_pre + " %s is deprecated" % function_name
|
||||
out_message = _utils.generate_message(
|
||||
_prefix,
|
||||
version=version,
|
||||
removal_version=removal_version,
|
||||
message=message)
|
||||
_utils.deprecation(out_message, stacklevel)
|
||||
return f(*args, **kwargs)
|
||||
return wrapper(f)
|
@ -15,6 +15,7 @@
|
||||
import warnings
|
||||
|
||||
from debtcollector import moves
|
||||
from debtcollector import removals
|
||||
from debtcollector import renames
|
||||
from debtcollector.tests import base as test_base
|
||||
|
||||
@ -50,6 +51,37 @@ class NewHotness(object):
|
||||
return 'cold'
|
||||
|
||||
|
||||
@removals.remove()
|
||||
def crimson_lightning(fake_input=None):
|
||||
return fake_input
|
||||
|
||||
|
||||
@removals.remove()
|
||||
def red_comet():
|
||||
return True
|
||||
|
||||
|
||||
@removals.remove()
|
||||
class EFSF(object):
|
||||
pass
|
||||
|
||||
|
||||
class ThingB(object):
|
||||
@removals.remove()
|
||||
def black_tristars(self):
|
||||
pass
|
||||
|
||||
@removals.remove()
|
||||
@classmethod
|
||||
def white_wolf(cls):
|
||||
pass
|
||||
|
||||
@removals.remove()
|
||||
@staticmethod
|
||||
def blue_giant():
|
||||
pass
|
||||
|
||||
|
||||
OldHotness = moves.moved_class(NewHotness, 'OldHotness', __name__)
|
||||
|
||||
|
||||
@ -147,3 +179,62 @@ class RenamedKwargTest(test_base.TestCase):
|
||||
warnings.simplefilter("always")
|
||||
self.assertEqual((1, 2), blip_blop(blop=2))
|
||||
self.assertEqual(0, len(capture))
|
||||
|
||||
|
||||
class RemovalTests(test_base.TestCase):
|
||||
def test_function_args(self):
|
||||
self.assertEqual(666, crimson_lightning(666))
|
||||
|
||||
def test_function_noargs(self):
|
||||
self.assertTrue(red_comet())
|
||||
|
||||
def test_warnings_emitted_function_args(self):
|
||||
with warnings.catch_warnings(record=True) as capture:
|
||||
warnings.simplefilter("always")
|
||||
self.assertEqual(666, crimson_lightning(666))
|
||||
self.assertEqual(1, len(capture))
|
||||
w = capture[0]
|
||||
self.assertEqual(DeprecationWarning, w.category)
|
||||
|
||||
def test_warnings_emitted_function_noargs(self):
|
||||
with warnings.catch_warnings(record=True) as capture:
|
||||
warnings.simplefilter("always")
|
||||
self.assertTrue(red_comet())
|
||||
self.assertEqual(1, len(capture))
|
||||
w = capture[0]
|
||||
self.assertEqual(DeprecationWarning, w.category)
|
||||
|
||||
def test_warnings_emitted_class(self):
|
||||
with warnings.catch_warnings(record=True) as capture:
|
||||
warnings.simplefilter("always")
|
||||
EFSF()
|
||||
self.assertEqual(1, len(capture))
|
||||
w = capture[0]
|
||||
self.assertEqual(DeprecationWarning, w.category)
|
||||
|
||||
def test_warnings_emitted_instancemethod(self):
|
||||
zeon = ThingB()
|
||||
with warnings.catch_warnings(record=True) as capture:
|
||||
warnings.simplefilter("always")
|
||||
zeon.black_tristars()
|
||||
self.assertEqual(1, len(capture))
|
||||
w = capture[0]
|
||||
self.assertEqual(DeprecationWarning, w.category)
|
||||
|
||||
def test_warnings_emitted_classmethod(self):
|
||||
zeon = ThingB()
|
||||
with warnings.catch_warnings(record=True) as capture:
|
||||
warnings.simplefilter("always")
|
||||
zeon.white_wolf()
|
||||
self.assertEqual(1, len(capture))
|
||||
w = capture[0]
|
||||
self.assertEqual(DeprecationWarning, w.category)
|
||||
|
||||
def test_warnings_emitted_staticmethod(self):
|
||||
zeon = ThingB()
|
||||
with warnings.catch_warnings(record=True) as capture:
|
||||
warnings.simplefilter("always")
|
||||
zeon.blue_giant()
|
||||
self.assertEqual(1, len(capture))
|
||||
w = capture[0]
|
||||
self.assertEqual(DeprecationWarning, w.category)
|
||||
|
@ -21,3 +21,8 @@ Renames
|
||||
-------
|
||||
|
||||
.. automodule:: debtcollector.renames
|
||||
|
||||
Removals
|
||||
--------
|
||||
|
||||
.. automodule:: debtcollector.removals
|
||||
|
@ -6,3 +6,4 @@ pbr>=0.6,!=0.7,<1.0
|
||||
Babel>=1.3
|
||||
six>=1.7.0
|
||||
oslo.utils>=1.2.0 # Apache-2.0
|
||||
wrapt>=1.7.0 # BSD License
|
||||
|
Loading…
x
Reference in New Issue
Block a user