1
0
mirror of https://github.com/scrapy/scrapy.git synced 2025-02-23 01:44:07 +00:00

simplify deprecation code (@dangra’s idea)

This commit is contained in:
Mikhail Korobov 2013-12-30 23:57:27 +06:00
parent 39458b7843
commit 1e96063ffa
3 changed files with 45 additions and 58 deletions

View File

@ -7,7 +7,7 @@ from scrapy import log
from scrapy.http import Request
from scrapy.utils.trackref import object_ref
from scrapy.utils.url import url_is_from_spider
from scrapy.utils.deprecate import deprecated_base_class
from scrapy.utils.deprecate import create_deprecated_class
class Spider(object_ref):
@ -65,8 +65,7 @@ class Spider(object_ref):
__repr__ = __str__
class BaseSpider(Spider):
__metaclass__ = deprecated_base_class(Spider, "scrapy.spider.BaseSpider was deprecated. Please inherit from scrapy.spider.Spider.")
BaseSpider = create_deprecated_class('BaseSpider', Spider)
class ObsoleteClass(object):

View File

@ -3,7 +3,7 @@ from __future__ import absolute_import
import inspect
import unittest
import warnings
from scrapy.utils.deprecate import deprecated_base_class
from scrapy.utils.deprecate import create_deprecated_class
class MyWarning(UserWarning):
pass
@ -19,17 +19,13 @@ class WarnWhenSubclassedTest(unittest.TestCase):
def test_no_warning_on_definition(self):
with warnings.catch_warnings(record=True) as w:
class Deprecated(NewName):
__metaclass__ = deprecated_base_class(NewName, "message")
Deprecated = create_deprecated_class('Deprecated', NewName)
self.assertEqual(w, [])
def test_warning_on_subclassing(self):
with warnings.catch_warnings(record=True) as w:
class Deprecated(NewName):
__metaclass__ = deprecated_base_class(NewName, "message", MyWarning)
Deprecated = create_deprecated_class('Deprecated', NewName, MyWarning)
class UserClass(Deprecated):
pass
@ -37,28 +33,28 @@ class WarnWhenSubclassedTest(unittest.TestCase):
self.assertEqual(len(w), 1)
msg = w[0]
assert issubclass(msg.category, MyWarning)
self.assertEqual(str(msg.message), "message")
self.assertEqual(
str(msg.message),
"Base class scrapy.tests.test_utils_deprecate.Deprecated of "
"scrapy.tests.test_utils_deprecate.UserClass was deprecated. "
"Please inherit from scrapy.tests.test_utils_deprecate.NewName."
)
self.assertEqual(msg.lineno, inspect.getsourcelines(UserClass)[1])
def test_warning_auto_message(self):
with warnings.catch_warnings(record=True) as w:
class Deprecated(NewName):
__metaclass__ = deprecated_base_class(NewName)
Deprecated = create_deprecated_class('Deprecated', NewName)
class UserClass2(Deprecated):
pass
msg = str(w[0].message)
self.assertIn("scrapy.tests.test_utils_deprecate.NewName", msg)
# this doesn't work:
# self.assertIn("scrapy.tests.test_utils_deprecate.Deprecated", msg)
self.assertIn("scrapy.tests.test_utils_deprecate.Deprecated", msg)
def test_issubclass(self):
with warnings.catch_warnings(record=True):
class DeprecatedName(NewName):
__metaclass__ = deprecated_base_class(NewName, "message", MyWarning)
DeprecatedName = create_deprecated_class('DeprecatedName', NewName)
class UpdatedUserClass1(NewName):
pass
@ -88,9 +84,7 @@ class WarnWhenSubclassedTest(unittest.TestCase):
def test_isinstance(self):
with warnings.catch_warnings(record=True):
class DeprecatedName(NewName):
__metaclass__ = deprecated_base_class(NewName, "message", MyWarning)
DeprecatedName = create_deprecated_class('DeprecatedName', NewName)
class UpdatedUserClass2(NewName):
pass
@ -114,14 +108,3 @@ class WarnWhenSubclassedTest(unittest.TestCase):
assert isinstance(OutdatedUserClass2(), DeprecatedName)
assert not isinstance(UnrelatedClass(), DeprecatedName)
assert not isinstance(OldStyleClass(), DeprecatedName)
def test_invalid_usage(self):
class SomeClass(object):
pass
def define_invalid():
class DeprecatedName(NewName):
__metaclass__ = deprecated_base_class(SomeClass)
self.assertRaises(ValueError, define_invalid)

