Skip to content

Commit

Permalink
Add keep_empty_types to support keeping empty values (#18)
Browse files Browse the repository at this point in the history
* add doc8

* add keep_empty_types

* update docstring

* update README

* move doc8 to dev dependencies
  • Loading branch information
ianlini authored Sep 29, 2019
1 parent 39d2b10 commit 61056e0
Show file tree
Hide file tree
Showing 5 changed files with 163 additions and 10 deletions.
25 changes: 24 additions & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ Flatten

.. code-block:: python
def flatten(d, reducer='tuple', inverse=False, enumerate_types=()):
def flatten(d, reducer='tuple', inverse=False, enumerate_types=(), keep_empty_types=()):
"""Flatten `Mapping` object.
Parameters
Expand All @@ -44,6 +44,15 @@ Flatten
Flatten these types using `enumerate`.
For example, if we set `enumerate_types` to ``(list,)``,
`list` indices become keys: ``{'a': ['b', 'c']}`` -> ``{('a', 0): 'b', ('a', 1): 'c'}``.
keep_empty_types : Sequence[type]
By default, ``flatten({1: 2, 3: {}})`` will give you ``{(1,): 2}``, that is, the key ``3``
will disappear.
This is also applied for the types in `enumerate_types`, that is,
``flatten({1: 2, 3: []}, enumerate_types=(list,))`` will give you ``{(1,): 2}``.
If you want to keep those empty values, you can specify the types in `keep_empty_types`:
>>> flatten({1: 2, 3: {}}, keep_empty_types=(dict,))
{(1,): 2, (3,): {}}
Returns
-------
Expand Down Expand Up @@ -145,6 +154,20 @@ We can even flatten a `list` directly:
(1,): 2,
(2,): 3}
If there is an empty dict in the values, by default, it will disappear after flattened:

.. code-block:: python
In [4]: flatten({1: 2, 3: {}})
Out[4]: {(1,): 2}
We can keep the empty dict in the result using ``keep_empty_types=(dict,)``:

.. code-block:: python
In [5]: flatten({1: 2, 3: {}}, keep_empty_types=(dict,))
Out[5]: {(1,): 2, (3,): {}}
Unflatten
`````````

Expand Down
70 changes: 69 additions & 1 deletion poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ pytest = "^4.6"
flake8 = "^3.7"
tox = "^3.14"
coverage = "^4.5"
doc8 = "^0.8.0"

[build-system]
requires = ["poetry>=0.12"]
Expand Down
32 changes: 24 additions & 8 deletions src/flatten_dict/flatten_dict.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
}


def flatten(d, reducer='tuple', inverse=False, enumerate_types=()):
def flatten(d, reducer='tuple', inverse=False, enumerate_types=(), keep_empty_types=()):
"""Flatten `Mapping` object.
Parameters
Expand All @@ -41,6 +41,15 @@ def flatten(d, reducer='tuple', inverse=False, enumerate_types=()):
Flatten these types using `enumerate`.
For example, if we set `enumerate_types` to ``(list,)``,
`list` indices become keys: ``{'a': ['b', 'c']}`` -> ``{('a', 0): 'b', ('a', 1): 'c'}``.
keep_empty_types : Sequence[type]
By default, ``flatten({1: 2, 3: {}})`` will give you ``{(1,): 2}``, that is, the key ``3``
will disappear.
This is also applied for the types in `enumerate_types`, that is,
``flatten({1: 2, 3: []}, enumerate_types=(list,))`` will give you ``{(1,): 2}``.
If you want to keep those empty values, you can specify the types in `keep_empty_types`:
>>> flatten({1: 2, 3: {}}, keep_empty_types=(dict,))
{(1,): 2, (3,): {}}
Returns
-------
Expand All @@ -61,13 +70,20 @@ def _flatten(d, parent=None):
for key, value in key_value_iterable:
flat_key = reducer(parent, key)
if isinstance(value, flattenable_types):
_flatten(value, flat_key)
else:
if inverse:
flat_key, value = value, flat_key
if flat_key in flat_dict:
raise ValueError("duplicated key '{}'".format(flat_key))
flat_dict[flat_key] = value
if value:
# recursively build the result
_flatten(value, flat_key)
continue
elif not isinstance(value, keep_empty_types):
# ignore the key that has an empty value
continue

# add an item to the result
if inverse:
flat_key, value = value, flat_key
if flat_key in flat_dict:
raise ValueError("duplicated key '{}'".format(flat_key))
flat_dict[flat_key] = value

_flatten(d)
return flat_dict
Expand Down
45 changes: 45 additions & 0 deletions src/flatten_dict/tests/flatten_dict_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,3 +214,48 @@ def test_flatten_dict_with_list_with_enumerate_types(

def test_flatten_list():
assert flatten([1, 2], enumerate_types=(list,)) == {(0,): 1, (1,): 2}


@pytest.fixture
def dict_with_empty_dict():
return {
'a': '0',
'b': {
'a': '1.0',
'b': '1.1',
},
'c': {
'a': '2.0',
'b': {
'a': '2.1.0',
'b': '2.1.1',
'c': {},
},
},
}


@pytest.fixture
def flat_tuple_dict_with_empty_dict():
return {
('a',): '0',
('b', 'a'): '1.0',
('b', 'b'): '1.1',
('c', 'a'): '2.0',
('c', 'b', 'a'): '2.1.0',
('c', 'b', 'b'): '2.1.1',
('c', 'b', 'c'): {},
}


def test_flatten_dict_with_empty_dict(dict_with_empty_dict, flat_tuple_dict):
assert flatten(dict_with_empty_dict) == flat_tuple_dict


def test_flatten_dict_with_empty_dict_kept(dict_with_empty_dict, flat_tuple_dict_with_empty_dict):
assert (flatten(dict_with_empty_dict, keep_empty_types=(dict,))
== flat_tuple_dict_with_empty_dict)


def test_flatten_dict_with_keep_empty_types(normal_dict, flat_tuple_dict):
assert flatten(normal_dict, keep_empty_types=(dict, str)) == flat_tuple_dict

0 comments on commit 61056e0

Please sign in to comment.