Recurse into containers too, add skip in asdict

This commit is contained in:
Hynek Schlawack 2015-02-04 16:11:26 +01:00
parent d28336c28a
commit 2321fc202a
2 changed files with 52 additions and 3 deletions

View File

@ -30,7 +30,7 @@ def fields(cl):
return copy.deepcopy(attrs) return copy.deepcopy(attrs)
def asdict(inst, recurse=True): def asdict(inst, recurse=True, skip=None):
""" """
Return the ``attrs`` attribute values of *i* as a dict. Optionally recurse Return the ``attrs`` attribute values of *i* as a dict. Optionally recurse
into other ``attrs``-decorated classes. into other ``attrs``-decorated classes.
@ -40,14 +40,30 @@ def asdict(inst, recurse=True):
:param recurse: Recurse into classes that are also ``attrs``-decorated. :param recurse: Recurse into classes that are also ``attrs``-decorated.
:type recurse: bool :type recurse: bool
:param skip: A filter function that causes elements to be left out if it
returns ``True``. Is called with the :class:`attr.Attribute` as the
first argument and the value as the second argument.
:type skip: callable
:rtype: :class:`dict` :rtype: :class:`dict`
""" """
attrs = fields(inst.__class__) attrs = fields(inst.__class__)
rv = {} rv = {}
for a in attrs: for a in attrs:
v = getattr(inst, a.name) v = getattr(inst, a.name)
if recurse is True and has(v.__class__): if skip is not None and skip(a, v):
rv[a.name] = asdict(v, recurse=True) continue
if recurse is True:
if has(v.__class__):
rv[a.name] = asdict(v, recurse=True, skip=skip)
elif isinstance(v, (tuple, list, set)):
rv[a.name] = [asdict(i, recurse=True, skip=skip) for i in v]
elif isinstance(v, dict):
rv[a.name] = dict((asdict(kk) if has(kk.__class__) else kk,
asdict(vv) if has(vv.__class__) else vv)
for kk, vv in iteritems(v))
else:
rv[a.name] = v
else: else:
rv[a.name] = v rv[a.name] = v
return rv return rv

View File

@ -92,6 +92,39 @@ class TestAsDict(object):
C(3, 4), C(3, 4),
)) ))
def test_skip(self):
"""
Attributes that are supposed to be skipped are skipped.
"""
assert {
"x": {"x": 1},
} == asdict(C(
C(1, 2),
C(3, 4),
), skip=lambda a, v: a.name == "y")
@pytest.mark.parametrize("container", [
list,
tuple,
])
def test_lists_tuples(self, container):
"""
If recurse is True, also recurse into lists.
"""
assert {
"x": 1,
"y": [{"x": 2, "y": 3}, {"x": 4, "y": 5}],
} == asdict(C(1, container([C(2, 3), C(4, 5)])))
def test_dicts(self):
"""
If recurse is True, also recurse into dicts.
"""
assert {
"x": 1,
"y": {"a": {"x": 4, "y": 5}},
} == asdict(C(1, {"a": C(4, 5)}))
class TestHas(object): class TestHas(object):
""" """