From 7871acd66553dca4d787d2d6fbff174851a10acd Mon Sep 17 00:00:00 2001 From: nramirezuy Date: Wed, 17 Jul 2013 15:22:08 -0300 Subject: [PATCH] Item multi inherit fixed --- scrapy/item.py | 17 ++++++----- tests/test_item.py | 70 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 7 deletions(-) diff --git a/scrapy/item.py b/scrapy/item.py index 9998010b2..8ac279642 100644 --- a/scrapy/item.py +++ b/scrapy/item.py @@ -24,18 +24,21 @@ class Field(dict): class ItemMeta(ABCMeta): def __new__(mcs, class_name, bases, attrs): + new_bases = tuple(base._class for base in bases if hasattr(base, '_class')) + _class = super(ItemMeta, mcs).__new__(mcs, 'x_' + class_name, new_bases, attrs) + fields = {} new_attrs = {} - for n, v in six.iteritems(attrs): + for n in dir(_class): + v = getattr(_class, n) if isinstance(v, Field): fields[n] = v - else: - new_attrs[n] = v + elif n in attrs: + new_attrs[n] = attrs[n] - cls = super(ItemMeta, mcs).__new__(mcs, class_name, bases, new_attrs) - cls.fields = cls.fields.copy() - cls.fields.update(fields) - return cls + new_attrs['fields'] = fields + new_attrs['_class'] = _class + return super(ItemMeta, mcs).__new__(mcs, class_name, bases, new_attrs) class DictItem(MutableMapping, BaseItem): diff --git a/tests/test_item.py b/tests/test_item.py index 5de77d22e..76061bbd9 100644 --- a/tests/test_item.py +++ b/tests/test_item.py @@ -134,6 +134,76 @@ class ItemTest(unittest.TestCase): self.assertEqual(list(i.keys()), ['keys']) self.assertEqual(list(i.values()), [3]) + def test_metaclass_multiple_inheritance_simple(self): + class A(Item): + save = Field(default='A') + + class B(A): pass + + class C(Item): + save = Field(default='C') + + class D(B, C): pass + + self.assertEqual(D(save='X')['save'], 'X') + self.assertEqual(D.fields, {'save': {'default': 'A'}}) + + # D class inverted + class E(C, B): pass + + self.assertEqual(E(save='X')['save'], 'X') + self.assertEqual(E.fields, {'save': {'default': 'C'}}) + + def test_metaclass_multiple_inheritance_diamond(self): + class A(Item): + save = Field(default='A') + load = Field(default='A') + + class B(A): pass + + class C(A): + save = Field(default='C') + + class D(B, C): + load = Field(default='D') + + self.assertEqual(D(save='X')['save'], 'X') + self.assertEqual(D(load='X')['load'], 'X') + self.assertEqual(D.fields, {'save': {'default': 'C'}, + 'load': {'default': 'D'}}) + + # D class inverted + class E(C, B): + load = Field(default='E') + + self.assertEqual(E(save='X')['save'], 'X') + self.assertEqual(E(load='X')['load'], 'X') + self.assertEqual(E.fields, {'save': {'default': 'C'}, + 'load': {'default': 'E'}}) + + def test_metaclass_multiple_inheritance_without_metaclass(self): + class A(Item): + save = Field(default='A') + + class B(A): pass + + class C(object): + not_allowed = Field(default='not_allowed') + save = Field(default='C') + + class D(B, C): pass + + self.assertRaises(KeyError, D, not_allowed='value') + self.assertEqual(D(save='X')['save'], 'X') + self.assertEqual(D.fields, {'save': {'default': 'A'}}) + + # D class inverted + class E(C, B): pass + + self.assertRaises(KeyError, E, not_allowed='value') + self.assertEqual(E(save='X')['save'], 'X') + self.assertEqual(E.fields, {'save': {'default': 'A'}}) + def test_to_dict(self): class TestItem(Item): name = Field()