1
0
mirror of https://github.com/scrapy/scrapy.git synced 2025-02-23 16:44:17 +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.http import Request
from scrapy.utils.trackref import object_ref from scrapy.utils.trackref import object_ref
from scrapy.utils.url import url_is_from_spider 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): class Spider(object_ref):
@ -65,8 +65,7 @@ class Spider(object_ref):
__repr__ = __str__ __repr__ = __str__
class BaseSpider(Spider): BaseSpider = create_deprecated_class('BaseSpider', Spider)
__metaclass__ = deprecated_base_class(Spider, "scrapy.spider.BaseSpider was deprecated. Please inherit from scrapy.spider.Spider.")
class ObsoleteClass(object): class ObsoleteClass(object):

View File

@ -3,7 +3,7 @@ from __future__ import absolute_import
import inspect import inspect
import unittest import unittest
import warnings import warnings
from scrapy.utils.deprecate import deprecated_base_class from scrapy.utils.deprecate import create_deprecated_class
class MyWarning(UserWarning): class MyWarning(UserWarning):
pass pass
@ -19,17 +19,13 @@ class WarnWhenSubclassedTest(unittest.TestCase):
def test_no_warning_on_definition(self): def test_no_warning_on_definition(self):
with warnings.catch_warnings(record=True) as w: with warnings.catch_warnings(record=True) as w:
Deprecated = create_deprecated_class('Deprecated', NewName)
class Deprecated(NewName):
__metaclass__ = deprecated_base_class(NewName, "message")
self.assertEqual(w, []) self.assertEqual(w, [])
def test_warning_on_subclassing(self): def test_warning_on_subclassing(self):
with warnings.catch_warnings(record=True) as w: with warnings.catch_warnings(record=True) as w:
Deprecated = create_deprecated_class('Deprecated', NewName, MyWarning)
class Deprecated(NewName):
__metaclass__ = deprecated_base_class(NewName, "message", MyWarning)
class UserClass(Deprecated): class UserClass(Deprecated):
pass pass
@ -37,28 +33,28 @@ class WarnWhenSubclassedTest(unittest.TestCase):
self.assertEqual(len(w), 1) self.assertEqual(len(w), 1)
msg = w[0] msg = w[0]
assert issubclass(msg.category, MyWarning) 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]) self.assertEqual(msg.lineno, inspect.getsourcelines(UserClass)[1])
def test_warning_auto_message(self): def test_warning_auto_message(self):
with warnings.catch_warnings(record=True) as w: with warnings.catch_warnings(record=True) as w:
Deprecated = create_deprecated_class('Deprecated', NewName)
class Deprecated(NewName):
__metaclass__ = deprecated_base_class(NewName)
class UserClass2(Deprecated): class UserClass2(Deprecated):
pass pass
msg = str(w[0].message) msg = str(w[0].message)
self.assertIn("scrapy.tests.test_utils_deprecate.NewName", msg) 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): def test_issubclass(self):
with warnings.catch_warnings(record=True): with warnings.catch_warnings(record=True):
DeprecatedName = create_deprecated_class('DeprecatedName', NewName)
class DeprecatedName(NewName):
__metaclass__ = deprecated_base_class(NewName, "message", MyWarning)
class UpdatedUserClass1(NewName): class UpdatedUserClass1(NewName):
pass pass
@ -88,9 +84,7 @@ class WarnWhenSubclassedTest(unittest.TestCase):
def test_isinstance(self): def test_isinstance(self):
with warnings.catch_warnings(record=True): with warnings.catch_warnings(record=True):
DeprecatedName = create_deprecated_class('DeprecatedName', NewName)
class DeprecatedName(NewName):
__metaclass__ = deprecated_base_class(NewName, "message", MyWarning)
class UpdatedUserClass2(NewName): class UpdatedUserClass2(NewName):
pass pass
@ -114,14 +108,3 @@ class WarnWhenSubclassedTest(unittest.TestCase):
assert isinstance(OutdatedUserClass2(), DeprecatedName) assert isinstance(OutdatedUserClass2(), DeprecatedName)
assert not isinstance(UnrelatedClass(), DeprecatedName) assert not isinstance(UnrelatedClass(), DeprecatedName)
assert not isinstance(OldStyleClass(), 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 import inspect
from scrapy.exceptions import ScrapyDeprecationWarning from scrapy.exceptions import ScrapyDeprecationWarning
def attribute(obj, oldattr, newattr, version='0.12'): def attribute(obj, oldattr, newattr, version='0.12'):
cname = obj.__class__.__name__ cname = obj.__class__.__name__
warnings.warn("%s.%s attribute is deprecated and will be no longer supported " 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) (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 Return a "deprecated" class that causes its subclasses to issue a warning.
they are subclassed. Subclasses of ``new_class`` are considered subclasses of this class.
In addition to that, subclasses of ``new_class`` are considered subclasses It can be used to rename a base class in a library. For example, if we
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
have have
class OldName(SomeClass): class OldName(SomeClass):
@ -30,30 +28,26 @@ def deprecated_base_class(new_class, message=None, category=ScrapyDeprecationWar
class NewName(SomeClass): class NewName(SomeClass):
# ... # ...
class OldName(NewName): OldName = create_deprecated_class('OldName', NewName)
__metaclass__ = deprecated_base_class(NewName, "OldName is deprecated. Please inherit from NewName.")
Then, if user class inherits from OldName, warning is issued. Also, if Then, if user class inherits from OldName, warning is issued. Also, if
some code uses ``issubclass(sub, OldName)`` or ``isinstance(sub(), OldName)`` 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 checks they'll still return True if sub is a subclass of NewName instead of
OldName. OldName.
""" """
class Metaclass(type):
deprecated = {}
class DeprecatedClass(type):
def __init__(cls, name, bases, clsdict): def __init__(cls, name, bases, clsdict):
if 'cls' in deprecated:
if not issubclass(cls, new_class): if message is not None:
raise ValueError("first parameter of `warn_when_subclassed` must be a superclass of %s" % cls) msg = message
else:
warn_message = message msg = "Base class {0} of {1} was deprecated. Please inherit from {2}."\
if warn_message is None: .format(_clspath(deprecated['cls']), _clspath(cls), _clspath(new_class))
# XXX: how to get a name of deprecated base class? warnings.warn(msg, warn_category, stacklevel=2)
cls_name = cls.__module__ + '.' + name super(DeprecatedClass, cls).__init__(name, bases, clsdict)
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)
# see http://www.python.org/dev/peps/pep-3119/#overloading-isinstance-and-issubclass # 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 # 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} candidates = {cls, new_class}
return any(c in candidates for c in mro) 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__)