Skip to content

Commit

Permalink
Merge pull request #23 from ducdetronquito/hotfix/0.4.1
Browse files Browse the repository at this point in the history
🐛  Release 0.4.1
  • Loading branch information
ducdetronquito authored Oct 28, 2020
2 parents 1880b2c + 209c287 commit 6c0f25a
Show file tree
Hide file tree
Showing 5 changed files with 53 additions and 21 deletions.
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Scalpl
.. image:: https://img.shields.io/badge/coverage-100%25-green.svg
:target: #

.. image:: https://img.shields.io/badge/pypi-v0.4.0-blue.svg
.. image:: https://img.shields.io/badge/pypi-v0.4.1-blue.svg
:target: https://pypi.python.org/pypi/scalpl/

.. image:: https://travis-ci.org/ducdetronquito/scalpl.svg?branch=master
Expand Down
2 changes: 1 addition & 1 deletion scalpl/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
from .scalpl import Cut

__version__ = "0.4.0"
__version__ = "0.4.1"
30 changes: 21 additions & 9 deletions scalpl/scalpl.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,16 +83,16 @@ def traverse(data: dict, keys: List[Union[str, int]], original_path: str):

class Cut:
"""
Cut is a simple wrapper over the built-in dict class.
Cut is a simple wrapper over the built-in dict class.
It enables the standard dict API to operate on nested dictionnaries
and cut accross list item by using dot-separated string keys.
It enables the standard dict API to operate on nested dictionnaries
and cut accross list item by using dot-separated string keys.
ex:
query = {...} # Any dict structure
proxy = Cut(query)
proxy['pokemon[0].level']
proxy['pokemon[0].level'] = 666
ex:
query = {...} # Any dict structure
proxy = Cut(query)
proxy['pokemon[0].level']
proxy['pokemon[0].level'] = 666
"""

__slots__ = ("data", "sep")
Expand Down Expand Up @@ -170,6 +170,9 @@ def __setitem__(self, path: str, value) -> None:
def __str__(self) -> str:
return str(self.data)

def __repr__(self) -> str:
return f"Cut: {self.data}"

def all(self: TCut, path: str) -> Iterator[TCut]:
"""Wrap each item of an Iterable."""
items = self[path]
Expand Down Expand Up @@ -231,7 +234,16 @@ def popitem(self):

def setdefault(self, path: str, default=None):
*keys, last_key = split_path(path, self.sep)
item = traverse(data=self.data, keys=keys, original_path=path)

item = self.data
for key in keys:
try:
item = item[key]
except KeyError:
item[key] = {}
item = item[key]
except IndexError as error:
raise index_error(key, path, error)

try:
return item[last_key]
Expand Down
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@
setup(
name="scalpl",
packages=["scalpl"],
version="0.4.0",
version="0.4.1",
description=("A lightweight wrapper to operate on nested dictionaries seamlessly."),
long_description=readme,
author="Guillaume Paulet",
author_email="[email protected]",
license="Public Domain",
url="https://github.com/ducdetronquito/scalpl",
download_url=("https://github.com/ducdetronquito/scalpl/archive/" "0.4.0.tar.gz"),
download_url=("https://github.com/ducdetronquito/scalpl/archive/" "0.4.1.tar.gz"),
tests_require=[
"addict",
"mypy",
Expand Down
36 changes: 28 additions & 8 deletions tests/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -475,7 +475,10 @@ def test_key_error_when_no_default_provided(

@pytest.mark.parametrize(
"data,path,failing_index",
[({"a": [[1]]}, "a[1][0]", 1), ({"a": [{"b": 1}]}, "a[1].b", 1),],
[
({"a": [[1]]}, "a[1][0]", 1),
({"a": [{"b": 1}]}, "a[1].b", 1),
],
)
def test_list_index_error_when_no_default_provided(
self, dict_type, data, path, failing_index
Expand Down Expand Up @@ -553,6 +556,7 @@ class TestSetdefault:
({"a": {"b": 1}}, "a.c", None),
({"a": {"b": {"c": 1}}}, "a.b.d", None),
({"a": [{"b": 1}]}, "a[0].c", None),
({"a": {"b": {"c": 42}}}, "a.d.e.f", None),
],
)
def test_setdefault(self, dict_type, data, key, result):
Expand All @@ -568,6 +572,7 @@ def test_setdefault(self, dict_type, data, key, result):
({"a": {"b": 1}}, "a.c", "default"),
({"a": {"b": {"c": 1}}}, "a.b.d", "default"),
({"a": [{"b": 1}]}, "a[0].c", "default"),
({"a": {"b": {"c": 42}}}, "a.d.e.f", "default"),
],
)
def test_with_default(self, dict_type, data, key, default):
Expand All @@ -586,13 +591,28 @@ def test_type_error(self, dict_type):
)
assert str(error.value) == str(expected_error)

def test_index_error(self, dict_type):
proxy = Cut(dict_type({"a": [42]}))
@pytest.mark.parametrize(
"data,key,error_message",
[
(
{"a": [42]},
"a[1]",
f"Cannot access index '1' in path 'a[1]', because of error:",
),
(
{"a": [{"b": 1}]},
"a[1].c",
f"Cannot access index '1' in path 'a[1].c', because of error:",
),
],
)
def test_nested_index_error(self, dict_type, data, key, error_message):
proxy = Cut(dict_type(data))
with pytest.raises(IndexError) as error:
proxy.setdefault("a[1]")
proxy.setdefault(key, 42)

expected_error = IndexError(
"Cannot access index '1' in path 'a[1]', "
f"because of error: {repr(IndexError('list index out of range'))}."
expected_error_message = (
f"{error_message} {repr(IndexError('list index out of range'))}."
)
assert str(error.value) == str(expected_error)

assert str(error.value) == expected_error_message

0 comments on commit 6c0f25a

Please sign in to comment.