mirror of https://github.com/scrapy/scrapy.git
some improvements to item exporters
- passed previous class attributes to instances attributes - better handling of constructor arguments - better coverage on unittets (including encoding) - updated documentation with new changes
This commit is contained in:
parent
de89909195
commit
0ed849248f
|
|
@ -67,6 +67,8 @@ it to the serialization library, if the exporter supports it.
|
|||
There are ways to customize how a field will be serialized, which are described
|
||||
next.
|
||||
|
||||
.. _topics-exporters-serializers:
|
||||
|
||||
1. Declaring a serializer in the field
|
||||
--------------------------------------
|
||||
|
||||
|
|
@ -120,9 +122,16 @@ these two items::
|
|||
BaseItemExporter
|
||||
----------------
|
||||
|
||||
.. class:: BaseItemExporter
|
||||
.. class:: BaseItemExporter(fields_to_export=None, export_empty_fields=False, encoding='utf-8')
|
||||
|
||||
This is the base class for all Item Exporters, and it's an abstract class.
|
||||
This is the (abstract) base class for all Item Exporters. It provides
|
||||
support for common features used by all (concrete) Item Exporters, such as
|
||||
defining what fields to export, whether to export empty fields, or which
|
||||
encoding to use.
|
||||
|
||||
These features can be configured through the constructor arguments which
|
||||
populate their respective attributes: :attr:`fields_to_export`,
|
||||
:attr:`export_empty_fields`, :attr:`encoding`.
|
||||
|
||||
.. method:: export_item(item)
|
||||
|
||||
|
|
@ -135,6 +144,12 @@ BaseItemExporter
|
|||
method (in your custom Item Exporters) if you want to control how a
|
||||
particular field or value will be serialized/exported.
|
||||
|
||||
By default, this method looks for a serializer :ref:`declared in the item
|
||||
field <topics-exporters-serializers>` and returns the result of applying
|
||||
that serializer to the value. If no serializer is found, it returns the
|
||||
value unchanged except for ``unicode`` values which are encoded to
|
||||
``str`` using the encoding declared in the :attr:`encoding` attribute.
|
||||
|
||||
:param field: the field being serialized
|
||||
:type field: :class:`~scrapy.item.Field` object
|
||||
|
||||
|
|
@ -162,24 +177,39 @@ BaseItemExporter
|
|||
Some exporters (like :class:`CsvItemExporter`) respect the order of the
|
||||
fields defined in this attribute.
|
||||
|
||||
.. attribute:: export_empty_elements
|
||||
.. attribute:: export_empty_fields
|
||||
|
||||
Whether to include empty elements in the exported XML (in case of
|
||||
empty/missing fields). Defaults to ``False``.
|
||||
Whether to include empty/unpopulated item fields in the exported data.
|
||||
Defaults to ``False``.
|
||||
|
||||
.. attribute:: encoding
|
||||
|
||||
The encoding that will be used to encode unicode values. This only
|
||||
affects unicode values (which are always serialized to str using this
|
||||
encoding). Other value types are passed unchanged to the specific
|
||||
serialization library.
|
||||
|
||||
.. highlight:: none
|
||||
|
||||
XmlItemExporter
|
||||
---------------
|
||||
|
||||
.. class:: XmlItemExporter(file)
|
||||
.. class:: XmlItemExporter(file, item_element='item', root_element='items', \**kwargs)
|
||||
|
||||
Exports Items in XML format to the specified file object. You must also set
|
||||
the :attr:`fields_to_export` attribute to use it.
|
||||
Exports Items in XML format to the specified file object.
|
||||
|
||||
The default output of this exporter would be::
|
||||
:param root_element: The name of root element in the exported XML.
|
||||
:type root_element: str
|
||||
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
:param item_element: The name of each item element in the exported XML.
|
||||
:type item_element: str
|
||||
|
||||
The additional keyword arguments of this constructor are passed to the
|
||||
:class:`BaseItemExporter` constructor.
|
||||
|
||||
A typical output of this exporter would be::
|
||||
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<items>
|
||||
<item>
|
||||
<name>Color TV</name>
|
||||
|
|
@ -191,62 +221,70 @@ XmlItemExporter
|
|||
</item>
|
||||
</items>
|
||||
|
||||
.. attribute:: root_element
|
||||
|
||||
The name of root element in the exported XML. Defaults to ``'items'``.
|
||||
|
||||
.. attribute:: item_element
|
||||
|
||||
The name of each item element in the exported XML. Defaults to ``'item'``.
|
||||
|
||||
CsvItemExporter
|
||||
---------------
|
||||
|
||||
.. class:: CsvItemExporter(\*args, \**kwargs)
|
||||
.. class:: CsvItemExporter(file, include_headers_line=False, \**kwargs)
|
||||
|
||||
Exports Items in CSV format. The constructor arguments will be passed to the
|
||||
`csv.writer`_ constructor. This exporter respects the order of fields in the
|
||||
:attr:`BaseItemExporter.fields_to_export` attribute.
|
||||
Exports Items in CSV format to the given file-like object. If the
|
||||
:attr:`fields_to_export` attribute is set, it will be used to define the
|
||||
CSV columns and their order. The :attr:`export_empty_fields` attribute has
|
||||
no effect on this exporter.
|
||||
|
||||
The default output of this exporter would be::
|
||||
:param include_headers_line: If enabled, makes the exporter output a header
|
||||
line with the field names taken from
|
||||
:attr:`BaseItemExporter.fields_to_export` so that attribute must also be
|
||||
set in order to work (otherwise it raises a :exc:`RuntimeError`)
|
||||
:type include_headers_line: boolean
|
||||
|
||||
The additional keyword arguments of this constructor are passed to the
|
||||
:class:`BaseItemExporter` constructor, and then to the `csv.writer`_
|
||||
constructor, so you can use any `csv.writer` constructor argument to
|
||||
customize this exporter.
|
||||
|
||||
A typical output of this exporter would be::
|
||||
|
||||
Color TV,1200
|
||||
DVD player,200
|
||||
|
||||
.. attribute:: include_headers_line
|
||||
|
||||
Makes the exporter output a header line with the field names taken from
|
||||
:attr:`BaseItemExporter.fields_to_export` so that attribute must also be
|
||||
set in order to work.
|
||||
|
||||
Defaults to ``False``.
|
||||
|
||||
.. _csv.writer: http://docs.python.org/library/csv.html#csv.writer
|
||||
|
||||
PickleItemExporter
|
||||
------------------
|
||||
|
||||
.. class:: PickleItemExporter(\*args, \**kwargs)
|
||||
.. class:: PickleItemExporter(file, protocol=0, \**kwargs)
|
||||
|
||||
Exports Items in pickle format. The constructor arguments will be passed to
|
||||
the `Pickler`_ constructor. This is a binary format, so no output examples
|
||||
are provided.
|
||||
Exports Items in pickle format to the given file-like object.
|
||||
|
||||
:param protocol: The pickle protocol to use.
|
||||
:type protocol: int
|
||||
|
||||
.. _Pickler: http://docs.python.org/library/pickle.html#pickle.Pickler
|
||||
For more information, refer to the `pickle module documentation`_.
|
||||
|
||||
The additional keyword arguments of this constructor are passed to the
|
||||
:class:`BaseItemExporter` constructor.
|
||||
|
||||
This isn't a human readable format, so no output examples are provided.
|
||||
|
||||
.. _pickle module: http://docs.python.org/library/pickle.html
|
||||
|
||||
PprintItemExporter
|
||||
------------------
|
||||
|
||||
.. class:: PprintItemExporter(file)
|
||||
.. class:: PprintItemExporter(file, \**kwargs)
|
||||
|
||||
Exports Items in pretty print format to the specified file object.
|
||||
|
||||
The default output of this exporter would be::
|
||||
The additional keyword arguments of this constructor are passed to the
|
||||
:class:`BaseItemExporter` constructor.
|
||||
|
||||
A typical output of this exporter would be::
|
||||
|
||||
{'name': 'Color TV', 'price': '1200'}
|
||||
{'name': 'DVD player', 'price': '200'}
|
||||
|
||||
Longer lines would get pretty-formatted.
|
||||
Longer lines (when present) are pretty-formatted.
|
||||
|
||||
JsonLinesItemExporter
|
||||
---------------------
|
||||
|
|
@ -254,11 +292,13 @@ JsonLinesItemExporter
|
|||
.. module:: scrapy.contrib.exporter.jsonlines
|
||||
:synopsis: JsonLines Item Exporter
|
||||
|
||||
.. class:: JsonLinesItemExporter(file, \*args, \**kwargs)
|
||||
.. class:: JsonLinesItemExporter(file, \**kwargs)
|
||||
|
||||
Exports Items in JSON format to the specified file object, writing one
|
||||
serialized item per line. The additional constructor arguments are passed to
|
||||
the `JSONEncoder` constructor.
|
||||
Exports Items in JSON format to the specified file-like object, writing one
|
||||
JSON-encoded item per line. The additional constructor arguments are passed
|
||||
to the :class:`BaseItemExporter` constructor, and to the `JSONEncoder`_
|
||||
constructor, so you can use any `JSONEncoder`_ constructor argument to
|
||||
customize the exporter.
|
||||
|
||||
The default output of this exporter would be::
|
||||
|
||||
|
|
|
|||
|
|
@ -11,20 +11,38 @@ from xml.sax.saxutils import XMLGenerator
|
|||
__all__ = ['BaseItemExporter', 'PprintItemExporter', 'PickleItemExporter', \
|
||||
'CsvItemExporter', 'XmlItemExporter']
|
||||
|
||||
identity = lambda x: x
|
||||
|
||||
class BaseItemExporter(object):
|
||||
|
||||
fields_to_export = None
|
||||
export_empty_fields = False
|
||||
def __init__(self, **kwargs):
|
||||
self._configure(kwargs)
|
||||
|
||||
def _configure(self, options, dont_fail=False):
|
||||
"""Configure the exporter by poping options from the ``options`` dict.
|
||||
If dont_fail is set, it won't raise an exception on unexpected options
|
||||
(useful for using with keyword arguments in subclasses constructors)
|
||||
"""
|
||||
self.fields_to_export = options.pop('fields_to_export', None)
|
||||
self.export_empty_fields = options.pop('export_empty_fields', False)
|
||||
self.encoding = options.pop('encoding', 'utf-8')
|
||||
if not dont_fail and options:
|
||||
raise TypeError("Unexpected options: %s" % ', '.join(options.keys()))
|
||||
|
||||
def export_item(self, item):
|
||||
raise NotImplementedError
|
||||
|
||||
def serialize_field(self, field, name, value):
|
||||
serializer = field.get('serializer', identity)
|
||||
serializer = field.get('serializer', self._to_str_if_unicode)
|
||||
return serializer(value)
|
||||
|
||||
def start_exporting(self):
|
||||
pass
|
||||
|
||||
def finish_exporting(self):
|
||||
pass
|
||||
|
||||
def _to_str_if_unicode(self, value):
|
||||
return value.encode(self.encoding) if isinstance(value, unicode) else value
|
||||
|
||||
def _get_serialized_fields(self, item, default_value=None, include_empty=None):
|
||||
"""Return the fields to export as an iterable of tuples (name,
|
||||
serialized_value)
|
||||
|
|
@ -52,22 +70,14 @@ class BaseItemExporter(object):
|
|||
|
||||
yield field_name, value
|
||||
|
||||
def start_exporting(self):
|
||||
pass
|
||||
|
||||
def finish_exporting(self):
|
||||
pass
|
||||
|
||||
|
||||
|
||||
class XmlItemExporter(BaseItemExporter):
|
||||
|
||||
item_element = 'item'
|
||||
root_element = 'items'
|
||||
|
||||
def __init__(self, file):
|
||||
super(XmlItemExporter, self).__init__()
|
||||
self.xg = XMLGenerator(file)
|
||||
def __init__(self, file, **kwargs):
|
||||
self.item_element = kwargs.pop('item_element', 'item')
|
||||
self.root_element = kwargs.pop('root_element', 'items')
|
||||
self._configure(kwargs)
|
||||
self.xg = XMLGenerator(file, encoding=self.encoding)
|
||||
|
||||
def start_exporting(self):
|
||||
self.xg.startDocument()
|
||||
|
|
@ -91,17 +101,16 @@ class XmlItemExporter(BaseItemExporter):
|
|||
|
||||
class CsvItemExporter(BaseItemExporter):
|
||||
|
||||
include_headers_line = False
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(CsvItemExporter, self).__init__()
|
||||
self.csv_writer = csv.writer(*args, **kwargs)
|
||||
def __init__(self, file, include_headers_line=False, **kwargs):
|
||||
self._configure(kwargs, dont_fail=True)
|
||||
self.include_headers_line = include_headers_line
|
||||
self.csv_writer = csv.writer(file, **kwargs)
|
||||
|
||||
def start_exporting(self):
|
||||
if self.include_headers_line:
|
||||
if not self.fields_to_export:
|
||||
raise RuntimeError("You must set fields_to_export in order to" + \
|
||||
" use include_headers_line")
|
||||
raise RuntimeError("You must set fields_to_export in order" + \
|
||||
" to use include_headers_line")
|
||||
self.csv_writer.writerow(self.fields_to_export)
|
||||
|
||||
def export_item(self, item):
|
||||
|
|
@ -114,9 +123,9 @@ class CsvItemExporter(BaseItemExporter):
|
|||
|
||||
class PickleItemExporter(BaseItemExporter):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(PickleItemExporter, self).__init__()
|
||||
self.pickler = Pickler(*args, **kwargs)
|
||||
def __init__(self, file, protocol=0, **kwargs):
|
||||
self._configure(kwargs)
|
||||
self.pickler = Pickler(file, protocol)
|
||||
|
||||
def export_item(self, item):
|
||||
self.pickler.dump(dict(self._get_serialized_fields(item)))
|
||||
|
|
@ -124,8 +133,8 @@ class PickleItemExporter(BaseItemExporter):
|
|||
|
||||
class PprintItemExporter(BaseItemExporter):
|
||||
|
||||
def __init__(self, file):
|
||||
super(PprintItemExporter, self).__init__()
|
||||
def __init__(self, file, **kwargs):
|
||||
self._configure(kwargs)
|
||||
self.file = file
|
||||
|
||||
def export_item(self, item):
|
||||
|
|
|
|||
|
|
@ -7,10 +7,10 @@ except ImportError:
|
|||
|
||||
class JsonLinesItemExporter(BaseItemExporter):
|
||||
|
||||
def __init__(self, file, *args, **kwargs):
|
||||
super(JsonLinesItemExporter, self).__init__()
|
||||
def __init__(self, file, **kwargs):
|
||||
self._configure(kwargs)
|
||||
self.file = file
|
||||
self.encoder = json.JSONEncoder(*args, **kwargs)
|
||||
self.encoder = json.JSONEncoder(**kwargs)
|
||||
|
||||
def export_item(self, item):
|
||||
itemdict = dict(self._get_serialized_fields(item))
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ from cStringIO import StringIO
|
|||
from twisted.trial import unittest
|
||||
|
||||
from scrapy.item import Item, Field
|
||||
from scrapy.utils.python import str_to_unicode
|
||||
from scrapy.contrib.exporter import BaseItemExporter, PprintItemExporter, \
|
||||
PickleItemExporter, CsvItemExporter, XmlItemExporter
|
||||
|
||||
|
|
@ -15,16 +16,21 @@ class TestItem(Item):
|
|||
class BaseItemExporterTest(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.i = TestItem(name=u'John', age='22')
|
||||
self.i = TestItem(name=u'John\xa3', age='22')
|
||||
self.output = StringIO()
|
||||
self.ie = self._get_exporter()
|
||||
|
||||
def _get_exporter(self):
|
||||
return BaseItemExporter()
|
||||
def _get_exporter(self, **kwargs):
|
||||
return BaseItemExporter(**kwargs)
|
||||
|
||||
def _check_output(self):
|
||||
pass
|
||||
|
||||
def _assert_expected_item(self, exported_dict):
|
||||
for k, v in exported_dict.items():
|
||||
exported_dict[k] = str_to_unicode(v)
|
||||
self.assertEqual(self.i, exported_dict)
|
||||
|
||||
def test_export_item(self):
|
||||
self.ie.start_exporting()
|
||||
try:
|
||||
|
|
@ -37,10 +43,104 @@ class BaseItemExporterTest(unittest.TestCase):
|
|||
|
||||
def test_serialize_field(self):
|
||||
self.assertEqual(self.ie.serialize_field( \
|
||||
self.i.fields['name'], 'name', self.i['name']), 'John')
|
||||
self.i.fields['name'], 'name', self.i['name']), 'John\xc2\xa3')
|
||||
self.assertEqual( \
|
||||
self.ie.serialize_field(self.i.fields['age'], 'age', self.i['age']), '22')
|
||||
|
||||
def test_fields_to_export(self):
|
||||
ie = self._get_exporter(fields_to_export=['name'])
|
||||
self.assertEqual(list(ie._get_serialized_fields(self.i)), [('name', 'John\xc2\xa3')])
|
||||
|
||||
ie = self._get_exporter(fields_to_export=['name'], encoding='latin-1')
|
||||
name = list(ie._get_serialized_fields(self.i))[0][1]
|
||||
assert isinstance(name, str)
|
||||
self.assertEqual(name, 'John\xa3')
|
||||
|
||||
def test_field_custom_serializer(self):
|
||||
def custom_serializer(value):
|
||||
return str(int(value) + 2)
|
||||
|
||||
class CustomFieldItem(Item):
|
||||
name = Field()
|
||||
age = Field(serializer=custom_serializer)
|
||||
|
||||
i = CustomFieldItem(name=u'John\xa3', age='22')
|
||||
|
||||
ie = self._get_exporter()
|
||||
self.assertEqual(ie.serialize_field(i.fields['name'], 'name', i['name']), 'John\xc2\xa3')
|
||||
self.assertEqual(ie.serialize_field(i.fields['age'], 'age', i['age']), '24')
|
||||
|
||||
|
||||
class PprintItemExporterTest(BaseItemExporterTest):
|
||||
|
||||
def _get_exporter(self, **kwargs):
|
||||
return PprintItemExporter(self.output, **kwargs)
|
||||
|
||||
def _check_output(self):
|
||||
self._assert_expected_item(eval(self.output.getvalue()))
|
||||
|
||||
|
||||
class PickleItemExporterTest(BaseItemExporterTest):
|
||||
|
||||
def _get_exporter(self, **kwargs):
|
||||
return PickleItemExporter(self.output, **kwargs)
|
||||
|
||||
def _check_output(self):
|
||||
self._assert_expected_item(pickle.loads(self.output.getvalue()))
|
||||
|
||||
|
||||
class CsvItemExporterTest(BaseItemExporterTest):
|
||||
|
||||
def _get_exporter(self, **kwargs):
|
||||
return CsvItemExporter(self.output, **kwargs)
|
||||
|
||||
def _check_output(self):
|
||||
self.assertEqual(self.output.getvalue(), '22,John\xc2\xa3\r\n')
|
||||
|
||||
def test_header(self):
|
||||
output = StringIO()
|
||||
ie = CsvItemExporter(output, include_headers_line=True)
|
||||
self.assertRaises(RuntimeError, ie.start_exporting)
|
||||
|
||||
ie = CsvItemExporter(output, include_headers_line=True, \
|
||||
fields_to_export=self.i.fields.keys())
|
||||
ie.start_exporting()
|
||||
ie.export_item(self.i)
|
||||
ie.finish_exporting()
|
||||
self.assertEqual(output.getvalue(), 'age,name\r\n22,John\xc2\xa3\r\n')
|
||||
|
||||
|
||||
class XmlItemExporterTest(BaseItemExporterTest):
|
||||
|
||||
def _get_exporter(self, **kwargs):
|
||||
return XmlItemExporter(self.output, **kwargs)
|
||||
|
||||
def _check_output(self):
|
||||
expected_value = '<?xml version="1.0" encoding="utf-8"?>\n<items><item><age>22</age><name>John\xc2\xa3</name></item></items>'
|
||||
self.assertEqual(self.output.getvalue(), expected_value)
|
||||
|
||||
|
||||
class JsonLinesItemExporterTest(BaseItemExporterTest):
|
||||
|
||||
def _get_exporter(self, **kwargs):
|
||||
try:
|
||||
import json
|
||||
except ImportError:
|
||||
try:
|
||||
import simplejson
|
||||
except ImportError:
|
||||
raise unittest.SkipTest("simplejson module not available")
|
||||
from scrapy.contrib.exporter.jsonlines import JsonLinesItemExporter
|
||||
return JsonLinesItemExporter(self.output, **kwargs)
|
||||
|
||||
def _check_output(self):
|
||||
import simplejson
|
||||
exported = simplejson.loads(self.output.getvalue().strip())
|
||||
self.assertEqual(exported, dict(self.i))
|
||||
|
||||
|
||||
class CustomItemExporterTest(unittest.TestCase):
|
||||
|
||||
def test_exporter_custom_serializer(self):
|
||||
class CustomItemExporter(BaseItemExporter):
|
||||
def serialize_field(self, field, name, value):
|
||||
|
|
@ -50,99 +150,13 @@ class BaseItemExporterTest(unittest.TestCase):
|
|||
return super(CustomItemExporter, self).serialize_field(field, \
|
||||
name, value)
|
||||
|
||||
i = TestItem(name=u'John', age='22')
|
||||
ie = CustomItemExporter()
|
||||
|
||||
self.assertEqual( \
|
||||
ie.serialize_field(self.i.fields['name'], 'name', self.i['name']), 'John')
|
||||
ie.serialize_field(i.fields['name'], 'name', i['name']), 'John')
|
||||
self.assertEqual(
|
||||
ie.serialize_field(self.i.fields['age'], 'age', self.i['age']), '23')
|
||||
|
||||
def test_field_custom_serializer(self):
|
||||
def custom_serializer(value):
|
||||
return str(int(value) + 2)
|
||||
|
||||
class CustomFieldItem(Item):
|
||||
name = Field()
|
||||
age = Field(serializer=custom_serializer)
|
||||
|
||||
i = CustomFieldItem(name=u'John', age='22')
|
||||
|
||||
self.assertEqual( \
|
||||
self.ie.serialize_field(i.fields['name'], 'name', i['name']), 'John')
|
||||
self.assertEqual( \
|
||||
self.ie.serialize_field(i.fields['age'], 'age', i['age']), '24')
|
||||
|
||||
def test_fields_to_export(self):
|
||||
ie = BaseItemExporter()
|
||||
ie.fields_to_export = ['name']
|
||||
|
||||
self.assertEqual(list(ie._get_serialized_fields(self.i)), [('name', 'John')])
|
||||
|
||||
class PprintItemExporterTest(BaseItemExporterTest):
|
||||
|
||||
def _get_exporter(self):
|
||||
return PprintItemExporter(self.output)
|
||||
|
||||
def _check_output(self):
|
||||
self.assertEqual(dict(self.i), eval(self.output.getvalue()))
|
||||
|
||||
|
||||
class PickleItemExporterTest(BaseItemExporterTest):
|
||||
|
||||
def _get_exporter(self):
|
||||
return PickleItemExporter(self.output)
|
||||
|
||||
def _check_output(self):
|
||||
self.assertEqual(dict(self.i), pickle.loads(self.output.getvalue()))
|
||||
|
||||
class CsvItemExporterTest(BaseItemExporterTest):
|
||||
|
||||
def _get_exporter(self):
|
||||
return CsvItemExporter(self.output)
|
||||
|
||||
def _check_output(self):
|
||||
self.assertEqual(self.output.getvalue(), '22,John\r\n')
|
||||
|
||||
def test_header(self):
|
||||
output = StringIO()
|
||||
ie = CsvItemExporter(output)
|
||||
ie.include_headers_line = True
|
||||
|
||||
self.assertRaises(RuntimeError, ie.start_exporting)
|
||||
|
||||
ie.fields_to_export = self.i.fields.keys()
|
||||
ie.start_exporting()
|
||||
ie.export_item(self.i)
|
||||
ie.finish_exporting()
|
||||
|
||||
self.assertEqual(output.getvalue(), 'age,name\r\n22,John\r\n')
|
||||
|
||||
|
||||
class XmlItemExporterTest(BaseItemExporterTest):
|
||||
|
||||
def _get_exporter(self):
|
||||
return XmlItemExporter(self.output)
|
||||
|
||||
def _check_output(self):
|
||||
expected_value = '<?xml version="1.0" encoding="iso-8859-1"?>\n<items><item><age>22</age><name>John</name></item></items>'
|
||||
self.assertEqual(self.output.getvalue(), expected_value)
|
||||
|
||||
|
||||
class JsonLinesItemExporterTest(BaseItemExporterTest):
|
||||
|
||||
def _get_exporter(self):
|
||||
try:
|
||||
import json
|
||||
except ImportError:
|
||||
try:
|
||||
import simplejson
|
||||
except ImportError:
|
||||
raise unittest.SkipTest("simplejson module not available")
|
||||
from scrapy.contrib.exporter.jsonlines import JsonLinesItemExporter
|
||||
return JsonLinesItemExporter(self.output)
|
||||
|
||||
def _check_output(self):
|
||||
self.assertEqual(self.output.getvalue(), '{"age": "22", "name": "John"}\n')
|
||||
ie.serialize_field(i.fields['age'], 'age', i['age']), '23')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
|
|||
Loading…
Reference in New Issue