From 0d9380040ce11b466ac0941290e146d8f0df9c40 Mon Sep 17 00:00:00 2001 From: Rikhil <23627977+rikhilrai@users.noreply.github.com> Date: Wed, 2 Feb 2022 11:32:28 +0000 Subject: [PATCH 1/8] feature (v0.1.4): enhance KeyError message --- README.md | 14 +- pyproject.toml | 2 +- .../case_insensitive_dict.py | 12 +- src/tests/test_case_insensitive_dict.py | 155 +++++++++++++----- 4 files changed, 121 insertions(+), 62 deletions(-) diff --git a/README.md b/README.md index f6ce3be..d5c054d 100644 --- a/README.md +++ b/README.md @@ -28,18 +28,6 @@ $ pip install -U case-insensitive-dictionary CaseInsensitiveDict: -```py ->>> from case_insensitive_dict import CaseInsensitiveDict - ->>> case_insensitive_dict = CaseInsensitiveDict[str, str](data={"Aa": "b"}) ->>> case_insensitive_dict.get("aa") -'b' ->>> case_insensitive_dict.get("Aa") -'b' -``` - -also supports generic keys: - ```py >>> from typing import Union @@ -53,7 +41,7 @@ also supports generic keys: ``` -and json encoding/decoding: +which also supports json encoding/decoding: ```py >>> import json diff --git a/pyproject.toml b/pyproject.toml index 8d2328c..bc2dce2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "case-insensitive-dictionary" -version = "0.1.3" +version = "0.1.4" description = "Case Insensitive Dictionary" authors = ["rikhilrai"] license = "MIT" diff --git a/src/case_insensitive_dict/case_insensitive_dict.py b/src/case_insensitive_dict/case_insensitive_dict.py index 1c762fe..62d228a 100644 --- a/src/case_insensitive_dict/case_insensitive_dict.py +++ b/src/case_insensitive_dict/case_insensitive_dict.py @@ -12,6 +12,7 @@ from typing import Tuple from typing import TypeVar +T = TypeVar('T') # pylint: disable=invalid-name KT = TypeVar('KT') # pylint: disable=invalid-name VT = TypeVar('VT') # pylint: disable=invalid-name @@ -30,6 +31,9 @@ def __init__(self, data: Optional[Mapping[KT, VT]] = None) -> None: data = {} self.update(data) + def __repr__(self) -> str: + return f'{self.__class__.__name__}({dict(self.items())!r})' + @staticmethod def _convert_key(key: KT) -> KT: if isinstance(key, str): @@ -40,7 +44,10 @@ def __setitem__(self, key: KT, value: VT) -> None: self._data[self._convert_key(key=key)] = (key, value) def __getitem__(self, key: KT) -> VT: - return self._data[self._convert_key(key=key)][1] + try: + return self._data[self._convert_key(key=key)][1] + except KeyError: + raise KeyError(f"Key: {key!r} not found.") from None def __delitem__(self, key: KT) -> None: del self._data[self._convert_key(key=key)] @@ -63,9 +70,6 @@ def __eq__(self, other: Any) -> bool: def copy(self) -> CaseInsensitiveDict[KT, VT]: return CaseInsensitiveDict(data=dict(self._data.values())) - def __repr__(self) -> str: - return f'{self.__class__.__name__}({dict(self.items())!r})' - class CaseInsensitiveDictJSONEncoder(JSONEncoder): def default(self, o: CaseInsensitiveDict[KT, VT]) -> Mapping[KT, VT]: diff --git a/src/tests/test_case_insensitive_dict.py b/src/tests/test_case_insensitive_dict.py index 5d46436..ed7dbb0 100644 --- a/src/tests/test_case_insensitive_dict.py +++ b/src/tests/test_case_insensitive_dict.py @@ -19,52 +19,52 @@ class CaseInsensitiveDictTestCase: class TestInit(CaseInsensitiveDictTestCase): # check that the store is structured as expected def test_store_written(self) -> None: - case_insensitive_dict = CaseInsensitiveDict[str, str](data={"a": "b"}) + case_insensitive_dict = CaseInsensitiveDict[str, str]({"a": "b"}) assert case_insensitive_dict._data == {"a": ("a", "b")} # check that the key in the store is lowered def test_store_written_case_insensitive(self) -> None: - case_insensitive_dict = CaseInsensitiveDict[str, str](data={"A": "b"}) + case_insensitive_dict = CaseInsensitiveDict[str, str]({"A": "b"}) assert case_insensitive_dict._data == {"a": ("A", "b")} # check instantiated with an empty dict def test_store_written_empty(self) -> None: - case_insensitive_dict = CaseInsensitiveDict[str, str](data={}) + case_insensitive_dict = CaseInsensitiveDict[str, str]({}) assert isinstance(case_insensitive_dict._data, dict) assert not case_insensitive_dict._data # check instantiated with none def test_store_written_none(self) -> None: - case_insensitive_dict = CaseInsensitiveDict[str, str](data=None) + case_insensitive_dict = CaseInsensitiveDict[str, str](None) assert isinstance(case_insensitive_dict._data, dict) assert not case_insensitive_dict._data # check instantiated with none value def test_store_written_with_optional_value(self) -> None: - case_insensitive_dict = CaseInsensitiveDict[str, Optional[str]](data={"A": None}) + case_insensitive_dict = CaseInsensitiveDict[str, Optional[str]]({"A": None}) assert case_insensitive_dict._data == {"a": ("A", None)} - # check instantiation with non-str key - def test_init_with_non_str_key(self) -> None: - case_insensitive_dict_int = CaseInsensitiveDict[int, str](data={1: "b"}) + # check instantiation with non-str keys + def test_init_with_non_str_keys(self) -> None: + case_insensitive_dict_int = CaseInsensitiveDict[int, str]({1: "b"}) assert case_insensitive_dict_int._data == {1: (1, "b")} - case_insensitive_dict_bool = CaseInsensitiveDict[bool, str](data={True: "b"}) + case_insensitive_dict_bool = CaseInsensitiveDict[bool, str]({True: "b"}) assert case_insensitive_dict_bool._data == {True: (True, "b")} class TestTyping(CaseInsensitiveDictTestCase): # check valid typing def test_valid_types(self) -> None: - case_insensitive_dict = CaseInsensitiveDict[str, str](data={"a": "b"}) + case_insensitive_dict = CaseInsensitiveDict[str, str]({"a": "b"}) # keys case_insensitive_dict['a'] # pylint: disable=pointless-statement case_insensitive_dict.get('a') # values case_insensitive_dict['b'] = 'a' - # check valid typing - def test_valid_types_non_str_key(self) -> None: - case_insensitive_dict_int = CaseInsensitiveDict[int, str](data={1: "b"}) + # check valid typings + def test_valid_types_non_str_keys(self) -> None: + case_insensitive_dict_int = CaseInsensitiveDict[int, str]({1: "b"}) # keys case_insensitive_dict_int[1] # pylint: disable=pointless-statement case_insensitive_dict_int.get(1) @@ -73,7 +73,7 @@ def test_valid_types_non_str_key(self) -> None: # check valid with union def test_valid_types_union(self) -> None: - case_insensitive_dict = CaseInsensitiveDict[Union[str, int], Union[str, int, bool]](data={"a": "b", "b": 1, 1: "c"}) + case_insensitive_dict = CaseInsensitiveDict[Union[str, int], Union[str, int, bool]]({"a": "b", "b": 1, 1: "c"}) # keys case_insensitive_dict[1] # pylint: disable=pointless-statement case_insensitive_dict.get(1) @@ -89,7 +89,7 @@ def test_valid_types_union(self) -> None: # check invalid types def test_invalid_type(self) -> None: - case_insensitive_dict = CaseInsensitiveDict[str, int](data={"a": "3"}) # type: ignore[dict-item] + case_insensitive_dict = CaseInsensitiveDict[str, int]({"a": "3"}) # type: ignore[dict-item] case_insensitive_dict[1] = 2 # type: ignore[index] case_insensitive_dict['b'] = "2" # type: ignore[assignment] @@ -97,15 +97,15 @@ def test_invalid_type(self) -> None: class TestContains(CaseInsensitiveDictTestCase): # check that key in CaseInsensitiveDict check works as expected def test_contains(self) -> None: - case_insensitive_dict = CaseInsensitiveDict[str, str](data={"A": "b"}) + case_insensitive_dict = CaseInsensitiveDict[str, str]({"A": "b"}) assert 'A' in case_insensitive_dict assert 'a' in case_insensitive_dict - # check contains with non-str key - def test_contains_with_non_str_key(self) -> None: - case_insensitive_dict_int = CaseInsensitiveDict[int, str](data={1: "b"}) + # check contains with non-str keys + def test_contains_with_non_str_keys(self) -> None: + case_insensitive_dict_int = CaseInsensitiveDict[int, str]({1: "b"}) assert 1 in case_insensitive_dict_int - case_insensitive_dict_bool = CaseInsensitiveDict[bool, str](data={True: "b"}) + case_insensitive_dict_bool = CaseInsensitiveDict[bool, str]({True: "b"}) assert True in case_insensitive_dict_bool @@ -123,9 +123,9 @@ def test_value(self) -> None: case_insensitive_dict["A"] = "c" assert case_insensitive_dict._data == {"a": ("A", "c")} - # check set item with non-str key - def test_set_item_with_non_str_key(self) -> None: - case_insensitive_dict = CaseInsensitiveDict[int, str](data={1: "b"}) + # check set item with non-str keys + def test_set_item_with_non_str_keys(self) -> None: + case_insensitive_dict = CaseInsensitiveDict[int, str]({1: "b"}) assert case_insensitive_dict._data == {1: (1, "b")} case_insensitive_dict[1] = "c" assert case_insensitive_dict._data == {1: (1, "c")} @@ -142,24 +142,29 @@ def test_value_returned(self) -> None: def test_key_missing(self) -> None: case_insensitive_dict = CaseInsensitiveDict[str, str]() assert case_insensitive_dict.get("b") is None - with pytest.raises(KeyError): + with pytest.raises(KeyError, match=r"Key: 'b' not found."): assert case_insensitive_dict["b"] + # check behaviour when key is missing and default passed + def test_key_missing_with_default(self) -> None: + case_insensitive_dict = CaseInsensitiveDict[str, str]() + assert case_insensitive_dict.get("b", 1) == 1 + # check value returned using get def test_value_returned_using_get(self) -> None: case_insensitive_dict = CaseInsensitiveDict[str, str]({"a": "b"}) assert case_insensitive_dict.get("A") == "b" assert case_insensitive_dict.get("a") == "b" - # check get item with non-str key - def test_get_item_with_non_str_key(self) -> None: - case_insensitive_dict = CaseInsensitiveDict[int, str](data={1: "b"}) + # check get item with non-str keys + def test_get_item_with_non_str_keys(self) -> None: + case_insensitive_dict = CaseInsensitiveDict[int, str]({1: "b"}) assert case_insensitive_dict.get(1) == "b" assert case_insensitive_dict[1] == "b" # check instantiated with none value def test_store_written_with_optional_value(self) -> None: - case_insensitive_dict = CaseInsensitiveDict[str, Optional[str]](data={"A": None}) + case_insensitive_dict = CaseInsensitiveDict[str, Optional[str]]({"A": None}) assert case_insensitive_dict.get("a") is None assert case_insensitive_dict["a"] is None assert "a" in case_insensitive_dict @@ -173,9 +178,9 @@ def test_value_removed(self) -> None: del case_insensitive_dict["A"] assert "a" not in case_insensitive_dict - # check del item with non-str key - def test_del_item_with_non_str_key(self) -> None: - case_insensitive_dict = CaseInsensitiveDict[int, str](data={1: "b"}) + # check del item with non-str keys + def test_del_item_with_non_str_keys(self) -> None: + case_insensitive_dict = CaseInsensitiveDict[int, str]({1: "b"}) assert case_insensitive_dict[1] == "b" del case_insensitive_dict[1] assert 1 not in case_insensitive_dict @@ -187,9 +192,9 @@ def test_iter(self) -> None: case_insensitive_dict = CaseInsensitiveDict[str, str]({"a": "b"}) assert list(case_insensitive_dict) == ["a"] - # check iter with non-str key - def test_iter_with_non_str_key(self) -> None: - case_insensitive_dict = CaseInsensitiveDict[Union[str, int], str](data={1: "b", "a": "c"}) + # check iter with non-str keys + def test_iter_with_non_str_keys(self) -> None: + case_insensitive_dict = CaseInsensitiveDict[Union[str, int], str]({1: "b", "a": "c"}) assert list(case_insensitive_dict) == [1, "a"] @@ -217,9 +222,9 @@ def test_lower_empty(self) -> None: assert isinstance(case_insensitive_dict.lower_items(), GeneratorType) assert not list(case_insensitive_dict.lower_items()) - # check lower items with non-str key - def test_lower_items_with_non_str_key(self) -> None: - case_insensitive_dict = CaseInsensitiveDict[Union[str, int], str](data={1: "b", "a": "c"}) + # check lower items with non-str keys + def test_lower_items_with_non_str_keys(self) -> None: + case_insensitive_dict = CaseInsensitiveDict[Union[str, int], str]({1: "b", "a": "c"}) assert list(case_insensitive_dict.lower_items()) == [(1, "b"), ("a", "c")] @@ -244,9 +249,9 @@ def test_not_equality(self) -> None: case_insensitive_dict = CaseInsensitiveDict[str, str]() assert case_insensitive_dict != 1 - # check equality with non-str key - def test_equality_with_non_str_key(self) -> None: - case_insensitive_dict = CaseInsensitiveDict[Union[str, int], str](data={1: "b", "a": "c"}) + # check equality with non-str keys + def test_equality_with_non_str_keys(self) -> None: + case_insensitive_dict = CaseInsensitiveDict[Union[str, int], str]({1: "b", "a": "c"}) assert case_insensitive_dict == {1: "b", "a": "c"} @@ -262,9 +267,9 @@ def test_copy_ids(self) -> None: case_insensitive_dict = CaseInsensitiveDict[str, str]({"A": "b"}) assert id(case_insensitive_dict) != id(case_insensitive_dict.copy()) - # check copy with non-str key - def test_copy_with_non_str_key(self) -> None: - case_insensitive_dict = CaseInsensitiveDict[Union[str, int], str](data={1: "b", "a": "c"}) + # check copy with non-str keys + def test_copy_with_non_str_keys(self) -> None: + case_insensitive_dict = CaseInsensitiveDict[Union[str, int], str]({1: "b", "a": "c"}) assert case_insensitive_dict.copy() == case_insensitive_dict assert case_insensitive_dict == case_insensitive_dict.copy() @@ -273,7 +278,7 @@ class TestJson(CaseInsensitiveDictTestCase): # check to_json def test_to_json(self) -> None: data: Dict[Union[bool, str, int], Union[str, int, bool]] = {"A": "a", "b": 1, "c": False, 2: "a", True: 2} - case_insensitive_dict = CaseInsensitiveDict[Union[bool, str, int], Union[str, int, bool]](data=data) + case_insensitive_dict = CaseInsensitiveDict[Union[bool, str, int], Union[str, int, bool]](data) json_string = json.dumps(obj=case_insensitive_dict, cls=CaseInsensitiveDictJSONEncoder) assert json_string == '{"A": "a", "b": 1, "c": false, "2": "a", "true": 2}' assert json_string == json.dumps(data) @@ -285,3 +290,65 @@ def test_from_json(self) -> None: expected_case_insensitive_dict = CaseInsensitiveDict[Union[bool, str, int], Union[str, int, bool]]({"A": "a", "b": 1, "c": False, '2': "a", 'true': 2}) assert case_insensitive_dict == expected_case_insensitive_dict assert case_insensitive_dict == json.loads(json_string) + + +class TestStrAndRepr(CaseInsensitiveDictTestCase): + # check string and representation + def test_str_and_repr(self) -> None: + case_insensitive_dict = CaseInsensitiveDict[str, str]({"A": "b"}) + assert case_insensitive_dict.__str__() == "CaseInsensitiveDict({'A': 'b'})" + assert case_insensitive_dict.__repr__() == "CaseInsensitiveDict({'A': 'b'})" + + +class TestDictMethods(CaseInsensitiveDictTestCase): + # check dict + def test_dict(self) -> None: + case_insensitive_dict = CaseInsensitiveDict[str, str]({"A": "b"}) + assert dict(case_insensitive_dict) == {'A': 'b'} + + # check dict with non-str keys + def test_dict_with_non_str_keys(self) -> None: + case_insensitive_dict = CaseInsensitiveDict[Union[str, int], str]({1: "b", "a": "c"}) + assert dict(case_insensitive_dict) == {1: "b", "a": "c"} + + # check keys + def test_keys(self) -> None: + case_insensitive_dict = CaseInsensitiveDict[str, str]({"A": "b"}) + assert list(case_insensitive_dict.keys()) == ["A"] + + # check falsey dict + def test_falsey(self) -> None: + case_insensitive_dict = CaseInsensitiveDict[str, str]() + assert not case_insensitive_dict + assert bool(case_insensitive_dict) is False + + # check truthy dict + def test_truthy(self) -> None: + case_insensitive_dict = CaseInsensitiveDict[str, str]({"a": "b"}) + assert case_insensitive_dict + assert bool(case_insensitive_dict) is True + + # check clear + def test_clear(self) -> None: + case_insensitive_dict = CaseInsensitiveDict[str, str]({"A": "b"}) + case_insensitive_dict.clear() + assert not case_insensitive_dict + + # check pop + def test_pop(self) -> None: + case_insensitive_dict = CaseInsensitiveDict[str, str]({"A": "b"}) + response = case_insensitive_dict.pop("a") + assert response == "b" + assert not case_insensitive_dict + + # check pop key not in dictionary + def test_pop_key_not_in_dictionary(self) -> None: + case_insensitive_dict = CaseInsensitiveDict[str, str]({"A": "b"}) + with pytest.raises(KeyError): + case_insensitive_dict.pop("b") + + # check pop key not in dictionary with default + def test_pop_key_not_in_dictionary_with_default(self) -> None: + case_insensitive_dict = CaseInsensitiveDict[str, str]({"A": "b"}) + response = case_insensitive_dict.pop("b", "a") + assert response == 'a' From 249525c4776c39be4abdae0b32d69b0da7dbd8bb Mon Sep 17 00:00:00 2001 From: Rikhil <23627977+rikhilrai@users.noreply.github.com> Date: Wed, 2 Feb 2022 11:33:06 +0000 Subject: [PATCH 2/8] . --- src/case_insensitive_dict/case_insensitive_dict.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/case_insensitive_dict/case_insensitive_dict.py b/src/case_insensitive_dict/case_insensitive_dict.py index 62d228a..870043d 100644 --- a/src/case_insensitive_dict/case_insensitive_dict.py +++ b/src/case_insensitive_dict/case_insensitive_dict.py @@ -12,7 +12,6 @@ from typing import Tuple from typing import TypeVar -T = TypeVar('T') # pylint: disable=invalid-name KT = TypeVar('KT') # pylint: disable=invalid-name VT = TypeVar('VT') # pylint: disable=invalid-name From bc3a83b9409da6e3736f486e7ad8986377bac0af Mon Sep 17 00:00:00 2001 From: Rikhil <23627977+rikhilrai@users.noreply.github.com> Date: Wed, 2 Feb 2022 12:25:20 +0000 Subject: [PATCH 3/8] . --- .../case_insensitive_dict.py | 15 +++ src/tests/test_case_insensitive_dict.py | 91 ++++++++++++++++--- 2 files changed, 91 insertions(+), 15 deletions(-) diff --git a/src/case_insensitive_dict/case_insensitive_dict.py b/src/case_insensitive_dict/case_insensitive_dict.py index 870043d..a9685f2 100644 --- a/src/case_insensitive_dict/case_insensitive_dict.py +++ b/src/case_insensitive_dict/case_insensitive_dict.py @@ -6,11 +6,14 @@ from typing import Any from typing import Dict from typing import Generic +from typing import Iterable from typing import Iterator from typing import Mapping from typing import Optional from typing import Tuple from typing import TypeVar +from typing import Union +from typing import overload KT = TypeVar('KT') # pylint: disable=invalid-name VT = TypeVar('VT') # pylint: disable=invalid-name @@ -23,7 +26,15 @@ class CaseInsensitiveDict(MutableMapping, Generic[KT, VT]): + @overload def __init__(self, data: Optional[Mapping[KT, VT]] = None) -> None: + ... + + @overload + def __init__(self, data: Optional[Iterable[Tuple[KT, VT]]] = None) -> None: + ... + + def __init__(self, data: Optional[Union[Mapping[KT, VT], Iterable[Tuple[KT, VT]]]] = None) -> None: # Mapping from lowercased key to tuple of (actual key, value) self._data: Dict[KT, Tuple[KT, VT]] = {} if data is None: @@ -69,6 +80,10 @@ def __eq__(self, other: Any) -> bool: def copy(self) -> CaseInsensitiveDict[KT, VT]: return CaseInsensitiveDict(data=dict(self._data.values())) + @classmethod + def fromkeys(cls, iterable: Iterable[KT], value: VT) -> CaseInsensitiveDict[KT, VT]: + return cls([(key, value) for key in iterable]) + class CaseInsensitiveDictJSONEncoder(JSONEncoder): def default(self, o: CaseInsensitiveDict[KT, VT]) -> Mapping[KT, VT]: diff --git a/src/tests/test_case_insensitive_dict.py b/src/tests/test_case_insensitive_dict.py index ed7dbb0..6aeab95 100644 --- a/src/tests/test_case_insensitive_dict.py +++ b/src/tests/test_case_insensitive_dict.py @@ -51,6 +51,17 @@ def test_init_with_non_str_keys(self) -> None: case_insensitive_dict_bool = CaseInsensitiveDict[bool, str]({True: "b"}) assert case_insensitive_dict_bool._data == {True: (True, "b")} + # check picks the last key/value if instantiated with conflicting cases + def test_store_written_with_conflicting_cases(self) -> None: + case_insensitive_dict = CaseInsensitiveDict[str, str]({"a": "b", "A": "c"}) + assert case_insensitive_dict._data == {"a": ("A", "c")} + + # check instantiated with list of tuples + def test_store_written_with_list_of_tuples(self) -> None: + data = [("A", "b")] + case_insensitive_dict = CaseInsensitiveDict[str, str](data) + assert case_insensitive_dict._data == {"a": ("A", "b")} + class TestTyping(CaseInsensitiveDictTestCase): # check valid typing @@ -300,55 +311,105 @@ def test_str_and_repr(self) -> None: assert case_insensitive_dict.__repr__() == "CaseInsensitiveDict({'A': 'b'})" +class TestFromKeys(CaseInsensitiveDictTestCase): + # check fromkeys + def test_fromkeys(self) -> None: + dictionary = dict.fromkeys(["A", "b"], "c") + assert dictionary == {"A": "c", "b": "c"} + case_insensitive_dict = CaseInsensitiveDict[str, str].fromkeys(["A", "b"], "c") + assert case_insensitive_dict == dictionary + + class TestDictMethods(CaseInsensitiveDictTestCase): # check dict def test_dict(self) -> None: - case_insensitive_dict = CaseInsensitiveDict[str, str]({"A": "b"}) - assert dict(case_insensitive_dict) == {'A': 'b'} + dictionary = {"A": "b"} + case_insensitive_dict = CaseInsensitiveDict[str, str](dictionary) + assert dict(case_insensitive_dict) == dict(dictionary) # check dict with non-str keys def test_dict_with_non_str_keys(self) -> None: case_insensitive_dict = CaseInsensitiveDict[Union[str, int], str]({1: "b", "a": "c"}) - assert dict(case_insensitive_dict) == {1: "b", "a": "c"} - - # check keys - def test_keys(self) -> None: - case_insensitive_dict = CaseInsensitiveDict[str, str]({"A": "b"}) - assert list(case_insensitive_dict.keys()) == ["A"] + assert dict(case_insensitive_dict) == dict({1: "b", "a": "c"}) # check falsey dict def test_falsey(self) -> None: case_insensitive_dict = CaseInsensitiveDict[str, str]() assert not case_insensitive_dict assert bool(case_insensitive_dict) is False + assert not {} + assert bool({}) is False # check truthy dict def test_truthy(self) -> None: - case_insensitive_dict = CaseInsensitiveDict[str, str]({"a": "b"}) + dictionary = {"a": "b"} + case_insensitive_dict = CaseInsensitiveDict[str, str](dictionary) assert case_insensitive_dict assert bool(case_insensitive_dict) is True + assert dictionary + assert bool(dictionary) is True # check clear def test_clear(self) -> None: - case_insensitive_dict = CaseInsensitiveDict[str, str]({"A": "b"}) + dictionary = {"A": "b"} + case_insensitive_dict = CaseInsensitiveDict[str, str](dictionary) case_insensitive_dict.clear() assert not case_insensitive_dict + dictionary.clear() + assert not dictionary + + # check reference to instantiated value not maintained + def test_reference(self) -> None: + dictionary = {"A": "b"} + case_insensitive_dict = CaseInsensitiveDict[str, str](dictionary) + assert id(case_insensitive_dict._data) != id(dictionary) + case_insensitive_dict.pop("a") + assert dictionary == {"A": "b"} # check pop def test_pop(self) -> None: - case_insensitive_dict = CaseInsensitiveDict[str, str]({"A": "b"}) - response = case_insensitive_dict.pop("a") - assert response == "b" + dictionary = {"A": "b"} + case_insensitive_dict = CaseInsensitiveDict[str, str](dictionary) + assert dictionary.pop("A") == case_insensitive_dict.pop("a") == "b" assert not case_insensitive_dict + assert not dictionary # check pop key not in dictionary def test_pop_key_not_in_dictionary(self) -> None: - case_insensitive_dict = CaseInsensitiveDict[str, str]({"A": "b"}) + dictionary = {"A": "b"} + case_insensitive_dict = CaseInsensitiveDict[str, str](dictionary) with pytest.raises(KeyError): case_insensitive_dict.pop("b") + with pytest.raises(KeyError): + dictionary.pop("b") # check pop key not in dictionary with default def test_pop_key_not_in_dictionary_with_default(self) -> None: - case_insensitive_dict = CaseInsensitiveDict[str, str]({"A": "b"}) + dictionary = {"A": "b"} + case_insensitive_dict = CaseInsensitiveDict[str, str](dictionary) + assert dictionary.pop("A") == case_insensitive_dict.pop("a") == "b" response = case_insensitive_dict.pop("b", "a") assert response == 'a' + + # check popitem + def test_pop_item(self) -> None: + dictionary = {"A": "b"} + case_insensitive_dict = CaseInsensitiveDict[str, str](dictionary) + assert dictionary.popitem() == case_insensitive_dict.popitem() == ("A", "b") + with pytest.raises(KeyError): + case_insensitive_dict.popitem() + with pytest.raises(KeyError): + dictionary.popitem() + + # check keys + def test_keys(self) -> None: + dictionary = {"A": "b"} + case_insensitive_dict = CaseInsensitiveDict[str, str](dictionary) + assert dictionary.keys() == case_insensitive_dict.keys() + assert list(dictionary.keys()) == list(case_insensitive_dict.keys()) == ["A"] + + # check values + def test_values(self) -> None: + dictionary = {"A": "b"} + case_insensitive_dict = CaseInsensitiveDict[str, str](dictionary) + assert list(dictionary.values()) == list(case_insensitive_dict.values()) == ["b"] From 1fda765dbb2c0b3bcb6d4062cd476030cd695be7 Mon Sep 17 00:00:00 2001 From: Rikhil <23627977+rikhilrai@users.noreply.github.com> Date: Wed, 2 Feb 2022 12:38:50 +0000 Subject: [PATCH 4/8] . --- README.md | 14 ++++++++++++++ src/tests/test_case_insensitive_dict.py | 6 ++++++ 2 files changed, 20 insertions(+) diff --git a/README.md b/README.md index d5c054d..dfa766a 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,20 @@ Install and update using [pip](https://pypi.org/project/case-insensitive-diction $ pip install -U case-insensitive-dictionary ``` +## API Reference + +| Method | Description | +| :------------------------ | :-------------------------------------------------------------------------------------------------------------------------------------------------- | +| clear() | Removes all elements from the dictionary. | +| copy() | Returns a copy of the dictionary. | +| get(key, default) | Returns the value (case-insensitively), of the item specified with the key.
Falls back to the default value if the specified key does not exist. | +| fromkeys(iterable, value) | Returns a dictionary with the specified keys and the specified value. | +| keys() | Returns the dictionary's keys. | +| values() | Returns the dictionary's values. | +| items() | Returns the key-value pairs. | +| pop(key) | Remove the specified item (case-insensitively).
The value of the removed item is the return value. | +| popitem() | Remove the last item that was inserted into the dictionary.
For Python version <3.7, popitem() removes a random item. | + ## Example CaseInsensitiveDict: diff --git a/src/tests/test_case_insensitive_dict.py b/src/tests/test_case_insensitive_dict.py index 6aeab95..38ccac7 100644 --- a/src/tests/test_case_insensitive_dict.py +++ b/src/tests/test_case_insensitive_dict.py @@ -413,3 +413,9 @@ def test_values(self) -> None: dictionary = {"A": "b"} case_insensitive_dict = CaseInsensitiveDict[str, str](dictionary) assert list(dictionary.values()) == list(case_insensitive_dict.values()) == ["b"] + + # check items + def test_items(self) -> None: + dictionary = {"A": "b"} + case_insensitive_dict = CaseInsensitiveDict[str, str](dictionary) + assert list(dictionary.items()) == list(case_insensitive_dict.items()) == [("A", "b")] From 04c6bc27d84b7a86d5ba4cfeb7f9f05e9dd2de9b Mon Sep 17 00:00:00 2001 From: Rikhil <23627977+rikhilrai@users.noreply.github.com> Date: Wed, 2 Feb 2022 12:39:31 +0000 Subject: [PATCH 5/8] . --- .coveragerc | 1 + 1 file changed, 1 insertion(+) diff --git a/.coveragerc b/.coveragerc index 0add24e..d64169d 100644 --- a/.coveragerc +++ b/.coveragerc @@ -15,6 +15,7 @@ exclude_lines = @(abc\.)?abstractmethod if TYPE_CHECKING if sys\.version_info + @overload fail_under = 95 From a296338deb824833c3f9722f8e2862e8eec0ec6f Mon Sep 17 00:00:00 2001 From: Rikhil <23627977+rikhilrai@users.noreply.github.com> Date: Wed, 2 Feb 2022 12:40:03 +0000 Subject: [PATCH 6/8] . --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index bc2dce2..7b1c063 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "case-insensitive-dictionary" -version = "0.1.4" +version = "0.2.0" description = "Case Insensitive Dictionary" authors = ["rikhilrai"] license = "MIT" From 1f4f13ea569afd2fd013d6a15c357713fe4fea3c Mon Sep 17 00:00:00 2001 From: Rikhil <23627977+rikhilrai@users.noreply.github.com> Date: Wed, 2 Feb 2022 12:48:12 +0000 Subject: [PATCH 7/8] . --- src/case_insensitive_dict/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/case_insensitive_dict/__init__.py b/src/case_insensitive_dict/__init__.py index d6a24cf..0c34020 100644 --- a/src/case_insensitive_dict/__init__.py +++ b/src/case_insensitive_dict/__init__.py @@ -8,7 +8,7 @@ from importlib_metadata import PackageNotFoundError # type: ignore[no-redef,misc] try: - __version__: str = version(__name__) + __version__: str = version('case-insensitive-dictionary') except PackageNotFoundError: __version__ = "unknown" From 75f5c4b811bd0eda237ba754c5f228076c402ce1 Mon Sep 17 00:00:00 2001 From: Rikhil <23627977+rikhilrai@users.noreply.github.com> Date: Wed, 2 Feb 2022 12:49:45 +0000 Subject: [PATCH 8/8] . --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 7b1c063..c620a33 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "case-insensitive-dictionary" version = "0.2.0" -description = "Case Insensitive Dictionary" +description = "Typed Python Case Insensitive Dictionary" authors = ["rikhilrai"] license = "MIT" readme = "README.md"