Skip to content

Commit

Permalink
Add get_lax()
Browse files Browse the repository at this point in the history
Closes Suor#87
  • Loading branch information
Suor committed Feb 26, 2023
1 parent 46b6904 commit 39cf70e
Show file tree
Hide file tree
Showing 5 changed files with 53 additions and 17 deletions.
2 changes: 1 addition & 1 deletion docs/cheatsheet.rst
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ Join :func:`merge` :func:`merge_with` :func:`join` :func:`join_
Transform :func:`walk` :func:`walk_keys` :func:`walk_values`
Filter :func:`select` :func:`select_keys` :func:`select_values` :func:`compact`
Dicts :ref:`*<colls>` :func:`flip` :func:`zipdict` :func:`pluck` :func:`where` :func:`itervalues` :func:`iteritems` :func:`zip_values` :func:`zip_dicts` :func:`project` :func:`omit`
Misc :func:`empty` :func:`get_in` :func:`set_in` :func:`update_in` :func:`del_in` :func:`has_path`
Misc :func:`empty` :func:`get_in` :func:`get_lax` :func:`set_in` :func:`update_in` :func:`del_in` :func:`has_path`
===================== ==============================================================


Expand Down
8 changes: 8 additions & 0 deletions docs/colls.rst
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,14 @@ Dict utils
Note that missing key or index, i.e. `KeyError` and `IndexError` result into `default` being return, while trying to use non-int index for a list will result into `TypeError`. This way funcy stays strict on types.


.. function:: get_lax(coll, path, default=None)

A version of :func:`get_in` that tolerates type along the path not working with an index::

get_lax([1, 2, 3], ["a"], "foo") # -> "foo"
get_lax({"a": None}, ["a", "b"]) # -> None


.. function:: set_in(coll, path, value)

Creates a nested collection with the ``value`` set at specified ``path``. Original collection is not changed::
Expand Down
5 changes: 5 additions & 0 deletions docs/descriptions.html
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,11 @@
<b>get_in<em>(coll, path, default=None)</em></b><br><br>
Returns a value at <em>path</em> in the given nested collection.
</div>
<div name="get_lax">
<b>get_lax<em>(coll, path, default=None)</em></b><br><br>
Returns a value at <em>path</em> in the given nested collection.<br>
Ignores <code>TypeError</code>s.
</div>
<div name="set_in">
<b>set_in<em>(coll, path, value)</em></b><br><br>
Creates a copy of <em>coll</em> with the <em>value</em> set at <em>path</em>.
Expand Down
13 changes: 12 additions & 1 deletion funcy/colls.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
'is_distinct', 'all', 'any', 'none', 'one', 'some',
'zipdict', 'flip', 'project', 'omit', 'zip_values', 'zip_dicts',
'where', 'pluck', 'pluck_attr', 'invoke', 'lwhere', 'lpluck', 'lpluck_attr', 'linvoke',
'get_in', 'set_in', 'update_in', 'del_in', 'has_path']
'get_in', 'get_lax', 'set_in', 'update_in', 'del_in', 'has_path']


### Generic ops
Expand Down Expand Up @@ -265,6 +265,17 @@ def get_in(coll, path, default=None):
return default
return coll

def get_lax(coll, path, default=None):
"""Returns a value at path in the given nested collection.
Does not raise on a wrong collection type along the way, but removes default.
"""
for key in path:
try:
coll = coll[key]
except (KeyError, IndexError, TypeError):
return default
return coll

def set_in(coll, path, value):
"""Creates a copy of coll with the value set at path."""
return update_in(coll, path, lambda _: value)
Expand Down
42 changes: 27 additions & 15 deletions tests/test_colls.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,27 +225,39 @@ def test_zip_dicts():
with pytest.raises(TypeError): list(zip_dicts())


def test_get_in():
@pytest.mark.parametrize("get", [get_in, get_lax])
def test_get(get):
d = {
"a": {
"b": "c",
"d": "e",
"f": {
"g": "h"
}
"f": {"g": "h"}
},
"i": "j"
}
assert get_in(d, ["m"]) is None
assert get_in(d, ["m", "n"], "foo") == "foo"
assert get_in(d, ["i"]) == "j"
assert get_in(d, ["a", "b"]) == "c"
assert get_in(d, ["a", "f", "g"]) == "h"

def test_get_in_list():
assert get_in([1, 2], [0]) == 1
assert get_in([1, 2], [3]) is None
assert get_in({'x': [1, 2]}, ['x', 1]) == 2
assert get(d, ["i"]) == "j"
assert get(d, ["a", "b"]) == "c"
assert get(d, ["a", "f", "g"]) == "h"
assert get(d, ["m"]) is None
assert get(d, ["a", "n"]) is None
assert get(d, ["m", "n"], "foo") == "foo"

@pytest.mark.parametrize("get", [get_in, get_lax])
def test_get_list(get):
assert get([1, 2], [0]) == 1
assert get([1, 2], [3]) is None
assert get({'x': [1, 2]}, ['x', 1]) == 2

def test_get_error():
with pytest.raises(TypeError): get_in([1, 2], ['a'])
assert get_lax([1, 2], ['a']) is None
assert get_lax([1, 2], ['a'], 'foo') == 'foo'

with pytest.raises(TypeError): get_in('abc', [2, 'a'])
assert get_lax('abc', [2, 'a']) is None

with pytest.raises(TypeError): get_in(None, ['a', 'b'])
assert get_lax({'a': None}, ['a', 'b']) is None


def test_set_in():
d = {
Expand Down

0 comments on commit 39cf70e

Please sign in to comment.