diff --git a/.gitignore b/.gitignore
index 049f78b..b240075 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,8 @@
 *.pyc
 /*.egg-info
+/.cache
+/.pytest_cache
+/.tox
 /MANIFEST
 /build
 /dist
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..2353337
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,14 @@
+arch:
+  - amd64
+  - ppc64le
+language: python
+python:
+  - "2.7"
+  - "3.5"
+  - "3.6"
+  - "3.7"
+  - "3.8"
+  - "3.9"
+  - "nightly"
+install: pip install tox-travis
+script: "tox"
diff --git a/CHANGELOG b/CHANGELOG
index 03ba52c..d273fb5 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,61 +1,147 @@
-1.2.1
-   - Fixed an issue in multiprocessing code.
-
-1.2.0
-   - Multiprocessing (by loisaidasam)
-   - Python 3 support
-   - Split up one big file into smaller more logical sub-modules
-   - Fixed https://github.com/exhuma/python-cluster/issues/11
-   - Documentation update.
-
-1.1.1b3
-   - Fixed bug #1727558
-   - Some more unit-tests
-   - ValueError changed to ClusteringError where appropriate
-
-1.1.1b2
-   - Fixed bug #1604859 (thanks to Willi Richert for reporting it)
-
-1.1.1b1
-   - Applied patch [1535137] (thanks ajaksu)
-     --> Topology output supported
-     --> data and raw_data are now properties.
-
-1.1.0b1
-   - KMeans Clustering implemented for simple numeric tuples.
-     Data in the form [(1,1), (2,1), (5,3), ...]
-     can be clustered.
-
-     Usage:
-
-     >>> from cluster import KMeansClustering
-     >>> cl = KMeansClustering([(1,1), (2,1), (5,3), ...])
-     >>> clusters = cl.getclusters(2)
-
-     the method "getclusters" takes the amount of clusters you would like to
-     have as parameter.
-
-     Only numeric values are supported in the tuples. The reason for this is
-     that the "centroid" method which I use, essentially returns a tuple of
-     floats. So you will lose any other kind of metadata. Once I figure out a
-     way how to recode that method, other types should be possible.
-
-1.0.1b2
-   - Optimized calculation of the hierarchical clustering by using the fact, that
-     the generated matrix is symmetrical.
-
-1.0.1b1
-   - Implemented complete-, average-, and uclus-linkage methods. You can select
-     one by specifying it in the constructor, for example:
-       
-       cl = HierarchicalClustering(data, distfunc, linkage='uclus')
-
-     or by setting it before starting the clustering process:
-
-       cl = HierarchicalClustering(data, distfunc)
-       cl.setLinkageMethod('uclus')
-       cl.cluster()
-
-   - Clustering is not executed on object creation, but on the first call of
-     "getlevel". You can force the creation of the clusters by calling the
-     "cluster" method as shown above.
+Release 1.4.1.post3
+===================
+
+This is a "house-keeping" commit. No new features or fixes are introduced.
+
+* Update CI test rules to include amd64 and ppc (santosh653)
+
+
+Release 1.4.1.post2
+===================
+
+This is a "house-keeping" commit. No new features or fixes are introduced.
+
+* Update changelog.
+* Removed the ``Pipfile`` which was introduced in ``1.4.1.post1``. The file
+  caused false positives on security checks. Additionally, having a ``Pipfile``
+  is mainly useful in applications, and not in libraries like this one.
+
+Release 1.4.1.post1
+===================
+
+This is a "house-keeping" commit. No new features or fixes are introduced.
+
+* Update changelog.
+* Switch doc-building to use ``pipenv`` & update ``Pipfile`` accordingly.
+
+Release 1.4.1
+=============
+
+* Fix clustering of dictionaries. See GitHub issue #28 (Tim Littlefair).
+
+Release 1.4.0
+=============
+
+* Added a "display" method to hierarchical clusters (by 1kastner).
+
+Release 1.3.2 & 1.3.3
+=====================
+
+* Fix regression introduced in 1.3.1 related to package version metadata.
+
+Release 1.3.1
+=============
+
+* Don't break if the cluster is initiated with iterable elements (GitHub Issue
+  #20).
+* Fix package version metadata in setup.py
+
+Release 1.3.0
+=============
+
+* Performance improvments for hierarchical clustering (at the cost of memory)
+* Cluster instances are now iterable. It will iterate over each element,
+  resulting in a flat list of items.
+* New option to specify a progress callback to hierarchical clustring. This
+  method will be called on each iteration for hierarchical clusters. It gets
+  two numeric values as argument: The total count of elements, and the number
+  of processed elements. It gives users a way to present to progress on screen.
+* The library now also has a ``__version__`` member.
+
+
+Release 1.2.2
+=============
+
+* Package metadata fixed.
+
+Release 1.2.1
+=============
+
+* Fixed an issue in multiprocessing code.
+
+Release 1.2.0
+=============
+
+* Multiprocessing (by loisaidasam)
+* Python 3 support
+* Split up one big file into smaller more logical sub-modules
+* Fixed https://github.com/exhuma/python-cluster/issues/11
+* Documentation update.
+* Migrated to GitHub
+
+Release 1.1.1b3
+===============
+
+* Fixed bug #1727558
+* Some more unit-tests
+* ValueError changed to ClusteringError where appropriate
+
+Release 1.1.1b2
+===============
+
+* Fixed bug #1604859 (thanks to Willi Richert for reporting it)
+
+Release 1.1.1b1
+===============
+
+* Applied SVN patch [1535137] (thanks ajaksu)
+
+  * Topology output supported
+  * ``data`` and ``raw_data`` are now properties.
+
+Release 1.1.0b1
+===============
+
+* KMeans Clustering implemented for simple numeric tuples.
+
+  Data in the form ``[(1,1), (2,1), (5,3), ...]`` can be clustered.
+
+  Usage::
+
+    >>> from cluster import KMeansClustering
+    >>> cl = KMeansClustering([(1,1), (2,1), (5,3), ...])
+    >>> clusters = cl.getclusters(2)
+
+  The method ``getclusters`` takes the amount of clusters you would like to
+  have as parameter.
+
+  Only numeric values are supported in the tuples. The reason for this is
+  that the "centroid" method which I use, essentially returns a tuple of
+  floats. So you will lose any other kind of metadata. Once I figure out a
+  way how to recode that method, other types should be possible.
+
+Release 1.0.1b2
+===============
+
+* Optimized calculation of the hierarchical clustering by using the fact, that
+  the generated matrix is symmetrical.
+
+Release 1.0.1b1
+===============
+
+* Implemented complete-, average-, and uclus-linkage methods. You can select
+  one by specifying it in the constructor, for example::
+
+      cl = HierarchicalClustering(data, distfunc, linkage='uclus')
+
+  or by setting it before starting the clustering process::
+
+      cl = HierarchicalClustering(data, distfunc)
+      cl.setLinkageMethod('uclus')
+      cl.cluster()
+
+* Clustering is not executed on object creation, but on the first call of
+  ``getlevel``. You can force the creation of the clusters by calling the
+  ``cluster`` method as shown above.
+
+.. vim: filetype=rst :
diff --git a/cluster/matrix.py b/cluster/matrix.py
index 8ecf287..14dd40d 100644
--- a/cluster/matrix.py
+++ b/cluster/matrix.py
@@ -22,6 +22,42 @@ from multiprocessing import Process, Queue, current_process
 
 logger = logging.getLogger(__name__)
 
+def _encapsulate_item_for_combinfunc(item):
+    """
+    This function has been extracted in order to 
+    make Github issue #28 easier to investigate.
+    It replaces the following two lines of code, 
+    which occur twice in method genmatrix, just
+    before the invocation of combinfunc.
+        if not hasattr(item, '__iter__') or isinstance(item, tuple):
+            item = [item]
+    Logging was added to the original two lines
+    and shows that the outcome of this snippet
+    has changed between Python2.7 and Python3.5.
+    This logging showed that the difference in 
+    outcome consisted of the handling of the builtin
+    str class, which was encapsulated into a list in
+    Python2.7 but returned naked in Python3.5.
+    Adding a test for this specific class to the 
+    set of conditions appears to give correct behaviour
+    under both versions.
+    """
+    encapsulated_item = None
+    if  (
+        not hasattr(item, '__iter__') or
+        isinstance(item, tuple) or
+        isinstance(item, str)
+    ):
+        encapsulated_item = [item]
+    else:
+        encapsulated_item = item
+    logging.debug(
+        "item class:%s encapsulated as:%s ",
+        item.__class__.__name__, 
+        encapsulated_item.__class__.__name__
+    )
+    return encapsulated_item
+
 
 class Matrix(object):
     """
@@ -123,10 +159,17 @@ class Matrix(object):
                         num_tasks_completed += 1
                 else:
                     # Otherwise do it here, in line
+                    """
                     if not hasattr(item, '__iter__') or isinstance(item, tuple):
                         item = [item]
                     if not hasattr(item2, '__iter__') or isinstance(item2, tuple):
                         item2 = [item2]
+                    """
+                    # See the comment in function _encapsulate_item_for_combinfunc
+                    # for details of why the lines above have been replaced
+                    # by function invocations
+                    item = _encapsulate_item_for_combinfunc(item)
+                    item2 = _encapsulate_item_for_combinfunc(item2)
                     row[col_index] = self.combinfunc(item, item2)
 
             if self.symmetric:
diff --git a/cluster/method/hierarchical.py b/cluster/method/hierarchical.py
index e63cd7d..295ed0d 100644
--- a/cluster/method/hierarchical.py
+++ b/cluster/method/hierarchical.py
@@ -56,7 +56,8 @@ class HierarchicalClustering(BaseClusterMethod):
 
     :param data: The collection of items to be clustered.
     :param distance_function: A function which takes two elements of ``data``
-        and returns a distance between both elements.
+        and returns a distance between both elements (note that the distance
+        should not be returned as negative value!)
     :param linkage: The method used to determine the distance between two
         clusters. See :py:meth:`~.HierarchicalClustering.set_linkage_method` for
         possible values.
@@ -206,3 +207,14 @@ class HierarchicalClustering(BaseClusterMethod):
             self.cluster()
 
         return self._data[0].getlevel(threshold)
+
+    def display(self):
+        """
+        Prints a simple dendogram-like representation of the full cluster
+        to the console.
+        """
+        # initialize the cluster if not yet done
+        if not self.__cluster_created:
+            self.cluster()
+
+        self._data[0].display()
diff --git a/cluster/test/test_hierarchical.py b/cluster/test/test_hierarchical.py
index 24bb892..8ee62da 100644
--- a/cluster/test/test_hierarchical.py
+++ b/cluster/test/test_hierarchical.py
@@ -66,13 +66,6 @@ class HClusterSmallListTestCase(Py23TestCase):
 
 class HClusterIntegerTestCase(Py23TestCase):
 
-    def __init__(self, *args, **kwargs):
-        super(HClusterIntegerTestCase, self).__init__(*args, **kwargs)
-        if hexversion < 0x030000f0:
-            self.assertCItemsEqual = self.assertItemsEqual
-        else:
-            self.assertCItemsEqual = self.assertCountEqual
-
     def setUp(self):
         self.__data = [791, 956, 676, 124, 564, 84, 24, 365, 594, 940, 398,
                        971, 131, 365, 542, 336, 518, 835, 134, 391]
@@ -238,12 +231,42 @@ class HClusterTuplesTestCase(Py23TestCase):
         result = cl.getlevel(40)
         self.assertIsNotNone(result)
 
+class Issue28TestCase(Py23TestCase):
+    '''
+    Test case to cover the case where the data consist
+    of dictionary keys, and the distance function executes 
+    on the values these keys are associated with in the
+    dictionary, rather than the keys themselves.
+
+    Behaviour for this test case differs between Python2.7
+    and Python3.5: on 2.7 the test behaves as expected, 
 
+    See Github issue #28.
+    '''
+
+    def testIssue28(self):
+        "Issue28 (Hierarchical Clustering)"
+
+        points1D = {
+            'p4' : 5, 'p2' : 6, 'p7' : 10,
+            'p9' : 120, 'p10' : 121, 'p11' : 119,
+        }
+
+        distance_func = lambda a,b : abs(points1D[a]-points1D[b])
+        cl = HierarchicalClustering(list(points1D.keys()), distance_func)
+        result = cl.getlevel(20)
+        self.assertIsNotNone(result)
+    
 if __name__ == '__main__':
+
+    import logging
+
     suite = unittest.TestSuite((
         unittest.makeSuite(HClusterIntegerTestCase),
         unittest.makeSuite(HClusterSmallListTestCase),
         unittest.makeSuite(HClusterStringTestCase),
+        unittest.makeSuite(Issue28TestCase),
     ))
 
+    logging.basicConfig(level=logging.DEBUG)
     unittest.TextTestRunner(verbosity=2).run(suite)
diff --git a/cluster/test/test_linkage.py b/cluster/test/test_linkage.py
index 0a5df07..e84b492 100644
--- a/cluster/test/test_linkage.py
+++ b/cluster/test/test_linkage.py
@@ -29,3 +29,14 @@ class LinkageMethods(unittest.TestCase):
         result = average(self.set_a, self.set_b, self.dist)
         expected = 22.5
         self.assertEqual(result, expected)
+
+if __name__ == '__main__':
+
+    import logging
+
+    suite = unittest.TestSuite((
+        unittest.makeSuite(LinkageMethods),
+    ))
+
+    logging.basicConfig(level=logging.DEBUG)
+    unittest.TextTestRunner(verbosity=2).run(suite)
diff --git a/cluster/version.txt b/cluster/version.txt
index 31e5c84..abd99ac 100644
--- a/cluster/version.txt
+++ b/cluster/version.txt
@@ -1 +1 @@
-1.3.3
+1.4.1.post3
diff --git a/debian/changelog b/debian/changelog
index 7f3742e..16509da 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,4 +1,4 @@
-python-cluster (1.3.3-4) UNRELEASED; urgency=medium
+python-cluster (1.4.1.post3-1) UNRELEASED; urgency=medium
 
   [ Ondřej Nový ]
   * Bump Standards-Version to 4.4.1.
@@ -13,8 +13,9 @@ python-cluster (1.3.3-4) UNRELEASED; urgency=medium
   * Update standards version to 4.5.0, no changes needed.
   * Bump debhelper from old 12 to 13.
   * Update standards version to 4.5.1, no changes needed.
+  * New upstream release.
 
- -- Ondřej Nový <onovy@debian.org>  Fri, 18 Oct 2019 15:57:47 +0200
+ -- Ondřej Nový <onovy@debian.org>  Mon, 16 May 2022 12:07:44 -0000
 
 python-cluster (1.3.3-3) unstable; urgency=medium
 
diff --git a/dev-requirements.txt b/dev-requirements.txt
new file mode 100644
index 0000000..6966869
--- /dev/null
+++ b/dev-requirements.txt
@@ -0,0 +1 @@
+sphinx
diff --git a/docs/_static/.gitkeep b/docs/_static/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/docs/changelog.rst b/docs/changelog.rst
index 225d58f..01c9361 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -1,13 +1,4 @@
 Changelog
-=========
+#########
 
-Release 1.3.0
--------------
-
-* Performance improvments for hierarchical clustering (at the cost of memory)
-* Cluster instances are now iterable.It will iterate over each element,
-  resulting in a flat list of items.
-* New option to specify a progress method. This method will be called on each
-  iteration for hierarchical clusters. It gives users a way to present to
-  progress on screen.
-* The library now also has a ``__version__`` member.
+.. include:: ../CHANGELOG
diff --git a/docs/conf.py b/docs/conf.py
index 3e66f30..173919c 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -14,6 +14,7 @@
 
 import sys
 import os
+from os.path import dirname, join
 
 # If extensions (or modules to document with autodoc) are in another directory,
 # add these directories to sys.path here. If the directory is relative to the
@@ -52,10 +53,14 @@ copyright = u'2014, Michel Albert'
 # |version| and |release|, also used in various other places throughout the
 # built documents.
 #
+version_file = join(dirname(__file__), '..', 'cluster', 'version.txt')
+with open(version_file) as fptr:
+    # The full version, including alpha/beta/rc tags.
+    release = fptr.read().strip()
+    versioninfo = release.split('.')
+
 # The short X.Y version.
-version = '1.3'
-# The full version, including alpha/beta/rc tags.
-release = '1.3.0'
+version = '%s.%s' % (versioninfo[0], versioninfo[1])
 
 # The language for content autogenerated by Sphinx. Refer to documentation
 # for a list of supported languages.
@@ -100,7 +105,7 @@ pygments_style = 'sphinx'
 
 # The theme to use for HTML and HTML Help pages.  See the documentation for
 # a list of builtin themes.
-html_theme = 'default'
+html_theme = 'alabaster'
 
 # Theme options are theme-specific and customize the look and feel of a theme
 # further.  For a list of options available for each theme, see the
diff --git a/pytest.ini b/pytest.ini
index 448864a..5eddc23 100644
--- a/pytest.ini
+++ b/pytest.ini
@@ -1,2 +1,3 @@
 [pytest]
+looponfailroots = cluster
 norecursedirs = env env3 env3_nonumpy .git
diff --git a/tox.ini b/tox.ini
new file mode 100644
index 0000000..aebb990
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,6 @@
+[tox]
+envlist = py27, py35, py36
+
+[testenv]
+deps = pytest
+commands = pytest