From 2321fc202a64d51a4a2c930f8585e16d3064bb3a Mon Sep 17 00:00:00 2001 From: Hynek Schlawack Date: Wed, 4 Feb 2015 16:11:26 +0100 Subject: [PATCH] Recurse into containers too, add skip in asdict --- attr/_funcs.py | 22 +++++++++++++++++++--- tests/test_funcs.py | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 3 deletions(-) diff --git a/attr/_funcs.py b/attr/_funcs.py index d02f7803..e837b402 100644 --- a/attr/_funcs.py +++ b/attr/_funcs.py @@ -30,7 +30,7 @@ def fields(cl): 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 into other ``attrs``-decorated classes. @@ -40,14 +40,30 @@ def asdict(inst, recurse=True): :param recurse: Recurse into classes that are also ``attrs``-decorated. :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` """ attrs = fields(inst.__class__) rv = {} for a in attrs: v = getattr(inst, a.name) - if recurse is True and has(v.__class__): - rv[a.name] = asdict(v, recurse=True) + if skip is not None and skip(a, v): + 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: rv[a.name] = v return rv diff --git a/tests/test_funcs.py b/tests/test_funcs.py index 7ef43080..aaf4a104 100644 --- a/tests/test_funcs.py +++ b/tests/test_funcs.py @@ -92,6 +92,39 @@ class TestAsDict(object): 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): """