View File

@ -4,6 +4,7 @@ import warnings
import inspect
from scrapy.exceptions import ScrapyDeprecationWarning
def attribute(obj, oldattr, newattr, version='0.12'):
cname = obj.__class__.__name__
warnings.warn("%s.%s attribute is deprecated and will be no longer supported "
@ -11,15 +12,12 @@ def attribute(obj, oldattr, newattr, version='0.12'):
(cname, oldattr, version, cname, newattr), ScrapyDeprecationWarning, stacklevel=3)
def deprecated_base_class(new_class, message=None, category=ScrapyDeprecationWarning):
def create_deprecated_class(name, new_class, warn_category=ScrapyDeprecationWarning, message=None):
"""
Return a metaclass that causes classes to issue a warning when
they are subclassed.
Return a "deprecated" class that causes its subclasses to issue a warning.
Subclasses of ``new_class`` are considered subclasses of this class.
In addition to that, subclasses of ``new_class`` are considered subclasses
of a class this metaclass is applied to.
It can be used to rename a base class of some user classes, e.g. if we
It can be used to rename a base class in a library. For example, if we
have
class OldName(SomeClass):
@ -30,30 +28,26 @@ def deprecated_base_class(new_class, message=None, category=ScrapyDeprecationWar
class NewName(SomeClass):
# ...
class OldName(NewName):
__metaclass__ = deprecated_base_class(NewName, "OldName is deprecated. Please inherit from NewName.")
OldName = create_deprecated_class('OldName', NewName)
Then, if user class inherits from OldName, warning is issued. Also, if
some code uses ``issubclass(sub, OldName)`` or ``isinstance(sub(), OldName)``
checks they'll still return True if sub is a subclass of NewName instead of
OldName.
"""
class Metaclass(type):
deprecated = {}
class DeprecatedClass(type):
def __init__(cls, name, bases, clsdict):
if not issubclass(cls, new_class):
raise ValueError("first parameter of `warn_when_subclassed` must be a superclass of %s" % cls)
warn_message = message
if warn_message is None:
# XXX: how to get a name of deprecated base class?
cls_name = cls.__module__ + '.' + name
new_name = new_class.__module__ + '.' + new_class.__name__
warn_message = "Base class of %s was deprecated. Please inherit from %s." % (cls_name, new_name)
if len(cls.mro()) > len(new_class.mro()) + 1:
warnings.warn(warn_message, category, stacklevel=2)
super(Metaclass, cls).__init__(name, bases, clsdict)
if 'cls' in deprecated:
if message is not None:
msg = message
else:
msg = "Base class {0} of {1} was deprecated. Please inherit from {2}."\
.format(_clspath(deprecated['cls']), _clspath(cls), _clspath(new_class))
warnings.warn(msg, warn_category, stacklevel=2)
super(DeprecatedClass, cls).__init__(name, bases, clsdict)
# see http://www.python.org/dev/peps/pep-3119/#overloading-isinstance-and-issubclass
# and http://docs.python.org/2/reference/datamodel.html#customizing-instance-and-subclass-checks
@ -70,5 +64,16 @@ def deprecated_base_class(new_class, message=None, category=ScrapyDeprecationWar
candidates = {cls, new_class}
return any(c in candidates for c in mro)
return Metaclass
deprecated_cls = DeprecatedClass(name, (new_class,), {})
deprecated['cls'] = deprecated_cls
frm = inspect.stack()[1]
parent_module = inspect.getmodule(frm[0])
if parent_module is not None:
deprecated_cls.__module__ = parent_module.__name__
return deprecated_cls
def _clspath(cls):
return '{}.{}'.format(cls.__module__, cls.__name__)