We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
List of fields incorrectly accessed for dataclass items
### Description

If I make a `dataclass` item and want to export to csv, I get this error:

```
...
  File "/home/tadej/miniconda3/envs/main/lib/python3.7/site-packages/scrapy/exporters.py", line 251, in _write_headers_and_set_fields_to_export
    self.fields_to_export = list(item.fields.keys())
AttributeError: 'CompanyItem' object has no attribute 'fields'
```
The problem stems from here

https://github.com/scrapy/scrapy/blob/master/scrapy/exporters.py#L243-L253

There should be an additional if case checking if the item is of type dataclass, and then accessing the fields differently, perhaps as
```python
[field.name for field in fields(item)]
```


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `scrapy/exporters.py`
Content:
```
1 """
2 Item Exporters are used to export/serialize items into different formats.
3 """
4 
5 import csv
6 import io
7 import marshal
8 import pickle
9 import pprint
10 import warnings
11 from xml.sax.saxutils import XMLGenerator
12 
13 from itemadapter import is_item, ItemAdapter
14 
15 from scrapy.exceptions import ScrapyDeprecationWarning
16 from scrapy.item import _BaseItem
17 from scrapy.utils.python import is_listlike, to_bytes, to_unicode
18 from scrapy.utils.serialize import ScrapyJSONEncoder
19 
20 
21 __all__ = ['BaseItemExporter', 'PprintItemExporter', 'PickleItemExporter',
22            'CsvItemExporter', 'XmlItemExporter', 'JsonLinesItemExporter',
23            'JsonItemExporter', 'MarshalItemExporter']
24 
25 
26 class BaseItemExporter:
27 
28     def __init__(self, *, dont_fail=False, **kwargs):
29         self._kwargs = kwargs
30         self._configure(kwargs, dont_fail=dont_fail)
31 
32     def _configure(self, options, dont_fail=False):
33         """Configure the exporter by poping options from the ``options`` dict.
34         If dont_fail is set, it won't raise an exception on unexpected options
35         (useful for using with keyword arguments in subclasses ``__init__`` methods)
36         """
37         self.encoding = options.pop('encoding', None)
38         self.fields_to_export = options.pop('fields_to_export', None)
39         self.export_empty_fields = options.pop('export_empty_fields', False)
40         self.indent = options.pop('indent', None)
41         if not dont_fail and options:
42             raise TypeError("Unexpected options: %s" % ', '.join(options.keys()))
43 
44     def export_item(self, item):
45         raise NotImplementedError
46 
47     def serialize_field(self, field, name, value):
48         serializer = field.get('serializer', lambda x: x)
49         return serializer(value)
50 
51     def start_exporting(self):
52         pass
53 
54     def finish_exporting(self):
55         pass
56 
57     def _get_serialized_fields(self, item, default_value=None, include_empty=None):
58         """Return the fields to export as an iterable of tuples
59         (name, serialized_value)
60         """
61         item = ItemAdapter(item)
62 
63         if include_empty is None:
64             include_empty = self.export_empty_fields
65 
66         if self.fields_to_export is None:
67             if include_empty:
68                 field_iter = item.field_names()
69             else:
70                 field_iter = item.keys()
71         else:
72             if include_empty:
73                 field_iter = self.fields_to_export
74             else:
75                 field_iter = (x for x in self.fields_to_export if x in item)
76 
77         for field_name in field_iter:
78             if field_name in item:
79                 field_meta = item.get_field_meta(field_name)
80                 value = self.serialize_field(field_meta, field_name, item[field_name])
81             else:
82                 value = default_value
83 
84             yield field_name, value
85 
86 
87 class JsonLinesItemExporter(BaseItemExporter):
88 
89     def __init__(self, file, **kwargs):
90         super().__init__(dont_fail=True, **kwargs)
91         self.file = file
92         self._kwargs.setdefault('ensure_ascii', not self.encoding)
93         self.encoder = ScrapyJSONEncoder(**self._kwargs)
94 
95     def export_item(self, item):
96         itemdict = dict(self._get_serialized_fields(item))
97         data = self.encoder.encode(itemdict) + '\n'
98         self.file.write(to_bytes(data, self.encoding))
99 
100 
101 class JsonItemExporter(BaseItemExporter):
102 
103     def __init__(self, file, **kwargs):
104         super().__init__(dont_fail=True, **kwargs)
105         self.file = file
106         # there is a small difference between the behaviour or JsonItemExporter.indent
107         # and ScrapyJSONEncoder.indent. ScrapyJSONEncoder.indent=None is needed to prevent
108         # the addition of newlines everywhere
109         json_indent = self.indent if self.indent is not None and self.indent > 0 else None
110         self._kwargs.setdefault('indent', json_indent)
111         self._kwargs.setdefault('ensure_ascii', not self.encoding)
112         self.encoder = ScrapyJSONEncoder(**self._kwargs)
113         self.first_item = True
114 
115     def _beautify_newline(self):
116         if self.indent is not None:
117             self.file.write(b'\n')
118 
119     def start_exporting(self):
120         self.file.write(b"[")
121         self._beautify_newline()
122 
123     def finish_exporting(self):
124         self._beautify_newline()
125         self.file.write(b"]")
126 
127     def export_item(self, item):
128         if self.first_item:
129             self.first_item = False
130         else:
131             self.file.write(b',')
132             self._beautify_newline()
133         itemdict = dict(self._get_serialized_fields(item))
134         data = self.encoder.encode(itemdict)
135         self.file.write(to_bytes(data, self.encoding))
136 
137 
138 class XmlItemExporter(BaseItemExporter):
139 
140     def __init__(self, file, **kwargs):
141         self.item_element = kwargs.pop('item_element', 'item')
142         self.root_element = kwargs.pop('root_element', 'items')
143         super().__init__(**kwargs)
144         if not self.encoding:
145             self.encoding = 'utf-8'
146         self.xg = XMLGenerator(file, encoding=self.encoding)
147 
148     def _beautify_newline(self, new_item=False):
149         if self.indent is not None and (self.indent > 0 or new_item):
150             self.xg.characters('\n')
151 
152     def _beautify_indent(self, depth=1):
153         if self.indent:
154             self.xg.characters(' ' * self.indent * depth)
155 
156     def start_exporting(self):
157         self.xg.startDocument()
158         self.xg.startElement(self.root_element, {})
159         self._beautify_newline(new_item=True)
160 
161     def export_item(self, item):
162         self._beautify_indent(depth=1)
163         self.xg.startElement(self.item_element, {})
164         self._beautify_newline()
165         for name, value in self._get_serialized_fields(item, default_value=''):
166             self._export_xml_field(name, value, depth=2)
167         self._beautify_indent(depth=1)
168         self.xg.endElement(self.item_element)
169         self._beautify_newline(new_item=True)
170 
171     def finish_exporting(self):
172         self.xg.endElement(self.root_element)
173         self.xg.endDocument()
174 
175     def _export_xml_field(self, name, serialized_value, depth):
176         self._beautify_indent(depth=depth)
177         self.xg.startElement(name, {})
178         if hasattr(serialized_value, 'items'):
179             self._beautify_newline()
180             for subname, value in serialized_value.items():
181                 self._export_xml_field(subname, value, depth=depth + 1)
182             self._beautify_indent(depth=depth)
183         elif is_listlike(serialized_value):
184             self._beautify_newline()
185             for value in serialized_value:
186                 self._export_xml_field('value', value, depth=depth + 1)
187             self._beautify_indent(depth=depth)
188         elif isinstance(serialized_value, str):
189             self.xg.characters(serialized_value)
190         else:
191             self.xg.characters(str(serialized_value))
192         self.xg.endElement(name)
193         self._beautify_newline()
194 
195 
196 class CsvItemExporter(BaseItemExporter):
197 
198     def __init__(self, file, include_headers_line=True, join_multivalued=',', **kwargs):
199         super().__init__(dont_fail=True, **kwargs)
200         if not self.encoding:
201             self.encoding = 'utf-8'
202         self.include_headers_line = include_headers_line
203         self.stream = io.TextIOWrapper(
204             file,
205             line_buffering=False,
206             write_through=True,
207             encoding=self.encoding,
208             newline=''  # Windows needs this https://github.com/scrapy/scrapy/issues/3034
209         )
210         self.csv_writer = csv.writer(self.stream, **self._kwargs)
211         self._headers_not_written = True
212         self._join_multivalued = join_multivalued
213 
214     def serialize_field(self, field, name, value):
215         serializer = field.get('serializer', self._join_if_needed)
216         return serializer(value)
217 
218     def _join_if_needed(self, value):
219         if isinstance(value, (list, tuple)):
220             try:
221                 return self._join_multivalued.join(value)
222             except TypeError:  # list in value may not contain strings
223                 pass
224         return value
225 
226     def export_item(self, item):
227         if self._headers_not_written:
228             self._headers_not_written = False
229             self._write_headers_and_set_fields_to_export(item)
230 
231         fields = self._get_serialized_fields(item, default_value='',
232                                              include_empty=True)
233         values = list(self._build_row(x for _, x in fields))
234         self.csv_writer.writerow(values)
235 
236     def _build_row(self, values):
237         for s in values:
238             try:
239                 yield to_unicode(s, self.encoding)
240             except TypeError:
241                 yield s
242 
243     def _write_headers_and_set_fields_to_export(self, item):
244         if self.include_headers_line:
245             if not self.fields_to_export:
246                 if isinstance(item, dict):
247                     # for dicts try using fields of the first item
248                     self.fields_to_export = list(item.keys())
249                 else:
250                     # use fields declared in Item
251                     self.fields_to_export = list(item.fields.keys())
252             row = list(self._build_row(self.fields_to_export))
253             self.csv_writer.writerow(row)
254 
255 
256 class PickleItemExporter(BaseItemExporter):
257 
258     def __init__(self, file, protocol=4, **kwargs):
259         super().__init__(**kwargs)
260         self.file = file
261         self.protocol = protocol
262 
263     def export_item(self, item):
264         d = dict(self._get_serialized_fields(item))
265         pickle.dump(d, self.file, self.protocol)
266 
267 
268 class MarshalItemExporter(BaseItemExporter):
269     """Exports items in a Python-specific binary format (see
270     :mod:`marshal`).
271 
272     :param file: The file-like object to use for exporting the data. Its
273                  ``write`` method should accept :class:`bytes` (a disk file
274                  opened in binary mode, a :class:`~io.BytesIO` object, etc)
275     """
276 
277     def __init__(self, file, **kwargs):
278         super().__init__(**kwargs)
279         self.file = file
280 
281     def export_item(self, item):
282         marshal.dump(dict(self._get_serialized_fields(item)), self.file)
283 
284 
285 class PprintItemExporter(BaseItemExporter):
286 
287     def __init__(self, file, **kwargs):
288         super().__init__(**kwargs)
289         self.file = file
290 
291     def export_item(self, item):
292         itemdict = dict(self._get_serialized_fields(item))
293         self.file.write(to_bytes(pprint.pformat(itemdict) + '\n'))
294 
295 
296 class PythonItemExporter(BaseItemExporter):
297     """This is a base class for item exporters that extends
298     :class:`BaseItemExporter` with support for nested items.
299 
300     It serializes items to built-in Python types, so that any serialization
301     library (e.g. :mod:`json` or msgpack_) can be used on top of it.
302 
303     .. _msgpack: https://pypi.org/project/msgpack/
304     """
305 
306     def _configure(self, options, dont_fail=False):
307         self.binary = options.pop('binary', True)
308         super(PythonItemExporter, self)._configure(options, dont_fail)
309         if self.binary:
310             warnings.warn(
311                 "PythonItemExporter will drop support for binary export in the future",
312                 ScrapyDeprecationWarning)
313         if not self.encoding:
314             self.encoding = 'utf-8'
315 
316     def serialize_field(self, field, name, value):
317         serializer = field.get('serializer', self._serialize_value)
318         return serializer(value)
319 
320     def _serialize_value(self, value):
321         if isinstance(value, _BaseItem):
322             return self.export_item(value)
323         elif is_item(value):
324             return dict(self._serialize_item(value))
325         elif is_listlike(value):
326             return [self._serialize_value(v) for v in value]
327         encode_func = to_bytes if self.binary else to_unicode
328         if isinstance(value, (str, bytes)):
329             return encode_func(value, encoding=self.encoding)
330         return value
331 
332     def _serialize_item(self, item):
333         for key, value in ItemAdapter(item).items():
334             key = to_bytes(key) if self.binary else key
335             yield key, self._serialize_value(value)
336 
337     def export_item(self, item):
338         result = dict(self._get_serialized_fields(item))
339         if self.binary:
340             result = dict(self._serialize_item(result))
341         return result
342
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/scrapy/exporters.py b/scrapy/exporters.py
--- a/scrapy/exporters.py
+++ b/scrapy/exporters.py
@@ -243,12 +243,8 @@
     def _write_headers_and_set_fields_to_export(self, item):
         if self.include_headers_line:
             if not self.fields_to_export:
-                if isinstance(item, dict):
-                    # for dicts try using fields of the first item
-                    self.fields_to_export = list(item.keys())
-                else:
-                    # use fields declared in Item
-                    self.fields_to_export = list(item.fields.keys())
+                # use declared field names, or keys if the item is a dict
+                self.fields_to_export = ItemAdapter(item).field_names()
             row = list(self._build_row(self.fields_to_export))
             self.csv_writer.writerow(row)

{"golden_diff": "diff --git a/scrapy/exporters.py b/scrapy/exporters.py\n--- a/scrapy/exporters.py\n+++ b/scrapy/exporters.py\n@@ -243,12 +243,8 @@\n     def _write_headers_and_set_fields_to_export(self, item):\n         if self.include_headers_line:\n             if not self.fields_to_export:\n-                if isinstance(item, dict):\n-                    # for dicts try using fields of the first item\n-                    self.fields_to_export = list(item.keys())\n-                else:\n-                    # use fields declared in Item\n-                    self.fields_to_export = list(item.fields.keys())\n+                # use declared field names, or keys if the item is a dict\n+                self.fields_to_export = ItemAdapter(item).field_names()\n             row = list(self._build_row(self.fields_to_export))\n             self.csv_writer.writerow(row)\n", "issue": "List of fields incorrectly accessed for dataclass items\n### Description\r\n\r\nIf I make a `dataclass` item and want to export to csv, I get this error:\r\n\r\n```\r\n...\r\n  File \"/home/tadej/miniconda3/envs/main/lib/python3.7/site-packages/scrapy/exporters.py\", line 251, in _write_headers_and_set_fields_to_export\r\n    self.fields_to_export = list(item.fields.keys())\r\nAttributeError: 'CompanyItem' object has no attribute 'fields'\r\n```\r\nThe problem stems from here\r\n\r\nhttps://github.com/scrapy/scrapy/blob/master/scrapy/exporters.py#L243-L253\r\n\r\nThere should be an additional if case checking if the item is of type dataclass, and then accessing the fields differently, perhaps as\r\n```python\r\n[field.name for field in fields(item)]\r\n```\r\n\n", "before_files": [{"content": "\"\"\"\nItem Exporters are used to export/serialize items into different formats.\n\"\"\"\n\nimport csv\nimport io\nimport marshal\nimport pickle\nimport pprint\nimport warnings\nfrom xml.sax.saxutils import XMLGenerator\n\nfrom itemadapter import is_item, ItemAdapter\n\nfrom scrapy.exceptions import ScrapyDeprecationWarning\nfrom scrapy.item import _BaseItem\nfrom scrapy.utils.python import is_listlike, to_bytes, to_unicode\nfrom scrapy.utils.serialize import ScrapyJSONEncoder\n\n\n__all__ = ['BaseItemExporter', 'PprintItemExporter', 'PickleItemExporter',\n           'CsvItemExporter', 'XmlItemExporter', 'JsonLinesItemExporter',\n           'JsonItemExporter', 'MarshalItemExporter']\n\n\nclass BaseItemExporter:\n\n    def __init__(self, *, dont_fail=False, **kwargs):\n        self._kwargs = kwargs\n        self._configure(kwargs, dont_fail=dont_fail)\n\n    def _configure(self, options, dont_fail=False):\n        \"\"\"Configure the exporter by poping options from the ``options`` dict.\n        If dont_fail is set, it won't raise an exception on unexpected options\n        (useful for using with keyword arguments in subclasses ``__init__`` methods)\n        \"\"\"\n        self.encoding = options.pop('encoding', None)\n        self.fields_to_export = options.pop('fields_to_export', None)\n        self.export_empty_fields = options.pop('export_empty_fields', False)\n        self.indent = options.pop('indent', None)\n        if not dont_fail and options:\n            raise TypeError(\"Unexpected options: %s\" % ', '.join(options.keys()))\n\n    def export_item(self, item):\n        raise NotImplementedError\n\n    def serialize_field(self, field, name, value):\n        serializer = field.get('serializer', lambda x: x)\n        return serializer(value)\n\n    def start_exporting(self):\n        pass\n\n    def finish_exporting(self):\n        pass\n\n    def _get_serialized_fields(self, item, default_value=None, include_empty=None):\n        \"\"\"Return the fields to export as an iterable of tuples\n        (name, serialized_value)\n        \"\"\"\n        item = ItemAdapter(item)\n\n        if include_empty is None:\n            include_empty = self.export_empty_fields\n\n        if self.fields_to_export is None:\n            if include_empty:\n                field_iter = item.field_names()\n            else:\n                field_iter = item.keys()\n        else:\n            if include_empty:\n                field_iter = self.fields_to_export\n            else:\n                field_iter = (x for x in self.fields_to_export if x in item)\n\n        for field_name in field_iter:\n            if field_name in item:\n                field_meta = item.get_field_meta(field_name)\n                value = self.serialize_field(field_meta, field_name, item[field_name])\n            else:\n                value = default_value\n\n            yield field_name, value\n\n\nclass JsonLinesItemExporter(BaseItemExporter):\n\n    def __init__(self, file, **kwargs):\n        super().__init__(dont_fail=True, **kwargs)\n        self.file = file\n        self._kwargs.setdefault('ensure_ascii', not self.encoding)\n        self.encoder = ScrapyJSONEncoder(**self._kwargs)\n\n    def export_item(self, item):\n        itemdict = dict(self._get_serialized_fields(item))\n        data = self.encoder.encode(itemdict) + '\\n'\n        self.file.write(to_bytes(data, self.encoding))\n\n\nclass JsonItemExporter(BaseItemExporter):\n\n    def __init__(self, file, **kwargs):\n        super().__init__(dont_fail=True, **kwargs)\n        self.file = file\n        # there is a small difference between the behaviour or JsonItemExporter.indent\n        # and ScrapyJSONEncoder.indent. ScrapyJSONEncoder.indent=None is needed to prevent\n        # the addition of newlines everywhere\n        json_indent = self.indent if self.indent is not None and self.indent > 0 else None\n        self._kwargs.setdefault('indent', json_indent)\n        self._kwargs.setdefault('ensure_ascii', not self.encoding)\n        self.encoder = ScrapyJSONEncoder(**self._kwargs)\n        self.first_item = True\n\n    def _beautify_newline(self):\n        if self.indent is not None:\n            self.file.write(b'\\n')\n\n    def start_exporting(self):\n        self.file.write(b\"[\")\n        self._beautify_newline()\n\n    def finish_exporting(self):\n        self._beautify_newline()\n        self.file.write(b\"]\")\n\n    def export_item(self, item):\n        if self.first_item:\n            self.first_item = False\n        else:\n            self.file.write(b',')\n            self._beautify_newline()\n        itemdict = dict(self._get_serialized_fields(item))\n        data = self.encoder.encode(itemdict)\n        self.file.write(to_bytes(data, self.encoding))\n\n\nclass XmlItemExporter(BaseItemExporter):\n\n    def __init__(self, file, **kwargs):\n        self.item_element = kwargs.pop('item_element', 'item')\n        self.root_element = kwargs.pop('root_element', 'items')\n        super().__init__(**kwargs)\n        if not self.encoding:\n            self.encoding = 'utf-8'\n        self.xg = XMLGenerator(file, encoding=self.encoding)\n\n    def _beautify_newline(self, new_item=False):\n        if self.indent is not None and (self.indent > 0 or new_item):\n            self.xg.characters('\\n')\n\n    def _beautify_indent(self, depth=1):\n        if self.indent:\n            self.xg.characters(' ' * self.indent * depth)\n\n    def start_exporting(self):\n        self.xg.startDocument()\n        self.xg.startElement(self.root_element, {})\n        self._beautify_newline(new_item=True)\n\n    def export_item(self, item):\n        self._beautify_indent(depth=1)\n        self.xg.startElement(self.item_element, {})\n        self._beautify_newline()\n        for name, value in self._get_serialized_fields(item, default_value=''):\n            self._export_xml_field(name, value, depth=2)\n        self._beautify_indent(depth=1)\n        self.xg.endElement(self.item_element)\n        self._beautify_newline(new_item=True)\n\n    def finish_exporting(self):\n        self.xg.endElement(self.root_element)\n        self.xg.endDocument()\n\n    def _export_xml_field(self, name, serialized_value, depth):\n        self._beautify_indent(depth=depth)\n        self.xg.startElement(name, {})\n        if hasattr(serialized_value, 'items'):\n            self._beautify_newline()\n            for subname, value in serialized_value.items():\n                self._export_xml_field(subname, value, depth=depth + 1)\n            self._beautify_indent(depth=depth)\n        elif is_listlike(serialized_value):\n            self._beautify_newline()\n            for value in serialized_value:\n                self._export_xml_field('value', value, depth=depth + 1)\n            self._beautify_indent(depth=depth)\n        elif isinstance(serialized_value, str):\n            self.xg.characters(serialized_value)\n        else:\n            self.xg.characters(str(serialized_value))\n        self.xg.endElement(name)\n        self._beautify_newline()\n\n\nclass CsvItemExporter(BaseItemExporter):\n\n    def __init__(self, file, include_headers_line=True, join_multivalued=',', **kwargs):\n        super().__init__(dont_fail=True, **kwargs)\n        if not self.encoding:\n            self.encoding = 'utf-8'\n        self.include_headers_line = include_headers_line\n        self.stream = io.TextIOWrapper(\n            file,\n            line_buffering=False,\n            write_through=True,\n            encoding=self.encoding,\n            newline=''  # Windows needs this https://github.com/scrapy/scrapy/issues/3034\n        )\n        self.csv_writer = csv.writer(self.stream, **self._kwargs)\n        self._headers_not_written = True\n        self._join_multivalued = join_multivalued\n\n    def serialize_field(self, field, name, value):\n        serializer = field.get('serializer', self._join_if_needed)\n        return serializer(value)\n\n    def _join_if_needed(self, value):\n        if isinstance(value, (list, tuple)):\n            try:\n                return self._join_multivalued.join(value)\n            except TypeError:  # list in value may not contain strings\n                pass\n        return value\n\n    def export_item(self, item):\n        if self._headers_not_written:\n            self._headers_not_written = False\n            self._write_headers_and_set_fields_to_export(item)\n\n        fields = self._get_serialized_fields(item, default_value='',\n                                             include_empty=True)\n        values = list(self._build_row(x for _, x in fields))\n        self.csv_writer.writerow(values)\n\n    def _build_row(self, values):\n        for s in values:\n            try:\n                yield to_unicode(s, self.encoding)\n            except TypeError:\n                yield s\n\n    def _write_headers_and_set_fields_to_export(self, item):\n        if self.include_headers_line:\n            if not self.fields_to_export:\n                if isinstance(item, dict):\n                    # for dicts try using fields of the first item\n                    self.fields_to_export = list(item.keys())\n                else:\n                    # use fields declared in Item\n                    self.fields_to_export = list(item.fields.keys())\n            row = list(self._build_row(self.fields_to_export))\n            self.csv_writer.writerow(row)\n\n\nclass PickleItemExporter(BaseItemExporter):\n\n    def __init__(self, file, protocol=4, **kwargs):\n        super().__init__(**kwargs)\n        self.file = file\n        self.protocol = protocol\n\n    def export_item(self, item):\n        d = dict(self._get_serialized_fields(item))\n        pickle.dump(d, self.file, self.protocol)\n\n\nclass MarshalItemExporter(BaseItemExporter):\n    \"\"\"Exports items in a Python-specific binary format (see\n    :mod:`marshal`).\n\n    :param file: The file-like object to use for exporting the data. Its\n                 ``write`` method should accept :class:`bytes` (a disk file\n                 opened in binary mode, a :class:`~io.BytesIO` object, etc)\n    \"\"\"\n\n    def __init__(self, file, **kwargs):\n        super().__init__(**kwargs)\n        self.file = file\n\n    def export_item(self, item):\n        marshal.dump(dict(self._get_serialized_fields(item)), self.file)\n\n\nclass PprintItemExporter(BaseItemExporter):\n\n    def __init__(self, file, **kwargs):\n        super().__init__(**kwargs)\n        self.file = file\n\n    def export_item(self, item):\n        itemdict = dict(self._get_serialized_fields(item))\n        self.file.write(to_bytes(pprint.pformat(itemdict) + '\\n'))\n\n\nclass PythonItemExporter(BaseItemExporter):\n    \"\"\"This is a base class for item exporters that extends\n    :class:`BaseItemExporter` with support for nested items.\n\n    It serializes items to built-in Python types, so that any serialization\n    library (e.g. :mod:`json` or msgpack_) can be used on top of it.\n\n    .. _msgpack: https://pypi.org/project/msgpack/\n    \"\"\"\n\n    def _configure(self, options, dont_fail=False):\n        self.binary = options.pop('binary', True)\n        super(PythonItemExporter, self)._configure(options, dont_fail)\n        if self.binary:\n            warnings.warn(\n                \"PythonItemExporter will drop support for binary export in the future\",\n                ScrapyDeprecationWarning)\n        if not self.encoding:\n            self.encoding = 'utf-8'\n\n    def serialize_field(self, field, name, value):\n        serializer = field.get('serializer', self._serialize_value)\n        return serializer(value)\n\n    def _serialize_value(self, value):\n        if isinstance(value, _BaseItem):\n            return self.export_item(value)\n        elif is_item(value):\n            return dict(self._serialize_item(value))\n        elif is_listlike(value):\n            return [self._serialize_value(v) for v in value]\n        encode_func = to_bytes if self.binary else to_unicode\n        if isinstance(value, (str, bytes)):\n            return encode_func(value, encoding=self.encoding)\n        return value\n\n    def _serialize_item(self, item):\n        for key, value in ItemAdapter(item).items():\n            key = to_bytes(key) if self.binary else key\n            yield key, self._serialize_value(value)\n\n    def export_item(self, item):\n        result = dict(self._get_serialized_fields(item))\n        if self.binary:\n            result = dict(self._serialize_item(result))\n        return result\n", "path": "scrapy/exporters.py"}], "after_files": [{"content": "\"\"\"\nItem Exporters are used to export/serialize items into different formats.\n\"\"\"\n\nimport csv\nimport io\nimport marshal\nimport pickle\nimport pprint\nimport warnings\nfrom xml.sax.saxutils import XMLGenerator\n\nfrom itemadapter import is_item, ItemAdapter\n\nfrom scrapy.exceptions import ScrapyDeprecationWarning\nfrom scrapy.item import _BaseItem\nfrom scrapy.utils.python import is_listlike, to_bytes, to_unicode\nfrom scrapy.utils.serialize import ScrapyJSONEncoder\n\n\n__all__ = ['BaseItemExporter', 'PprintItemExporter', 'PickleItemExporter',\n           'CsvItemExporter', 'XmlItemExporter', 'JsonLinesItemExporter',\n           'JsonItemExporter', 'MarshalItemExporter']\n\n\nclass BaseItemExporter:\n\n    def __init__(self, *, dont_fail=False, **kwargs):\n        self._kwargs = kwargs\n        self._configure(kwargs, dont_fail=dont_fail)\n\n    def _configure(self, options, dont_fail=False):\n        \"\"\"Configure the exporter by poping options from the ``options`` dict.\n        If dont_fail is set, it won't raise an exception on unexpected options\n        (useful for using with keyword arguments in subclasses ``__init__`` methods)\n        \"\"\"\n        self.encoding = options.pop('encoding', None)\n        self.fields_to_export = options.pop('fields_to_export', None)\n        self.export_empty_fields = options.pop('export_empty_fields', False)\n        self.indent = options.pop('indent', None)\n        if not dont_fail and options:\n            raise TypeError(\"Unexpected options: %s\" % ', '.join(options.keys()))\n\n    def export_item(self, item):\n        raise NotImplementedError\n\n    def serialize_field(self, field, name, value):\n        serializer = field.get('serializer', lambda x: x)\n        return serializer(value)\n\n    def start_exporting(self):\n        pass\n\n    def finish_exporting(self):\n        pass\n\n    def _get_serialized_fields(self, item, default_value=None, include_empty=None):\n        \"\"\"Return the fields to export as an iterable of tuples\n        (name, serialized_value)\n        \"\"\"\n        item = ItemAdapter(item)\n\n        if include_empty is None:\n            include_empty = self.export_empty_fields\n\n        if self.fields_to_export is None:\n            if include_empty:\n                field_iter = item.field_names()\n            else:\n                field_iter = item.keys()\n        else:\n            if include_empty:\n                field_iter = self.fields_to_export\n            else:\n                field_iter = (x for x in self.fields_to_export if x in item)\n\n        for field_name in field_iter:\n            if field_name in item:\n                field_meta = item.get_field_meta(field_name)\n                value = self.serialize_field(field_meta, field_name, item[field_name])\n            else:\n                value = default_value\n\n            yield field_name, value\n\n\nclass JsonLinesItemExporter(BaseItemExporter):\n\n    def __init__(self, file, **kwargs):\n        super().__init__(dont_fail=True, **kwargs)\n        self.file = file\n        self._kwargs.setdefault('ensure_ascii', not self.encoding)\n        self.encoder = ScrapyJSONEncoder(**self._kwargs)\n\n    def export_item(self, item):\n        itemdict = dict(self._get_serialized_fields(item))\n        data = self.encoder.encode(itemdict) + '\\n'\n        self.file.write(to_bytes(data, self.encoding))\n\n\nclass JsonItemExporter(BaseItemExporter):\n\n    def __init__(self, file, **kwargs):\n        super().__init__(dont_fail=True, **kwargs)\n        self.file = file\n        # there is a small difference between the behaviour or JsonItemExporter.indent\n        # and ScrapyJSONEncoder.indent. ScrapyJSONEncoder.indent=None is needed to prevent\n        # the addition of newlines everywhere\n        json_indent = self.indent if self.indent is not None and self.indent > 0 else None\n        self._kwargs.setdefault('indent', json_indent)\n        self._kwargs.setdefault('ensure_ascii', not self.encoding)\n        self.encoder = ScrapyJSONEncoder(**self._kwargs)\n        self.first_item = True\n\n    def _beautify_newline(self):\n        if self.indent is not None:\n            self.file.write(b'\\n')\n\n    def start_exporting(self):\n        self.file.write(b\"[\")\n        self._beautify_newline()\n\n    def finish_exporting(self):\n        self._beautify_newline()\n        self.file.write(b\"]\")\n\n    def export_item(self, item):\n        if self.first_item:\n            self.first_item = False\n        else:\n            self.file.write(b',')\n            self._beautify_newline()\n        itemdict = dict(self._get_serialized_fields(item))\n        data = self.encoder.encode(itemdict)\n        self.file.write(to_bytes(data, self.encoding))\n\n\nclass XmlItemExporter(BaseItemExporter):\n\n    def __init__(self, file, **kwargs):\n        self.item_element = kwargs.pop('item_element', 'item')\n        self.root_element = kwargs.pop('root_element', 'items')\n        super().__init__(**kwargs)\n        if not self.encoding:\n            self.encoding = 'utf-8'\n        self.xg = XMLGenerator(file, encoding=self.encoding)\n\n    def _beautify_newline(self, new_item=False):\n        if self.indent is not None and (self.indent > 0 or new_item):\n            self.xg.characters('\\n')\n\n    def _beautify_indent(self, depth=1):\n        if self.indent:\n            self.xg.characters(' ' * self.indent * depth)\n\n    def start_exporting(self):\n        self.xg.startDocument()\n        self.xg.startElement(self.root_element, {})\n        self._beautify_newline(new_item=True)\n\n    def export_item(self, item):\n        self._beautify_indent(depth=1)\n        self.xg.startElement(self.item_element, {})\n        self._beautify_newline()\n        for name, value in self._get_serialized_fields(item, default_value=''):\n            self._export_xml_field(name, value, depth=2)\n        self._beautify_indent(depth=1)\n        self.xg.endElement(self.item_element)\n        self._beautify_newline(new_item=True)\n\n    def finish_exporting(self):\n        self.xg.endElement(self.root_element)\n        self.xg.endDocument()\n\n    def _export_xml_field(self, name, serialized_value, depth):\n        self._beautify_indent(depth=depth)\n        self.xg.startElement(name, {})\n        if hasattr(serialized_value, 'items'):\n            self._beautify_newline()\n            for subname, value in serialized_value.items():\n                self._export_xml_field(subname, value, depth=depth + 1)\n            self._beautify_indent(depth=depth)\n        elif is_listlike(serialized_value):\n            self._beautify_newline()\n            for value in serialized_value:\n                self._export_xml_field('value', value, depth=depth + 1)\n            self._beautify_indent(depth=depth)\n        elif isinstance(serialized_value, str):\n            self.xg.characters(serialized_value)\n        else:\n            self.xg.characters(str(serialized_value))\n        self.xg.endElement(name)\n        self._beautify_newline()\n\n\nclass CsvItemExporter(BaseItemExporter):\n\n    def __init__(self, file, include_headers_line=True, join_multivalued=',', **kwargs):\n        super().__init__(dont_fail=True, **kwargs)\n        if not self.encoding:\n            self.encoding = 'utf-8'\n        self.include_headers_line = include_headers_line\n        self.stream = io.TextIOWrapper(\n            file,\n            line_buffering=False,\n            write_through=True,\n            encoding=self.encoding,\n            newline=''  # Windows needs this https://github.com/scrapy/scrapy/issues/3034\n        )\n        self.csv_writer = csv.writer(self.stream, **self._kwargs)\n        self._headers_not_written = True\n        self._join_multivalued = join_multivalued\n\n    def serialize_field(self, field, name, value):\n        serializer = field.get('serializer', self._join_if_needed)\n        return serializer(value)\n\n    def _join_if_needed(self, value):\n        if isinstance(value, (list, tuple)):\n            try:\n                return self._join_multivalued.join(value)\n            except TypeError:  # list in value may not contain strings\n                pass\n        return value\n\n    def export_item(self, item):\n        if self._headers_not_written:\n            self._headers_not_written = False\n            self._write_headers_and_set_fields_to_export(item)\n\n        fields = self._get_serialized_fields(item, default_value='',\n                                             include_empty=True)\n        values = list(self._build_row(x for _, x in fields))\n        self.csv_writer.writerow(values)\n\n    def _build_row(self, values):\n        for s in values:\n            try:\n                yield to_unicode(s, self.encoding)\n            except TypeError:\n                yield s\n\n    def _write_headers_and_set_fields_to_export(self, item):\n        if self.include_headers_line:\n            if not self.fields_to_export:\n                # use declared field names, or keys if the item is a dict\n                self.fields_to_export = ItemAdapter(item).field_names()\n            row = list(self._build_row(self.fields_to_export))\n            self.csv_writer.writerow(row)\n\n\nclass PickleItemExporter(BaseItemExporter):\n\n    def __init__(self, file, protocol=4, **kwargs):\n        super().__init__(**kwargs)\n        self.file = file\n        self.protocol = protocol\n\n    def export_item(self, item):\n        d = dict(self._get_serialized_fields(item))\n        pickle.dump(d, self.file, self.protocol)\n\n\nclass MarshalItemExporter(BaseItemExporter):\n    \"\"\"Exports items in a Python-specific binary format (see\n    :mod:`marshal`).\n\n    :param file: The file-like object to use for exporting the data. Its\n                 ``write`` method should accept :class:`bytes` (a disk file\n                 opened in binary mode, a :class:`~io.BytesIO` object, etc)\n    \"\"\"\n\n    def __init__(self, file, **kwargs):\n        super().__init__(**kwargs)\n        self.file = file\n\n    def export_item(self, item):\n        marshal.dump(dict(self._get_serialized_fields(item)), self.file)\n\n\nclass PprintItemExporter(BaseItemExporter):\n\n    def __init__(self, file, **kwargs):\n        super().__init__(**kwargs)\n        self.file = file\n\n    def export_item(self, item):\n        itemdict = dict(self._get_serialized_fields(item))\n        self.file.write(to_bytes(pprint.pformat(itemdict) + '\\n'))\n\n\nclass PythonItemExporter(BaseItemExporter):\n    \"\"\"This is a base class for item exporters that extends\n    :class:`BaseItemExporter` with support for nested items.\n\n    It serializes items to built-in Python types, so that any serialization\n    library (e.g. :mod:`json` or msgpack_) can be used on top of it.\n\n    .. _msgpack: https://pypi.org/project/msgpack/\n    \"\"\"\n\n    def _configure(self, options, dont_fail=False):\n        self.binary = options.pop('binary', True)\n        super(PythonItemExporter, self)._configure(options, dont_fail)\n        if self.binary:\n            warnings.warn(\n                \"PythonItemExporter will drop support for binary export in the future\",\n                ScrapyDeprecationWarning)\n        if not self.encoding:\n            self.encoding = 'utf-8'\n\n    def serialize_field(self, field, name, value):\n        serializer = field.get('serializer', self._serialize_value)\n        return serializer(value)\n\n    def _serialize_value(self, value):\n        if isinstance(value, _BaseItem):\n            return self.export_item(value)\n        elif is_item(value):\n            return dict(self._serialize_item(value))\n        elif is_listlike(value):\n            return [self._serialize_value(v) for v in value]\n        encode_func = to_bytes if self.binary else to_unicode\n        if isinstance(value, (str, bytes)):\n            return encode_func(value, encoding=self.encoding)\n        return value\n\n    def _serialize_item(self, item):\n        for key, value in ItemAdapter(item).items():\n            key = to_bytes(key) if self.binary else key\n            yield key, self._serialize_value(value)\n\n    def export_item(self, item):\n        result = dict(self._get_serialized_fields(item))\n        if self.binary:\n            result = dict(self._serialize_item(result))\n        return result\n", "path": "scrapy/exporters.py"}]}