diff --git a/scrapy/tests/test_utils_deprecate.py b/scrapy/tests/test_utils_deprecate.py index f1f0d77d8..4e95069e5 100644 --- a/scrapy/tests/test_utils_deprecate.py +++ b/scrapy/tests/test_utils_deprecate.py @@ -42,6 +42,27 @@ class WarnWhenSubclassedTest(unittest.TestCase): ) self.assertEqual(msg.lineno, inspect.getsourcelines(UserClass)[1]) + def test_warning_on_instance(self): + with warnings.catch_warnings(record=True) as w: + Deprecated = create_deprecated_class('Deprecated', NewName, + warn_category=MyWarning) + + class UserClass(Deprecated): + pass + + _, lineno = Deprecated(), inspect.getlineno(inspect.currentframe()) + _ = UserClass() + + self.assertEqual(len(w), 2) + msg = w[1] + assert issubclass(msg.category, MyWarning) + self.assertEqual( + str(msg.message), + "scrapy.tests.test_utils_deprecate.Deprecated is deprecated, " + "instanciate scrapy.tests.test_utils_deprecate.NewName instead." + ) + self.assertEqual(msg.lineno, lineno) + def test_warning_auto_message(self): with warnings.catch_warnings(record=True) as w: Deprecated = create_deprecated_class('Deprecated', NewName) diff --git a/scrapy/utils/deprecate.py b/scrapy/utils/deprecate.py index 40df25e26..7a0098ddf 100644 --- a/scrapy/utils/deprecate.py +++ b/scrapy/utils/deprecate.py @@ -14,11 +14,16 @@ def attribute(obj, oldattr, newattr, version='0.12'): def create_deprecated_class(name, new_class, clsdict=None, warn_category=ScrapyDeprecationWarning, - warn_message="{cls} inherits from deprecated class {old}, "\ - "please inherit from {new}."): + subclass_warn_message="{cls} inherits from "\ + "deprecated class {old}, please inherit "\ + "from {new}.", + instance_warn_message="{cls} is deprecated, "\ + "instanciate {new} instead."): """ Return a "deprecated" class that causes its subclasses to issue a warning. Subclasses of ``new_class`` are considered subclasses of this class. + It also warns when the deprecated class is instanciated, but do not when + its subclasses are instanciated. It can be used to rename a base class in a library. For example, if we have @@ -50,10 +55,11 @@ def create_deprecated_class(name, new_class, clsdict=None, return cls def __init__(cls, name, bases, clsdict_): - if cls is not cls.__class__.deprecated_class: - msg = warn_message.format(cls=_clspath(cls), - old=_clspath(cls.__class__.deprecated_class), - new=_clspath(new_class)) + old = cls.__class__.deprecated_class + if cls is not old: + msg = subclass_warn_message.format(cls=_clspath(cls), + old=_clspath(old), + new=_clspath(new_class)) warnings.warn(msg, warn_category, stacklevel=2) super(DeprecatedClass, cls).__init__(name, bases, clsdict_) @@ -72,6 +78,13 @@ def create_deprecated_class(name, new_class, clsdict=None, candidates = {cls, new_class} return any(c in candidates for c in mro) + def __call__(cls, *args, **kwargs): + if cls is cls.__class__.deprecated_class: + msg = instance_warn_message.format(cls=_clspath(cls), + new=_clspath(new_class)) + warnings.warn(msg, warn_category, stacklevel=2) + return super(DeprecatedClass, cls).__call__(*args, **kwargs) + deprecated_cls = DeprecatedClass(name, (new_class,), clsdict or {}) frm = inspect.stack()[1] parent_module = inspect.getmodule(frm[0])