Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multiple itemadapter classes #68

Merged
merged 6 commits into from
Mar 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
27 changes: 13 additions & 14 deletions itemadapter/adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
1 change: 1 addition & 0 deletions pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ disable=
missing-function-docstring,
missing-module-docstring,
raise-missing-from,
too-many-return-statements,
unused-argument,


Expand Down
11 changes: 6 additions & 5 deletions tests/test_itemadapter.py
Original file line number Diff line number Diff line change
@@ -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):
Expand All @@ -13,5 +14,5 @@ def test_repr(self):
self.assertEqual(repr(adapter), "<ItemAdapter for dict(foo='bar')>")

def test_repr_subclass(self):
adapter = SubclassedItemAdapter(dict(foo="bar"))
self.assertEqual(repr(adapter), "<SubclassedItemAdapter for dict(foo='bar')>")
adapter = DictOnlyItemAdapter(dict(foo="bar"))
self.assertEqual(repr(adapter), "<DictOnlyItemAdapter for dict(foo='bar')>")