diff --git a/README.md b/README.md index 61b30d3..ae32b0a 100644 --- a/README.md +++ b/README.md @@ -374,6 +374,46 @@ class attribute in order to handle custom item classes: >>> ``` +### Multiple adapter classes + +If you need to have different handlers and/or priorities for different cases +you can subclass the `ItemAdapter` class and set the `ADAPTER_CLASSES` +attribute as needed: + + +**Example** +```python +>>> from collections import deque +>>> from itemadapter.adapter import ( +... ItemAdapter, +... AttrsAdapter, +... DataclassAdapter, +... DictAdapter, +... PydanticAdapter, +... ScrapyItemAdapter, +... ) +>>> from scrapy.item import Item, Field +>>> +>>> class BuiltinTypesItemAdapter(ItemAdapter): +... ADAPTER_CLASSES = deque([DictAdapter, DataclassAdapter]) +... +>>> class ThirdPartyTypesItemAdapter(ItemAdapter): +... ADAPTER_CLASSES = deque([AttrsAdapter, PydanticAdapter, ScrapyItemAdapter]) +... +>>> class ScrapyItem(Item): +... foo = Field() +... +>>> BuiltinTypesItemAdapter.is_item(dict()) +True +>>> ThirdPartyTypesItemAdapter.is_item(dict()) +False +>>> BuiltinTypesItemAdapter.is_item(ScrapyItem(foo="bar")) +False +>>> ThirdPartyTypesItemAdapter.is_item(ScrapyItem(foo="bar")) +True +>>> +``` + --- ## More examples diff --git a/itemadapter/adapter.py b/itemadapter/adapter.py index 9e015ab..5b36353 100644 --- a/itemadapter/adapter.py +++ b/itemadapter/adapter.py @@ -361,17 +361,16 @@ def asdict(self) -> dict: """Return a dict object with the contents of the adapter. This works slightly different than calling `dict(adapter)`: it's applied recursively to nested items (if there are any). """ - return {key: _asdict(value) for key, value in self.items()} - - -def _asdict(obj: Any) -> Any: - """Helper for ItemAdapter.asdict().""" - if isinstance(obj, dict): - return {key: _asdict(value) for key, value in obj.items()} - if isinstance(obj, (list, set, tuple)): - return obj.__class__(_asdict(x) for x in obj) - if isinstance(obj, ItemAdapter): - return obj.asdict() - if ItemAdapter.is_item(obj): - return ItemAdapter(obj).asdict() - return obj + return {key: self._asdict(value) for key, value in self.items()} + + @classmethod + def _asdict(cls, obj: Any) -> Any: + if isinstance(obj, dict): + return {key: cls._asdict(value) for key, value in obj.items()} + if isinstance(obj, (list, set, tuple)): + return obj.__class__(cls._asdict(x) for x in obj) + if isinstance(obj, cls): + return obj.asdict() + if cls.is_item(obj): + return cls(obj).asdict() + return obj diff --git a/pylintrc b/pylintrc index e89aaba..1fcbaa6 100644 --- a/pylintrc +++ b/pylintrc @@ -7,6 +7,7 @@ disable= missing-function-docstring, missing-module-docstring, raise-missing-from, + too-many-return-statements, unused-argument, diff --git a/tests/test_itemadapter.py b/tests/test_itemadapter.py index 1acaaad..91f7a21 100644 --- a/tests/test_itemadapter.py +++ b/tests/test_itemadapter.py @@ -1,10 +1,11 @@ import unittest +from collections import deque -from itemadapter.adapter import ItemAdapter +from itemadapter.adapter import ItemAdapter, DictAdapter -class SubclassedItemAdapter(ItemAdapter): - pass +class DictOnlyItemAdapter(ItemAdapter): + ADAPTER_CLASSES = deque([DictAdapter]) class ItemAdapterTestCase(unittest.TestCase): @@ -13,5 +14,5 @@ def test_repr(self): self.assertEqual(repr(adapter), "") def test_repr_subclass(self): - adapter = SubclassedItemAdapter(dict(foo="bar")) - self.assertEqual(repr(adapter), "") + adapter = DictOnlyItemAdapter(dict(foo="bar")) + self.assertEqual(repr(adapter), "")