make sure input processors always receive iterables as input

This commit is contained in:
Pablo Hoffman 2009-08-19 16:49:49 -03:00
parent 1d351c2866
commit 907fad6da8
3 changed files with 47 additions and 9 deletions

View File

@ -91,7 +91,8 @@ called for a particular field (the same applies for any other field)::
l = XPathItemLoader(Product(), some_xpath_selector)
l.add_xpath('name', xpath1) # (1)
l.add_xpath('name', xpath2) # (2)
return l.load_item() # (3)
l.add_value('name', 'test') # (3)
return l.load_item() # (4)
So what happens is:
@ -103,19 +104,31 @@ So what happens is:
processor* used in (1). The result of the input processor is appended to the
data collected in (1) (if any).
3. The data collected in (1) and (2) is passed through the *output processor* of
3. This case is similar to the previous ones, except that the values to be
collected is assigned directly, instead of being extracted from a XPath.
However, the value is still passed through the input processors. In this
case, since the value is not iterable it is converted to an iterable of a
single element before passing it to the input processor, because input
processor always receive iterables.
4. The data collected in (1) and (2) is passed through the *output processor* of
the ``name`` field. The result of the output processor is the value assigned to
the ``name`` field in the item.
It's worth noticing that processors are just callable objects, which are called
with the data to be parsed, and return a parsed value. So you can use any
function as input or output processor, provided they can receive only one
positional (required) argument.
function as input or output processor. They only requirement is that they must
accept one (and only one) positional argument, which will be an iterator.
.. note:: Both input and output processors must receive an iterator as their
first argument. The output of those functions can be anything. The result of
input processors will be appended to an internal list (in the Loader)
containing the collected values (for that field). The result of the output
processors is the value that will be finally assigned to the item.
The other thing you need to keep in mind is that the values returned by input
processors are collected internally (in lists) and then passed to output
processors to populate the fields, so output processors should expect iterables
as input.
processors to populate the fields.
Last, but not least, Scrapy comes with some :ref:`commonly used processors
<topics-loaders-available-processors>` built-in for convenience.

View File

@ -26,10 +26,12 @@ class ItemLoader(object):
self._values = defaultdict(list)
def add_value(self, field_name, value):
value = arg_to_iter(value)
processed_value = self._process_input_value(field_name, value)
self._values[field_name] += arg_to_iter(processed_value)
def replace_value(self, field_name, value):
value = arg_to_iter(value)
processed_value = self._process_input_value(field_name, value)
self._values[field_name] = arg_to_iter(processed_value)

View File

@ -70,7 +70,30 @@ class ItemLoaderTest(unittest.TestCase):
self.assertEqual(il.get_collected_values('name'), [u'Pepe'])
self.assertEqual(il.get_output_value('name'), [u'Pepe'])
def test_apply_concat_filter(self):
def test_iter_on_input_processor_input(self):
class NameFirstItemLoader(NameItemLoader):
name_in = TakeFirst()
il = NameFirstItemLoader()
il.add_value('name', u'marta')
self.assertEqual(il.get_collected_values('name'), [u'marta'])
il = NameFirstItemLoader()
il.add_value('name', [u'marta', u'jose'])
self.assertEqual(il.get_collected_values('name'), [u'marta'])
il = NameFirstItemLoader()
il.replace_value('name', u'marta')
self.assertEqual(il.get_collected_values('name'), [u'marta'])
il = NameFirstItemLoader()
il.replace_value('name', [u'marta', u'jose'])
self.assertEqual(il.get_collected_values('name'), [u'marta'])
il = NameFirstItemLoader()
il.add_value('name', u'marta')
il.add_value('name', [u'jose', u'pedro'])
self.assertEqual(il.get_collected_values('name'), [u'marta', u'jose'])
def test_map_compose_filter(self):
def filter_world(x):
return None if x == 'world' else x
@ -78,7 +101,7 @@ class ItemLoaderTest(unittest.TestCase):
self.assertEqual(proc(['hello', 'world', 'this', 'is', 'scrapy']),
['HELLO', 'THIS', 'IS', 'SCRAPY'])
def test_map_concat_filter_multil(self):
def test_map_compose_filter_multil(self):
class TestItemLoader(NameItemLoader):
name_in = MapCompose(lambda v: v.title(), lambda v: v[:-1])
@ -121,7 +144,7 @@ class ItemLoaderTest(unittest.TestCase):
il.add_value('name', u'marta')
self.assertEqual(il.get_output_value('name'), [u'Marta'])
def test_empty_map_concat(self):
def test_empty_map_compose(self):
class IdentityDefaultedItemLoader(DefaultedItemLoader):
name_in = MapCompose()