mirror of https://github.com/scrapy/scrapy.git
Rename warn_when_subclassed to deprecated_base_class; add some magic for issubclass and isinstance checks to work if subclasses of non-deprecated class are checked against deprecated class.
This commit is contained in:
parent
04788673e7
commit
39458b7843
|
|
@ -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 warn_when_subclassed
|
||||
from scrapy.utils.deprecate import deprecated_base_class
|
||||
|
||||
|
||||
class Spider(object_ref):
|
||||
|
|
@ -66,10 +66,7 @@ class Spider(object_ref):
|
|||
|
||||
|
||||
class BaseSpider(Spider):
|
||||
__metaclass__ = warn_when_subclassed(
|
||||
superclass=Spider,
|
||||
message="scrapy.spider.BaseSpider was deprecated. Please inherit from scrapy.spider.Spider."
|
||||
)
|
||||
__metaclass__ = deprecated_base_class(Spider, "scrapy.spider.BaseSpider was deprecated. Please inherit from scrapy.spider.Spider.")
|
||||
|
||||
|
||||
class ObsoleteClass(object):
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import gzip
|
|||
import inspect
|
||||
import warnings
|
||||
from cStringIO import StringIO
|
||||
from scrapy.utils.trackref import object_ref
|
||||
|
||||
from twisted.trial import unittest
|
||||
|
||||
|
|
@ -141,12 +142,54 @@ class BaseSpiderDeprecationTest(unittest.TestCase):
|
|||
def test_basespider_is_deprecated(self):
|
||||
with warnings.catch_warnings(record=True) as w:
|
||||
|
||||
class MySpider(BaseSpider):
|
||||
class MySpider1(BaseSpider):
|
||||
pass
|
||||
|
||||
self.assertEqual(len(w), 1)
|
||||
self.assertEqual(w[0].category, ScrapyDeprecationWarning)
|
||||
self.assertEqual(w[0].lineno, inspect.getsourcelines(MySpider)[1])
|
||||
self.assertEqual(w[0].lineno, inspect.getsourcelines(MySpider1)[1])
|
||||
|
||||
def test_basespider_issubclass(self):
|
||||
class MySpider2(Spider):
|
||||
pass
|
||||
|
||||
class MySpider2a(MySpider2):
|
||||
pass
|
||||
|
||||
class Foo(object):
|
||||
pass
|
||||
|
||||
class Foo2(object_ref):
|
||||
pass
|
||||
|
||||
assert issubclass(MySpider2, BaseSpider)
|
||||
assert issubclass(MySpider2a, BaseSpider)
|
||||
assert not issubclass(Foo, BaseSpider)
|
||||
assert not issubclass(Foo2, BaseSpider)
|
||||
|
||||
def test_basespider_isinstance(self):
|
||||
class MySpider3(Spider):
|
||||
name = 'myspider3'
|
||||
|
||||
class MySpider3a(MySpider3):
|
||||
pass
|
||||
|
||||
class Foo(object):
|
||||
pass
|
||||
|
||||
class Foo2(object_ref):
|
||||
pass
|
||||
|
||||
assert isinstance(MySpider3(), BaseSpider)
|
||||
assert isinstance(MySpider3a(), BaseSpider)
|
||||
assert not isinstance(Foo(), BaseSpider)
|
||||
assert not isinstance(Foo2(), BaseSpider)
|
||||
|
||||
def test_crawl_spider(self):
|
||||
assert issubclass(CrawlSpider, Spider)
|
||||
assert issubclass(CrawlSpider, BaseSpider)
|
||||
assert isinstance(CrawlSpider(name='foo'), Spider)
|
||||
assert isinstance(CrawlSpider(name='foo'), BaseSpider)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ from __future__ import absolute_import
|
|||
import inspect
|
||||
import unittest
|
||||
import warnings
|
||||
from scrapy.utils.deprecate import warn_when_subclassed
|
||||
from scrapy.utils.deprecate import deprecated_base_class
|
||||
|
||||
class MyWarning(UserWarning):
|
||||
pass
|
||||
|
|
@ -21,7 +21,7 @@ class WarnWhenSubclassedTest(unittest.TestCase):
|
|||
with warnings.catch_warnings(record=True) as w:
|
||||
|
||||
class Deprecated(NewName):
|
||||
__metaclass__ = warn_when_subclassed(NewName, "message")
|
||||
__metaclass__ = deprecated_base_class(NewName, "message")
|
||||
|
||||
self.assertEqual(w, [])
|
||||
|
||||
|
|
@ -29,7 +29,7 @@ class WarnWhenSubclassedTest(unittest.TestCase):
|
|||
with warnings.catch_warnings(record=True) as w:
|
||||
|
||||
class Deprecated(NewName):
|
||||
__metaclass__ = warn_when_subclassed(NewName, "message", MyWarning)
|
||||
__metaclass__ = deprecated_base_class(NewName, "message", MyWarning)
|
||||
|
||||
class UserClass(Deprecated):
|
||||
pass
|
||||
|
|
@ -39,3 +39,89 @@ class WarnWhenSubclassedTest(unittest.TestCase):
|
|||
assert issubclass(msg.category, MyWarning)
|
||||
self.assertEqual(str(msg.message), "message")
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
def test_issubclass(self):
|
||||
with warnings.catch_warnings(record=True):
|
||||
|
||||
class DeprecatedName(NewName):
|
||||
__metaclass__ = deprecated_base_class(NewName, "message", MyWarning)
|
||||
|
||||
class UpdatedUserClass1(NewName):
|
||||
pass
|
||||
|
||||
class UpdatedUserClass1a(NewName):
|
||||
pass
|
||||
|
||||
class OutdatedUserClass1(DeprecatedName):
|
||||
pass
|
||||
|
||||
class UnrelatedClass(object):
|
||||
pass
|
||||
|
||||
class OldStyleClass:
|
||||
pass
|
||||
|
||||
assert issubclass(UpdatedUserClass1, NewName)
|
||||
assert issubclass(UpdatedUserClass1a, NewName)
|
||||
assert issubclass(UpdatedUserClass1, DeprecatedName)
|
||||
assert issubclass(UpdatedUserClass1a, DeprecatedName)
|
||||
assert issubclass(OutdatedUserClass1, DeprecatedName)
|
||||
assert not issubclass(UnrelatedClass, DeprecatedName)
|
||||
assert not issubclass(OldStyleClass, DeprecatedName)
|
||||
assert not issubclass(OldStyleClass, DeprecatedName)
|
||||
|
||||
self.assertRaises(TypeError, issubclass, object(), DeprecatedName)
|
||||
|
||||
def test_isinstance(self):
|
||||
with warnings.catch_warnings(record=True):
|
||||
|
||||
class DeprecatedName(NewName):
|
||||
__metaclass__ = deprecated_base_class(NewName, "message", MyWarning)
|
||||
|
||||
class UpdatedUserClass2(NewName):
|
||||
pass
|
||||
|
||||
class UpdatedUserClass2a(NewName):
|
||||
pass
|
||||
|
||||
class OutdatedUserClass2(DeprecatedName):
|
||||
pass
|
||||
|
||||
class UnrelatedClass(object):
|
||||
pass
|
||||
|
||||
class OldStyleClass:
|
||||
pass
|
||||
|
||||
assert isinstance(UpdatedUserClass2(), NewName)
|
||||
assert isinstance(UpdatedUserClass2a(), NewName)
|
||||
assert isinstance(UpdatedUserClass2(), DeprecatedName)
|
||||
assert isinstance(UpdatedUserClass2a(), DeprecatedName)
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
"""Some helpers for deprecation messages"""
|
||||
|
||||
import warnings
|
||||
|
||||
import inspect
|
||||
from scrapy.exceptions import ScrapyDeprecationWarning
|
||||
|
||||
def attribute(obj, oldattr, newattr, version='0.12'):
|
||||
|
|
@ -11,15 +11,64 @@ def attribute(obj, oldattr, newattr, version='0.12'):
|
|||
(cname, oldattr, version, cname, newattr), ScrapyDeprecationWarning, stacklevel=3)
|
||||
|
||||
|
||||
def warn_when_subclassed(superclass, message, category=ScrapyDeprecationWarning):
|
||||
def deprecated_base_class(new_class, message=None, category=ScrapyDeprecationWarning):
|
||||
"""
|
||||
Return a metaclass that causes classes to
|
||||
issue a warning when they are subclassed.
|
||||
Return a metaclass that causes classes to issue a warning when
|
||||
they are subclassed.
|
||||
|
||||
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
|
||||
have
|
||||
|
||||
class OldName(SomeClass):
|
||||
# ...
|
||||
|
||||
and we want to rename it to NewName, we can do the following::
|
||||
|
||||
class NewName(SomeClass):
|
||||
# ...
|
||||
|
||||
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
|
||||
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):
|
||||
def __init__(cls, name, bases, clsdict):
|
||||
if len(cls.mro()) > len(superclass.mro()) + 1:
|
||||
warnings.warn(message, category, stacklevel=2)
|
||||
|
||||
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)
|
||||
|
||||
# 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
|
||||
# for implementation details
|
||||
def __instancecheck__(cls, inst):
|
||||
return any(cls.__subclasscheck__(c)
|
||||
for c in {type(inst), inst.__class__})
|
||||
|
||||
def __subclasscheck__(cls, sub):
|
||||
if not inspect.isclass(sub):
|
||||
raise TypeError("issubclass() arg 1 must be a class")
|
||||
|
||||
mro = getattr(sub, '__mro__', ())
|
||||
candidates = {cls, new_class}
|
||||
return any(c in candidates for c in mro)
|
||||
|
||||
return Metaclass
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue