Source code for scrapy.utils.item

from collections.abc import MutableMapping

from scrapy.item import BaseItem


def _is_dataclass_instance(obj):
    """
    Return True if *obj* is a dataclass object, False otherwise.
    Taken from https://docs.python.org/3/library/dataclasses.html#dataclasses.is_dataclass.
    """
    try:
        from dataclasses import is_dataclass
    except ImportError:
        return False
    else:
        return is_dataclass(obj) and not isinstance(obj, type)


[docs]def is_item_like(obj): """Return ``True`` if *obj* is an instance of a :ref:`supported item type <item-types>`; return ``False`` otherwise.""" return isinstance(obj, (BaseItem, dict)) or _is_dataclass_instance(obj)
[docs]class ItemAdapter(MutableMapping): """Wrapper class to interact with any :ref:`supported item type <item-types>` using the same, :class:`dict`-like API. .. invisible-code-block: python import sys .. skip: start if(sys.version_info < (3, 6), reason="python 3.6+ only") >>> from dataclasses import dataclass >>> from scrapy.utils.item import ItemAdapter >>> @dataclass ... class InventoryItem: ... name: str ... price: int ... >>> item = InventoryItem(name="foo", price=10) >>> adapter = ItemAdapter(item) >>> adapter.item is item True >>> adapter["name"] 'foo' >>> adapter["name"] = "bar" >>> adapter["price"] = 5 >>> item InventoryItem(name='bar', price=5) .. skip: end """ def __init__(self, item): if not is_item_like(item): raise TypeError("Expected a valid item, got %r instead: %s" % (type(item), item)) self.item = item def __repr__(self): return "ItemAdapter for type %s: %r" % (self.item.__class__.__name__, self.item) def __getitem__(self, field_name): if _is_dataclass_instance(self.item): if field_name in iter(self): return getattr(self.item, field_name) raise KeyError(field_name) return self.item[field_name] def __setitem__(self, field_name, value): if _is_dataclass_instance(self.item): if field_name in iter(self): setattr(self.item, field_name, value) else: raise KeyError("%s does not support field: %s" % (self.item.__class__.__name__, field_name)) else: self.item[field_name] = value def __delitem__(self, field_name): if _is_dataclass_instance(self.item): if field_name in iter(self): try: delattr(self.item, field_name) except AttributeError: raise KeyError(field_name) else: raise KeyError("%s does not support field: %s" % (self.item.__class__.__name__, field_name)) else: del self.item[field_name] def __iter__(self): if _is_dataclass_instance(self.item): return iter(attr for attr in dir(self.item) if attr in self.field_names()) return iter(self.item) def __len__(self): if _is_dataclass_instance(self.item): return len(list(iter(self))) return len(self.item) def get_field(self, field_name): """ Return the appropriate class:`scrapy.item.Field` object if the wrapped item is a BaseItem object, None otherwise. """ if isinstance(self.item, BaseItem): return self.item.fields.get(field_name) return None def field_names(self): """ Return a list with the names of all the defined fields for the item """ if _is_dataclass_instance(self.item): from dataclasses import fields return [field.name for field in fields(self.item)] elif isinstance(self.item, dict): return list(self.item.keys()) elif isinstance(self.item, BaseItem): return list(self.item.fields.keys())