Repository: NOhs/dkey

Issue:
Set function should remove the deprecated key entry
Currently the setitem function only warns. However, once the key is overridden, the warning should be removed to not further propagate to code that is unrelated to the deprecation.

Discussion:
codacy-bot: 
![Codacy](https://app.codacy.com/assets/images/favicon.png) Here is an overview of what got changed by this pull request:


```diff

Issues
======
+ Solved 1
- Added 5
           

Complexity increasing per file
==============================
- tests/test_dkey.py  2
         

Clones removed
==============
+ tests/test_dkey.py  -13
         
```


See the complete overview on [Codacy](https://app.codacy.com/app/NOhs/dkey/pullRequest?prid=2964213&bid=10968330)
        
codacy-bot: 
![Codacy](https://app.codacy.com/assets/images/favicon.png) Here is an overview of what got changed by this pull request:


```diff

Issues
======
- Added 5
           

Complexity increasing per file
==============================
- tests/test_dkey.py  2
         
```


See the complete overview on [Codacy](https://app.codacy.com/app/NOhs/dkey/pullRequest?prid=2964213&bid=10968330)
        
codacy-bot: 
![Codacy](https://app.codacy.com/assets/images/favicon.png) Here is an overview of what got changed by this pull request:


```diff

Issues
======
- Added 4
           

Complexity increasing per file
==============================
- tests/test_dkey.py  2
         

Clones added
============
- dkey/_dkey.py  2
         

Clones removed
==============
+ tests/test_dkey.py  -17
         
```


See the complete overview on [Codacy](https://app.codacy.com/app/NOhs/dkey/pullRequest?prid=2964213&bid=10968330)
        
codacy-bot: 
![Codacy](https://app.codacy.com/assets/images/favicon.png) Here is an overview of what got changed by this pull request:


```diff

Issues
======
+ Solved 2
- Added 4
           

Complexity increasing per file
==============================
- tests/test_dkey.py  2
         

Clones removed
==============
+ tests/test_dkey.py  -17
         
```


See the complete overview on [Codacy](https://app.codacy.com/app/NOhs/dkey/pullRequest?prid=2964213&bid=10968330)
        
codacy-bot: 
![Codacy](https://app.codacy.com/assets/images/favicon.png) Here is an overview of what got changed by this pull request:


```diff

Issues
======
+ Solved 2
           

Complexity increasing per file
==============================
- tests/test_dkey.py  2
         

Clones removed
==============
+ tests/test_dkey.py  -17
         
```


See the complete overview on [Codacy](https://app.codacy.com/app/NOhs/dkey/pullRequest?prid=2964213&bid=10968330)
        

Patch:
diff --git a/.travis.yml b/.travis.yml
index 1069918..50fc290 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -17,3 +17,4 @@ deploy:
     secure: ZYN/wuXcpWOuWtuEJrrdd7uhdID5yOk6XN9zeGgFVQASnI5PvgqGe/DeflMkSAem5gaa/shGNVL0454tI2L047HxN11MiGyk6wlDUAHbx6iQm4REzgHufxfmoVtqUXHnJqxARboTWw3vosorAd+u6fyhdcqI66bs4OEH23op2uCPSz05s5jgPDNkgFOjAetVfnnFEquEskVBoi0mor9BPGrswDdmFcgO/75qFYRru+BCp2+dxZvplgsFOVuLBigcp3Qw/INxbzNS694c1dDp3UApAWdr2GkWMnQrzfg3g/yydug7xHuRm3bCQp7rFFwhGlDKHGOq7YcQR4KBWr1efUq7BL+AEZFTqNvqDj1nTytGfUIDiluWogHZgh+TecjH1koVw+2k5Zz1aWESbxNoEfQMGj640GMxB1jFbeI/SNaBt7gs5Zx88wtVPUCUKab5Th+sXQvMUihP3gvCRLWSXnzjgwv5Fo6Ro8Vo4xeM7lhqqEkKyiL4E5TquQ626ayN5m3dZvEAxwHAOi2Uaky/LGAb9SJKUA5rwICYBU1YYGY7TA5qBbxDwpp/k2nqs2IyV11oMvlsfK3xs5E9cUn3MivZUAX9MQgvdlvij1G6lsc3a0RiHln1LYHnls6WembE1B8NO1RM4KitlIDWHGxFfPOYQpvqkotuABnF+gJL+es=
   password:
     secure: OKEQrOFbxC0kgPt0RkPUr0Oz+XCjDnSiMrHMJzT2Nm8J2XO1UDxpwWIeaq7gTFmLoNhImAroL/YsoNrHBkaQemEkxKC55Qnt5SBoZvdlspA7ozaBtVOZ7+hAs4k0yoNXxARt/7KxuN+5qhZxySfNBpaFClvMnzW7V/e+9kc759cFGkzimbiOwN+t/apTXKaO7IvdnYCpASeL1/W7yjlN56ea9LYC/AtIVqqiQrC8j2wbI78mxoJN2hiysSOfxwo/j5vMToSrHwHdLXGpqq0igU+bwdJi/0bNfw2wIrRQfFFbaYuryJgH6ilDKHwsKpbMFTfO2zHbhWYOVCWdrHf7n5DCj1cuGT6SQSxDVy5riGWSif6lPUD5eAlZsB2Y7mxwB650ptFJqSoRmwJXk+AXfrKalntNgirVid31NRLA1MSLcLa6QWVTqsD16vV+ScvYMwqr6Qvj41OUzD892kvD93tFCy85XRQAIpJX73KV9YdDJPbUHZz0XtmXebrguRe28y13PFHqPjJq4cT7OyjYSq6ObwyWCLatlf6IKNMWRq4bVfwr83uaWZjlqsahMMAx9LbZCiRGp8smYV65oBd5I3I33U4Udlm7we91MZp8JN0rxzrh7Y03w+kJwYIdIrTenB3Vx1tS1Vy+E48jeljfzRN9jEmCQ93LE5JmnfAHe68=
+  distributions: "sdist bdist_wheel"
diff --git a/README.md b/README.md
index e468bce..a2ecb97 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-[![Build Status](https://travis-ci.org/NOhs/dkey.svg?branch=master)](https://travis-ci.org/NOhs/dkey) [![Coverage Status](https://coveralls.io/repos/github/NOhs/dkey/badge.svg?branch=master)](https://coveralls.io/github/NOhs/dkey?branch=master) [![Documentation Status](https://readthedocs.org/projects/dkey/badge/?version=latest)](https://dkey.readthedocs.io/en/latest/?badge=latest) [![PyPI version](https://badge.fury.io/py/dkey.svg)](https://badge.fury.io/py/dkey) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/24cc8c86e18b44d2b3cb14270bca97bb)](https://www.codacy.com/app/NOhs/dkey?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=NOhs/dkey&amp;utm_campaign=Badge_Grade)
+[![Build Status](https://travis-ci.org/NOhs/dkey.svg?branch=master)](https://travis-ci.org/NOhs/dkey) [![Coverage Status](https://coveralls.io/repos/github/NOhs/dkey/badge.svg?branch=master)](https://coveralls.io/github/NOhs/dkey?branch=master) [![Documentation Status](https://readthedocs.org/projects/dkey/badge/?version=latest)](https://dkey.readthedocs.io/en/latest/?badge=latest) [![PyPI version](https://badge.fury.io/py/dkey.svg)](https://badge.fury.io/py/dkey) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/24cc8c86e18b44d2b3cb14270bca97bb)](https://www.codacy.com/app/NOhs/dkey?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=NOhs/dkey&amp;utm_campaign=Badge_Grade) [![Requirements Status](https://requires.io/github/NOhs/dkey/requirements.svg?branch=master)](https://requires.io/github/NOhs/dkey/requirements/?branch=master)
 
 # dkey - deprecating dict keys
 
@@ -42,7 +42,8 @@ def my_func():
     customer = customer_info()
 
     # Worked with the old version, will warn to use 'last name' in the future
-    # and will return 'last name' which is the replacement for 'name'
+    # and will return the value set for 'last name' which is the replacement
+    # for 'name'
     print(customer['name'])
 
     # Worked with the old version, will warn that in future release
@@ -54,10 +55,6 @@ Several things are done here:
 
 -   To ensure nothing breaks, access to the new dict with the old key still works
 
--   For key replacements, old keys are simply mapped to the new object (no duplication is created)
-
--   For key removals, old keys will raise a warning but are still in the dict
-
 -   To ensure developers are made aware of the deprecation a `DeprecationWarning` is
     raised which automatically tells which old key to replace with what, or which
     key is no longer available.
@@ -71,5 +68,4 @@ have a look at the documentation website which explains all available options:
 
 ## Limitations
 
--   No deprecation warnings can be generated to indicate that the number of entries of the dict has change.
 -   No automatic docstring changes are possible at the moment
diff --git a/dkey/__init__.py b/dkey/__init__.py
index 6186de7..74d21db 100644
--- a/dkey/__init__.py
+++ b/dkey/__init__.py
@@ -1,6 +1,6 @@
 '''
 This module provides a thin wrapper that can be used to set certain keys in dictionaries as deprecated.
-This allows e.g. for an easy way to gently push out interface changes instead of just introducing breaking 
+This allows e.g. for an easy way to gently push out interface changes instead of just introducing breaking
 changes without any prior warnings.
 
 Installation
@@ -100,7 +100,7 @@ the changes than what the default message offers. You can pass those details to
             dkey('name', 'last name', deprecated_in='1.1.12', removed_in='2.0.0',
                 details='`name` has been replaced by the two fields `first name` and `last name`.'))
 
-Which will result in the warning: 
+Which will result in the warning:
 
     Key `name` is deprecated since version 1.1.12. It will be removed in version 2.0.0.
     `name` has been replaced by the two fields `first name` and `last name`.
@@ -136,13 +136,31 @@ the string `'end user'` and it will be used to spawn the warning.
     function.
 
 
+Other dict functions
+--------------------
+
+Functions other than the item access via `[key]` will also warn appropriately. E.g., if you iterate over
+the dict::
+
+    for key in customer_info():
+        print(key)
+
+a warning is generated each time a deprecated key appears in the loop. Also if you try to delete a deprecated key,
+a warning will be generated::
+
+    customer = customer_info()
+    del customer['cleartext password']
+
+Similarly, the other functions of dict are wrapped.
+
+.. note:: Due to :any:`dict.items`, :any:`dict.keys`, and :any:`dict.values` returning view objects,
+          warnings are only generated when calling these functions, not when iterating over the respective
+          deprecated items.
+
 Limitations
 -----------
 
-- Currently, only key access can be checked and deprecation warnings are shown. There are no warnings
-  for changes in the number of entries in the dict.
-
-- Furthermore, no automatic doc-string adaptations are possible as of now
+- No automatic doc-string adaptations are possible as of now
 '''
 
 from ._dkey import deprecate_keys as deprecate_keys
diff --git a/dkey/_dkey.py b/dkey/_dkey.py
index dc03378..d08f04f 100644
--- a/dkey/_dkey.py
+++ b/dkey/_dkey.py
@@ -1,50 +1,486 @@
+"""Implementation file of the :any:`dkey` module."""
+
 from warnings import warn as _warn
 
 _warning_types = {'developer': DeprecationWarning, 'end user': FutureWarning}
 
+_DEFAULT = object()
+
 class deprecate_keys(dict):
-    '''
-    Wrapper for dicts that allows to set certain keys as deprecated.
+    """Wrapper for dicts that allows to set certain keys as deprecated."""
 
-    Attributes
-    ----------
-    dictionary: dict
-        The dictionary to wrap
-    *args
-        Zero or more keys that should show deprecation warnings.
-        Use :any:`dkey.dkey` for each key.
+    def __init__(self, dictionary, *args):
+        """
+        Construct the wrapper class.
 
-    Warns
-    -----
-    ArbitraryWarning
-        If a deprecated key is used, shows a warning
-        that was set for this key. (see also :any:`dkey.dkey`)
-    '''
+        Internally, the items are stored in the same order as the given dictionary (CPython >=3.6).
+        The deprecated old keys that were replaced with new ones are positioned
+        just before their respective new keys.
 
-    def __init__(self, dictionary, *args):
-        super().__init__(dictionary)
+        Parameters
+        ----------
+        dictionary: dict
+            The dictionary to wrap
+        *args
+            Zero or more keys that should show deprecation warnings.
+            Use :any:`dkey.dkey` for each key.
+
+        """
+        super().__init__()
         self._key_mappings = {}
+        _key_mappings_new = {}
         for mapping in args:
             self._key_mappings[mapping['old key']] = mapping
+            _key_mappings_new[mapping['new key']] = mapping
+            if not mapping['new key'] in dictionary:
+                raise ValueError(f'The new key `{mapping["new key"]}` which should replace the '
+                                 +f'old key `{mapping["old key"]}` is not in the given dict.')
+
+        for key, value in dictionary.items():
+            try:
+                mapping = _key_mappings_new[key]
+                if not (mapping['old key'] == mapping['new key']):
+                    super().__setitem__(mapping['old key'], value)
+            except KeyError:
+                # Not deprecated.
+                pass
+
+            super().__setitem__(key, value)
+
+    def __eq__(self, other):
+        """
+        Return `True` if identical to `other`.
+
+        Warns if either this or `other` contains deprecated keys.
+
+        Parameters
+        ----------
+        other
+            The other object to compare this one to
+
+        Returns
+        -------
+        bool
+            True if they are equal, False otherwise
+
+        Warns
+        -----
+        CustomWarning
+            Warns with the warnings stored for all contained keys.
+
+        """
+        for mapping in self._key_mappings.values():
+            deprecate_keys._warn_deprecation(mapping)
+
+        try:
+            for mapping in other._key_mappings.values():
+                deprecate_keys._warn_deprecation(mapping)
+        except AttributeError:
+            pass
+
+        return super().__eq__(other)
+
+
+    def __ne__(self, other):
+        """
+        Return `True` if not identical to `other`.
+
+        Warns if either this or `other` contains deprecated keys.
+
+        Parameters
+        ----------
+        other
+            The other object to compare this one to
+
+        Returns
+        -------
+        bool
+            True if they are not equal, False otherwise
+
+        Warns
+        -----
+        CustomWarning
+            Warns with the warnings stored for all contained keys.
+
+        """
+        for mapping in self._key_mappings.values():
+            deprecate_keys._warn_deprecation(mapping)
+
+        try:
+            for mapping in other._key_mappings.values():
+                deprecate_keys._warn_deprecation(mapping)
+        except AttributeError:
+            pass
+
+        return super().__ne__(other)
+
 
     def __getitem__(self, key):
-        key, _ = self._check_deprecated(key)
+        """
+        Get the value of the item of the given key `key`.
+
+        Warns if the given key is deprecated. If the key does not exist, it will behave
+        the same as a normal dict, i.e. it will raise a :any:`KeyError`.
+
+        Parameters
+        ----------
+        key
+            The key for which to return the value
+
+        Returns
+        -------
+        value
+            The value stored for the given key
+
+        Raises
+        ------
+        KeyError
+            If the key is not found
+
+        Warns
+        -----
+        CustomWarning
+            Warns with the warning stored for the given key if the key is deprecated.
+
+        """
+        self._check_deprecated(key)
+
         return super().__getitem__(key)
 
     def __setitem__(self, key, value):
-        _, deprecated = self._check_deprecated(key)
+        """
+        Set the value of the item of the given key `key` to `value`.
+
+        Warns if the given key is deprecated.
+
+        Parameters
+        ----------
+        key
+            The key under which to store the given value
+        value
+            The value to store
+
+        Warns
+        -----
+        CustomWarning
+            Warns with the warning stored for the given key if the key is deprecated.
+            Further access to the given key will not spawn additional warnings.
 
-        # Warn only once. Now a new value is stored that
-        # is unrelated to the old deprecated value.
-        if deprecated:
+        """
+        if self._check_deprecated(key):
             del self._key_mappings[key]
 
-        return super().__setitem__(key, value)
+        super().__setitem__(key, value)
+
+    def __delitem__(self, key):
+        """
+        Remove the item with key `key` and its associated value.
+
+        Warns if the given key is deprecated.
+
+        Parameters
+        ----------
+        key
+            The key of the item which to remove.
+
+        Raises
+        ------
+        KeyError
+            Raises a :any:`KeyError` if the given key does not exist
+
+        Warns
+        -----
+        CustomWarning
+            Warns with the warning stored for the given key if the key is deprecated.
+            Further access to the given key will not spawn additional warnings.
+
+        """
+        self.pop(key)
+
+    def __contains__(self, key):
+        """
+        Return `True` if the given key `key` is in this dict, else `False`.
+
+        Warns if the given key is deprecated.
+
+        Parameters
+        ----------
+        key
+            The key to search in this dict.
+
+        Returns
+        -------
+        IsInDict : bool
+            `True` if the given key is in this dict. `False`, otherwise.
+
+        Warns
+        -----
+        CustomWarning
+            Warns with the warning stored for the given key if the key is deprecated.
+
+        """
+        if self._check_deprecated(key):
+            return True
+        else:
+            return super().__contains__(key)
+
+    def __iter__(self):
+        """
+        Return an iterator over the keys of the dictionary.
+
+        Warns for each deprecated item accessed.
+
+        Returns
+        -------
+        Iterator : iterator
+            An iterator over the wrapped dict
+
+        Warns
+        -----
+        CustomWarning
+            Warns whenever a deprecated item in the dict is returned. Each item
+            will warn with its set warning type and message.
+
+        """
+        for key in iter(super().keys()):
+            self._check_deprecated(key)
+            yield key
+
+    def clear(self):
+        """
+        Remove all entries from the dict.
+
+        Will also remove all deprecation warnings and all keys.
+        """
+        self._key_mappings = dict()
+        super().clear()
+
+    def copy(self):
+        """
+        Return a shallow copy of this wrapped dict.
+
+        Will return a wrapped dict that, as the original, will warn
+        with the deprecation warnings set for this dict.
+
+        Returns
+        -------
+        copy : deprecate_keys
+            A shallow copy of the underlying dict and a shallow
+            copy of the underlying deprecation key structure.
+
+        """
+        output = deprecate_keys(super())
+        output._key_mappings = self._key_mappings.copy()
+
+        return output
+
+    def get(self, key, default=None):
+        """
+        Get the value stored under `key` or `default` if this key doesn't exist.
+
+        This function is the same as :any:`deprecate_keys.__getitem__` except that
+        it does not throw for non existing keys, but returns the given default
+        value instead.
+
+        Parameters
+        ----------
+        key
+            The key for which to return the value
+        default, optional
+            The value to return, if the given key is not stored in the dict. Defaults
+            to :any:`None` if not given.
+
+        Returns
+        -------
+        value
+            The value stored for `key` or `default` if `key` is not in the dict.
+
+        """
+        try:
+            return self.__getitem__(key)
+        except KeyError:
+            return default
+
+    def pop(self, key, default=_DEFAULT):
+        """
+        Remove and return the item with key `key`.
+
+        If the key is not in the dict and no default value is set,
+        this function will raise an exception. If a default value is
+        given, this value will be returned instead.
+
+        Parameters
+        ----------
+        key
+            The key to pop
+        default : optional
+            The value to return if the given `key` is not in the dict.
+            If non is given, an exception is raised instead.
+
+        Returns
+        -------
+        value
+            The value of the key given or the given default value, or
+            if no default value is given, an exception is raised.
+
+        Raises
+        ------
+        KeyError
+            If `key` is not in the dict and no default value is given,
+            this function raises a :any:`KeyError`.
+
+        Warns
+        -----
+        CustomWarning
+            Warns if the popped item is a deprecated key. The deprecation
+            information for this key is removed.
+
+        """
+        if self._check_deprecated(key):
+            del self._key_mappings[key]
+
+        if default is _DEFAULT:
+            return super().pop(key)
+        else:
+            return super().pop(key, default)
+
+    def popitem(self):
+        """
+        Pop an item from the dict.
+
+        This function has the same behaviour as :any:`dict.popitem`. Therefore,
+        for Python < 3.6 this function will pop an arbitrary item, whereas for
+        Python >= 3.6 this function will raise the last item added to this dict.
+        If the dict is empty, this function will raise a :any:`KeyError`.
+
+        Returns
+        -------
+        key
+            The key popped
+        value
+            The associated value of the popped key
+
+        Raises
+        ------
+        KeyError
+            If the dict is empty.
+
+        Warns
+        -----
+        CustomWarning
+            Warns if the popped item is a deprecated key. The deprecation
+            information for this key is removed.
+
+        """
+        item = super().popitem()
+
+        if self._check_deprecated(item[0]):
+            del self._key_mappings[item[0]]
+
+        return item
+
+    def items(self):
+        """
+        Return a new view of the dictionary's items: iterator of `(key, value)` pairs.
+
+        Works the same as the plain :any:`dict.items` function except that
+        it warns about all deprecated keys contained before returning.
+
+        Returns
+        -------
+        dict_items
+            Basically an iterator over `(key, value)` pairs of the dict. For more information
+            about dict views see: `dictionary view <https://docs.python.org/3/library/stdtypes.html#dict-views>`_
+
+        Warns
+        -----
+        CustomWarning
+            Warns for each deprecated item in the dictionary before returning.
+
+        """
+        for mapping in self._key_mappings.values():
+            self._warn_deprecation(mapping)
+
+        return super().items()
+
+    def values(self):
+        """
+        Return a new view of the dictionary's values.
+
+        Works the same as the plain :any:`dict.values` function except that
+        it warns about all deprecated keys associated with returned values before returning
+        the view.
+
+        Returns
+        -------
+        dict_values
+            Basically an iterator over the contained values of the dict. For more information
+            about dict views see: `dictionary view <https://docs.python.org/3/library/stdtypes.html#dict-views>`_
+
+        Warns
+        -----
+        CustomWarning
+            Warns for each deprecated item in the dictionary before returning.
+
+        """
+        for mapping in self._key_mappings.values():
+            self._warn_deprecation(mapping)
+
+        return super().values()
+
+    def keys(self):
+        """
+        Return a new view of the dictionary's keys.
+
+        Works the same as the plain :any:`dict.keys` function except that
+        it warns about all deprecated keys before returning the view.
+
+        Returns
+        -------
+        dict_keys
+            Basically an iterator over the contained keys of the dict. For more information
+            about dict views see: `dictionary view <https://docs.python.org/3/library/stdtypes.html#dict-views>`_
+
+        Warns
+        -----
+        CustomWarning
+            Warns for each deprecated item in the dictionary before returning.
+
+        """
+        for mapping in self._key_mappings.values():
+            self._warn_deprecation(mapping)
+
+        return super().keys()
+
+    def __len__(self):
+        """
+        Return the number of items in the dict.
+
+        Will raise warnings, if the dict contains deprecated values. One for
+        each deprecated value.
+
+        Returns
+        -------
+        int
+            The number of items in the dict
+
+        Warns
+        -----
+        CustomWarning
+            Warns for each deprecated item in the dictionary before returning.
+
+        """
+        for mapping in self._key_mappings.values():
+            self._warn_deprecation(mapping)
+
+        return super().__len__()
 
     def _check_deprecated(self, key):
-        '''Checks if the given key is in the dict of deprecated keys. If it is
-        it warns and returns the appropriate new key. Which is in case of a
-        removal the old key, and in case of a replacement the new key.
+        """
+        Check if the given key is deprecated and warn if it is.
+
+        Warns using the warning type and message stored with the key and returns True.
+        Otherwise it returns False and does not warn.
 
         Parameters
         ----------
@@ -53,24 +489,46 @@ class deprecate_keys(dict):
 
         Returns
         -------
-        key
-            The old key, if the key will be removed at some point. The new
-            key if the key has a replacement.
-        '''
-        deprecated = False
+        deprecated : bool
+            Whether the key is deprecated or not
+
+        """
         try:
             mapping = self._key_mappings[key]
-            _warn(mapping['warning message'], mapping['warning type'])
-            key = mapping['new key']
-            deprecated = True
+            self._warn_deprecation(mapping)
+
+            return True
         except KeyError:
-            pass
+            return False
+
+    @staticmethod
+    def _warn_deprecation(mapping):
+        """
+        Warn with the given deprecated key mapping.
+
+        Uses the default Python :any:`warnings.warn` function
+        extracting the `'warning message'` and `'warning type'`
+        from the given mapping (dict).
+
+        Parameters
+        ----------
+        mapping: dict
+            Dict that needs to contain the two keys `'warning message'`,
+            which should be a :any:`str`, and `'warning type'` which needs
+            to be a valid subclass of :any:`Exception`.
+
+        Warns
+        -----
+        CustomWarning
+            Warns with the given message and warning type.
 
-        return key, deprecated
+        """
+        _warn(mapping['warning message'], mapping['warning type'])
 
 
 def dkey(*args, deprecated_in=None, removed_in=None, details=None, warning_type='developer'):
-    '''Function that converts a key into a deprecation lookup dict.
+    """
+    Convert a key into a deprecation lookup dict.
 
     To use the :any:`dkey.deprecate_keys` function it is easiest to generate
     its input with this function. This function generates:
@@ -112,8 +570,8 @@ def dkey(*args, deprecated_in=None, removed_in=None, details=None, warning_type=
     ------
     ValueError
         If zero or more than two keys are passed to this function.
-    '''
 
+    """
     if len(args) == 0:
         raise ValueError('No key given')
     elif len(args) > 2:
diff --git a/docs/conf.py b/docs/conf.py
index a11f798..27cf6fd 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -116,7 +116,7 @@ html_theme = 'default'
 # Add any paths that contain custom static files (such as style sheets) here,
 # relative to this directory. They are copied after the builtin static files,
 # so a file named "default.css" will overwrite the builtin "default.css".
-html_static_path = ['_static']
+html_static_path = []
 
 # Custom sidebar templates, must be a dictionary that maps document names
 # to template names.
diff --git a/docs/index.rst b/docs/index.rst
index d2edb91..9760684 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -11,6 +11,9 @@ Module documentation
 --------------------
 
 .. autoclass:: dkey.deprecate_keys
+    :members:
+
+    .. automethod:: __init__
 
 .. autofunction:: dkey.dkey
 


Test patch:
diff --git a/tests/test_dkey.py b/tests/test_dkey.py
index d4dc203..fa6ae8d 100644
--- a/tests/test_dkey.py
+++ b/tests/test_dkey.py
@@ -1,124 +1,243 @@
-import warnings
+"""Test module testing all features of dkey."""
 
-from nose.tools import with_setup, eq_, assert_raises
+import warnings
+import unittest
+from contextlib import contextmanager
 
 from dkey import deprecate_keys, dkey
 
-def setup_check_all_warnings():
-    warnings.simplefilter("always")
-
-@with_setup(setup_check_all_warnings)
-def test_empty_mapping():
-    my_dict = deprecate_keys({'a': 12})
-
-    with warnings.catch_warnings(record=True) as w:
-        eq_(12, my_dict['a'])
-        eq_(len(w), 0)
-
-@with_setup(setup_check_all_warnings)
-def test_replacing():
-    my_dict = deprecate_keys({'b': 12}, dkey('a', 'b'))
-    with warnings.catch_warnings(record=True) as w:
-        eq_(12, my_dict['b'])
-        eq_(len(w), 0)
-    with warnings.catch_warnings(record=True) as w:
-        eq_(12, my_dict['a'])
-        eq_(len(w), 1)
-        eq_(w[0].category, DeprecationWarning)
-        eq_(str(w[0].message), 'Key `a` is deprecated. Use `b` from now on.')
-
-@with_setup(setup_check_all_warnings)
-def test_removing():
-    my_dict = deprecate_keys({'a': 12}, dkey('a'))
-    with warnings.catch_warnings(record=True) as w:
-        eq_(12, my_dict['a'])
-        eq_(len(w), 1)
-        eq_(w[0].category, DeprecationWarning)
-        eq_(str(w[0].message), 'Key `a` is deprecated. It shouldn\'t be used anymore.')
-
-@with_setup(setup_check_all_warnings)
-def test_setitem():
-    my_dict = deprecate_keys({'a': 12}, dkey('a'))
-    with warnings.catch_warnings(record=True) as w:
-        my_dict['a'] = 100
-        eq_(len(w), 1)
-        eq_(w[0].category, DeprecationWarning)
-        eq_(str(w[0].message), 'Key `a` is deprecated. It shouldn\'t be used anymore.')
-        eq_(100, my_dict['a'])
-        eq_(len(w), 1)
-        my_dict['a'] = 120
-        eq_(len(w), 1)
-        eq_(120, my_dict['a'])
-        eq_(len(w), 1)
-
-    my_dict = deprecate_keys({'b': 12}, dkey('a', 'b'))
-    with warnings.catch_warnings(record=True) as w:
-        my_dict['a'] = 100
-        eq_(len(w), 1)
-        eq_(w[0].category, DeprecationWarning)
-        eq_(str(w[0].message), 'Key `a` is deprecated. Use `b` from now on.')
-        eq_(100, my_dict['a'])
-        eq_(len(w), 1)
-        my_dict['a'] = 120
-        eq_(len(w), 1)
-        eq_(120, my_dict['a'])
-        eq_(len(w), 1)
-        eq_(my_dict['b'], 12)
-
-@with_setup(setup_check_all_warnings)
-def test_custom_warning():
-    my_dict = deprecate_keys({'a': 12}, dkey('a', warning_type=UserWarning))
-    with warnings.catch_warnings(record=True) as w:
-        eq_(12, my_dict['a'])
-        eq_(len(w), 1)
-        eq_(w[0].category, UserWarning)
-        eq_(str(w[0].message), 'Key `a` is deprecated. It shouldn\'t be used anymore.')
-
-@with_setup(setup_check_all_warnings)
-def test_end_user_warning():
-    my_dict = deprecate_keys({'a': 12}, dkey('a', warning_type='end user'))
-    with warnings.catch_warnings(record=True) as w:
-        eq_(12, my_dict['a'])
-        eq_(len(w), 1)
-        eq_(w[0].category, FutureWarning)
-        eq_(str(w[0].message), 'Key `a` is deprecated. It shouldn\'t be used anymore.')
-
-@with_setup(setup_check_all_warnings)
-def test_developer_warning():
-    my_dict = deprecate_keys({'a': 12}, dkey('a', warning_type='developer'))
-    with warnings.catch_warnings(record=True) as w:
-        eq_(12, my_dict['a'])
-        eq_(len(w), 1)
-        eq_(w[0].category, DeprecationWarning)
-        eq_(str(w[0].message), 'Key `a` is deprecated. It shouldn\'t be used anymore.')
-
-def test_deprecated_in():
-    my_dict = deprecate_keys({'a': 12}, dkey('a', deprecated_in='1.81.1'))
-    with warnings.catch_warnings(record=True) as w:
-        eq_(12, my_dict['a'])
-        eq_(len(w), 1)
-        eq_(w[0].category, DeprecationWarning)
-        eq_(str(w[0].message), 'Key `a` is deprecated since version 1.81.1. It shouldn\'t be used anymore.')
-
-def test_removed_in():
-    my_dict = deprecate_keys({'a': 12}, dkey('a', removed_in='2.0'))
-    with warnings.catch_warnings(record=True) as w:
-        eq_(12, my_dict['a'])
-        eq_(len(w), 1)
-        eq_(w[0].category, DeprecationWarning)
-        eq_(str(w[0].message), 'Key `a` is deprecated. It will be removed in version 2.0. It shouldn\'t be used anymore.')
-
-def test_added_details():
-    my_dict = deprecate_keys({'a': 12}, dkey('a', details='Get rid of it!'))
-    with warnings.catch_warnings(record=True) as w:
-        eq_(12, my_dict['a'])
-        eq_(len(w), 1)
-        eq_(w[0].category, DeprecationWarning)
-        eq_(str(w[0].message), 'Key `a` is deprecated. Get rid of it!')
-
-def test_number_of_keys_incorrect():
-    with assert_raises(ValueError):
-        dkey()
-
-    with assert_raises(ValueError):
-        dkey('a', 'b', 'c')
+class dkey_test_case(unittest.TestCase):
+    def test_number_of_keys_incorrect(self):
+        with self.assertRaises(ValueError):
+            dkey()
+
+        with self.assertRaises(ValueError):
+            dkey('a', 'b', 'c')
+
+class deprecate_keys_test_case(unittest.TestCase):
+    @contextmanager
+    def assertNotWarns(self, warning):
+        with warnings.catch_warnings(record=True) as w:
+            warnings.simplefilter('always')
+            yield
+            if len(w) > 0:
+                self.assertNotEqual(w[0].category, warning)
+            else:
+                self.assertTrue(True)
+
+    def get_key_assertions(self):
+        return ((key, self.assertWarns if self.is_deprecated(key) else self.assertNotWarns) for key in (x[0] for x in self.example_case['items']))
+
+    def is_deprecated(self, key):
+        return (key == self.example_case['removed key']) or (key == self.example_case['replaced key'][0])
+
+    def setUp(self):
+        self.example_case = {'items': (('a', 12), ('b', 13), ('c', 13), ('d', 14)), 'removed key': 'a', 'replaced key': ('b', 'c'), 'no key': 'f', 'default value': 100}
+
+        self.regular_dict = dict(self.example_case['items'])
+
+        items_for_dkey = (item for item in self.example_case['items'] if item[0] != self.example_case['replaced key'][0])
+        dkeys = [dkey(self.example_case['removed key']), dkey(*self.example_case['replaced key'])]
+        self.deprecated_dict = deprecate_keys(dict(items_for_dkey), *dkeys)
+
+    def _refill_dict(self):
+        for key, val in self.example_case['items']:
+            self.deprecated_dict[key] = val
+
+    def test_wrong_new_key(self):
+        with self.assertRaises(ValueError):
+            deprecate_keys({'a': 12}, dkey('b', 'c'))
+
+        with self.assertRaises(ValueError):
+            deprecate_keys({'a': 12}, dkey('b'))
+
+    def test_empty_mapping(self):
+        my_dict = deprecate_keys({'a': 12})
+        warnings.simplefilter("always")
+        with warnings.catch_warnings(record=True) as w:
+            self.assertEqual(12, my_dict['a'])
+            self.assertEqual(len(w), 0)
+
+    def test_pop(self):
+        for key, assertion in self.get_key_assertions():
+            with assertion(DeprecationWarning):
+                val = self.deprecated_dict.pop(key)
+            self.assertEqual(self.regular_dict.pop(key), val)
+
+        self.assertEqual(len(self.regular_dict), len(self.deprecated_dict))
+
+        with self.assertNotWarns(DeprecationWarning):
+            self._refill_dict()
+
+        with self.assertRaises(KeyError):
+            self.deprecated_dict.pop(self.example_case['no key'])
+
+    def test_pop_default(self):
+        for key, assertion in self.get_key_assertions():
+            with assertion(DeprecationWarning):
+                val = self.deprecated_dict.pop(key, self.example_case['default value'])
+            self.assertEqual(self.regular_dict.pop(key, self.example_case['default value']), val)
+
+        self.assertEqual(len(self.regular_dict), len(self.deprecated_dict))
+
+        with self.assertNotWarns(DeprecationWarning):
+            self._refill_dict()
+
+        with self.assertNotWarns(DeprecationWarning):
+            self.assertEqual(self.deprecated_dict.pop(self.example_case['no key'], self.example_case['default value']), self.regular_dict.pop(self.example_case['no key'], self.example_case['default value']))
+
+    def test_popitem(self):
+        num_deprecations = 0
+        with warnings.catch_warnings(record=True) as w:
+            warnings.simplefilter('always')
+            while self.deprecated_dict:
+                l = len(w)
+                dep_item = self.deprecated_dict.popitem()
+                self.assertEqual(dep_item, self.regular_dict.popitem())
+                if self.is_deprecated(dep_item[0]):
+                    self.assertEqual(len(w), l + 1)
+                    num_deprecations = num_deprecations + 1
+
+        with self.assertNotWarns(DeprecationWarning):
+                self._refill_dict()
+
+    def test_clear(self):
+        with self.assertNotWarns(DeprecationWarning):
+            self.deprecated_dict.clear()
+            self._refill_dict()
+
+    def test_del(self):
+        for key, assertion in self.get_key_assertions():
+            with assertion(DeprecationWarning):
+                del self.deprecated_dict[key]
+            with self.assertRaises(KeyError):
+                del self.deprecated_dict[key]
+
+        with self.assertNotWarns(DeprecationWarning):
+            self._refill_dict()
+
+    def _test_in(self, dictionary):
+        for key, assertion in self.get_key_assertions():
+            with assertion(DeprecationWarning):
+                self.assertTrue(key in dictionary)
+
+        with self.assertNotWarns(DeprecationWarning):
+            self.assertFalse(self.example_case['no key'] in dictionary)
+
+    def test_in(self):
+        self._test_in(self.deprecated_dict)
+
+    def test_iter(self):
+        num_deprecations = 0
+        with warnings.catch_warnings(record=True) as w:
+            warnings.simplefilter('always')
+            for key in self.deprecated_dict:
+                if self.is_deprecated(key):
+                    self.assertEqual(len(w), num_deprecations + 1)
+                    num_deprecations = len(w)
+
+    def test_copy(self):
+        dict_copy = self.deprecated_dict.copy()
+        self._test_in(dict_copy)
+
+    def test_get(self):
+        for key, assertion in self.get_key_assertions():
+            with assertion(DeprecationWarning):
+                val = self.deprecated_dict.get(key)
+            self.assertEqual(self.regular_dict.get(key), val)
+
+        with self.assertNotWarns(DeprecationWarning):
+            self.assertEqual(self.regular_dict.get(self.example_case['no key']), self.deprecated_dict.get('no key'))
+
+    def test_get_default(self):
+        for key, assertion in self.get_key_assertions():
+            with assertion(DeprecationWarning):
+                val = self.deprecated_dict.get(key, self.example_case['default value'])
+            self.assertEqual(self.regular_dict.get(key, self.example_case['default value']), val)
+
+        with self.assertNotWarns(DeprecationWarning):
+            self.assertEqual(self.deprecated_dict.get(self.example_case['no key'], self.example_case['default value']), self.regular_dict.get(self.example_case['no key'], self.example_case['default value']))
+
+    def test_replacing(self):
+        with self.assertWarnsRegex(DeprecationWarning, f'Key `{self.example_case["replaced key"][0]}` is deprecated. Use `{self.example_case["replaced key"][1]}` from now on.'):
+            self.assertEqual(self.deprecated_dict[self.example_case['replaced key'][0]], self.regular_dict[self.example_case['replaced key'][0]])
+
+        with self.assertNotWarns(DeprecationWarning):
+            self.assertEqual(self.deprecated_dict[self.example_case['replaced key'][1]], self.regular_dict[self.example_case['replaced key'][1]])
+
+    def test_removing(self):
+        with self.assertWarnsRegex(DeprecationWarning, f'Key `{self.example_case["removed key"][0]}` is deprecated. It shouldn\'t be used anymore.'):
+            self.assertEqual(self.deprecated_dict[self.example_case['removed key']], self.regular_dict[self.example_case['removed key']])
+
+    def test_setitem(self):
+        for key, assertion in self.get_key_assertions():
+            with assertion(DeprecationWarning):
+                self.deprecated_dict[key] = self.example_case['default value']
+
+            with self.assertNotWarns(DeprecationWarning):
+                self.deprecated_dict[key] = self.example_case['default value']
+
+    def test_items(self):
+        with self.assertWarns(DeprecationWarning):
+            self.assertEqual(self.deprecated_dict.items(), self.regular_dict.items())
+
+    def test_values(self):
+        with self.assertWarns(DeprecationWarning):
+            self.assertEqual(set(self.deprecated_dict.values()), set(self.regular_dict.values()))
+
+    def test_keys(self):
+        with self.assertWarns(DeprecationWarning):
+            self.assertEqual(self.deprecated_dict.keys(), self.regular_dict.keys())
+
+    def test_equality(self):
+        with self.assertWarns(DeprecationWarning):
+            self.assertTrue(self.deprecated_dict == self.regular_dict)
+
+        with self.assertWarns(DeprecationWarning):
+            self.assertTrue(self.deprecated_dict == self.deprecated_dict)
+
+        with self.assertWarns(DeprecationWarning):
+            self.assertTrue(self.regular_dict == self.deprecated_dict)
+
+    def test_inequality(self):
+        with self.assertWarns(DeprecationWarning):
+            self.assertFalse(self.deprecated_dict != self.regular_dict)
+
+        with self.assertWarns(DeprecationWarning):
+            self.assertFalse(self.deprecated_dict != self.deprecated_dict)
+
+        with self.assertWarns(DeprecationWarning):
+            self.assertFalse(self.regular_dict != self.deprecated_dict)
+
+    def test_custom_warning_type(self):
+        my_dict = deprecate_keys({'a': 12}, dkey('a', warning_type=UserWarning))
+        with self.assertWarns(UserWarning):
+            self.assertEqual(my_dict['a'], 12)
+
+    def test_end_user_warning(self):
+        my_dict = deprecate_keys({'a': 12}, dkey('a', warning_type='end user'))
+        with self.assertWarns(FutureWarning):
+            self.assertEqual(my_dict['a'], 12)
+
+    def test_developer_warning(self):
+        my_dict = deprecate_keys({'a': 12}, dkey('a', warning_type='developer'))
+        with self.assertWarns(DeprecationWarning):
+            self.assertEqual(my_dict['a'], 12)
+
+    def test_deprecated_in(self):
+        version = '1.81.1'
+        my_dict = deprecate_keys({'a': 12}, dkey('a', deprecated_in=version))
+        with self.assertWarnsRegex(DeprecationWarning, f'Key `a` is deprecated since version {version}. It shouldn\'t be used anymore.'):
+            self.assertEqual(my_dict['a'], 12)
+
+    def test_removed_in(self):
+        version = '2.0'
+        my_dict = deprecate_keys({'a': 12}, dkey('a', removed_in=version))
+        with self.assertWarnsRegex(DeprecationWarning, f'Key `a` is deprecated. It will be removed in version {version}. It shouldn\'t be used anymore.'):
+            self.assertEqual(my_dict['a'], 12)
+
+    def test_added_details(self):
+        details = 'Get rid of it!'
+        my_dict = deprecate_keys({'a': 12}, dkey('a', details=details))
+        with self.assertWarnsRegex(DeprecationWarning, details):
+            self.assertEqual(my_dict['a'], 12)