docs: switch `scrapy.Item` examples to dataclasses (#7513)

* docs: switch `scrapy.Item` examples to dataclasses

* make serializer doc generic

* use modern type hints

* docs/spiders: switch TestItem consumer snippets to attribute access

Since the TestItem migration to @dataclass, the existing
item["id"] = ... assignments would raise TypeError on copy-paste.
Switch to item.id = ... to match the new dataclass declaration.

The snippets sit under .. skip: next so docs-tests still pass either
way, but the change keeps the examples runnable for readers.
This commit is contained in:
Omkar Kabde 2026-06-08 13:10:28 +05:30 committed by GitHub
parent 58af57a3ea
commit 5149e2c679
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 50 additions and 40 deletions

View File

@ -93,24 +93,25 @@ described next.
1. Declaring a serializer in the field
--------------------------------------
If you use :class:`~scrapy.Item` you can declare a serializer in the
:ref:`field metadata <topics-items-fields>`. The serializer must be
a callable which receives a value and returns its serialized form.
Every :ref:`item type <item-types>` except :class:`dict` lets you declare a
serializer in the :ref:`field metadata <topics-items-fields>`. The serializer
must be a callable which receives a value and returns its serialized form.
Example:
.. code-block:: python
import scrapy
from dataclasses import dataclass, field
def serialize_price(value):
return f"$ {str(value)}"
class Product(scrapy.Item):
name = scrapy.Field()
price = scrapy.Field(serializer=serialize_price)
@dataclass
class Product:
name: str
price: float = field(metadata={"serializer": serialize_price})
2. Overriding the serialize_field() method

View File

@ -102,14 +102,13 @@ One approach to overcome this is to define items using the
.. code-block:: python
from dataclasses import dataclass, field
from typing import Optional
@dataclass
class InventoryItem:
name: Optional[str] = field(default=None)
price: Optional[float] = field(default=None)
stock: Optional[int] = field(default=None)
name: str | None = field(default=None)
price: float | None = field(default=None)
stock: int | None = field(default=None)
.. _topics-loaders-processors:
@ -228,7 +227,8 @@ metadata. Here is an example:
.. code-block:: python
import scrapy
from dataclasses import dataclass, field
from itemloaders.processors import Join, MapCompose, TakeFirst
from w3lib.html import remove_tags
@ -238,14 +238,21 @@ metadata. Here is an example:
return value
class Product(scrapy.Item):
name = scrapy.Field(
input_processor=MapCompose(remove_tags),
output_processor=Join(),
@dataclass
class Product:
name: str | None = field(
default=None,
metadata={
"input_processor": MapCompose(remove_tags),
"output_processor": Join(),
},
)
price = scrapy.Field(
input_processor=MapCompose(remove_tags, filter_price),
output_processor=TakeFirst(),
price: str | None = field(
default=None,
metadata={
"input_processor": MapCompose(remove_tags, filter_price),
"output_processor": TakeFirst(),
},
)

View File

@ -337,17 +337,18 @@ respectively), the pipeline will put the results under the respective field
When using :ref:`item types <item-types>` for which fields are defined beforehand,
you must define both the URLs field and the results field. For example, when
using the images pipeline, items must define both the ``image_urls`` and the
``images`` field. For instance, using the :class:`~scrapy.Item` class:
``images`` field. For instance, using a dataclass:
.. code-block:: python
import scrapy
from dataclasses import dataclass, field
class MyItem(scrapy.Item):
@dataclass
class MyItem:
# ... other item fields ...
image_urls = scrapy.Field()
images = scrapy.Field()
image_urls: list[str] = field(default_factory=list)
images: list[dict] = field(default_factory=list)
If you want to use another field name for the URLs key or for the results key,
it is also possible to override it.

View File

@ -457,13 +457,14 @@ with a ``TestItem`` declared in a ``myproject.items`` module:
.. code-block:: python
import scrapy
from dataclasses import dataclass
class TestItem(scrapy.Item):
id = scrapy.Field()
name = scrapy.Field()
description = scrapy.Field()
@dataclass
class TestItem:
id: str | None = None
name: str | None = None
description: str | None = None
.. currentmodule:: scrapy.spiders
@ -556,7 +557,6 @@ Let's now take a look at an example CrawlSpider with rules:
.. code-block:: python
import scrapy
from scrapy.spiders import CrawlSpider, Rule
from scrapy.linkextractors import LinkExtractor
@ -576,7 +576,7 @@ Let's now take a look at an example CrawlSpider with rules:
def parse_item(self, response):
self.logger.info("Hi, this is an item page! %s", response.url)
item = scrapy.Item()
item = {}
item["id"] = response.xpath('//td[@id="item_id"]/text()').re(r"ID: (\d+)")
item["name"] = response.xpath('//td[@id="item_name"]/text()').get()
item["description"] = response.xpath(
@ -714,9 +714,9 @@ These spiders are pretty easy to use, let's have a look at one example:
)
item = TestItem()
item["id"] = node.xpath("@id").get()
item["name"] = node.xpath("name").get()
item["description"] = node.xpath("description").get()
item.id = node.xpath("@id").get()
item.name = node.xpath("name").get()
item.description = node.xpath("description").get()
return item
Basically what we did up there was to create a spider that downloads a feed from
@ -778,9 +778,9 @@ Let's see an example similar to the previous one, but using a
self.logger.info("Hi, this is a row!: %r", row)
item = TestItem()
item["id"] = row["id"]
item["name"] = row["name"]
item["description"] = row["description"]
item.id = row["id"]
item.name = row["name"]
item.description = row["description"]
return item

View File

@ -3,10 +3,11 @@
# See documentation in:
# https://docs.scrapy.org/en/latest/topics/items.html
import scrapy
from dataclasses import dataclass
class ${ProjectName}Item(scrapy.Item):
@dataclass
class ${ProjectName}Item:
# define the fields for your item here like:
# name = scrapy.Field()
# name: str | None = None
pass