Repository: dedupeio/dedupe (language: python)

Issue:
FEAT: Use black formatting
Feature request for developing, not a huge deal but I find it very helpful.

I love the automatic formatting that [black](https://github.com/psf/black) does. It's becoming quite a standard for python projects. Do you want to change to that? formatting all existing code would add some noise to git blame, although [that can be mitigated](https://black.readthedocs.io/en/stable/guides/introducing_black_to_your_project.html). Besides that all that would need to happen is add to requirements and then add a CI check alongside the other static linters.


Discussion / hints:
sure, let's do it. i'm actually interested in an black autoformatter instead of a linter https://github.com/psf/black/actions/runs/17913292/workflow

Fix patch:
diff --git a/.github/workflows/pythonpackage.yml b/.github/workflows/pythonpackage.yml
index 032354b97..82f2701b6 100644
--- a/.github/workflows/pythonpackage.yml
+++ b/.github/workflows/pythonpackage.yml
@@ -3,7 +3,32 @@ name: tests
 on: [push, pull_request]
 
 jobs:
+  format:
+    # We want all subsequent steps of CI to refer to the fresh version of code,
+    # so run this before anything else.
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v1
+      - name: Set up Python 3.10
+        uses: actions/setup-python@v2
+        with:
+          python-version: "3.10"
+      - name: Install Black
+        run: pip install black
+      - name: Run black --check .
+        run: black --check .
+      - name: If needed, commit black changes to the pull request
+        if: failure()
+        run: |
+          black .
+          git config --global user.name 'autoblack'
+          git config --global user.email 'cclauss@users.noreply.github.com'
+          git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/$GITHUB_REPOSITORY
+          git checkout $GITHUB_HEAD_REF
+          git commit -am "fixup: Format Python code with Black"
+          git push
   test:
+    needs: format
     timeout-minutes: 30
     runs-on: ${{ matrix.os }}
     strategy:
diff --git a/dedupe/__init__.py b/dedupe/__init__.py
index da170c47f..cf75318a5 100644
--- a/dedupe/__init__.py
+++ b/dedupe/__init__.py
@@ -1,6 +1,7 @@
 #!/usr/bin/python
 # -*- coding: utf-8 -*-
 from pkgutil import extend_path
+
 __path__ = extend_path(__path__, __name__)
 
 from dedupe._init import *  # noqa
diff --git a/dedupe/_init.py b/dedupe/_init.py
index 26216286b..5f1bea319 100644
--- a/dedupe/_init.py
+++ b/dedupe/_init.py
@@ -1,4 +1,9 @@
 from dedupe.api import StaticDedupe, Dedupe  # noqa: F401
 from dedupe.api import StaticRecordLink, RecordLink  # noqa: F401
 from dedupe.api import StaticGazetteer, Gazetteer  # noqa: F401
-from dedupe.convenience import console_label, training_data_dedupe, training_data_link, canonicalize  # noqa: F401
+from dedupe.convenience import (  # noqa: F401
+    console_label,
+    training_data_dedupe,
+    training_data_link,
+    canonicalize,
+)
diff --git a/dedupe/_typing.py b/dedupe/_typing.py
index ca56aca32..66c293672 100644
--- a/dedupe/_typing.py
+++ b/dedupe/_typing.py
@@ -1,13 +1,7 @@
 import numpy
 import sys
 
-from typing import (Iterator,
-                    Tuple,
-                    Mapping,
-                    Union,
-                    Iterable,
-                    List,
-                    Any)
+from typing import Iterator, Tuple, Mapping, Union, Iterable, List, Any
 
 if sys.version_info >= (3, 8):
     from typing import TypedDict, Protocol, Literal
@@ -25,10 +19,9 @@
 Clusters = Iterable[Cluster]
 Data = Mapping[RecordID, RecordDict]
 TrainingExample = Tuple[RecordDict, RecordDict]
-Links = Iterable[Union[numpy.ndarray,
-                       Tuple[Tuple[RecordID, RecordID], float]]]
+Links = Iterable[Union[numpy.ndarray, Tuple[Tuple[RecordID, RecordID], float]]]
 LookupResults = Iterable[Tuple[RecordID, Tuple[Tuple[RecordID, float], ...]]]
-JoinConstraint = Literal['one-to-one', 'many-to-one', 'many-to-many']
+JoinConstraint = Literal["one-to-one", "many-to-one", "many-to-many"]
 
 
 class TrainingData(TypedDict):
diff --git a/dedupe/api.py b/dedupe/api.py
index b1921b678..da0271c85 100644
--- a/dedupe/api.py
+++ b/dedupe/api.py
@@ -26,32 +26,36 @@
 import dedupe.labeler as labeler
 import dedupe.predicates
 
-from typing import (Mapping,
-                    Optional,
-                    List,
-                    Tuple,
-                    Set,
-                    Dict,
-                    Union,
-                    Generator,
-                    Iterable,
-                    Sequence,
-                    BinaryIO,
-                    cast,
-                    TextIO)
+from typing import (
+    Mapping,
+    Optional,
+    List,
+    Tuple,
+    Set,
+    Dict,
+    Union,
+    Generator,
+    Iterable,
+    Sequence,
+    BinaryIO,
+    cast,
+    TextIO,
+)
 from typing_extensions import Literal
-from dedupe._typing import (Data,
-                            Clusters,
-                            RecordPairs,
-                            RecordID,
-                            RecordDict,
-                            Blocks,
-                            TrainingExample,
-                            LookupResults,
-                            Links,
-                            TrainingData,
-                            Classifier,
-                            JoinConstraint)
+from dedupe._typing import (
+    Data,
+    Clusters,
+    RecordPairs,
+    RecordID,
+    RecordDict,
+    Blocks,
+    TrainingExample,
+    LookupResults,
+    Links,
+    TrainingData,
+    Classifier,
+    JoinConstraint,
+)
 
 logger = logging.getLogger(__name__)
 
@@ -61,10 +65,9 @@ class Matching(object):
     Base Class for Record Matching Classes
     """
 
-    def __init__(self,
-                 num_cores: Optional[int],
-                 in_memory: bool = False,
-                 **kwargs) -> None:
+    def __init__(
+        self, num_cores: Optional[int], in_memory: bool = False, **kwargs
+    ) -> None:
 
         if num_cores is None:
             self.num_cores = multiprocessing.cpu_count()
@@ -81,8 +84,10 @@ def __init__(self,
     def fingerprinter(self) -> blocking.Fingerprinter:
 
         if self._fingerprinter is None:
-            raise ValueError('the record fingerprinter is not intialized, '
-                             'please run the train method')
+            raise ValueError(
+                "the record fingerprinter is not intialized, "
+                "please run the train method"
+            )
 
         return self._fingerprinter
 
@@ -93,8 +98,7 @@ class IntegralMatching(Matching):
     pairs before deciding on any matches
     """
 
-    def score(self,
-              pairs: RecordPairs) -> Union[numpy.memmap, numpy.ndarray]:
+    def score(self, pairs: RecordPairs) -> Union[numpy.memmap, numpy.ndarray]:
         """
         Scores pairs of records. Returns pairs of tuples of records id and
         associated probabilities that the pair of records are match
@@ -104,16 +108,17 @@ def score(self,
 
         """
         try:
-            matches = core.scoreDuplicates(pairs,
-                                           self.data_model,
-                                           self.classifier,
-                                           self.num_cores)
+            matches = core.scoreDuplicates(
+                pairs, self.data_model, self.classifier, self.num_cores
+            )
         except RuntimeError:
-            raise RuntimeError('''
+            raise RuntimeError(
+                """
                 You need to either turn off multiprocessing or protect
                 the calls to the Dedupe methods with a
                 `if __name__ == '__main__'` in your main module, see
-                https://docs.python.org/3/library/multiprocessing.html#the-spawn-and-forkserver-start-methods''')
+                https://docs.python.org/3/library/multiprocessing.html#the-spawn-and-forkserver-start-methods"""
+            )
 
         return matches
 
@@ -130,9 +135,9 @@ class DedupeMatching(IntegralMatching):
     def __init__(self, *args, **kwargs) -> None:
         super().__init__(*args, **kwargs)
 
-    def partition(self,
-                  data: Data,
-                  threshold: float = 0.5) -> Clusters:  # pragma: no cover
+    def partition(
+        self, data: Data, threshold: float = 0.5
+    ) -> Clusters:  # pragma: no cover
         """
         Identifies records that all refer to the same entity, returns
         tuples containing a sequence of record ids and corresponding
@@ -198,10 +203,10 @@ def _add_singletons(self, data, clusters):
             yield (record_ids, score)
 
         for singleton in singletons:
-            yield (singleton, ), (1.0, )
+            yield (singleton,), (1.0,)
 
     def pairs(self, data):
-        '''
+        """
         Yield pairs of records that share common fingerprints.
 
         Each pair will occur at most once. If you override this
@@ -224,7 +229,7 @@ def pairs(self, data):
               (3, {'name' : 'Sam', 'address' : '123 Main'}))
              ]
 
-        '''
+        """
 
         self.fingerprinter.index_all(data)
 
@@ -234,43 +239,55 @@ def pairs(self, data):
         # bottlenecks, so we'll use sqlite3 to avoid doing them in memory
         with tempfile.TemporaryDirectory() as temp_dir:
             if self.in_memory:
-                con = sqlite3.connect(':memory:')
+                con = sqlite3.connect(":memory:")
             else:
-                con = sqlite3.connect(temp_dir + '/blocks.db')
+                con = sqlite3.connect(temp_dir + "/blocks.db")
 
             # Set journal mode to WAL.
-            con.execute('pragma journal_mode=off')
+            con.execute("pragma journal_mode=off")
 
-            con.execute('''CREATE TABLE blocking_map
+            con.execute(
+                """CREATE TABLE blocking_map
                            (block_key text, record_id {id_type})
-                        '''.format(id_type=id_type))
+                        """.format(
+                    id_type=id_type
+                )
+            )
 
-            con.executemany("INSERT INTO blocking_map values (?, ?)",
-                            self.fingerprinter(data.items()))
+            con.executemany(
+                "INSERT INTO blocking_map values (?, ?)",
+                self.fingerprinter(data.items()),
+            )
 
             self.fingerprinter.reset_indices()
 
-            con.execute('''CREATE UNIQUE INDEX record_id_block_key_idx
-                           ON blocking_map (record_id, block_key)''')
-            con.execute('''CREATE INDEX block_key_idx
-                           ON blocking_map (block_key)''')
-            con.execute('''ANALYZE''')
-            pairs = con.execute('''SELECT DISTINCT a.record_id, b.record_id
+            con.execute(
+                """CREATE UNIQUE INDEX record_id_block_key_idx
+                           ON blocking_map (record_id, block_key)"""
+            )
+            con.execute(
+                """CREATE INDEX block_key_idx
+                           ON blocking_map (block_key)"""
+            )
+            con.execute("""ANALYZE""")
+            pairs = con.execute(
+                """SELECT DISTINCT a.record_id, b.record_id
                                    FROM blocking_map a
                                    INNER JOIN blocking_map b
                                    USING (block_key)
-                                   WHERE a.record_id < b.record_id''')
+                                   WHERE a.record_id < b.record_id"""
+            )
 
             for a_record_id, b_record_id in pairs:
-                yield ((a_record_id, data[a_record_id]),
-                       (b_record_id, data[b_record_id]))
+                yield (
+                    (a_record_id, data[a_record_id]),
+                    (b_record_id, data[b_record_id]),
+                )
 
             pairs.close()
             con.close()
 
-    def cluster(self,
-                scores: numpy.ndarray,
-                threshold: float = 0.5) -> Clusters:
+    def cluster(self, scores: numpy.ndarray, threshold: float = 0.5) -> Clusters:
         r"""From the similarity scores of pairs of records, decide which groups
         of records are all referring to the same entity.
 
@@ -379,52 +396,67 @@ def pairs(self, data_1: Data, data_2: Data) -> RecordPairs:
         # bottlenecks, so we'll use sqlite3 to avoid doing them in memory
         with tempfile.TemporaryDirectory() as temp_dir:
             if self.in_memory:
-                con = sqlite3.connect(':memory:')
+                con = sqlite3.connect(":memory:")
             else:
-                con = sqlite3.connect(temp_dir + '/blocks.db')
+                con = sqlite3.connect(temp_dir + "/blocks.db")
 
-            con.execute('pragma journal_mode=off')
+            con.execute("pragma journal_mode=off")
 
-            con.executescript('''CREATE TABLE blocking_map_a
+            con.executescript(
+                """CREATE TABLE blocking_map_a
                                  (block_key text, record_id {id_type_a});
 
                                  CREATE TABLE blocking_map_b
                                  (block_key text, record_id {id_type_b});
-                              '''.format(id_type_a=id_type_a,
-                                         id_type_b=id_type_b))
+                              """.format(
+                    id_type_a=id_type_a, id_type_b=id_type_b
+                )
+            )
 
-            con.executemany("INSERT INTO blocking_map_a values (?, ?)",
-                            self.fingerprinter(data_1.items()))
+            con.executemany(
+                "INSERT INTO blocking_map_a values (?, ?)",
+                self.fingerprinter(data_1.items()),
+            )
 
-            con.executemany("INSERT INTO blocking_map_b values (?, ?)",
-                            self.fingerprinter(data_2.items(), target=True))
+            con.executemany(
+                "INSERT INTO blocking_map_b values (?, ?)",
+                self.fingerprinter(data_2.items(), target=True),
+            )
 
             self.fingerprinter.reset_indices()
 
-            con.executescript('''CREATE INDEX block_key_a_idx
+            con.executescript(
+                """CREATE INDEX block_key_a_idx
                                  ON blocking_map_a (record_id, block_key);
 
                                  CREATE INDEX block_key_b_idx
-                                 ON blocking_map_b (block_key, record_id);''')
-            con.execute('''ANALYZE''')
+                                 ON blocking_map_b (block_key, record_id);"""
+            )
+            con.execute("""ANALYZE""")
 
-            pairs = con.execute('''SELECT DISTINCT a.record_id, b.record_id
+            pairs = con.execute(
+                """SELECT DISTINCT a.record_id, b.record_id
                                    FROM blocking_map_a a
                                    INNER JOIN blocking_map_b b
-                                   USING (block_key)''')
+                                   USING (block_key)"""
+            )
 
             for a_record_id, b_record_id in pairs:
-                yield ((a_record_id, data_1[a_record_id]),
-                       (b_record_id, data_2[b_record_id]))
+                yield (
+                    (a_record_id, data_1[a_record_id]),
+                    (b_record_id, data_2[b_record_id]),
+                )
 
             pairs.close()
             con.close()
 
-    def join(self,
-             data_1: Data,
-             data_2: Data,
-             threshold: float = 0.5,
-             constraint: JoinConstraint = "one-to-one") -> Links:
+    def join(
+        self,
+        data_1: Data,
+        data_2: Data,
+        threshold: float = 0.5,
+        constraint: JoinConstraint = "one-to-one",
+    ) -> Links:
         """
         Identifies pairs of records that refer to the same entity.
 
@@ -491,19 +523,20 @@ def join(self,
 
         """
 
-        assert constraint in {'one-to-one', 'many-to-one', 'many-to-many'}, (
-            '%s is an invalid constraint option. Valid options include '
-            'one-to-one, many-to-one, or many-to-many' % constraint)
+        assert constraint in {"one-to-one", "many-to-one", "many-to-many"}, (
+            "%s is an invalid constraint option. Valid options include "
+            "one-to-one, many-to-one, or many-to-many" % constraint
+        )
 
         pairs = self.pairs(data_1, data_2)
         pair_scores = self.score(pairs)
 
-        if constraint == 'one-to-one':
+        if constraint == "one-to-one":
             links = self.one_to_one(pair_scores, threshold)
-        elif constraint == 'many-to-one':
+        elif constraint == "many-to-one":
             links = self.many_to_one(pair_scores, threshold)
         else:
-            links = pair_scores[pair_scores['score'] > threshold]
+            links = pair_scores[pair_scores["score"] > threshold]
 
         links = list(links)
 
@@ -518,9 +551,7 @@ def join(self,
 
         return links
 
-    def one_to_one(self,
-                   scores: numpy.ndarray,
-                   threshold: float = 0.0) -> Links:
+    def one_to_one(self, scores: numpy.ndarray, threshold: float = 0.0) -> Links:
         """From the similarity scores of pairs of records, decide which
         pairs refer to the same entity.
 
@@ -569,15 +600,13 @@ def one_to_one(self,
 
         """
         if threshold:
-            scores = scores[scores['score'] > threshold]
+            scores = scores[scores["score"] > threshold]
 
         logger.debug("matching done, begin clustering")
 
         yield from clustering.greedyMatching(scores)
 
-    def many_to_one(self,
-                    scores: numpy.ndarray,
-                    threshold: float = 0.0) -> Links:
+    def many_to_one(self, scores: numpy.ndarray, threshold: float = 0.0) -> Links:
         """
         From the similarity scores of pairs of records, decide which
         pairs refer to the same entity.
@@ -631,19 +660,17 @@ def many_to_one(self,
 
 
 class GazetteerMatching(Matching):
-
-    def __init__(self,
-                 num_cores: Optional[int],
-                 in_memory: bool = False,
-                 **kwargs) -> None:
+    def __init__(
+        self, num_cores: Optional[int], in_memory: bool = False, **kwargs
+    ) -> None:
 
         super().__init__(num_cores, in_memory, **kwargs)
 
         if self.in_memory:
-            self.db = ':memory:'
+            self.db = ":memory:"
         else:
             self.temp_dir = tempfile.TemporaryDirectory()
-            self.db = self.temp_dir.name + '/blocks.db'
+            self.db = self.temp_dir.name + "/blocks.db"
 
         self.indexed_data: Dict[RecordID, RecordDict] = {}
 
@@ -674,22 +701,30 @@ def index(self, data: Data) -> None:  # pragma: no cover
         con = sqlite3.connect(self.db)
 
         # Set journal mode to WAL.
-        con.execute('pragma journal_mode=wal')
+        con.execute("pragma journal_mode=wal")
 
-        con.execute('''CREATE TABLE IF NOT EXISTS indexed_records
+        con.execute(
+            """CREATE TABLE IF NOT EXISTS indexed_records
                        (block_key text,
                         record_id {id_type},
                         UNIQUE(block_key, record_id))
-                    '''.format(id_type=id_type))
-
-        con.executemany("REPLACE INTO indexed_records VALUES (?, ?)",
-                        self.fingerprinter(data.items(), target=True))
-
-        con.execute('''CREATE INDEX IF NOT EXISTS
+                    """.format(
+                id_type=id_type
+            )
+        )
+
+        con.executemany(
+            "REPLACE INTO indexed_records VALUES (?, ?)",
+            self.fingerprinter(data.items(), target=True),
+        )
+
+        con.execute(
+            """CREATE INDEX IF NOT EXISTS
                        indexed_records_block_key_idx
                        ON indexed_records
-                       (block_key, record_id)''')
-        con.execute('''ANALYZE''')
+                       (block_key, record_id)"""
+        )
+        con.execute("""ANALYZE""")
 
         con.commit()
         con.close()
@@ -708,15 +743,16 @@ def unindex(self, data: Data) -> None:  # pragma: no cover
         """
 
         for field in self.fingerprinter.index_fields:
-            self.fingerprinter.unindex({record[field]
-                                        for record
-                                        in data.values()},
-                                       field)
+            self.fingerprinter.unindex(
+                {record[field] for record in data.values()}, field
+            )
 
         con = sqlite3.connect(self.db)
-        con.executemany('''DELETE FROM indexed_records
-                           WHERE record_id = ?''',
-                        ((k, ) for k in data.keys()))
+        con.executemany(
+            """DELETE FROM indexed_records
+                           WHERE record_id = ?""",
+            ((k,) for k in data.keys()),
+        )
 
         con.commit()
         con.close()
@@ -762,36 +798,44 @@ def blocks(self, data: Data) -> Blocks:
 
         con = sqlite3.connect(self.db, check_same_thread=False)
 
-        con.execute('BEGIN')
+        con.execute("BEGIN")
 
-        con.execute('''CREATE TEMPORARY TABLE blocking_map
+        con.execute(
+            """CREATE TEMPORARY TABLE blocking_map
                        (block_key text, record_id {id_type})
-                    '''.format(id_type=id_type))
-        con.executemany("INSERT INTO blocking_map VALUES (?, ?)",
-                        self.fingerprinter(data.items()))
-
-        pairs = con.execute('''SELECT DISTINCT a.record_id, b.record_id
+                    """.format(
+                id_type=id_type
+            )
+        )
+        con.executemany(
+            "INSERT INTO blocking_map VALUES (?, ?)", self.fingerprinter(data.items())
+        )
+
+        pairs = con.execute(
+            """SELECT DISTINCT a.record_id, b.record_id
                                FROM blocking_map a
                                INNER JOIN indexed_records b
                                USING (block_key)
-                               ORDER BY a.record_id''')
+                               ORDER BY a.record_id"""
+        )
 
-        pair_blocks = itertools.groupby(pairs,
-                                        lambda x: x[0])
+        pair_blocks = itertools.groupby(pairs, lambda x: x[0])
 
         for _, pair_block in pair_blocks:
 
-            yield [((a_record_id, data[a_record_id]),
-                    (b_record_id, self.indexed_data[b_record_id]))
-                   for a_record_id, b_record_id
-                   in pair_block]
+            yield [
+                (
+                    (a_record_id, data[a_record_id]),
+                    (b_record_id, self.indexed_data[b_record_id]),
+                )
+                for a_record_id, b_record_id in pair_block
+            ]
 
         pairs.close()
         con.execute("ROLLBACK")
         con.close()
 
-    def score(self,
-              blocks: Blocks) -> Generator[numpy.ndarray, None, None]:
+    def score(self, blocks: Blocks) -> Generator[numpy.ndarray, None, None]:
         """
         Scores groups of pairs of records. Yields structured numpy arrays
         representing pairs of records in the group and the associated
@@ -802,17 +846,18 @@ def score(self,
 
         """
 
-        matches = core.scoreGazette(blocks,
-                                    self.data_model,
-                                    self.classifier,
-                                    self.num_cores)
+        matches = core.scoreGazette(
+            blocks, self.data_model, self.classifier, self.num_cores
+        )
 
         return matches
 
-    def many_to_n(self,
-                  score_blocks: Iterable[numpy.ndarray],
-                  threshold: float = 0.0,
-                  n_matches: int = 1) -> Links:
+    def many_to_n(
+        self,
+        score_blocks: Iterable[numpy.ndarray],
+        threshold: float = 0.0,
+        n_matches: int = 1,
+    ) -> Links:
         """
         For each group of scored pairs, yield the highest scoring N pairs
 
@@ -838,15 +883,15 @@ def many_to_n(self,
 
         """
 
-        yield from clustering.gazetteMatching(score_blocks,
-                                              threshold,
-                                              n_matches)
+        yield from clustering.gazetteMatching(score_blocks, threshold, n_matches)
 
-    def search(self,
-               data: Data,
-               threshold: float = 0.0,
-               n_matches: int = 1,
-               generator: bool = False) -> LookupResults:  # pragma: no cover
+    def search(
+        self,
+        data: Data,
+        threshold: float = 0.0,
+        n_matches: int = 1,
+        generator: bool = False,
+    ) -> LookupResults:  # pragma: no cover
         """
         Identifies pairs of records that could refer to the same entity,
         returns tuples containing tuples of possible matches, with a
@@ -903,9 +948,7 @@ def search(self,
         else:
             return list(results)
 
-    def _format_search_results(self,
-                               search_d: Data,
-                               results: Links) -> LookupResults:
+    def _format_search_results(self, search_d: Data, results: Links) -> LookupResults:
 
         seen: Set[RecordID] = set()
 
@@ -922,7 +965,7 @@ def _format_search_results(self,
             yield a, tuple(prepared_result)
             seen.add(a)
 
-        for k in (search_d.keys() - seen):
+        for k in search_d.keys() - seen:
             yield k, ()
 
 
@@ -931,11 +974,13 @@ class StaticMatching(Matching):
     Class for initializing a dedupe object from a settings file.
     """
 
-    def __init__(self,
-                 settings_file: BinaryIO,
-                 num_cores: Optional[int] = None,
-                 in_memory: bool = False,
-                 **kwargs) -> None:  # pragma: no cover
+    def __init__(
+        self,
+        settings_file: BinaryIO,
+        num_cores: Optional[int] = None,
+        in_memory: bool = False,
+        **kwargs
+    ) -> None:  # pragma: no cover
         """
         Args:
             settings_file: A file object containing settings
@@ -970,13 +1015,15 @@ def __init__(self,
             raise SettingsFileLoadingException(
                 "This settings file is not compatible with "
                 "the current version of dedupe. This can happen "
-                "if you have recently upgraded dedupe.")
+                "if you have recently upgraded dedupe."
+            )
         except:  # noqa: E722
             raise SettingsFileLoadingException(
                 "Something has gone wrong with loading the settings file. "
-                "Try deleting the file")
+                "Try deleting the file"
+            )
 
-        logger.info('Predicate set:')
+        logger.info("Predicate set:")
         for predicate in self.predicates:
             logger.info(predicate)
 
@@ -987,13 +1034,16 @@ class ActiveMatching(Matching):
     """
     Class for training a matcher.
     """
+
     classifier = rlr.RegularizedLogisticRegression()
 
-    def __init__(self,
-                 variable_definition: Sequence[Mapping],
-                 num_cores: Optional[int] = None,
-                 in_memory: bool = False,
-                 **kwargs) -> None:
+    def __init__(
+        self,
+        variable_definition: Sequence[Mapping],
+        num_cores: Optional[int] = None,
+        in_memory: bool = False,
+        **kwargs
+    ) -> None:
         """
         Args:
             variable_definition: A list of dictionaries describing
@@ -1023,36 +1073,37 @@ def __init__(self,
         self.data_model = datamodel.DataModel(variable_definition)
 
         self.training_pairs: TrainingData
-        self.training_pairs = {'distinct': [],
-                               'match': []}
-        self.active_learner: Optional[Union[labeler.DedupeDisagreementLearner,
-                                            labeler.RecordLinkDisagreementLearner]]
+        self.training_pairs = {"distinct": [], "match": []}
+        self.active_learner: Optional[
+            Union[
+                labeler.DedupeDisagreementLearner, labeler.RecordLinkDisagreementLearner
+            ]
+        ]
         self.active_learner = None
 
     def cleanup_training(self) -> None:  # pragma: no cover
-        '''
+        """
         Clean up data we used for training. Free up memory.
-        '''
+        """
         del self.training_pairs
         del self.active_learner
 
     def _read_training(self, training_file: TextIO) -> None:
-        '''
+        """
         Read training from previously built training data file object
 
         Args:
             training_file: file object containing the training data
-        '''
+        """
 
-        logger.info('reading training from file')
-        training_pairs = json.load(training_file,
-                                   object_hook=serializer._from_json)
+        logger.info("reading training from file")
+        training_pairs = json.load(training_file, object_hook=serializer._from_json)
 
         self.mark_pairs(training_pairs)
 
-    def train(self,
-              recall: float = 1.00,
-              index_predicates: bool = True) -> None:  # pragma: no cover
+    def train(
+        self, recall: float = 1.00, index_predicates: bool = True
+    ) -> None:  # pragma: no cover
         """
         Learn final pairwise classifier and fingerprinting rules. Requires that
         adequate training data has been already been provided.
@@ -1075,14 +1126,14 @@ def train(self,
                               together.
 
         """
-        assert self.active_learner is not None, \
-               "Please initialize with the prepare_training method"
+        assert (
+            self.active_learner is not None
+        ), "Please initialize with the prepare_training method"
 
         examples, y = flatten_training(self.training_pairs)
         self.classifier.fit(self.data_model.distances(examples), y)
 
-        self.predicates = self.active_learner.learn_predicates(
-            recall, index_predicates)
+        self.predicates = self.active_learner.learn_predicates(recall, index_predicates)
         self._fingerprinter = blocking.Fingerprinter(self.predicates)
         self.fingerprinter.reset_indices()
 
@@ -1099,13 +1150,14 @@ def write_training(self, file_obj: TextIO) -> None:  # pragma: no cover
 
         """
 
-        json.dump(self.training_pairs,
-                  file_obj,
-                  cls=serializer.TupleEncoder,
-                  ensure_ascii=True)
+        json.dump(
+            self.training_pairs,
+            file_obj,
+            cls=serializer.TupleEncoder,
+            ensure_ascii=True,
+        )
 
-    def write_settings(self,
-                       file_obj: BinaryIO) -> None:  # pragma: no cover
+    def write_settings(self, file_obj: BinaryIO) -> None:  # pragma: no cover
         """
         Write a settings file containing the
         data model and predicates to a file object
@@ -1124,26 +1176,27 @@ def write_settings(self,
         pickle.dump(self.predicates, file_obj)
 
     def uncertain_pairs(self) -> List[TrainingExample]:
-        '''
-        Returns a list of pairs of records from the sample of record pairs
-        tuples that Dedupe is most curious to have labeled.
+        """
+         Returns a list of pairs of records from the sample of record pairs
+         tuples that Dedupe is most curious to have labeled.
 
-        This method is mainly useful for building a user interface for training
-        a matching model.
+         This method is mainly useful for building a user interface for training
+         a matching model.
 
-       .. code:: python
+        .. code:: python
 
-          > pair = matcher.uncertain_pairs()
-          > print(pair)
-          [({'name' : 'Georgie Porgie'}, {'name' : 'Georgette Porgette'})]
+           > pair = matcher.uncertain_pairs()
+           > print(pair)
+           [({'name' : 'Georgie Porgie'}, {'name' : 'Georgette Porgette'})]
 
-        '''
-        assert self.active_learner is not None, \
-               "Please initialize with the prepare_training method"
+        """
+        assert (
+            self.active_learner is not None
+        ), "Please initialize with the prepare_training method"
         return [self.active_learner.pop()]
 
     def mark_pairs(self, labeled_pairs: TrainingData) -> None:
-        '''
+        """
         Add users labeled pairs of records to training data and update the
         matching model
 
@@ -1180,11 +1233,11 @@ def mark_pairs(self, labeled_pairs: TrainingData) -> None:
            the data or training file supplied to the
            :func:`~prepare_training` method.
 
-        '''
+        """
         self._checkTrainingPairs(labeled_pairs)
 
-        self.training_pairs['match'].extend(labeled_pairs['match'])
-        self.training_pairs['distinct'].extend(labeled_pairs['distinct'])
+        self.training_pairs["match"].extend(labeled_pairs["match"])
+        self.training_pairs["distinct"].extend(labeled_pairs["distinct"])
 
         if self.active_learner:
             examples, y = flatten_training(labeled_pairs)
@@ -1193,45 +1246,50 @@ def mark_pairs(self, labeled_pairs: TrainingData) -> None:
                 self.active_learner.mark(examples, y)
             except dedupe.predicates.NoIndexError as e:
                 raise UserWarning(
-                    ('The record\n'
-                     '{unknown}\n'
-                     'is not known to to the active learner. '
-                     'Make sure all `labeled_pairs` '
-                     'are in the data or training file '
-                     'of the `prepare_training()` method').format(
-                         unknown=e.failing_record))
+                    (
+                        "The record\n"
+                        "{unknown}\n"
+                        "is not known to to the active learner. "
+                        "Make sure all `labeled_pairs` "
+                        "are in the data or training file "
+                        "of the `prepare_training()` method"
+                    ).format(unknown=e.failing_record)
+                )
 
     def _checkTrainingPairs(self, labeled_pairs: TrainingData) -> None:
         try:
             labeled_pairs.items()
-            labeled_pairs['match']
-            labeled_pairs['distinct']
+            labeled_pairs["match"]
+            labeled_pairs["distinct"]
         except (AttributeError, KeyError):
-            raise ValueError('labeled_pairs must be a dictionary with keys '
-                             '"distinct" and "match"')
+            raise ValueError(
+                "labeled_pairs must be a dictionary with keys " '"distinct" and "match"'
+            )
 
-        if labeled_pairs['match']:
-            pair = labeled_pairs['match'][0]
+        if labeled_pairs["match"]:
+            pair = labeled_pairs["match"][0]
             self._checkRecordPair(pair)
 
-        if labeled_pairs['distinct']:
-            pair = labeled_pairs['distinct'][0]
+        if labeled_pairs["distinct"]:
+            pair = labeled_pairs["distinct"][0]
             self._checkRecordPair(pair)
 
-        if not labeled_pairs['distinct'] and not labeled_pairs['match']:
+        if not labeled_pairs["distinct"] and not labeled_pairs["match"]:
             warnings.warn("Didn't return any labeled record pairs")
 
     def _checkRecordPair(self, record_pair: TrainingExample) -> None:
         try:
             a, b = record_pair
         except ValueError:
-            raise ValueError("The elements of data_sample must be pairs "
-                             "of record_pairs")
+            raise ValueError(
+                "The elements of data_sample must be pairs " "of record_pairs"
+            )
         try:
             record_pair[0].keys() and record_pair[1].keys()
         except AttributeError:
-            raise ValueError("A pair of record_pairs must be made up of two "
-                             "dictionaries ")
+            raise ValueError(
+                "A pair of record_pairs must be made up of two " "dictionaries "
+            )
 
         self.data_model.check(record_pair[0])
         self.data_model.check(record_pair[1])
@@ -1256,12 +1314,14 @@ class Dedupe(ActiveMatching, DedupeMatching):
     canopies = True
     ActiveLearner = labeler.DedupeDisagreementLearner
 
-    def prepare_training(self,
-                         data: Data,
-                         training_file: TextIO = None,
-                         sample_size: int = 1500,
-                         blocked_proportion: float = 0.9) -> None:
-        '''
+    def prepare_training(
+        self,
+        data: Data,
+        training_file: TextIO = None,
+        sample_size: int = 1500,
+        blocked_proportion: float = 0.9,
+    ) -> None:
+        """
         Initialize the active learner with your data and, optionally,
         existing training data.
 
@@ -1282,7 +1342,7 @@ def prepare_training(self,
            # or
            with open('training_file.json') as f:
                matcher.prepare_training(data_d, training_file=f)
-        '''
+        """
         self._checkData(data)
 
         # Reset active learner
@@ -1295,18 +1355,19 @@ def prepare_training(self,
         # existing training data, so add them to data dictionary
         examples, y = flatten_training(self.training_pairs)
 
-        self.active_learner = self.ActiveLearner(self.data_model,
-                                                 data,
-                                                 blocked_proportion,
-                                                 sample_size,
-                                                 index_include=examples)
+        self.active_learner = self.ActiveLearner(
+            self.data_model,
+            data,
+            blocked_proportion,
+            sample_size,
+            index_include=examples,
+        )
 
         self.active_learner.mark(examples, y)
 
     def _checkData(self, data: Data) -> None:
         if len(data) == 0:
-            raise ValueError(
-                'Dictionary of records is empty.')
+            raise ValueError("Dictionary of records is empty.")
 
         self.data_model.check(next(iter(data.values())))
 
@@ -1319,13 +1380,15 @@ class Link(ActiveMatching):
     canopies = False
     ActiveLearner = labeler.RecordLinkDisagreementLearner
 
-    def prepare_training(self,
-                         data_1: Data,
-                         data_2: Data,
-                         training_file: Optional[TextIO] = None,
-                         sample_size: int = 1500,
-                         blocked_proportion: float = 0.9) -> None:
-        '''
+    def prepare_training(
+        self,
+        data_1: Data,
+        data_2: Data,
+        training_file: Optional[TextIO] = None,
+        sample_size: int = 1500,
+        blocked_proportion: float = 0.9,
+    ) -> None:
+        """
         Initialize the active learner with your data and, optionally,
         existing training data.
 
@@ -1351,7 +1414,7 @@ def prepare_training(self,
            # or
            with open('training_file.json') as f:
                matcher.prepare_training(data_1, data_2, training_file=f)
-        '''
+        """
         self._checkData(data_1, data_2)
 
         # Reset active learner
@@ -1364,22 +1427,22 @@ def prepare_training(self,
         # existing training data, so add them to data dictionaries
         examples, y = flatten_training(self.training_pairs)
 
-        self.active_learner = self.ActiveLearner(self.data_model,
-                                                 data_1,
-                                                 data_2,
-                                                 blocked_proportion,
-                                                 sample_size,
-                                                 index_include=examples)
+        self.active_learner = self.ActiveLearner(
+            self.data_model,
+            data_1,
+            data_2,
+            blocked_proportion,
+            sample_size,
+            index_include=examples,
+        )
 
         self.active_learner.mark(examples, y)
 
     def _checkData(self, data_1: Data, data_2: Data) -> None:
         if len(data_1) == 0:
-            raise ValueError(
-                'Dictionary of records from first dataset is empty.')
+            raise ValueError("Dictionary of records from first dataset is empty.")
         elif len(data_2) == 0:
-            raise ValueError(
-                'Dictionary of records from second dataset is empty.')
+            raise ValueError("Dictionary of records from second dataset is empty.")
 
         self.data_model.check(next(iter(data_1.values())))
         self.data_model.check(next(iter(data_2.values())))
@@ -1429,16 +1492,18 @@ class SettingsFileLoadingException(Exception):
     pass
 
 
-def flatten_training(training_pairs: TrainingData) -> Tuple[List[TrainingExample], numpy.ndarray]:
+def flatten_training(
+    training_pairs: TrainingData,
+) -> Tuple[List[TrainingExample], numpy.ndarray]:
     examples: List[TrainingExample] = []
     y = []
 
-    for label in ('match', 'distinct'):
-        label = cast(Literal['match', 'distinct'], label)
+    for label in ("match", "distinct"):
+        label = cast(Literal["match", "distinct"], label)
 
         pairs = training_pairs[label]
         examples.extend(pairs)
-        encoded_y = 1 if label == 'match' else 0
+        encoded_y = 1 if label == "match" else 0
         y.extend([encoded_y] * len(pairs))
 
     return examples, numpy.array(y)
diff --git a/dedupe/backport.py b/dedupe/backport.py
index da9f024f5..442111055 100644
--- a/dedupe/backport.py
+++ b/dedupe/backport.py
@@ -1,9 +1,9 @@
-
 import platform
 
-if platform.system() == 'Darwin':
+if platform.system() == "Darwin":
     import multiprocessing
-    ctx = multiprocessing.get_context('spawn')
+
+    ctx = multiprocessing.get_context("spawn")
     Queue = ctx.Queue
     Process = ctx.Process  # type: ignore
     Pool = ctx.Pool
@@ -11,4 +11,4 @@
     Lock = ctx.Lock
     RLock = ctx.RLock
 else:
-    from multiprocessing import Process, Pool, Queue, SimpleQueue, Lock, RLock  # type: ignore # noqa 
+    from multiprocessing import Process, Pool, Queue, SimpleQueue, Lock, RLock  # type: ignore # noqa
diff --git a/dedupe/blocking.py b/dedupe/blocking.py
index 1597c1d89..a2f51a325 100644
--- a/dedupe/blocking.py
+++ b/dedupe/blocking.py
@@ -20,35 +20,32 @@ def index_list():
 
 
 class Fingerprinter(object):
-    '''Takes in a record and returns all blocks that record belongs to'''
+    """Takes in a record and returns all blocks that record belongs to"""
 
     def __init__(self, predicates: List[dedupe.predicates.Predicate]) -> None:
 
         self.predicates = predicates
 
-        self.index_fields: Dict[str,
-                                Dict[str,
-                                     List[dedupe.predicates.IndexPredicate]]]
+        self.index_fields: Dict[str, Dict[str, List[dedupe.predicates.IndexPredicate]]]
         self.index_fields = defaultdict(index_list)
-        '''
+        """
         A dictionary of all the fingerprinter methods that use an
         index of data field values. The keys are the field names,
         which can be useful to know for indexing the data.
-        '''
+        """
 
         self.index_predicates = []
 
         for full_predicate in predicates:
             for predicate in full_predicate:
-                if hasattr(predicate, 'index'):
-                    self.index_fields[predicate.field][predicate.type].append(
-                        predicate)
+                if hasattr(predicate, "index"):
+                    self.index_fields[predicate.field][predicate.type].append(predicate)
                     self.index_predicates.append(predicate)
 
-    def __call__(self,
-                 records: Iterable[Record],
-                 target: bool = False) -> Generator[Tuple[str, RecordID], None, None]:
-        '''
+    def __call__(
+        self, records: Iterable[Record], target: bool = False
+    ) -> Generator[Tuple[str, RecordID], None, None]:
+        """
         Generate the predicates for records. Yields tuples of (predicate,
         record_id).
 
@@ -83,12 +80,12 @@ def __call__(self,
            > print list(blocked_ids)
            [('foo:1', 1), ..., ('bar:1', 100)]
 
-        '''
+        """
 
         start_time = time.perf_counter()
-        predicates = [(':' + str(i), predicate)
-                      for i, predicate
-                      in enumerate(self.predicates)]
+        predicates = [
+            (":" + str(i), predicate) for i, predicate in enumerate(self.predicates)
+        ]
 
         for i, record in enumerate(records):
             record_id, instance = record
@@ -99,23 +96,22 @@ def __call__(self,
                     yield block_key + pred_id, record_id
 
             if i and i % 10000 == 0:
-                logger.info('%(iteration)d, %(elapsed)f2 seconds',
-                            {'iteration': i,
-                             'elapsed': time.perf_counter() - start_time})
+                logger.info(
+                    "%(iteration)d, %(elapsed)f2 seconds",
+                    {"iteration": i, "elapsed": time.perf_counter() - start_time},
+                )
 
     def reset_indices(self) -> None:
-        '''
+        """
         Fingeprinter indicdes can take up a lot of memory. If you are
         done with blocking, the method will reset the indices to free up.
         If you need to block again, the data will need to be re-indexed.
-        '''
+        """
         for predicate in self.index_predicates:
             predicate.reset()
 
-    def index(self,
-              docs: Docs,
-              field: str) -> None:
-        '''
+    def index(self, docs: Docs, field: str) -> None:
+        """
         Add docs to the indices used by fingerprinters.
 
         Some fingerprinter methods depend upon having an index of
@@ -131,7 +127,7 @@ def index(self,
             field: fieldname or key associated with the values you are
                    indexing
 
-        '''
+        """
         indices = extractIndices(self.index_fields[field])
 
         for doc in docs:
@@ -149,7 +145,7 @@ def index(self,
                 predicate.bust_cache()
 
     def unindex(self, docs: Docs, field: str) -> None:
-        '''Remove docs from indices used by fingerprinters
+        """Remove docs from indices used by fingerprinters
 
         Args:
             docs: an iterator of values from your data to remove. While
@@ -158,7 +154,7 @@ def unindex(self, docs: Docs, field: str) -> None:
                   operation.
             field: fieldname or key associated with the values you are
                    unindexing
-        '''
+        """
 
         indices = extractIndices(self.index_fields[field])
 
@@ -181,10 +177,7 @@ def unindex(self, docs: Docs, field: str) -> None:
 
     def index_all(self, data: Data):
         for field in self.index_fields:
-            unique_fields = {record[field]
-                             for record
-                             in data.values()
-                             if record[field]}
+            unique_fields = {record[field] for record in data.values() if record[field]}
             self.index(unique_fields, field)
 
 
diff --git a/dedupe/canonical.py b/dedupe/canonical.py
index e213bef3b..6d2d5a694 100644
--- a/dedupe/canonical.py
+++ b/dedupe/canonical.py
@@ -22,8 +22,7 @@ def getCentroid(attribute_variants, comparator):
     average_distance = distance_matrix.mean(0)
 
     # there can be ties for minimum, average distance string
-    min_dist_indices = numpy.where(
-        average_distance == average_distance.min())[0]
+    min_dist_indices = numpy.where(average_distance == average_distance.min())[0]
 
     if len(min_dist_indices) > 1:
         centroid = breakCentroidTie(attribute_variants, min_dist_indices)
@@ -66,6 +65,6 @@ def getCanonicalRep(record_cluster):
         if key_values:
             canonical_rep[key] = getCentroid(key_values, comparator)
         else:
-            canonical_rep[key] = ''
+            canonical_rep[key] = ""
 
     return canonical_rep
diff --git a/dedupe/canopy_index.py b/dedupe/canopy_index.py
index 1d095f4d8..982e3c0d9 100644
--- a/dedupe/canopy_index.py
+++ b/dedupe/canopy_index.py
@@ -27,7 +27,7 @@ def initSearch(self):
         for wid, docs in self.index._wordinfo.items():
             if len(docs) > threshold:
                 word = self.lexicon._words[wid]
-                logger.info('Removing stop word {}'.format(word))
+                logger.info("Removing stop word {}".format(word))
                 del self.index._wordinfo[wid]
                 continue
             if isinstance(docs, dict):
diff --git a/dedupe/clustering.py b/dedupe/clustering.py
index 212a01128..005527196 100644
--- a/dedupe/clustering.py
+++ b/dedupe/clustering.py
@@ -11,21 +11,15 @@
 import fastcluster
 import hcluster
 
-from typing import (Iterable,
-                    Dict,
-                    cast,
-                    List,
-                    Set,
-                    Generator,
-                    Sequence,
-                    Tuple)
+from typing import Iterable, Dict, cast, List, Set, Generator, Sequence, Tuple
 from dedupe._typing import Clusters, RecordID, Links
 
 logger = logging.getLogger(__name__)
 
 
-def connected_components(edgelist: numpy.ndarray,
-                         max_components: int) -> Generator[numpy.ndarray, None, None]:
+def connected_components(
+    edgelist: numpy.ndarray, max_components: int
+) -> Generator[numpy.ndarray, None, None]:
 
     if len(edgelist) == 0:
         raise StopIteration()
@@ -39,29 +33,27 @@ def connected_components(edgelist: numpy.ndarray,
     # have to create a new memmapped array with all the fields
     # we want and copy things over.
     with tempfile.TemporaryDirectory() as path:
-        filename = path + '/unlabeled_edgelist'
-        edgelist = numpy.memmap(filename,
-                                dtype=(unlabeled_edgelist.dtype.descr
-                                       + [('label', 'int32')]),
-                                mode='w+',
-                                shape=unlabeled_edgelist.shape)
-
-        if hasattr(unlabeled_edgelist, 'filename'):
-            copy_mmap_record_arrays(unlabeled_edgelist,
-                                    edgelist,
-                                    ['pairs', 'score'])
+        filename = path + "/unlabeled_edgelist"
+        edgelist = numpy.memmap(
+            filename,
+            dtype=(unlabeled_edgelist.dtype.descr + [("label", "int32")]),
+            mode="w+",
+            shape=unlabeled_edgelist.shape,
+        )
+
+        if hasattr(unlabeled_edgelist, "filename"):
+            copy_mmap_record_arrays(unlabeled_edgelist, edgelist, ["pairs", "score"])
         else:
-            copy_to_mmap_record_array(unlabeled_edgelist,
-                                      edgelist,
-                                      ['pairs', 'score'])
+            copy_to_mmap_record_array(unlabeled_edgelist, edgelist, ["pairs", "score"])
 
         yield from _connected_components(edgelist, max_components)
 
         edgelist._mmap.close()  # type: ignore
 
 
-def _connected_components(edgelist: numpy.ndarray,
-                          max_components: int) -> Generator[numpy.ndarray, None, None]:
+def _connected_components(
+    edgelist: numpy.ndarray, max_components: int
+) -> Generator[numpy.ndarray, None, None]:
     component_stops = union_find(edgelist)
 
     start = 0
@@ -70,32 +62,31 @@ def _connected_components(edgelist: numpy.ndarray,
         sub_graph = edgelist[start:stop]
         start = stop
 
-        n_components = len(numpy.unique(sub_graph['pairs']))
+        n_components = len(numpy.unique(sub_graph["pairs"]))
 
         if n_components > max_components:
-            min_score = numpy.min(sub_graph['score'])
+            min_score = numpy.min(sub_graph["score"])
             min_score_logit = numpy.log(min_score) - numpy.log(1 - min_score)
             threshold = 1 / (1 + numpy.exp(-min_score_logit - 1))
-            logger.warning('A component contained %s elements. '
-                           'Components larger than %s are '
-                           're-filtered. The threshold for this '
-                           'filtering is %s' % (n_components,
-                                                max_components,
-                                                threshold))
+            logger.warning(
+                "A component contained %s elements. "
+                "Components larger than %s are "
+                "re-filtered. The threshold for this "
+                "filtering is %s" % (n_components, max_components, threshold)
+            )
             # slices of memmaped arrays are also memmaped arrays,
             # which is what we want. The components should
             # already sorted by score so we can slice as oppose
             # to selecting like `sub_graph[sub_graph['score'] >
             # threshold]`, which would lead to an in memory copy being
             # made
-            cut_point = numpy.searchsorted(sub_graph['score'], threshold)
-            filtered_sub_graph = sub_graph[max(cut_point, 2):]
+            cut_point = numpy.searchsorted(sub_graph["score"], threshold)
+            filtered_sub_graph = sub_graph[max(cut_point, 2) :]
 
-            for sub_graph in _connected_components(filtered_sub_graph,
-                                                   max_components):
-                yield sub_graph[['pairs', 'score']]
+            for sub_graph in _connected_components(filtered_sub_graph, max_components):
+                yield sub_graph[["pairs", "score"]]
         else:
-            yield sub_graph[['pairs', 'score']]
+            yield sub_graph[["pairs", "score"]]
 
 
 def union_find(scored_pairs: numpy.ndarray) -> numpy.ndarray:
@@ -104,19 +95,21 @@ def union_find(scored_pairs: numpy.ndarray) -> numpy.ndarray:
 
     components = {}
 
-    edgelist = scored_pairs['pairs']
-    labels = scored_pairs['label']
+    edgelist = scored_pairs["pairs"]
+    labels = scored_pairs["label"]
 
-    it = numpy.nditer(edgelist, ['external_loop'])
+    it = numpy.nditer(edgelist, ["external_loop"])
 
     n_edges = len(scored_pairs)
 
-    array_type = 'H'
-    for array_type in ('H', 'I', 'L', 'Q'):
+    array_type = "H"
+    for array_type in ("H", "I", "L", "Q"):
         if (2 ** (8 * array.array(array_type).itemsize) - 1) > n_edges:
             break
     else:
-        raise ValueError("You are making more comparisons than your machine can handle, try raising the threshold")
+        raise ValueError(
+            "You are making more comparisons than your machine can handle, try raising the threshold"
+        )
 
     a: RecordID
     b: RecordID
@@ -159,15 +152,14 @@ def union_find(scored_pairs: numpy.ndarray) -> numpy.ndarray:
     # change. This will allow us to slice pieces of the
     # memmapped array. Those slices will also be memmaped
     # arrays.
-    scored_pairs.sort(order=('label', 'score'))
-    return numpy.cumsum(numpy.unique(scored_pairs['label'],
-                                     return_counts=True)[1])
+    scored_pairs.sort(order=("label", "score"))
+    return numpy.cumsum(numpy.unique(scored_pairs["label"], return_counts=True)[1])
 
 
-def condensedDistance(dupes: numpy.ndarray) -> Tuple[Dict[int, RecordID],
-                                                     numpy.ndarray,
-                                                     int]:
-    '''
+def condensedDistance(
+    dupes: numpy.ndarray,
+) -> Tuple[Dict[int, RecordID], numpy.ndarray, int]:
+    """
     Convert the pairwise list of distances in dupes to "condensed
     distance matrix" required by the hierarchical clustering
     algorithms. Also return a dictionary that maps the distance matrix
@@ -183,13 +175,13 @@ def condensedDistance(dupes: numpy.ndarray) -> Tuple[Dict[int, RecordID],
     where (row,col) is index of an uncondensed square N X N distance matrix.
 
     See http://docs.scipy.org/doc/scipy/reference/generated/scipy.spatial.distance.squareform.html
-    '''
+    """
 
-    candidate_set = numpy.unique(dupes['pairs'])
+    candidate_set = numpy.unique(dupes["pairs"])
 
     i_to_id = dict(enumerate(candidate_set))
 
-    ids = candidate_set.searchsorted(dupes['pairs'])
+    ids = candidate_set.searchsorted(dupes["pairs"])
     row = ids[:, 0]
     col = ids[:, 1]
 
@@ -198,16 +190,16 @@ def condensedDistance(dupes: numpy.ndarray) -> Tuple[Dict[int, RecordID],
     # alternate form thanks to wolfram alpa
     index = row * (2 * N - row - 3) // 2 + col - 1
 
-    condensed_distances = numpy.ones(N * (N-1) // 2, 'f4')
-    condensed_distances[index] = 1 - dupes['score']
+    condensed_distances = numpy.ones(N * (N - 1) // 2, "f4")
+    condensed_distances[index] = 1 - dupes["score"]
 
     return i_to_id, condensed_distances, N
 
 
-def cluster(dupes: numpy.ndarray,
-            threshold: float = .5,
-            max_components: int = 30000) -> Clusters:
-    '''
+def cluster(
+    dupes: numpy.ndarray, threshold: float = 0.5, max_components: int = 30000
+) -> Clusters:
+    """
     Takes in a list of duplicate pairs and clusters them in to a
     list records that all refer to the same entity based on a given
     threshold
@@ -216,7 +208,7 @@ def cluster(dupes: numpy.ndarray,
     threshold -- number between 0 and 1 (default is .5). lowering the
                  number will increase precision, raising it will increase
                  recall
-    '''
+    """
     distance_threshold = 1 - threshold
     dupe_sub_graphs = connected_components(dupes, max_components)
 
@@ -225,13 +217,13 @@ def cluster(dupes: numpy.ndarray,
 
             i_to_id, condensed_distances, N = condensedDistance(sub_graph)
 
-            linkage = fastcluster.linkage(condensed_distances,
-                                          method='centroid',
-                                          preserve_input=True)
+            linkage = fastcluster.linkage(
+                condensed_distances, method="centroid", preserve_input=True
+            )
 
-            partition = hcluster.fcluster(linkage,
-                                          distance_threshold,
-                                          criterion='distance')
+            partition = hcluster.fcluster(
+                linkage, distance_threshold, criterion="distance"
+            )
 
             clusters: Dict[int, List[int]] = defaultdict(list)
 
@@ -245,20 +237,20 @@ def cluster(dupes: numpy.ndarray,
                     yield tuple(i_to_id[i] for i in cluster), scores
 
         else:
-            (ids, score), = sub_graph
+            ((ids, score),) = sub_graph
             if score > threshold:
                 yield tuple(ids), (score,) * 2
 
 
-def confidences(cluster: Sequence[int],
-                squared_distances: numpy.ndarray,
-                d: int) -> numpy.ndarray:
-    '''
+def confidences(
+    cluster: Sequence[int], squared_distances: numpy.ndarray, d: int
+) -> numpy.ndarray:
+    """
     We calculate a per record score that is similar to a standard
     deviation.  The main reason is that these record scores can be
     used to calculate the standard deviation of an entire cluster,
     which is a reasonable metric for clusters.
-    '''
+    """
     scores_d = dict.fromkeys(cluster, 0.0)
     C = 2 * d - 3
     for i, j in itertools.combinations(cluster, 2):
@@ -277,7 +269,7 @@ def greedyMatching(dupes: numpy.ndarray) -> Links:
     A: Set[RecordID] = set()
     B: Set[RecordID] = set()
 
-    dupes.sort(order='score')
+    dupes.sort(order="score")
     dupes = dupes[::-1]
 
     for (a, b), score in dupes:
@@ -288,13 +280,13 @@ def greedyMatching(dupes: numpy.ndarray) -> Links:
             yield (a, b), score
 
 
-def gazetteMatching(scored_blocks: Iterable[numpy.ndarray],
-                    threshold: float = 0,
-                    n_matches: int = 1) -> Links:
+def gazetteMatching(
+    scored_blocks: Iterable[numpy.ndarray], threshold: float = 0, n_matches: int = 1
+) -> Links:
 
     for block in scored_blocks:
-        block = block[block['score'] > threshold]
-        block.sort(order='score')
+        block = block[block["score"] > threshold]
+        block.sort(order="score")
         block = block[::-1]
 
         if len(block):
@@ -304,13 +296,13 @@ def gazetteMatching(scored_blocks: Iterable[numpy.ndarray],
                 yield block.copy()
 
 
-def pair_gazette_matching(scored_pairs: numpy.ndarray,
-                          threshold: float = 0.0,
-                          n_matches: int = 1) -> Links:
+def pair_gazette_matching(
+    scored_pairs: numpy.ndarray, threshold: float = 0.0, n_matches: int = 1
+) -> Links:
 
-    scored_pairs.sort(order='pairs')
+    scored_pairs.sort(order="pairs")
 
-    group_key = scored_pairs['pairs'][:, 0]
+    group_key = scored_pairs["pairs"][:, 0]
     change_points = numpy.where(numpy.roll(group_key, 1) != group_key)[0]
     scored_blocks = numpy.split(scored_pairs, change_points)
 
@@ -320,46 +312,50 @@ def pair_gazette_matching(scored_pairs: numpy.ndarray,
 
 
 def copy_to_mmap_record_array(source, target, fields, chunksize=100000):
-    '''
+    """
     Writing into a memmapped array allocates memory equivalent to the
     amount that you are writing. With big arrays this is undesirable
     so we write in chunks
-    '''
+    """
 
     start = 0
-    stops = itertools.chain(range(chunksize, source.size, chunksize),
-                            [source.size])
+    stops = itertools.chain(range(chunksize, source.size, chunksize), [source.size])
     for stop in stops:
         shape = (stop - start,)
         source_slice = source[start:stop]
-        target_slice = numpy.memmap(target.filename,
-                                    dtype=target.dtype,
-                                    offset=(start * target.dtype.itemsize),
-                                    shape=shape)
+        target_slice = numpy.memmap(
+            target.filename,
+            dtype=target.dtype,
+            offset=(start * target.dtype.itemsize),
+            shape=shape,
+        )
         target_slice[fields] = source_slice[fields]
         start = stop
 
 
 def copy_mmap_record_arrays(source, target, fields, chunksize=100000):
-    '''
+    """
     Writing into a memmapped array allocates memory equivalent to the
     amount that you are writing. With big arrays this is undesirable
     so we write in chunks
-    '''
+    """
 
     start = 0
-    stops = itertools.chain(range(chunksize, source.size, chunksize),
-                            [source.size])
+    stops = itertools.chain(range(chunksize, source.size, chunksize), [source.size])
     for stop in stops:
         shape = (stop - start,)
-        source_slice = numpy.memmap(source.filename,
-                                    dtype=source.dtype,
-                                    offset=(start * source.dtype.itemsize),
-                                    shape=shape)
-        target_slice = numpy.memmap(target.filename,
-                                    dtype=target.dtype,
-                                    offset=(start * target.dtype.itemsize),
-                                    shape=shape)
+        source_slice = numpy.memmap(
+            source.filename,
+            dtype=source.dtype,
+            offset=(start * source.dtype.itemsize),
+            shape=shape,
+        )
+        target_slice = numpy.memmap(
+            target.filename,
+            dtype=target.dtype,
+            offset=(start * target.dtype.itemsize),
+            shape=shape,
+        )
         target_slice[fields] = source_slice[fields]
 
         start = stop
diff --git a/dedupe/convenience.py b/dedupe/convenience.py
index 11b86f200..2509a38e1 100644
--- a/dedupe/convenience.py
+++ b/dedupe/convenience.py
@@ -13,7 +13,14 @@
 import dedupe
 from dedupe.core import unique
 from dedupe.canonical import getCanonicalRep
-from dedupe._typing import Data, TrainingData, RecordDict, TrainingExample, Literal, RecordID
+from dedupe._typing import (
+    Data,
+    TrainingData,
+    RecordDict,
+    TrainingExample,
+    Literal,
+    RecordID,
+)
 
 IndicesIterator = Iterator[Tuple[int, int]]
 
@@ -33,8 +40,9 @@ def randomPairs(n_records: int, sample_size: int) -> IndicesIterator:
         random_pairs = numpy.arange(n)
     else:
         try:
-            random_pairs = numpy.array(random.sample(range(n), sample_size),
-                                       dtype=numpy.uint)
+            random_pairs = numpy.array(
+                random.sample(range(n), sample_size), dtype=numpy.uint
+            )
         except OverflowError:
             return randomPairsWithReplacement(n_records, sample_size)
 
@@ -49,7 +57,9 @@ def randomPairs(n_records: int, sample_size: int) -> IndicesIterator:
     return zip(i, j)
 
 
-def randomPairsMatch(n_records_A: int, n_records_B: int, sample_size: int) -> IndicesIterator:
+def randomPairsMatch(
+    n_records_A: int, n_records_B: int, sample_size: int
+) -> IndicesIterator:
     """
     Return random combinations of indices for record list A and B
     """
@@ -73,14 +83,15 @@ def randomPairsWithReplacement(n_records: int, sample_size: int) -> IndicesItera
     warnings.warn("The same record pair may appear more than once in the sample")
 
     try:
-        random_indices = numpy.random.randint(n_records,
-                                              size=sample_size * 2)
+        random_indices = numpy.random.randint(n_records, size=sample_size * 2)
     except (OverflowError, ValueError):
-        max_int: int = numpy.iinfo('int').max
-        warnings.warn("Asked to sample pairs from %d records, will only sample pairs from first %d records" % (n_records, max_int))
+        max_int: int = numpy.iinfo("int").max
+        warnings.warn(
+            "Asked to sample pairs from %d records, will only sample pairs from first %d records"
+            % (n_records, max_int)
+        )
 
-        random_indices = numpy.random.randint(max_int,
-                                              size=sample_size * 2)
+        random_indices = numpy.random.randint(max_int, size=sample_size * 2)
 
     random_indices = random_indices.reshape((-1, 2))
     random_indices.sort(axis=1)
@@ -109,7 +120,7 @@ def _mark_pair(deduper: dedupe.api.ActiveMatching, labeled_pair: LabeledPair) ->
 
 
 def console_label(deduper: dedupe.api.ActiveMatching) -> None:  # pragma: no cover
-    '''
+    """
     Train a matcher instance (Dedupe, RecordLink, or Gazetteer) from the command line.
     Example
 
@@ -118,7 +129,7 @@ def console_label(deduper: dedupe.api.ActiveMatching) -> None:  # pragma: no cov
        > deduper = dedupe.Dedupe(variables)
        > deduper.prepare_training(data)
        > dedupe.console_label(deduper)
-    '''
+    """
 
     finished = False
     use_previous = False
@@ -141,44 +152,44 @@ def console_label(deduper: dedupe.api.ActiveMatching) -> None:  # pragma: no cov
             except IndexError:
                 break
 
-        n_match = len(deduper.training_pairs['match']) + sum(
-            label == 'match' for _, label in labeled
+        n_match = len(deduper.training_pairs["match"]) + sum(
+            label == "match" for _, label in labeled
         )
-        n_distinct = len(deduper.training_pairs['distinct']) + sum(
-            label == 'distinct' for _, label in labeled
+        n_distinct = len(deduper.training_pairs["distinct"]) + sum(
+            label == "distinct" for _, label in labeled
         )
 
         for record in record_pair:
             for field in fields:
-                line = '%s : %s' % (field, record[field])
+                line = "%s : %s" % (field, record[field])
                 _print(line)
             _print()
-        _print('{0}/10 positive, {1}/10 negative'.format(n_match, n_distinct))
-        _print('Do these records refer to the same thing?')
+        _print("{0}/10 positive, {1}/10 negative".format(n_match, n_distinct))
+        _print("Do these records refer to the same thing?")
 
         valid_response = False
-        user_input = ''
+        user_input = ""
         while not valid_response:
             if labeled:
-                _print('(y)es / (n)o / (u)nsure / (f)inished / (p)revious')
-                valid_responses = {'y', 'n', 'u', 'f', 'p'}
+                _print("(y)es / (n)o / (u)nsure / (f)inished / (p)revious")
+                valid_responses = {"y", "n", "u", "f", "p"}
             else:
-                _print('(y)es / (n)o / (u)nsure / (f)inished')
-                valid_responses = {'y', 'n', 'u', 'f'}
+                _print("(y)es / (n)o / (u)nsure / (f)inished")
+                valid_responses = {"y", "n", "u", "f"}
             user_input = input()
             if user_input in valid_responses:
                 valid_response = True
 
-        if user_input == 'y':
-            labeled.insert(0, (record_pair, 'match'))
-        elif user_input == 'n':
-            labeled.insert(0, (record_pair, 'distinct'))
-        elif user_input == 'u':
-            labeled.insert(0, (record_pair, 'unsure'))
-        elif user_input == 'f':
-            _print('Finished labeling')
+        if user_input == "y":
+            labeled.insert(0, (record_pair, "match"))
+        elif user_input == "n":
+            labeled.insert(0, (record_pair, "distinct"))
+        elif user_input == "u":
+            labeled.insert(0, (record_pair, "unsure"))
+        elif user_input == "f":
+            _print("Finished labeling")
             finished = True
-        elif user_input == 'p':
+        elif user_input == "p":
             use_previous = True
             unlabeled.append(record_pair)
 
@@ -189,11 +200,10 @@ def console_label(deduper: dedupe.api.ActiveMatching) -> None:  # pragma: no cov
         _mark_pair(deduper, labeled_pair)
 
 
-def training_data_link(data_1: Data,
-                       data_2: Data,
-                       common_key: str,
-                       training_size: int = 50000) -> TrainingData:  # pragma: nocover
-    '''
+def training_data_link(
+    data_1: Data, data_2: Data, common_key: str, training_size: int = 50000
+) -> TrainingData:  # pragma: nocover
+    """
     Construct training data for consumption by the func:`mark_pairs`
     method from already linked datasets.
 
@@ -214,7 +224,7 @@ def training_data_link(data_1: Data,
          Every match must be identified by the sharing of a common key.
          This function assumes that if two records do not share a common key
          then they are distinct records.
-    '''
+    """
 
     identified_records: Dict[str, Tuple[List[RecordID], List[RecordID]]]
     identified_records = collections.defaultdict(lambda: ([], []))
@@ -234,30 +244,28 @@ def training_data_link(data_1: Data,
     keys_1 = list(data_1.keys())
     keys_2 = list(data_2.keys())
 
-    random_pairs = [(keys_1[i], keys_2[j])
-                    for i, j
-                    in randomPairsMatch(len(data_1), len(data_2),
-                                        training_size)]
+    random_pairs = [
+        (keys_1[i], keys_2[j])
+        for i, j in randomPairsMatch(len(data_1), len(data_2), training_size)
+    ]
 
-    distinct_pairs = {
-        pair for pair in random_pairs if pair not in matched_pairs}
+    distinct_pairs = {pair for pair in random_pairs if pair not in matched_pairs}
 
-    matched_records = [(data_1[key_1], data_2[key_2])
-                       for key_1, key_2 in matched_pairs]
-    distinct_records = [(data_1[key_1], data_2[key_2])
-                        for key_1, key_2 in distinct_pairs]
+    matched_records = [(data_1[key_1], data_2[key_2]) for key_1, key_2 in matched_pairs]
+    distinct_records = [
+        (data_1[key_1], data_2[key_2]) for key_1, key_2 in distinct_pairs
+    ]
 
     training_pairs: TrainingData
-    training_pairs = {'match': matched_records,
-                      'distinct': distinct_records}
+    training_pairs = {"match": matched_records, "distinct": distinct_records}
 
     return training_pairs
 
 
-def training_data_dedupe(data: Data,
-                         common_key: str,
-                         training_size: int = 50000) -> TrainingData:  # pragma: nocover
-    '''
+def training_data_dedupe(
+    data: Data, common_key: str, training_size: int = 50000
+) -> TrainingData:  # pragma: nocover
+    """
     Construct training data for consumption by the func:`mark_pairs`
     method from an already deduplicated dataset.
 
@@ -275,7 +283,7 @@ def training_data_dedupe(data: Data,
          Every match must be identified by the sharing of a common key.
          This function assumes that if two records do not share a common key
          then they are distinct records.
-    '''
+    """
 
     identified_records: Dict[str, List[RecordID]]
     identified_records = collections.defaultdict(list)
@@ -299,20 +307,16 @@ def training_data_dedupe(data: Data,
     pair_indices = randomPairs(len(unique_record_ids), training_size)
     distinct_pairs = set()
     for i, j in pair_indices:
-        distinct_pairs.add((unique_record_ids_l[i],
-                            unique_record_ids_l[j]))
+        distinct_pairs.add((unique_record_ids_l[i], unique_record_ids_l[j]))
 
     distinct_pairs -= matched_pairs
 
-    matched_records = [(data[key_1], data[key_2])
-                       for key_1, key_2 in matched_pairs]
+    matched_records = [(data[key_1], data[key_2]) for key_1, key_2 in matched_pairs]
 
-    distinct_records = [(data[key_1], data[key_2])
-                        for key_1, key_2 in distinct_pairs]
+    distinct_records = [(data[key_1], data[key_2]) for key_1, key_2 in distinct_pairs]
 
     training_pairs: TrainingData
-    training_pairs = {'match': matched_records,
-                      'distinct': distinct_records}
+    training_pairs = {"match": matched_records, "distinct": distinct_records}
 
     return training_pairs
 
diff --git a/dedupe/core.py b/dedupe/core.py
index 7d7237639..ef6434341 100644
--- a/dedupe/core.py
+++ b/dedupe/core.py
@@ -9,25 +9,23 @@
 import multiprocessing
 import multiprocessing.dummy
 import queue
-from typing import (Iterator,
-                    Tuple,
-                    Mapping,
-                    Sequence,
-                    Union,
-                    Generator,
-                    Optional,
-                    Any,
-                    Type,
-                    Iterable,
-                    overload)
+from typing import (
+    Iterator,
+    Tuple,
+    Mapping,
+    Sequence,
+    Union,
+    Generator,
+    Optional,
+    Any,
+    Type,
+    Iterable,
+    overload,
+)
 
 import numpy
 
-from dedupe._typing import (RecordPairs,
-                            RecordID,
-                            Blocks,
-                            Data,
-                            Literal)
+from dedupe._typing import RecordPairs, RecordID, Blocks, Data, Literal
 from dedupe.backport import RLock
 
 
@@ -39,14 +37,16 @@ class BlockingError(Exception):
 
 
 class ScoreDupes(object):
-    def __init__(self,
-                 data_model,
-                 classifier,
-                 records_queue: _Queue,
-                 exception_queue: _Queue,
-                 score_file_path: str,
-                 dtype: numpy.dtype,
-                 offset):
+    def __init__(
+        self,
+        data_model,
+        classifier,
+        records_queue: _Queue,
+        exception_queue: _Queue,
+        score_file_path: str,
+        dtype: numpy.dtype,
+        offset,
+    ):
         self.data_model = data_model
         self.classifier = classifier
         self.records_queue = records_queue
@@ -82,22 +82,23 @@ def fieldDistance(self, record_pairs: RecordPairs) -> None:
                 with self.offset.get_lock():
 
                     fp: numpy.memmap
-                    fp = numpy.memmap(self.score_file_path,
-                                      dtype=self.dtype,
-                                      offset=self.offset.value,
-                                      shape=(len(record_ids), ))
-                    fp['pairs'] = record_ids
-                    fp['score'] = scores
+                    fp = numpy.memmap(
+                        self.score_file_path,
+                        dtype=self.dtype,
+                        offset=self.offset.value,
+                        shape=(len(record_ids),),
+                    )
+                    fp["pairs"] = record_ids
+                    fp["score"] = scores
 
                     fp.flush()
 
                     self.offset.value += len(record_ids) * self.dtype.itemsize
 
 
-def scoreDuplicates(record_pairs: RecordPairs,
-                    data_model,
-                    classifier,
-                    num_cores: int = 1) -> Union[numpy.memmap, numpy.ndarray]:
+def scoreDuplicates(
+    record_pairs: RecordPairs, data_model, classifier, num_cores: int = 1
+) -> Union[numpy.memmap, numpy.ndarray]:
     if num_cores < 2:
         from multiprocessing.dummy import Process, Queue
     else:
@@ -105,10 +106,12 @@ def scoreDuplicates(record_pairs: RecordPairs,
 
     first, record_pairs = peek(record_pairs)
     if first is None:
-        raise BlockingError("No records have been blocked together. "
-                            "Is the data you are trying to match like "
-                            "the data you trained on? If so, try adding "
-                            "more training data.")
+        raise BlockingError(
+            "No records have been blocked together. "
+            "Is the data you are trying to match like "
+            "the data you trained on? If so, try adding "
+            "more training data."
+        )
 
     record_pairs_queue: _Queue = Queue(2)
     exception_queue: _Queue = Queue()
@@ -117,22 +120,22 @@ def scoreDuplicates(record_pairs: RecordPairs,
 
     # explicitly defining the lock from the "spawn context" seems to
     # be necessary for python 3.7 on mac os.
-    offset = multiprocessing.Value('Q', 0, lock=RLock())
+    offset = multiprocessing.Value("Q", 0, lock=RLock())
 
     id_type = sniff_id_type(first)
-    dtype = numpy.dtype([('pairs', id_type, 2),
-                         ('score', 'f4')])
+    dtype = numpy.dtype([("pairs", id_type, 2), ("score", "f4")])
 
     n_map_processes = max(num_cores, 1)
-    score_records = ScoreDupes(data_model,
-                               classifier,
-                               record_pairs_queue,
-                               exception_queue,
-                               score_file_path,
-                               dtype,
-                               offset)
-    map_processes = [Process(target=score_records)
-                     for _ in range(n_map_processes)]
+    score_records = ScoreDupes(
+        data_model,
+        classifier,
+        record_pairs_queue,
+        exception_queue,
+        score_file_path,
+        dtype,
+        offset,
+    )
+    map_processes = [Process(target=score_records) for _ in range(n_map_processes)]
 
     for process in map_processes:
         process.start()
@@ -152,18 +155,16 @@ def scoreDuplicates(record_pairs: RecordPairs,
     scored_pairs: Union[numpy.memmap, numpy.ndarray]
 
     if offset.value:  # type: ignore
-        scored_pairs = numpy.memmap(score_file_path,
-                                    dtype=dtype)
+        scored_pairs = numpy.memmap(score_file_path, dtype=dtype)
     else:
         scored_pairs = numpy.array([], dtype=dtype)
 
     return scored_pairs
 
 
-def fillQueue(queue: _Queue,
-              iterable: RecordPairs,
-              stop_signals: int,
-              chunk_size: int = 20000) -> None:
+def fillQueue(
+    queue: _Queue, iterable: RecordPairs, stop_signals: int, chunk_size: int = 20000
+) -> None:
     iterable = iter(iterable)
 
     while True:
@@ -195,22 +196,19 @@ def __call__(self, block: RecordPairs) -> numpy.ndarray:
         id_type = sniff_id_type(record_ids)
         ids = numpy.array(record_ids, dtype=id_type)
 
-        dtype = numpy.dtype([('pairs', id_type, 2),
-                             ('score', 'f4')])
+        dtype = numpy.dtype([("pairs", id_type, 2), ("score", "f4")])
 
-        scored_pairs = numpy.empty(shape=len(scores),
-                                   dtype=dtype)
+        scored_pairs = numpy.empty(shape=len(scores), dtype=dtype)
 
-        scored_pairs['pairs'] = ids
-        scored_pairs['score'] = scores
+        scored_pairs["pairs"] = ids
+        scored_pairs["score"] = scores
 
         return scored_pairs
 
 
-def scoreGazette(record_pairs: Blocks,
-                 data_model,
-                 classifier,
-                 num_cores: int = 1) -> Generator[numpy.ndarray, None, None]:
+def scoreGazette(
+    record_pairs: Blocks, data_model, classifier, num_cores: int = 1
+) -> Generator[numpy.ndarray, None, None]:
 
     first, record_pairs = peek(record_pairs)
     if first is None:
@@ -242,9 +240,11 @@ def close(self):
 
             def join(self):
                 pass
+
         pool = MockPool()
     else:
         from .backport import Pool
+
         pool = Pool(processes=num_cores)
         imap = functools.partial(pool.imap_unordered, chunksize=20000)
 
@@ -276,8 +276,7 @@ def index(data: Mapping[Any, Any], offset: int = 0) -> Mapping[int, Any]:
     if isIndexed(data, offset):
         return data
     else:
-        data = dict(zip(itertools.count(offset),
-                        data.values()))
+        data = dict(zip(itertools.count(offset), data.values()))
         return data
 
 
@@ -295,7 +294,9 @@ def sniff_id_type(ids: Sequence[Tuple[str, str]]) -> Tuple[Type[str], int]:
     ...
 
 
-def sniff_id_type(ids: Sequence[Tuple[RecordID, RecordID]]) -> Union[Type[int], Tuple[Type[str], int]]:
+def sniff_id_type(
+    ids: Sequence[Tuple[RecordID, RecordID]]
+) -> Union[Type[int], Tuple[Type[str], int]]:
     example = ids[0][0]
     python_type = type(example)
     dtype: Union[Type[int], Tuple[Type[str], int]]
@@ -305,27 +306,27 @@ def sniff_id_type(ids: Sequence[Tuple[RecordID, RecordID]]) -> Union[Type[int],
         int(example)  # make sure we can cast to int
         dtype = int
     else:
-        raise ValueError('Invalid type for record id')
+        raise ValueError("Invalid type for record id")
 
     return dtype
 
 
-def sqlite_id_type(data: Data) -> Literal['text', 'integer']:
+def sqlite_id_type(data: Data) -> Literal["text", "integer"]:
 
     example = next(iter(data.keys()))
     python_type = type(example)
 
     if python_type is bytes or python_type is str:
-        return 'text'
+        return "text"
     elif python_type is int:
-        return 'integer'
+        return "integer"
     else:
-        raise ValueError('Invalid type for record id')
+        raise ValueError("Invalid type for record id")
 
 
 def unique(seq: Iterable) -> list:
     """Return the unique elements of a collection even if those elements are
-       unhashable and unsortable, like dicts and sets"""
+    unhashable and unsortable, like dicts and sets"""
     cleaned: list = []
     for each in seq:
         if each not in cleaned:
diff --git a/dedupe/datamodel.py b/dedupe/datamodel.py
index a452c00b9..b4f0a04fe 100644
--- a/dedupe/datamodel.py
+++ b/dedupe/datamodel.py
@@ -9,8 +9,9 @@
 from dedupe.variables.base import MissingDataType
 from dedupe.variables.interaction import InteractionType
 
-for _, module, _ in pkgutil.iter_modules(dedupe.variables.__path__,  # type: ignore
-                                         'dedupe.variables.'):
+for _, module, _ in pkgutil.iter_modules(  # type: ignore
+    dedupe.variables.__path__, "dedupe.variables."
+):
     __import__(module)
 
 
@@ -18,11 +19,10 @@
 
 
 class DataModel(object):
-
     def __init__(self, fields):
 
         if not fields:
-            raise ValueError('The field definitions cannot be empty')
+            raise ValueError("The field definitions cannot be empty")
         primary_fields, variables = typifyFields(fields)
         self.primary_fields = primary_fields
         self._derived_start = len(variables)
@@ -58,9 +58,9 @@ def predicates(self, index_predicates=True, canopies=True):
         predicates = set()
         for definition in self.primary_fields:
             for predicate in definition.predicates:
-                if hasattr(predicate, 'index'):
+                if hasattr(predicate, "index"):
                     if index_predicates:
-                        if hasattr(predicate, 'canopy'):
+                        if hasattr(predicate, "canopy"):
                             if canopies:
                                 predicates.add(predicate)
                         else:
@@ -74,18 +74,16 @@ def predicates(self, index_predicates=True, canopies=True):
     def distances(self, record_pairs):
         num_records = len(record_pairs)
 
-        distances = numpy.empty((num_records, len(self)), 'f4')
+        distances = numpy.empty((num_records, len(self)), "f4")
         field_comparators = self._field_comparators
 
         for i, (record_1, record_2) in enumerate(record_pairs):
 
             for field, compare, start, stop in field_comparators:
                 if record_1[field] is not None and record_2[field] is not None:
-                    distances[i, start:stop] = compare(record_1[field],
-                                                       record_2[field])
-                elif hasattr(compare, 'missing'):
-                    distances[i, start:stop] = compare(record_1[field],
-                                                       record_2[field])
+                    distances[i, start:stop] = compare(record_1[field], record_2[field])
+                elif hasattr(compare, "missing"):
+                    distances[i, start:stop] = compare(record_1[field], record_2[field])
                 else:
                     distances[i, start:stop] = numpy.nan
 
@@ -99,8 +97,7 @@ def _derivedDistances(self, primary_distances):
         current_column = self._derived_start
 
         for interaction in self._interaction_indices:
-            distances[:, current_column] =\
-                numpy.prod(distances[:, interaction], axis=1)
+            distances[:, current_column] = numpy.prod(distances[:, interaction], axis=1)
 
             current_column += 1
 
@@ -109,8 +106,9 @@ def _derivedDistances(self, primary_distances):
         distances[:, :current_column][missing_data] = 0
 
         if self._missing_field_indices:
-            distances[:, current_column:] =\
+            distances[:, current_column:] = (
                 1 - missing_data[:, self._missing_field_indices]
+            )
 
         return distances
 
@@ -118,9 +116,11 @@ def check(self, record):
         for field_comparator in self._field_comparators:
             field = field_comparator[0]
             if field not in record:
-                raise ValueError("Records do not line up with data model. "
-                                 "The field '%s' is in data_model but not "
-                                 "in a record" % field)
+                raise ValueError(
+                    "Records do not line up with data model. "
+                    "The field '%s' is in data_model but not "
+                    "in a record" % field
+                )
 
 
 def typifyFields(fields):
@@ -130,47 +130,57 @@ def typifyFields(fields):
 
     for definition in fields:
         try:
-            field_type = definition['type']
+            field_type = definition["type"]
         except TypeError:
-            raise TypeError("Incorrect field specification: field "
-                            "specifications are dictionaries that must "
-                            "include a type definition, ex. "
-                            "{'field' : 'Phone', type: 'String'}")
+            raise TypeError(
+                "Incorrect field specification: field "
+                "specifications are dictionaries that must "
+                "include a type definition, ex. "
+                "{'field' : 'Phone', type: 'String'}"
+            )
         except KeyError:
-            raise KeyError("Missing field type: fields "
-                           "specifications are dictionaries that must "
-                           "include a type definition, ex. "
-                           "{'field' : 'Phone', type: 'String'}")
-
-        if field_type != 'Custom':
+            raise KeyError(
+                "Missing field type: fields "
+                "specifications are dictionaries that must "
+                "include a type definition, ex. "
+                "{'field' : 'Phone', type: 'String'}"
+            )
+
+        if field_type != "Custom":
             only_custom = False
 
-        if field_type == 'Interaction':
+        if field_type == "Interaction":
             continue
 
-        if field_type == 'FuzzyCategorical' and 'other fields' not in definition:
-            definition['other fields'] = [d['field'] for d in fields
-                                          if ('field' in d and
-                                              d['field'] != definition['field'])]
+        if field_type == "FuzzyCategorical" and "other fields" not in definition:
+            definition["other fields"] = [
+                d["field"]
+                for d in fields
+                if ("field" in d and d["field"] != definition["field"])
+            ]
 
         try:
             field_class = FIELD_CLASSES[field_type]
         except KeyError:
-            raise KeyError("Field type %s not valid. Valid types include %s"
-                           % (definition['type'], ', '.join(FIELD_CLASSES)))
+            raise KeyError(
+                "Field type %s not valid. Valid types include %s"
+                % (definition["type"], ", ".join(FIELD_CLASSES))
+            )
 
         field_object = field_class(definition)
         primary_fields.append(field_object)
 
-        if hasattr(field_object, 'higher_vars'):
+        if hasattr(field_object, "higher_vars"):
             data_model.extend(field_object.higher_vars)
         else:
             data_model.append(field_object)
 
     if only_custom:
-        raise ValueError("At least one of the field types needs to be a type"
-                         "other than 'Custom'. 'Custom' types have no associated"
-                         "blocking rules")
+        raise ValueError(
+            "At least one of the field types needs to be a type"
+            "other than 'Custom'. 'Custom' types have no associated"
+            "blocking rules"
+        )
 
     return primary_fields, data_model
 
@@ -191,7 +201,7 @@ def interactions(definitions, primary_fields):
     interactions = []
 
     for definition in definitions:
-        if definition['type'] == 'Interaction':
+        if definition["type"] == "Interaction":
             field = interaction_class(definition)
             field.expandInteractions(field_d)
             interactions.extend(field.higher_vars)
@@ -200,9 +210,7 @@ def interactions(definitions, primary_fields):
 
 
 def missing_field_indices(variables):
-    return [i for i, definition
-            in enumerate(variables)
-            if definition.has_missing]
+    return [i for i, definition in enumerate(variables) if definition.has_missing]
 
 
 def interaction_indices(variables):
@@ -211,11 +219,10 @@ def interaction_indices(variables):
     field_names = [field.name for field in variables]
 
     for definition in variables:
-        if hasattr(definition, 'interaction_fields'):
+        if hasattr(definition, "interaction_fields"):
             interaction_indices = []
             for interaction_field in definition.interaction_fields:
-                interaction_indices.append(
-                    field_names.index(interaction_field))
+                interaction_indices.append(field_names.index(interaction_field))
             indices.append(interaction_indices)
 
     return indices
diff --git a/dedupe/labeler.py b/dedupe/labeler.py
index f9577dc69..ac1084a95 100644
--- a/dedupe/labeler.py
+++ b/dedupe/labeler.py
@@ -16,7 +16,6 @@
 
 
 class ActiveLearner(ABC):
-
     @abstractmethod
     def transform(self) -> None:
         pass
@@ -57,8 +56,7 @@ def candidates(self, new_candidates):
 
         random_pair = random.choice(self._candidates)
         exact_match = (random_pair[0], random_pair[0])
-        self.fit_transform([exact_match, random_pair],
-                           [1, 0])
+        self.fit_transform([exact_match, random_pair], [1, 0])
 
     def transform(self, pairs):
         return self.data_model.distances(pairs)
@@ -127,7 +125,6 @@ def __len__(self):
 
 
 class BlockLearner(object):
-
     def __init__(self, data_model, *args):
         self.data_model = data_model
 
@@ -142,11 +139,10 @@ def fit_transform(self, pairs, y):
         dupes = [pair for label, pair in zip(y, pairs) if label]
 
         new_dupes = [pair for pair in dupes if pair not in self._old_dupes]
-        new_uncovered = (not all(self.predict(new_dupes)))
+        new_uncovered = not all(self.predict(new_dupes))
 
         if new_uncovered:
-            self.current_predicates = self.block_learner.learn(dupes,
-                                                               recall=1.0)
+            self.current_predicates = self.block_learner.learn(dupes, recall=1.0)
             self._cached_labels = None
             self._old_dupes = dupes
 
@@ -174,16 +170,11 @@ def predict(self, candidates):
 
     def _remove(self, index):
         if self._cached_labels is not None:
-            self._cached_labels = numpy.delete(self._cached_labels,
-                                               index,
-                                               axis=0)
+            self._cached_labels = numpy.delete(self._cached_labels, index, axis=0)
 
 
 class DedupeBlockLearner(BlockLearner):
-
-    def __init__(self, data_model,
-                 data,
-                 index_include):
+    def __init__(self, data_model, data, index_include):
         super().__init__(data_model)
 
         N_SAMPLED_RECORDS = 5000
@@ -193,12 +184,11 @@ def __init__(self, data_model,
         sampled_records = Sample(index_data, N_SAMPLED_RECORDS)
         preds = self.data_model.predicates()
 
-        self.block_learner = training.DedupeBlockLearner(preds,
-                                                         sampled_records,
-                                                         index_data)
+        self.block_learner = training.DedupeBlockLearner(
+            preds, sampled_records, index_data
+        )
 
-        self.candidates = self._sample(sampled_records,
-                                       N_SAMPLED_RECORD_PAIRS)
+        self.candidates = self._sample(sampled_records, N_SAMPLED_RECORD_PAIRS)
         examples_to_index = self.candidates.copy()
 
         if index_include:
@@ -236,26 +226,19 @@ def _sample(self, data, sample_size):
 
         # consider using a reservoir sampling strategy, which would
         # be more memory efficient and probably about as fast
-        normalized_weights = (numpy.fromiter(weights.values(),
-                                             dtype=float)
-                              / sum(weights.values()))
+        normalized_weights = numpy.fromiter(weights.values(), dtype=float) / sum(
+            weights.values()
+        )
         rng = numpy.random.default_rng()
-        sample_indices = rng.choice(len(weights),
-                                    size=sample_size,
-                                    replace=False,
-                                    p=normalized_weights)
+        sample_indices = rng.choice(
+            len(weights), size=sample_size, replace=False, p=normalized_weights
+        )
         keys = list(weights.keys())
-        return [(data[keys[i][0]], data[keys[i][1]])
-                for i in sample_indices]
+        return [(data[keys[i][0]], data[keys[i][1]]) for i in sample_indices]
 
 
 class RecordLinkBlockLearner(BlockLearner):
-
-    def __init__(self,
-                 data_model,
-                 data_1,
-                 data_2,
-                 index_include):
+    def __init__(self, data_model, data_1, data_2, index_include):
 
         super().__init__(data_model)
 
@@ -268,14 +251,13 @@ def __init__(self,
 
         preds = self.data_model.predicates(canopies=False)
 
-        self.block_learner = training.RecordLinkBlockLearner(preds,
-                                                             sampled_records_1,
-                                                             sampled_records_2,
-                                                             index_data)
+        self.block_learner = training.RecordLinkBlockLearner(
+            preds, sampled_records_1, sampled_records_2, index_data
+        )
 
-        self.candidates = self._sample(sampled_records_1,
-                                       sampled_records_2,
-                                       N_SAMPLED_RECORD_PAIRS)
+        self.candidates = self._sample(
+            sampled_records_1, sampled_records_2, N_SAMPLED_RECORD_PAIRS
+        )
         examples_to_index = self.candidates.copy()
 
         if index_include:
@@ -317,17 +299,15 @@ def _sample(self, data_1, data_2, sample_size):
 
         # consider using a reservoir sampling strategy, which would
         # be more memory efficient and probably about as fast
-        normalized_weights = (numpy.fromiter(weights.values(),
-                                             dtype=float)
-                              / sum(weights.values()))
+        normalized_weights = numpy.fromiter(weights.values(), dtype=float) / sum(
+            weights.values()
+        )
         rng = numpy.random.default_rng()
-        sample_indices = rng.choice(len(weights),
-                                    size=sample_size,
-                                    replace=False,
-                                    p=normalized_weights)
+        sample_indices = rng.choice(
+            len(weights), size=sample_size, replace=False, p=normalized_weights
+        )
         keys = list(weights.keys())
-        return [(data_1[keys[i][0]], data_2[keys[i][1]])
-                for i in sample_indices]
+        return [(data_1[keys[i][0]], data_2[keys[i][1]]) for i in sample_indices]
 
 
 class DisagreementLearner(ActiveLearner):
@@ -363,9 +343,11 @@ def pop(self) -> TrainingExample:
         else:
             uncertain_index = numpy.std(probs, axis=1).argmax()
 
-        logger.debug("Classifier: %.2f, Covered: %s",
-                     probs[uncertain_index][0],
-                     bool(probs[uncertain_index][1]))
+        logger.debug(
+            "Classifier: %.2f, Covered: %s",
+            probs[uncertain_index][0],
+            bool(probs[uncertain_index][1]),
+        )
 
         uncertain_pair = self.candidates.pop(uncertain_index)
 
@@ -394,47 +376,44 @@ def learn_predicates(self, recall, index_predicates):
         if not index_predicates:
             old_preds = self.blocker.block_learner.blocker.predicates.copy()
 
-            no_index_predicates = [pred for pred in old_preds
-                                   if not hasattr(pred, 'index')]
+            no_index_predicates = [
+                pred for pred in old_preds if not hasattr(pred, "index")
+            ]
             self.blocker.block_learner.blocker.predicates = no_index_predicates
 
-            learned_preds = self.blocker.block_learner.learn(dupes,
-                                                             recall=recall,
-                                                             candidate_types='random forest')
+            learned_preds = self.blocker.block_learner.learn(
+                dupes, recall=recall, candidate_types="random forest"
+            )
 
             self.blocker.block_learner.blocker.predicates = old_preds
 
         else:
-            learned_preds = self.blocker.block_learner.learn(dupes,
-                                                             recall=recall,
-                                                             candidate_types='random forest')
+            learned_preds = self.blocker.block_learner.learn(
+                dupes, recall=recall, candidate_types="random forest"
+            )
 
         return learned_preds
 
 
 class DedupeDisagreementLearner(DisagreementLearner):
-
-    def __init__(self,
-                 data_model,
-                 data,
-                 blocked_proportion,
-                 sample_size,
-                 index_include):
+    def __init__(
+        self, data_model, data, blocked_proportion, sample_size, index_include
+    ):
 
         self.data_model = data_model
 
         data = core.index(data)
 
-        random_pair = (random.choice(list(data.values())),
-                       random.choice(list(data.values())))
+        random_pair = (
+            random.choice(list(data.values())),
+            random.choice(list(data.values())),
+        )
         exact_match = (random_pair[0], random_pair[0])
 
         index_include = index_include.copy()
         index_include.append(exact_match)
 
-        self.blocker = DedupeBlockLearner(data_model,
-                                          data,
-                                          index_include)
+        self.blocker = DedupeBlockLearner(data_model, data, index_include)
 
         self.candidates = self.blocker.candidates
 
@@ -443,19 +422,13 @@ def __init__(self,
 
         self._common_init()
 
-        self.mark([exact_match] * 4 + [random_pair],
-                  [1] * 4 + [0])
+        self.mark([exact_match] * 4 + [random_pair], [1] * 4 + [0])
 
 
 class RecordLinkDisagreementLearner(DisagreementLearner):
-
-    def __init__(self,
-                 data_model,
-                 data_1,
-                 data_2,
-                 blocked_proportion,
-                 sample_size,
-                 index_include):
+    def __init__(
+        self, data_model, data_1, data_2, blocked_proportion, sample_size, index_include
+    ):
 
         self.data_model = data_model
 
@@ -464,17 +437,16 @@ def __init__(self,
         offset = len(data_1)
         data_2 = core.index(data_2, offset)
 
-        random_pair = (random.choice(list(data_1.values())),
-                       random.choice(list(data_2.values())))
+        random_pair = (
+            random.choice(list(data_1.values())),
+            random.choice(list(data_2.values())),
+        )
         exact_match = (random_pair[0], random_pair[0])
 
         index_include = index_include.copy()
         index_include.append(exact_match)
 
-        self.blocker = RecordLinkBlockLearner(data_model,
-                                              data_1,
-                                              data_2,
-                                              index_include)
+        self.blocker = RecordLinkBlockLearner(data_model, data_1, data_2, index_include)
         self.candidates = self.blocker.candidates
 
         self.classifier = RLRLearner(self.data_model)
@@ -482,12 +454,10 @@ def __init__(self,
 
         self._common_init()
 
-        self.mark([exact_match] * 4 + [random_pair],
-                  [1] * 4 + [0])
+        self.mark([exact_match] * 4 + [random_pair], [1] * 4 + [0])
 
 
 class Sample(dict):
-
     def __init__(self, d, sample_size):
         if len(d) <= sample_size:
             super().__init__(d)
diff --git a/dedupe/predicates.py b/dedupe/predicates.py
index d66fbbb3a..c2780fb6a 100644
--- a/dedupe/predicates.py
+++ b/dedupe/predicates.py
@@ -62,14 +62,14 @@ def __len__(self):
     def __call__(self, record, **kwargs) -> tuple:
         pass
 
-    def __add__(self, other: 'Predicate') -> 'CompoundPredicate':
+    def __add__(self, other: "Predicate") -> "CompoundPredicate":
 
         if isinstance(other, CompoundPredicate):
             return CompoundPredicate((self,) + tuple(other))
         elif isinstance(other, Predicate):
             return CompoundPredicate((self, other))
         else:
-            raise ValueError('Can only combine predicates')
+            raise ValueError("Can only combine predicates")
 
 
 class SimplePredicate(Predicate):
@@ -107,9 +107,9 @@ def __init__(self, field):
     @staticmethod
     def func(column):
         if column:
-            return ('1',)
+            return ("1",)
         else:
-            return ('0',)
+            return ("0",)
 
     def __call__(self, record, **kwargs):
         column = record[self.field]
@@ -118,24 +118,24 @@ def __call__(self, record, **kwargs):
 
 class IndexPredicate(Predicate):
     def __init__(self, threshold, field):
-        self.__name__ = '(%s, %s)' % (threshold, field)
+        self.__name__ = "(%s, %s)" % (threshold, field)
         self.field = field
         self.threshold = threshold
         self.index = None
 
     def __getstate__(self):
         odict = self.__dict__.copy()
-        odict['index'] = None
-        odict['_cache'] = {}
-        if 'canopy' in odict:
-            odict['canopy'] = {}
+        odict["index"] = None
+        odict["_cache"] = {}
+        if "canopy" in odict:
+            odict["canopy"] = {}
         return odict
 
     def __setstate__(self, d):
         self.__dict__.update(d)
 
         # backwards compatibility
-        if not hasattr(self, 'index'):
+        if not hasattr(self, "index"):
             self.index = None
 
     def reset(self):
@@ -174,15 +174,16 @@ def __call__(self, record, **kwargs):
             try:
                 doc_id = self.index._doc_to_id[doc]
             except AttributeError:
-                raise NoIndexError("Attempting to block with an index "
-                                   "predicate without indexing records",
-                                   record)
+                raise NoIndexError(
+                    "Attempting to block with an index "
+                    "predicate without indexing records",
+                    record,
+                )
 
             if doc_id in self.canopy:
                 block_key = self.canopy[doc_id]
             else:
-                canopy_members = self.index.search(doc,
-                                                   self.threshold)
+                canopy_members = self.index.search(doc, self.threshold)
                 for member in canopy_members:
                     if member not in self.canopy:
                         self.canopy[member] = doc_id
@@ -205,10 +206,12 @@ def __init__(self, *args, **kwargs):
         self._cache = {}
 
     def freeze(self, records_1, records_2):
-        self._cache = {(record[self.field], False): self(record, False)
-                       for record in records_1}
-        self._cache.update({(record[self.field], True): self(record, True)
-                            for record in records_2})
+        self._cache = {
+            (record[self.field], False): self(record, False) for record in records_1
+        }
+        self._cache.update(
+            {(record[self.field], True): self(record, True) for record in records_2}
+        )
         self.index = None
 
     def reset(self):
@@ -229,9 +232,11 @@ def __call__(self, record, target=False, **kwargs):
                     else:
                         centers = self.index.search(doc, self.threshold)
                 except AttributeError:
-                    raise NoIndexError("Attempting to block with an index "
-                                       "predicate without indexing records",
-                                       record)
+                    raise NoIndexError(
+                        "Attempting to block with an index "
+                        "predicate without indexing records",
+                        record,
+                    )
                 result = [str(center) for center in centers]
                 self._cache[(column, target)] = result
                 return result
@@ -254,7 +259,6 @@ class TfidfSearchPredicate(SearchPredicate, TfidfPredicate):
 
 
 class TfidfTextPredicate(object):
-
     def preprocess(self, doc):
         return tuple(words(doc))
 
@@ -269,33 +273,27 @@ def preprocess(self, doc):
         return tuple(sorted(ngrams(" ".join(strip_punc(doc).split()), 2)))
 
 
-class TfidfTextSearchPredicate(TfidfTextPredicate,
-                               TfidfSearchPredicate):
+class TfidfTextSearchPredicate(TfidfTextPredicate, TfidfSearchPredicate):
     type = "TfidfTextSearchPredicate"
 
 
-class TfidfSetSearchPredicate(TfidfSetPredicate,
-                              TfidfSearchPredicate):
+class TfidfSetSearchPredicate(TfidfSetPredicate, TfidfSearchPredicate):
     type = "TfidfSetSearchPredicate"
 
 
-class TfidfNGramSearchPredicate(TfidfNGramPredicate,
-                                TfidfSearchPredicate):
+class TfidfNGramSearchPredicate(TfidfNGramPredicate, TfidfSearchPredicate):
     type = "TfidfNGramSearchPredicate"
 
 
-class TfidfTextCanopyPredicate(TfidfTextPredicate,
-                               TfidfCanopyPredicate):
+class TfidfTextCanopyPredicate(TfidfTextPredicate, TfidfCanopyPredicate):
     type = "TfidfTextCanopyPredicate"
 
 
-class TfidfSetCanopyPredicate(TfidfSetPredicate,
-                              TfidfCanopyPredicate):
+class TfidfSetCanopyPredicate(TfidfSetPredicate, TfidfCanopyPredicate):
     type = "TfidfSetCanopyPredicate"
 
 
-class TfidfNGramCanopyPredicate(TfidfNGramPredicate,
-                                TfidfCanopyPredicate):
+class TfidfNGramCanopyPredicate(TfidfNGramPredicate, TfidfCanopyPredicate):
     type = "TfidfNGramCanopyPredicate"
 
 
@@ -330,30 +328,29 @@ def __eq__(self, other):
         return frozenset(self) == frozenset(other)
 
     def __call__(self, record, **kwargs):
-        predicate_keys = [predicate(record, **kwargs)
-                          for predicate in self]
+        predicate_keys = [predicate(record, **kwargs) for predicate in self]
         return [
-            u':'.join(
+            ":".join(
                 # must escape : to avoid confusion with : join separator
-                b.replace(u':', u'\\:') for b in block_key
+                b.replace(":", "\\:")
+                for b in block_key
             )
-            for block_key
-            in itertools.product(*predicate_keys)
+            for block_key in itertools.product(*predicate_keys)
         ]
 
-    def __add__(self, other: Predicate) -> 'CompoundPredicate':  # type: ignore
+    def __add__(self, other: Predicate) -> "CompoundPredicate":  # type: ignore
 
         if isinstance(other, CompoundPredicate):
             return CompoundPredicate(tuple(self) + tuple(other))
         elif isinstance(other, Predicate):
             return CompoundPredicate(tuple(self) + (other,))
         else:
-            raise ValueError('Can only combine predicates')
+            raise ValueError("Can only combine predicates")
 
 
 def wholeFieldPredicate(field: Any) -> Tuple[str]:
     """return the whole field"""
-    return (str(field), )
+    return (str(field),)
 
 
 def tokenFieldPredicate(field):
@@ -400,11 +397,11 @@ def nearIntegersPredicate(field: str) -> Set[str]:
 
 
 def hundredIntegerPredicate(field: str) -> Set[str]:
-    return {str(int(i))[:-2] + '00' for i in integers(field)}
+    return {str(int(i))[:-2] + "00" for i in integers(field)}
 
 
 def hundredIntegersOddPredicate(field: str) -> Set[str]:
-    return {str(int(i))[:-2] + '0' + str(int(i) % 2) for i in integers(field)}
+    return {str(int(i))[:-2] + "0" + str(int(i) % 2) for i in integers(field)}
 
 
 def firstIntegerPredicate(field: str) -> Sequence[str]:
@@ -420,7 +417,7 @@ def ngramsTokens(field: Sequence[Any], n: int) -> Set[str]:
     n_tokens = len(field)
     for i in range(n_tokens):
         for j in range(i + n, min(n_tokens, i + n) + 1):
-            grams.add(' '.join(str(tok) for tok in field[i:j]))
+            grams.add(" ".join(str(tok) for tok in field[i:j]))
     return grams
 
 
@@ -433,44 +430,47 @@ def commonThreeTokens(field: str) -> Set[str]:
 
 
 def fingerprint(field: str) -> Tuple[str]:
-    return (u''.join(sorted(field.split())).strip(),)
+    return ("".join(sorted(field.split())).strip(),)
 
 
 def oneGramFingerprint(field: str) -> Tuple[str]:
-    return (u''.join(sorted(set(ngrams(field.replace(' ', ''), 1)))).strip(),)
+    return ("".join(sorted(set(ngrams(field.replace(" ", ""), 1)))).strip(),)
 
 
 def twoGramFingerprint(field: str) -> Tuple[str, ...]:
     if len(field) > 1:
-        return (u''.join(sorted(gram.strip() for gram
-                                in set(ngrams(field.replace(' ', ''), 2)))),)
+        return (
+            "".join(
+                sorted(gram.strip() for gram in set(ngrams(field.replace(" ", ""), 2)))
+            ),
+        )
     else:
         return ()
 
 
 def commonFourGram(field: str) -> Set[str]:
     """return 4-grams"""
-    return set(ngrams(field.replace(' ', ''), 4))
+    return set(ngrams(field.replace(" ", ""), 4))
 
 
 def commonSixGram(field: str) -> Set[str]:
     """return 6-grams"""
-    return set(ngrams(field.replace(' ', ''), 6))
+    return set(ngrams(field.replace(" ", ""), 6))
 
 
 def sameThreeCharStartPredicate(field: str) -> Tuple[str]:
     """return first three characters"""
-    return initials(field.replace(' ', ''), 3)
+    return initials(field.replace(" ", ""), 3)
 
 
 def sameFiveCharStartPredicate(field: str) -> Tuple[str]:
     """return first five characters"""
-    return initials(field.replace(' ', ''), 5)
+    return initials(field.replace(" ", ""), 5)
 
 
 def sameSevenCharStartPredicate(field: str) -> Tuple[str]:
     """return first seven characters"""
-    return initials(field.replace(' ', ''), 7)
+    return initials(field.replace(" ", ""), 7)
 
 
 def suffixArray(field):
@@ -481,7 +481,7 @@ def suffixArray(field):
 
 
 def sortedAcronym(field: str) -> Tuple[str]:
-    return (''.join(sorted(each[0] for each in field.split())),)
+    return ("".join(sorted(each[0] for each in field.split())),)
 
 
 def doubleMetaphone(field):
@@ -489,10 +489,13 @@ def doubleMetaphone(field):
 
 
 def metaphoneToken(field):
-    return {metaphone_token for metaphone_token
-            in itertools.chain(*(doublemetaphone(token)
-                                 for token in set(field.split())))
-            if metaphone_token}
+    return {
+        metaphone_token
+        for metaphone_token in itertools.chain(
+            *(doublemetaphone(token) for token in set(field.split()))
+        )
+        if metaphone_token
+    }
 
 
 def wholeSetPredicate(field_set):
@@ -515,11 +518,11 @@ def commonThreeElementsPredicate(field):
 
 
 def lastSetElementPredicate(field_set):
-    return (str(max(field_set)), )
+    return (str(max(field_set)),)
 
 
 def firstSetElementPredicate(field_set):
-    return (str(min(field_set)), )
+    return (str(min(field_set)),)
 
 
 def magnitudeOfCardinality(field_set):
@@ -544,12 +547,14 @@ def latLongGridPredicate(field, digits=1):
 
 def orderOfMagnitude(field):
     if field > 0:
-        return (str(int(round(math.log10(field)))), )
+        return (str(int(round(math.log10(field)))),)
     else:
         return ()
 
 
-def roundTo1(field):  # thanks http://stackoverflow.com/questions/3410976/how-to-round-a-number-to-significant-figures-in-python
+def roundTo1(
+    field,
+):  # thanks http://stackoverflow.com/questions/3410976/how-to-round-a-number-to-significant-figures-in-python
     abs_num = abs(field)
     order = int(math.floor(math.log10(abs_num)))
     rounded = round(abs_num, -order)
diff --git a/dedupe/serializer.py b/dedupe/serializer.py
index 6ace1a1ec..e4dd52cda 100644
--- a/dedupe/serializer.py
+++ b/dedupe/serializer.py
@@ -2,18 +2,17 @@
 
 
 def _from_json(json_object):
-    if '__class__' in json_object:
-        if json_object['__class__'] == 'frozenset':
-            return frozenset(json_object['__value__'])
-        if json_object['__class__'] == 'tuple':
-            return tuple(json_object['__value__'])
+    if "__class__" in json_object:
+        if json_object["__class__"] == "frozenset":
+            return frozenset(json_object["__value__"])
+        if json_object["__class__"] == "tuple":
+            return tuple(json_object["__value__"])
     return json_object
 
 
 def hint_tuples(item):
     if isinstance(item, tuple):
-        return {'__class__': 'tuple',
-                '__value__': [hint_tuples(e) for e in item]}
+        return {"__class__": "tuple", "__value__": [hint_tuples(e) for e in item]}
     if isinstance(item, list):
         return [hint_tuples(e) for e in item]
     if isinstance(item, dict):
@@ -31,6 +30,5 @@ def iterencode(self, obj):
 
     def default(self, python_object):
         if isinstance(python_object, frozenset):
-            return {'__class__': 'frozenset',
-                    '__value__': list(python_object)}
+            return {"__class__": "frozenset", "__value__": list(python_object)}
         return super().default(python_object)
diff --git a/dedupe/tfidf.py b/dedupe/tfidf.py
index e52590ac5..f1d53d6dc 100644
--- a/dedupe/tfidf.py
+++ b/dedupe/tfidf.py
@@ -32,8 +32,9 @@ def search(self, doc, threshold=0):
         query_list = self._parseTerms(doc)
 
         if query_list:
-            results = [center for score, center
-                       in self._index.apply(query_list, threshold)]
+            results = [
+                center for score, center in self._index.apply(query_list, threshold)
+            ]
         else:
             results = []
 
diff --git a/dedupe/training.py b/dedupe/training.py
index 5483f44d6..4578d34b3 100644
--- a/dedupe/training.py
+++ b/dedupe/training.py
@@ -11,8 +11,7 @@
 from abc import ABC
 import math
 
-from typing import (Dict, Sequence, Iterable, Tuple, List,
-                    Union, FrozenSet, Optional)
+from typing import Dict, Sequence, Iterable, Tuple, List, Union, FrozenSet, Optional
 
 from . import blocking
 from .predicates import Predicate
@@ -23,12 +22,14 @@
 
 
 class BlockLearner(ABC):
-    def learn(self, matches, recall, candidate_types='simple'):
-        '''
+    def learn(self, matches, recall, candidate_types="simple"):
+        """
         Takes in a set of training pairs and predicates and tries to find
         a good set of blocking rules.
-        '''
-        assert matches, "You must supply at least one pair of matching records to learn blocking rules."
+        """
+        assert (
+            matches
+        ), "You must supply at least one pair of matching records to learn blocking rules."
 
         comparison_cover = self.comparison_cover
         match_cover = self.cover(matches)
@@ -37,8 +38,9 @@ def learn(self, matches, recall, candidate_types='simple'):
             del match_cover[key]
 
         coverable_dupes = frozenset.union(*match_cover.values())
-        uncoverable_dupes = [pair for i, pair in enumerate(matches)
-                             if i not in coverable_dupes]
+        uncoverable_dupes = [
+            pair for i, pair in enumerate(matches) if i not in coverable_dupes
+        ]
 
         target_cover = int(recall * len(matches))
 
@@ -46,27 +48,25 @@ def learn(self, matches, recall, candidate_types='simple'):
             logger.debug(uncoverable_dupes)
             target_cover = len(coverable_dupes)
 
-        if candidate_types == 'simple':
-            candidate_cover = self.simple_candidates(match_cover,
-                                                     comparison_cover)
-        elif candidate_types == 'random forest':
-            candidate_cover = self.random_forest_candidates(match_cover,
-                                                            comparison_cover)
+        if candidate_types == "simple":
+            candidate_cover = self.simple_candidates(match_cover, comparison_cover)
+        elif candidate_types == "random forest":
+            candidate_cover = self.random_forest_candidates(
+                match_cover, comparison_cover
+            )
         else:
-            raise ValueError('candidate_type is not valid')
+            raise ValueError("candidate_type is not valid")
 
         searcher = BranchBound(target_cover, 2500)
         final_predicates = searcher.search(candidate_cover)
 
-        logger.info('Final predicate set:')
+        logger.info("Final predicate set:")
         for predicate in final_predicates:
             logger.info(predicate)
 
         return final_predicates
 
-    def simple_candidates(self,
-                          match_cover: Cover,
-                          comparison_cover: Cover) -> Cover:
+    def simple_candidates(self, match_cover: Cover, comparison_cover: Cover) -> Cover:
         candidates = {}
         for predicate, coverage in match_cover.items():
             predicate.count = len(comparison_cover[predicate])  # type: ignore
@@ -74,10 +74,9 @@ def simple_candidates(self,
 
         return candidates
 
-    def random_forest_candidates(self,
-                                 match_cover: Cover,
-                                 comparison_cover: Cover,
-                                 K: Optional[int] = None) -> Cover:
+    def random_forest_candidates(
+        self, match_cover: Cover, comparison_cover: Cover, K: Optional[int] = None
+    ) -> Cover:
         predicates = list(match_cover)
         matches = list(frozenset.union(*match_cover.values()))
         pred_sample_size = max(int(math.sqrt(len(predicates))), 5)
@@ -87,12 +86,11 @@ def random_forest_candidates(self,
 
         n_samples = 5000
         for _ in range(n_samples):
-            sample_predicates = random.sample(predicates,
-                                              pred_sample_size)
+            sample_predicates = random.sample(predicates, pred_sample_size)
             resampler = Resampler(matches)
-            sample_match_cover = {pred: resampler(pairs)
-                                  for pred, pairs
-                                  in match_cover.items()}
+            sample_match_cover = {
+                pred: resampler(pairs) for pred, pairs in match_cover.items()
+            }
 
             # initialize variables that will be
             # the base for the constructing k-conjunctions
@@ -103,12 +101,11 @@ def random_forest_candidates(self,
 
             def score(predicate: Predicate) -> float:
                 try:
-                    return (len(covered_sample_matches &
-                                sample_match_cover[predicate]) /
-                            len(covered_comparisons &
-                                comparison_cover[predicate]))
+                    return len(
+                        covered_sample_matches & sample_match_cover[predicate]
+                    ) / len(covered_comparisons & comparison_cover[predicate])
                 except ZeroDivisionError:
-                    return 0.
+                    return 0.0
 
             for _ in range(K):
                 next_predicate = max(sample_predicates, key=score)
@@ -133,10 +130,10 @@ def cover(self, pairs) -> Cover:
         predicate_cover = {}
         for predicate in self.blocker.predicates:  # type: ignore
             coverage = frozenset(
-                i for i, (record_1, record_2)
-                in enumerate(pairs)
-                if (set(predicate(record_1)) &
-                    set(predicate(record_2, target=True))))
+                i
+                for i, (record_1, record_2) in enumerate(pairs)
+                if (set(predicate(record_1)) & set(predicate(record_2, target=True)))
+            )
             if coverage:
                 predicate_cover[predicate] = coverage
 
@@ -147,7 +144,6 @@ def cover(self, pairs) -> Cover:
 
 
 class DedupeBlockLearner(BlockLearner):
-
     def __init__(self, predicates, sampled_records, data):
 
         self.blocker = blocking.Fingerprinter(predicates)
@@ -179,7 +175,8 @@ def coveredPairs(blocker, records):
             pairs = frozenset(
                 pair
                 for block in pred_cover.values()
-                for pair in itertools.combinations(sorted(block), 2))
+                for pair in itertools.combinations(sorted(block), 2)
+            )
             if pairs:
                 cover[predicate] = pairs
 
@@ -187,15 +184,14 @@ def coveredPairs(blocker, records):
 
 
 class RecordLinkBlockLearner(BlockLearner):
-
     def __init__(self, predicates, sampled_records_1, sampled_records_2, data_2):
 
         self.blocker = blocking.Fingerprinter(predicates)
         self.blocker.index_all(data_2)
 
-        self.comparison_cover = self.coveredPairs(self.blocker,
-                                                  sampled_records_1,
-                                                  sampled_records_2)
+        self.comparison_cover = self.coveredPairs(
+            self.blocker, sampled_records_1, sampled_records_2
+        )
 
     def coveredPairs(self, blocker, records_1, records_2):
         cover = {}
@@ -216,9 +212,8 @@ def coveredPairs(self, blocker, records_1, records_2):
 
         for predicate, blocks in cover.items():
             pairs = frozenset(
-                pair
-                for A, B in blocks.values()
-                for pair in itertools.product(A, B))
+                pair for A, B in blocks.values() for pair in itertools.product(A, B)
+            )
             if pairs:
                 pair_cover[predicate] = pairs
 
@@ -230,13 +225,13 @@ def __init__(self, target: int, max_calls: int) -> None:
         self.target: int = target
         self.calls: int = max_calls
 
-        self.cheapest_score: float = float('inf')
+        self.cheapest_score: float = float("inf")
         self.original_cover: Cover = {}
         self.cheapest: Tuple[Predicate, ...] = ()
 
-    def search(self,
-               candidates: Cover,
-               partial: Tuple[Predicate, ...] = ()) -> Tuple[Predicate, ...]:
+    def search(
+        self, candidates: Cover, partial: Tuple[Predicate, ...] = ()
+    ) -> Tuple[Predicate, ...]:
         if self.calls <= 0:
             return self.cheapest
 
@@ -256,9 +251,9 @@ def search(self,
         else:
             window = self.cheapest_score - score
 
-            candidates = {p: cover
-                          for p, cover in candidates.items()
-                          if p.count < window}  # type: ignore
+            candidates = {
+                p: cover for p, cover in candidates.items() if p.count < window  # type: ignore
+            }
 
             reachable = self.reachable(candidates) + covered
 
@@ -268,8 +263,7 @@ def search(self,
 
                 best = max(candidates, key=order_by)
 
-                remaining = self.uncovered_by(candidates,
-                                              candidates[best])
+                remaining = self.uncovered_by(candidates, candidates[best])
                 try:
                     self.search(remaining, partial + (best,))
                 except RecursionError:
@@ -298,8 +292,7 @@ def score(partial: Iterable[Predicate]) -> float:
 
     def covered(self, partial: Tuple[Predicate, ...]) -> int:
         if partial:
-            return len(frozenset.union(*(self.original_cover[p]
-                                         for p in partial)))
+            return len(frozenset.union(*(self.original_cover[p] for p in partial)))
         else:
             return 0
 
@@ -315,8 +308,10 @@ def remove_dominated(coverage: Cover, dominator: Predicate) -> Cover:
         dominant_cover = coverage[dominator]
 
         for pred, cover in coverage.copy().items():
-            if (dominator.count <= pred.count and  # type: ignore
-                    dominant_cover >= cover):
+            if (
+                dominator.count <= pred.count  # type: ignore
+                and dominant_cover >= cover
+            ):
                 del coverage[pred]
 
         return coverage
@@ -333,7 +328,6 @@ def uncovered_by(coverage: Cover, covered: frozenset) -> Cover:
 
 
 class InfiniteSet(object):
-
     def __and__(self, item):
         return item
 
@@ -342,7 +336,6 @@ def __rand__(self, item):
 
 
 class Resampler(object):
-
     def __init__(self, sequence: Sequence):
 
         sampled = random.choices(sequence, k=len(sequence))
@@ -361,7 +354,7 @@ def __init__(self, sequence: Sequence):
     @functools.lru_cache()
     def __call__(self, iterable: Iterable) -> frozenset:
 
-        result = itertools.chain.from_iterable(self.replacements[k]
-                                               for k in iterable
-                                               if k in self.replacements)
+        result = itertools.chain.from_iterable(
+            self.replacements[k] for k in iterable if k in self.replacements
+        )
         return frozenset(result)
diff --git a/dedupe/variables/__init__.py b/dedupe/variables/__init__.py
index 3ad9513f4..b36383a61 100644
--- a/dedupe/variables/__init__.py
+++ b/dedupe/variables/__init__.py
@@ -1,2 +1,3 @@
 from pkgutil import extend_path
+
 __path__ = extend_path(__path__, __name__)
diff --git a/dedupe/variables/base.py b/dedupe/variables/base.py
index 2fe5cef31..0656d46e7 100644
--- a/dedupe/variables/base.py
+++ b/dedupe/variables/base.py
@@ -18,10 +18,10 @@ def __eq__(self, other):
 
     def __init__(self, definition):
 
-        if definition.get('has missing', False):
+        if definition.get("has missing", False):
             self.has_missing = True
             try:
-                exists_pred = predicates.ExistsPredicate(definition['field'])
+                exists_pred = predicates.ExistsPredicate(definition["field"])
                 self.predicates.append(exists_pred)
             except KeyError:
                 pass
@@ -30,7 +30,7 @@ def __init__(self, definition):
 
     def __getstate__(self):
         odict = self.__dict__.copy()
-        odict['predicates'] = None
+        odict["predicates"] = None
 
         return odict
 
@@ -39,8 +39,7 @@ class DerivedType(Variable):
     type = "Derived"
 
     def __init__(self, definition):
-        self.name = "(%s: %s)" % (str(definition['name']),
-                                  str(definition['type']))
+        self.name = "(%s: %s)" % (str(definition["name"]), str(definition["type"]))
         super(DerivedType, self).__init__(definition)
 
 
@@ -61,19 +60,20 @@ class FieldType(Variable):
     _Predicate = predicates.SimplePredicate
 
     def __init__(self, definition):
-        self.field = definition['field']
+        self.field = definition["field"]
 
-        if 'variable name' in definition:
-            self.name = definition['variable name']
+        if "variable name" in definition:
+            self.name = definition["variable name"]
         else:
             self.name = "(%s: %s)" % (self.field, self.type)
 
-        self.predicates = [self._Predicate(pred, self.field)
-                           for pred in self._predicate_functions]
+        self.predicates = [
+            self._Predicate(pred, self.field) for pred in self._predicate_functions
+        ]
 
-        self.predicates += indexPredicates(self._index_predicates,
-                                           self._index_thresholds,
-                                           self.field)
+        self.predicates += indexPredicates(
+            self._index_predicates, self._index_thresholds, self.field
+        )
 
         super(FieldType, self).__init__(definition)
 
@@ -87,14 +87,18 @@ def __init__(self, definition):
         try:
             self.comparator = definition["comparator"]
         except KeyError:
-            raise KeyError("For 'Custom' field types you must define "
-                           "a 'comparator' function in the field "
-                           "definition. ")
-
-        if 'variable name' not in definition:
-            self.name = "(%s: %s, %s)" % (self.field,
-                                          self.type,
-                                          self.comparator.__name__)
+            raise KeyError(
+                "For 'Custom' field types you must define "
+                "a 'comparator' function in the field "
+                "definition. "
+            )
+
+        if "variable name" not in definition:
+            self.name = "(%s: %s, %s)" % (
+                self.field,
+                self.type,
+                self.comparator.__name__,
+            )
 
 
 def allSubclasses(cls):
diff --git a/dedupe/variables/categorical_type.py b/dedupe/variables/categorical_type.py
index f31dba92e..37555f257 100644
--- a/dedupe/variables/categorical_type.py
+++ b/dedupe/variables/categorical_type.py
@@ -25,9 +25,9 @@ def __init__(self, definition):
 
         self.higher_vars = []
         for higher_var in self.comparator.dummy_names:
-            dummy_var = DerivedType({'name': higher_var,
-                                     'type': 'Dummy',
-                                     'has missing': self.has_missing})
+            dummy_var = DerivedType(
+                {"name": higher_var, "type": "Dummy", "has missing": self.has_missing}
+            )
             self.higher_vars.append(dummy_var)
 
     def __len__(self):
diff --git a/dedupe/variables/exists.py b/dedupe/variables/exists.py
index f9cacba9f..a97036e75 100644
--- a/dedupe/variables/exists.py
+++ b/dedupe/variables/exists.py
@@ -17,9 +17,9 @@ def __init__(self, definition):
 
         self.higher_vars = []
         for higher_var in self.cat_comparator.dummy_names:
-            dummy_var = DerivedType({'name': higher_var,
-                                     'type': 'Dummy',
-                                     'has missing': self.has_missing})
+            dummy_var = DerivedType(
+                {"name": higher_var, "type": "Dummy", "has missing": self.has_missing}
+            )
             self.higher_vars.append(dummy_var)
 
     def comparator(self, field_1, field_2):
diff --git a/dedupe/variables/interaction.py b/dedupe/variables/interaction.py
index 129add456..d8ac0cb63 100644
--- a/dedupe/variables/interaction.py
+++ b/dedupe/variables/interaction.py
@@ -16,8 +16,9 @@ def __init__(self, definition):
 
     def expandInteractions(self, field_model):
 
-        self.interaction_fields = self.atomicInteractions(self.interactions,
-                                                          field_model)
+        self.interaction_fields = self.atomicInteractions(
+            self.interactions, field_model
+        )
         for field in self.interaction_fields:
             if field_model[field].has_missing:
                 self.has_missing = True
@@ -25,19 +26,25 @@ def expandInteractions(self, field_model):
         self.categorical(field_model)
 
     def categorical(self, field_model):
-        categoricals = [field for field in self.interaction_fields
-                        if hasattr(field_model[field], "higher_vars")]
-        noncategoricals = [field for field in self.interaction_fields
-                           if not hasattr(field_model[field], "higher_vars")]
-
-        dummies = [field_model[field].higher_vars
-                   for field in categoricals]
+        categoricals = [
+            field
+            for field in self.interaction_fields
+            if hasattr(field_model[field], "higher_vars")
+        ]
+        noncategoricals = [
+            field
+            for field in self.interaction_fields
+            if not hasattr(field_model[field], "higher_vars")
+        ]
+
+        dummies = [field_model[field].higher_vars for field in categoricals]
 
         self.higher_vars = []
         for combo in itertools.product(*dummies):
             var_names = [field.name for field in combo] + noncategoricals
-            higher_var = InteractionType({'has missing': self.has_missing,
-                                          'interaction variables': var_names})
+            higher_var = InteractionType(
+                {"has missing": self.has_missing, "interaction variables": var_names}
+            )
             self.higher_vars.append(higher_var)
 
     def atomicInteractions(self, interactions, field_model):
@@ -47,11 +54,13 @@ def atomicInteractions(self, interactions, field_model):
             try:
                 field_model[field]
             except KeyError:
-                raise KeyError("The interaction variable %s is "
-                               "not a named variable in the variable "
-                               "definition" % field)
+                raise KeyError(
+                    "The interaction variable %s is "
+                    "not a named variable in the variable "
+                    "definition" % field
+                )
 
-            if hasattr(field_model[field], 'interaction_fields'):
+            if hasattr(field_model[field], "interaction_fields"):
                 sub_interactions = field_model[field].interaction_fields
                 atoms = self.atomicInteractions(sub_interactions, field_model)
                 atomic_interactions.extend(atoms)
diff --git a/dedupe/variables/price.py b/dedupe/variables/price.py
index 4804a4e7b..b85ed8c78 100644
--- a/dedupe/variables/price.py
+++ b/dedupe/variables/price.py
@@ -4,9 +4,11 @@
 
 
 class PriceType(FieldType):
-    _predicate_functions = [predicates.orderOfMagnitude,
-                            predicates.wholeFieldPredicate,
-                            predicates.roundTo1]
+    _predicate_functions = [
+        predicates.orderOfMagnitude,
+        predicates.wholeFieldPredicate,
+        predicates.roundTo1,
+    ]
     type = "Price"
 
     @staticmethod
diff --git a/dedupe/variables/set.py b/dedupe/variables/set.py
index 57a8db256..f80a75957 100644
--- a/dedupe/variables/set.py
+++ b/dedupe/variables/set.py
@@ -6,22 +6,26 @@
 class SetType(FieldType):
     type = "Set"
 
-    _predicate_functions = (predicates.wholeSetPredicate,
-                            predicates.commonSetElementPredicate,
-                            predicates.lastSetElementPredicate,
-                            predicates.commonTwoElementsPredicate,
-                            predicates.commonThreeElementsPredicate,
-                            predicates.magnitudeOfCardinality,
-                            predicates.firstSetElementPredicate)
+    _predicate_functions = (
+        predicates.wholeSetPredicate,
+        predicates.commonSetElementPredicate,
+        predicates.lastSetElementPredicate,
+        predicates.commonTwoElementsPredicate,
+        predicates.commonThreeElementsPredicate,
+        predicates.magnitudeOfCardinality,
+        predicates.firstSetElementPredicate,
+    )
 
-    _index_predicates = (predicates.TfidfSetSearchPredicate,
-                         predicates.TfidfSetCanopyPredicate)
+    _index_predicates = (
+        predicates.TfidfSetSearchPredicate,
+        predicates.TfidfSetCanopyPredicate,
+    )
     _index_thresholds = (0.2, 0.4, 0.6, 0.8)
 
     def __init__(self, definition):
         super(SetType, self).__init__(definition)
 
-        if 'corpus' not in definition:
-            definition['corpus'] = []
+        if "corpus" not in definition:
+            definition["corpus"] = []
 
-        self.comparator = CosineSetSimilarity(definition['corpus'])
+        self.comparator = CosineSetSimilarity(definition["corpus"])
diff --git a/dedupe/variables/string.py b/dedupe/variables/string.py
index d561005b3..e1499b6f7 100644
--- a/dedupe/variables/string.py
+++ b/dedupe/variables/string.py
@@ -9,25 +9,26 @@
 
 crfEd = CRFEditDistance()
 
-base_predicates = (predicates.wholeFieldPredicate,
-                   predicates.firstTokenPredicate,
-                   predicates.firstTwoTokensPredicate,
-                   predicates.commonIntegerPredicate,
-                   predicates.nearIntegersPredicate,
-                   predicates.firstIntegerPredicate,
-                   predicates.hundredIntegerPredicate,
-                   predicates.hundredIntegersOddPredicate,
-                   predicates.alphaNumericPredicate,
-                   predicates.sameThreeCharStartPredicate,
-                   predicates.sameFiveCharStartPredicate,
-                   predicates.sameSevenCharStartPredicate,
-                   predicates.commonTwoTokens,
-                   predicates.commonThreeTokens,
-                   predicates.fingerprint,
-                   predicates.oneGramFingerprint,
-                   predicates.twoGramFingerprint,
-                   predicates.sortedAcronym
-                   )
+base_predicates = (
+    predicates.wholeFieldPredicate,
+    predicates.firstTokenPredicate,
+    predicates.firstTwoTokensPredicate,
+    predicates.commonIntegerPredicate,
+    predicates.nearIntegersPredicate,
+    predicates.firstIntegerPredicate,
+    predicates.hundredIntegerPredicate,
+    predicates.hundredIntegersOddPredicate,
+    predicates.alphaNumericPredicate,
+    predicates.sameThreeCharStartPredicate,
+    predicates.sameFiveCharStartPredicate,
+    predicates.sameSevenCharStartPredicate,
+    predicates.commonTwoTokens,
+    predicates.commonThreeTokens,
+    predicates.fingerprint,
+    predicates.oneGramFingerprint,
+    predicates.twoGramFingerprint,
+    predicates.sortedAcronym,
+)
 
 
 class BaseStringType(FieldType):
@@ -37,31 +38,38 @@ class BaseStringType(FieldType):
     def __init__(self, definition):
         super(BaseStringType, self).__init__(definition)
 
-        self.predicates += indexPredicates((predicates.LevenshteinCanopyPredicate,
-                                            predicates.LevenshteinSearchPredicate),
-                                           (1, 2, 3, 4),
-                                           self.field)
+        self.predicates += indexPredicates(
+            (
+                predicates.LevenshteinCanopyPredicate,
+                predicates.LevenshteinSearchPredicate,
+            ),
+            (1, 2, 3, 4),
+            self.field,
+        )
 
 
 class ShortStringType(BaseStringType):
     type = "ShortString"
 
-    _predicate_functions = (base_predicates +
-                            (predicates.commonFourGram,
-                             predicates.commonSixGram,
-                             predicates.tokenFieldPredicate,
-                             predicates.suffixArray,
-                             predicates.doubleMetaphone,
-                             predicates.metaphoneToken))
-
-    _index_predicates = [predicates.TfidfNGramCanopyPredicate,
-                         predicates.TfidfNGramSearchPredicate]
+    _predicate_functions = base_predicates + (
+        predicates.commonFourGram,
+        predicates.commonSixGram,
+        predicates.tokenFieldPredicate,
+        predicates.suffixArray,
+        predicates.doubleMetaphone,
+        predicates.metaphoneToken,
+    )
+
+    _index_predicates = [
+        predicates.TfidfNGramCanopyPredicate,
+        predicates.TfidfNGramSearchPredicate,
+    ]
     _index_thresholds = (0.2, 0.4, 0.6, 0.8)
 
     def __init__(self, definition):
         super(ShortStringType, self).__init__(definition)
 
-        if definition.get('crf', False) is True:
+        if definition.get("crf", False) is True:
             self.comparator = crfEd
         else:
             self.comparator = affineGap
@@ -70,10 +78,12 @@ def __init__(self, definition):
 class StringType(ShortStringType):
     type = "String"
 
-    _index_predicates = [predicates.TfidfNGramCanopyPredicate,
-                         predicates.TfidfNGramSearchPredicate,
-                         predicates.TfidfTextCanopyPredicate,
-                         predicates.TfidfTextSearchPredicate]
+    _index_predicates = [
+        predicates.TfidfNGramCanopyPredicate,
+        predicates.TfidfNGramSearchPredicate,
+        predicates.TfidfTextCanopyPredicate,
+        predicates.TfidfTextSearchPredicate,
+    ]
 
 
 class TextType(BaseStringType):
@@ -81,14 +91,16 @@ class TextType(BaseStringType):
 
     _predicate_functions = base_predicates
 
-    _index_predicates = [predicates.TfidfTextCanopyPredicate,
-                         predicates.TfidfTextSearchPredicate]
+    _index_predicates = [
+        predicates.TfidfTextCanopyPredicate,
+        predicates.TfidfTextSearchPredicate,
+    ]
     _index_thresholds = (0.2, 0.4, 0.6, 0.8)
 
     def __init__(self, definition):
         super(TextType, self).__init__(definition)
 
-        if 'corpus' not in definition:
-            definition['corpus'] = []
+        if "corpus" not in definition:
+            definition["corpus"] = []
 
-        self.comparator = CosineTextSimilarity(definition['corpus'])
+        self.comparator = CosineTextSimilarity(definition["corpus"])
diff --git a/docs/conf.py b/docs/conf.py
index 5381c8856..6e9c54942 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -18,96 +18,96 @@
 # 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
 # documentation root, use os.path.abspath to make it absolute, like shown here.
-sys.path.insert(0, os.path.abspath('.'))
-sys.path.insert(0, os.path.abspath('../dedupe'))
-sys.path.insert(0, os.path.abspath('..'))
+sys.path.insert(0, os.path.abspath("."))
+sys.path.insert(0, os.path.abspath("../dedupe"))
+sys.path.insert(0, os.path.abspath(".."))
 
 # -- General configuration ------------------------------------------------
 
 # If your documentation needs a minimal Sphinx version, state it here.
-#needs_sphinx = '1.0'
+# needs_sphinx = '1.0'
 
 # Add any Sphinx extension module names here, as strings. They can be
 # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
 # ones.
 extensions = [
-    'sphinx.ext.autodoc',
-    'sphinx.ext.napoleon',
-    'sphinx_autodoc_typehints',
-    'sphinx.ext.mathjax',
-    'sphinx.ext.viewcode',
+    "sphinx.ext.autodoc",
+    "sphinx.ext.napoleon",
+    "sphinx_autodoc_typehints",
+    "sphinx.ext.mathjax",
+    "sphinx.ext.viewcode",
 ]
 
 # Add any paths that contain templates here, relative to this directory.
-templates_path = ['_templates']
+templates_path = ["_templates"]
 
 # The suffix of source filenames.
-source_suffix = '.rst'
+source_suffix = ".rst"
 
 # The encoding of source files.
-#source_encoding = 'utf-8-sig'
+# source_encoding = 'utf-8-sig'
 
 # The master toctree document.
-master_doc = 'index'
+master_doc = "index"
 
 # General information about the project.
-project = u'dedupe'
-copyright = u'2020, Forest Gregg, Derek Eder, and contributors'
+project = "dedupe"
+copyright = "2020, Forest Gregg, Derek Eder, and contributors"
 
 # The version info for the project you're documenting, acts as replacement for
 # |version| and |release|, also used in various other places throughout the
 # built documents.
 #
 # The short X.Y version.
-version = '2.0'
+version = "2.0"
 
 # The full version, including alpha/beta/rc tags.
-release = '2.0.11'
+release = "2.0.11"
 
 # The language for content autogenerated by Sphinx. Refer to documentation
 # for a list of supported languages.
-#language = None
+# language = None
 
 # There are two options for replacing |today|: either, you set today to some
 # non-false value, then it is used:
-#today = ''
+# today = ''
 # Else, today_fmt is used as the format for a strftime call.
-#today_fmt = '%B %d, %Y'
+# today_fmt = '%B %d, %Y'
 
 # List of patterns, relative to source directory, that match files and
 # directories to ignore when looking for source files.
-exclude_patterns = ['_build']
+exclude_patterns = ["_build"]
 
 # The reST default role (used for this markup: `text`) to use for all
 # documents.
-default_role = 'py:obj'
+default_role = "py:obj"
 
 # If true, '()' will be appended to :func: etc. cross-reference text.
-#add_function_parentheses = True
+# add_function_parentheses = True
 
 # If true, the current module name will be prepended to all description
 # unit titles (such as .. function::).
-#add_module_names = True
+# add_module_names = True
 
 # If true, sectionauthor and moduleauthor directives will be shown in the
 # output. They are ignored by default.
-#show_authors = False
+# show_authors = False
 
 # The name of the Pygments (syntax highlighting) style to use.
-pygments_style = 'sphinx'
+pygments_style = "sphinx"
 
 # A list of ignored prefixes for module index sorting.
-#modindex_common_prefix = []
+# modindex_common_prefix = []
 
 # If true, keep warnings as "system message" paragraphs in the built documents.
-#keep_warnings = False
+# keep_warnings = False
 
 
 # -- Options for HTML output ----------------------------------------------
 
 # The theme to use for HTML and HTML Help pages.  See the documentation for
 # a list of builtin themes.
-html_theme = 'sphinx_rtd_theme'
+html_theme = "sphinx_rtd_theme"
 
 # Custom stylesheet
 # html_style = 'css/custom.css'
@@ -116,7 +116,7 @@
 # further.  For a list of options available for each theme, see the
 # documentation.
 # html_theme_options = {
-    # 'canonical_url': 'https://docs.dedupe.io/'
+# 'canonical_url': 'https://docs.dedupe.io/'
 # }
 
 # Add any paths that contain custom themes here, relative to this directory.
@@ -124,115 +124,118 @@
 
 # The name for this set of Sphinx documents.  If None, it defaults to
 # "<project> v<release> documentation".
-#html_title = None
+# html_title = None
 
 # A shorter title for the navigation bar.  Default is the same as html_title.
-#html_short_title = None
+# html_short_title = None
 
 # The name of an image file (relative to this directory) to place at the top
 # of the sidebar.
-#html_logo = None
+# html_logo = None
 
 # The name of an image file (within the static path) to use as favicon of the
 # docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
 # pixels large.
-#html_favicon = None
+# html_favicon = None
 
 # 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 = ["_static"]
 
 # Add any extra paths that contain custom files (such as robots.txt or
 # .htaccess) here, relative to this directory. These files are copied
 # directly to the root of the documentation.
-#html_extra_path = []
+# html_extra_path = []
 
 # If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
 # using the given strftime format.
-#html_last_updated_fmt = '%b %d, %Y'
+# html_last_updated_fmt = '%b %d, %Y'
 
 # If true, SmartyPants will be used to convert quotes and dashes to
 # typographically correct entities.
-#html_use_smartypants = True
+# html_use_smartypants = True
 
 # Custom sidebar templates, maps document names to template names.
-#html_sidebars = {}
+# html_sidebars = {}
 
 # Additional templates that should be rendered to pages, maps page names to
 # template names.
-#html_additional_pages = {}
+# html_additional_pages = {}
 
 # If false, no module index is generated.
-#html_domain_indices = True
+# html_domain_indices = True
 
 # If false, no index is generated.
-#html_use_index = True
+# html_use_index = True
 
 # If true, the index is split into individual pages for each letter.
-#html_split_index = False
+# html_split_index = False
 
 # If true, links to the reST sources are added to the pages.
-#html_show_sourcelink = True
+# html_show_sourcelink = True
 
 # If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
-#html_show_sphinx = True
+# html_show_sphinx = True
 
 # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
-#html_show_copyright = True
+# html_show_copyright = True
 
 # If true, an OpenSearch description file will be output, and all pages will
 # contain a <link> tag referring to it.  The value of this option must be the
 # base URL from which the finished HTML is served.
-#html_use_opensearch = ''
+# html_use_opensearch = ''
 
 # This is the file name suffix for HTML files (e.g. ".xhtml").
-#html_file_suffix = None
+# html_file_suffix = None
 
 # Output file base name for HTML help builder.
-htmlhelp_basename = 'dedupedoc'
+htmlhelp_basename = "dedupedoc"
 
 
 # -- Options for LaTeX output ---------------------------------------------
 
 latex_elements = {
-# The paper size ('letterpaper' or 'a4paper').
-#'papersize': 'letterpaper',
-
-# The font size ('10pt', '11pt' or '12pt').
-#'pointsize': '10pt',
-
-# Additional stuff for the LaTeX preamble.
-#'preamble': '',
+    # The paper size ('letterpaper' or 'a4paper').
+    #'papersize': 'letterpaper',
+    # The font size ('10pt', '11pt' or '12pt').
+    #'pointsize': '10pt',
+    # Additional stuff for the LaTeX preamble.
+    #'preamble': '',
 }
 
 # Grouping the document tree into LaTeX files. List of tuples
 # (source start file, target name, title,
 #  author, documentclass [howto, manual, or own class]).
 latex_documents = [
-  ('index', 'dedupe.tex', u'dedupe Documentation',
-   u'Forest Gregg, Derek Eder, and contributors', 'manual'),
+    (
+        "index",
+        "dedupe.tex",
+        "dedupe Documentation",
+        "Forest Gregg, Derek Eder, and contributors",
+        "manual",
+    ),
 ]
 
 # The name of an image file (relative to this directory) to place at the top of
 # the title page.
-#latex_logo = None
+# latex_logo = None
 
 # For "manual" documents, if this is true, then toplevel headings are parts,
 # not chapters.
-#latex_use_parts = False
+# latex_use_parts = False
 
 # If true, show page references after internal links.
-#latex_show_pagerefs = False
+# latex_show_pagerefs = False
 
 # If true, show URL addresses after external links.
-#latex_show_urls = False
+# latex_show_urls = False
 
 # Documents to append as an appendix to all manuals.
-#latex_appendices = []
+# latex_appendices = []
 
 # If false, no module index is generated.
-#latex_domain_indices = True
+# latex_domain_indices = True
 
 
 # -- Options for manual page output ---------------------------------------
@@ -240,12 +243,17 @@
 # One entry per manual page. List of tuples
 # (source start file, name, description, authors, manual section).
 man_pages = [
-    ('index', 'dedupe', u'dedupe Documentation',
-     [u'Forest Gregg, Derek Eder, and contributors'], 1)
+    (
+        "index",
+        "dedupe",
+        "dedupe Documentation",
+        ["Forest Gregg, Derek Eder, and contributors"],
+        1,
+    )
 ]
 
 # If true, show URL addresses after external links.
-#man_show_urls = False
+# man_show_urls = False
 
 
 # -- Options for Texinfo output -------------------------------------------
@@ -254,25 +262,31 @@
 # (source start file, target name, title, author,
 #  dir menu entry, description, category)
 texinfo_documents = [
-  ('index', 'dedupe', u'dedupe Documentation',
-   u'Forest Gregg, Derek Eder, and contributors', 'dedupe', 'One line description of project.',
-   'Miscellaneous'),
+    (
+        "index",
+        "dedupe",
+        "dedupe Documentation",
+        "Forest Gregg, Derek Eder, and contributors",
+        "dedupe",
+        "One line description of project.",
+        "Miscellaneous",
+    ),
 ]
 
 # Documents to append as an appendix to all manuals.
-#texinfo_appendices = []
+# texinfo_appendices = []
 
 # If false, no module index is generated.
-#texinfo_domain_indices = True
+# texinfo_domain_indices = True
 
 # How to display URL addresses: 'footnote', 'no', or 'inline'.
-#texinfo_show_urls = 'footnote'
+# texinfo_show_urls = 'footnote'
 
 # If true, do not generate a @detailmenu in the "Top" node's menu.
-#texinfo_no_detailmenu = False
+# texinfo_no_detailmenu = False
 
-sphinx_autodoc_typehints = 'signature'
+sphinx_autodoc_typehints = "signature"
 typehints_document_rtype = False
 
 
-autoclass_content = 'both'
+autoclass_content = "both"
diff --git a/requirements.txt b/requirements.txt
index 55ce6dee4..2ac278e33 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,3 +1,4 @@
+black
 pytest
 pytest-cov
 mock
diff --git a/setup.cfg b/setup.cfg
index f70f4fc81..2c909dac1 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -4,3 +4,4 @@ norecursedirs=docs
 
 [flake8]
 max-line-length=160
+extend-ignore = E203
diff --git a/setup.py b/setup.py
index 8a75df83a..6c792ecfc 100644
--- a/setup.py
+++ b/setup.py
@@ -4,52 +4,59 @@
 try:
     from setuptools import setup, Extension
 except ImportError:
-    raise ImportError("setuptools module required, please go to https://pypi.python.org/pypi/setuptools and follow the instructions for installing setuptools")
+    raise ImportError(
+        "setuptools module required, please go to https://pypi.python.org/pypi/setuptools and follow the instructions for installing setuptools"
+    )
 
 from Cython.Build import cythonize
 
-install_requires = ["fastcluster",
-                    'dedupe-hcluster',
-                    'affinegap>=1.3',
-                    'categorical-distance>=1.9',
-                    'dedupe-variable-datetime',
-                    'rlr>=2.4.3',
-                    'numpy>=1.13',
-                    'doublemetaphone',
-                    'highered>=0.2.0',
-                    'simplecosine>=1.2',
-                    'haversine>=0.4.1',
-                    'BTrees>=4.1.4',
-                    'zope.index',
-                    'Levenshtein_search==1.4.5',
-                    'typing_extensions']
+install_requires = [
+    "fastcluster",
+    "dedupe-hcluster",
+    "affinegap>=1.3",
+    "categorical-distance>=1.9",
+    "dedupe-variable-datetime",
+    "rlr>=2.4.3",
+    "numpy>=1.13",
+    "doublemetaphone",
+    "highered>=0.2.0",
+    "simplecosine>=1.2",
+    "haversine>=0.4.1",
+    "BTrees>=4.1.4",
+    "zope.index",
+    "Levenshtein_search==1.4.5",
+    "typing_extensions",
+]
 
 
 setup(
-    name='dedupe',
-    url='https://github.com/dedupeio/dedupe',
-    version='2.0.14',
-    author='Forest Gregg',
-    author_email='fgregg@datamade.us',
-    description='A python library for accurate and scaleable data deduplication and entity-resolution',
-    packages=['dedupe', 'dedupe.variables'],
-    ext_modules=cythonize([Extension('dedupe.cpredicates', ['dedupe/cpredicates.pyx'])]),
+    name="dedupe",
+    url="https://github.com/dedupeio/dedupe",
+    version="2.0.14",
+    author="Forest Gregg",
+    author_email="fgregg@datamade.us",
+    description="A python library for accurate and scaleable data deduplication and entity-resolution",
+    packages=["dedupe", "dedupe.variables"],
+    ext_modules=cythonize(
+        [Extension("dedupe.cpredicates", ["dedupe/cpredicates.pyx"])]
+    ),
     install_requires=install_requires,
-    python_requires='>3.6',
+    python_requires=">3.6",
     classifiers=[
-        'Development Status :: 4 - Beta',
-        'Intended Audience :: Developers',
-        'Intended Audience :: Science/Research',
-        'License :: OSI Approved :: MIT License',
-        'Natural Language :: English',
-        'Operating System :: MacOS :: MacOS X',
-        'Operating System :: Microsoft :: Windows',
-        'Operating System :: POSIX',
-        'Programming Language :: Cython',
-        'Programming Language :: Python :: 3',
-        'Topic :: Software Development :: Libraries :: Python Modules',
-        'Topic :: Scientific/Engineering',
-        'Topic :: Scientific/Engineering :: Information Analysis'],
+        "Development Status :: 4 - Beta",
+        "Intended Audience :: Developers",
+        "Intended Audience :: Science/Research",
+        "License :: OSI Approved :: MIT License",
+        "Natural Language :: English",
+        "Operating System :: MacOS :: MacOS X",
+        "Operating System :: Microsoft :: Windows",
+        "Operating System :: POSIX",
+        "Programming Language :: Cython",
+        "Programming Language :: Python :: 3",
+        "Topic :: Software Development :: Libraries :: Python Modules",
+        "Topic :: Scientific/Engineering",
+        "Topic :: Scientific/Engineering :: Information Analysis",
+    ],
     long_description="""
     dedupe is a library that uses machine learning to perform de-duplication and entity resolution quickly on structured data. dedupe is the open source engine for `dedupe.io <https://dedupe.io>`_
 
@@ -68,5 +75,5 @@
         "Twitter": "https://twitter.com/DedupeIo",
         "Changelog": "https://github.com/dedupeio/dedupe/blob/master/CHANGELOG.md",
         "Mailing List": "https://groups.google.com/forum/#!forum/open-source-deduplication",
-    }
+    },
 )


Test patch:
diff --git a/tests/canonical.py b/tests/canonical.py
index 7b0680906..93f2abe65 100644
--- a/tests/canonical.py
+++ b/tests/canonical.py
@@ -18,8 +18,7 @@ def canonicalImport(filename):
     with open(filename) as f:
         reader = csv.DictReader(f)
         for (i, row) in enumerate(reader):
-            clean_row = {k: preProcess(v) for (k, v) in
-                         row.items()}
+            clean_row = {k: preProcess(v) for (k, v) in row.items()}
             data_d[i] = clean_row
 
     return data_d, reader.fieldnames
@@ -29,21 +28,25 @@ def evaluateDuplicates(found_dupes, true_dupes):
     true_positives = found_dupes.intersection(true_dupes)
     false_positives = found_dupes.difference(true_dupes)
 
-    print('found duplicate')
+    print("found duplicate")
     print(len(found_dupes))
 
-    print('precision')
+    print("precision")
     print(1 - len(false_positives) / float(len(found_dupes)))
 
-    print('recall')
+    print("recall")
     print(len(true_positives) / float(len(true_dupes)))
 
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     optp = optparse.OptionParser()
-    optp.add_option('-v', '--verbose', dest='verbose', action='count',
-                    help='Increase verbosity (specify multiple times for more)'
-                    )
+    optp.add_option(
+        "-v",
+        "--verbose",
+        dest="verbose",
+        action="count",
+        help="Increase verbosity (specify multiple times for more)",
+    )
     (opts, args) = optp.parse_args()
     log_level = logging.WARNING
     if opts.verbose is not None:
@@ -54,19 +57,18 @@ def evaluateDuplicates(found_dupes, true_dupes):
     logging.basicConfig()
     logging.getLogger().setLevel(log_level)
 
-    settings_file = 'canonical_learned_settings'
-    raw_data = 'tests/datasets/restaurant-nophone-training.csv'
+    settings_file = "canonical_learned_settings"
+    raw_data = "tests/datasets/restaurant-nophone-training.csv"
 
     data_d, header = canonicalImport(raw_data)
 
-    training_pairs = dedupe.training_data_dedupe(data_d,
-                                                 'unique_id',
-                                                 5000)
+    training_pairs = dedupe.training_data_dedupe(data_d, "unique_id", 5000)
 
     duplicates = set()
-    for _, pair in groupby(sorted(data_d.items(),
-                                  key=lambda x: x[1]['unique_id']),
-                           key=lambda x: x[1]['unique_id']):
+    for _, pair in groupby(
+        sorted(data_d.items(), key=lambda x: x[1]["unique_id"]),
+        key=lambda x: x[1]["unique_id"],
+    ):
         pair = list(pair)
         if len(pair) == 2:
             a, b = pair
@@ -74,33 +76,33 @@ def evaluateDuplicates(found_dupes, true_dupes):
 
     t0 = time.time()
 
-    print('number of known duplicate pairs', len(duplicates))
+    print("number of known duplicate pairs", len(duplicates))
 
     if os.path.exists(settings_file):
-        with open(settings_file, 'rb') as f:
+        with open(settings_file, "rb") as f:
             deduper = dedupe.StaticDedupe(f)
 
     else:
-        fields = [{'field': 'name', 'type': 'String'},
-                  {'field': 'name', 'type': 'Exact'},
-                  {'field': 'address', 'type': 'String'},
-                  {'field': 'cuisine', 'type': 'ShortString',
-                   'has missing': True},
-                  {'field': 'city', 'type': 'ShortString'}
-                  ]
+        fields = [
+            {"field": "name", "type": "String"},
+            {"field": "name", "type": "Exact"},
+            {"field": "address", "type": "String"},
+            {"field": "cuisine", "type": "ShortString", "has missing": True},
+            {"field": "city", "type": "ShortString"},
+        ]
 
         deduper = dedupe.Dedupe(fields, num_cores=5)
         deduper.prepare_training(data_d, sample_size=10000)
         deduper.mark_pairs(training_pairs)
         deduper.train(index_predicates=True)
-        with open(settings_file, 'wb') as f:
+        with open(settings_file, "wb") as f:
             deduper.write_settings(f)
 
     # print candidates
-    print('clustering...')
+    print("clustering...")
     clustered_dupes = deduper.partition(data_d, threshold=0.5)
 
-    print('Evaluate Clustering')
+    print("Evaluate Clustering")
     confirm_dupes = set([])
     for dupes, score in clustered_dupes:
         for pair in combinations(dupes, 2):
@@ -108,4 +110,4 @@ def evaluateDuplicates(found_dupes, true_dupes):
 
     evaluateDuplicates(confirm_dupes, duplicates)
 
-    print('ran in ', time.time() - t0, 'seconds')
+    print("ran in ", time.time() - t0, "seconds")
diff --git a/tests/canonical_gazetteer.py b/tests/canonical_gazetteer.py
index 8b656266c..f218c28e6 100644
--- a/tests/canonical_gazetteer.py
+++ b/tests/canonical_gazetteer.py
@@ -17,8 +17,7 @@ def canonicalImport(filename):
     with open(filename) as f:
         reader = csv.DictReader(f)
         for i, row in enumerate(reader):
-            clean_row = {k: preProcess(v) for (k, v) in
-                         row.items()}
+            clean_row = {k: preProcess(v) for (k, v) in row.items()}
             data_d[filename + str(i)] = clean_row
 
     return data_d, reader.fieldnames
@@ -28,22 +27,26 @@ def evaluateDuplicates(found_dupes, true_dupes):
     true_positives = found_dupes.intersection(true_dupes)
     false_positives = found_dupes.difference(true_dupes)
 
-    print('found duplicate')
+    print("found duplicate")
     print(len(found_dupes))
 
-    print('precision')
+    print("precision")
     print(1 - len(false_positives) / float(len(found_dupes)))
 
-    print('recall')
+    print("recall")
     print(len(true_positives) / float(len(true_dupes)))
 
 
-if __name__ == '__main__':
+if __name__ == "__main__":
 
     optp = optparse.OptionParser()
-    optp.add_option('-v', '--verbose', dest='verbose', action='count',
-                    help='Increase verbosity (specify multiple times for more)'
-                    )
+    optp.add_option(
+        "-v",
+        "--verbose",
+        dest="verbose",
+        action="count",
+        help="Increase verbosity (specify multiple times for more)",
+    )
     (opts, args) = optp.parse_args()
     log_level = logging.WARNING
     if opts.verbose:
@@ -53,20 +56,21 @@ def evaluateDuplicates(found_dupes, true_dupes):
             log_level = logging.DEBUG
     logging.getLogger().setLevel(log_level)
 
-    settings_file = 'canonical_gazetteer_learned_settings'
+    settings_file = "canonical_gazetteer_learned_settings"
 
-    data_1, header = canonicalImport('tests/datasets/restaurant-1.csv')
-    data_2, _ = canonicalImport('tests/datasets/restaurant-2.csv')
+    data_1, header = canonicalImport("tests/datasets/restaurant-1.csv")
+    data_2, _ = canonicalImport("tests/datasets/restaurant-2.csv")
 
-    training_pairs = dedupe.training_data_link(data_1, data_2, 'unique_id', 5000)
+    training_pairs = dedupe.training_data_link(data_1, data_2, "unique_id", 5000)
 
     all_data = data_1.copy()
     all_data.update(data_2)
 
     duplicates_s = set()
-    for _, pair in itertools.groupby(sorted(all_data.items(),
-                                            key=lambda x: x[1]['unique_id']),
-                                     key=lambda x: x[1]['unique_id']):
+    for _, pair in itertools.groupby(
+        sorted(all_data.items(), key=lambda x: x[1]["unique_id"]),
+        key=lambda x: x[1]["unique_id"],
+    ):
         pair = list(pair)
         if len(pair) == 2:
             a, b = pair
@@ -74,24 +78,25 @@ def evaluateDuplicates(found_dupes, true_dupes):
 
     t0 = time.time()
 
-    print('number of known duplicate pairs', len(duplicates_s))
+    print("number of known duplicate pairs", len(duplicates_s))
 
     if os.path.exists(settings_file):
-        with open(settings_file, 'rb') as f:
+        with open(settings_file, "rb") as f:
             gazetteer = dedupe.StaticGazetteer(f)
     else:
-        fields = [{'field': 'name', 'type': 'String'},
-                  {'field': 'address', 'type': 'String'},
-                  {'field': 'cuisine', 'type': 'String'},
-                  {'field': 'city', 'type': 'String'}
-                  ]
+        fields = [
+            {"field": "name", "type": "String"},
+            {"field": "address", "type": "String"},
+            {"field": "cuisine", "type": "String"},
+            {"field": "city", "type": "String"},
+        ]
 
         gazetteer = dedupe.Gazetteer(fields)
         gazetteer.prepare_training(data_1, data_2, sample_size=10000)
         gazetteer.mark_pairs(training_pairs)
         gazetteer.train()
 
-        with open(settings_file, 'wb') as f:
+        with open(settings_file, "wb") as f:
             gazetteer.write_settings(f)
 
     gazetteer.index(data_2)
@@ -99,13 +104,12 @@ def evaluateDuplicates(found_dupes, true_dupes):
     gazetteer.index(data_2)
 
     # print candidates
-    print('clustering...')
-    results = gazetteer.search(
-        data_1, n_matches=1, generator=True)
-
-    print('Evaluate Clustering')
-    confirm_dupes_a = set(frozenset([a, b])
-                          for a, result in results
-                          for b, score in result)
+    print("clustering...")
+    results = gazetteer.search(data_1, n_matches=1, generator=True)
+
+    print("Evaluate Clustering")
+    confirm_dupes_a = set(
+        frozenset([a, b]) for a, result in results for b, score in result
+    )
 
     evaluateDuplicates(confirm_dupes_a, duplicates_s)
diff --git a/tests/canonical_matching.py b/tests/canonical_matching.py
index 538c1b00e..767e91fde 100644
--- a/tests/canonical_matching.py
+++ b/tests/canonical_matching.py
@@ -17,8 +17,7 @@ def canonicalImport(filename):
     with open(filename) as f:
         reader = csv.DictReader(f)
         for i, row in enumerate(reader):
-            clean_row = {k: preProcess(v) for (k, v) in
-                         row.items()}
+            clean_row = {k: preProcess(v) for (k, v) in row.items()}
             data_d[filename + str(i)] = clean_row
 
     return data_d, reader.fieldnames
@@ -28,22 +27,26 @@ def evaluateDuplicates(found_dupes, true_dupes):
     true_positives = found_dupes.intersection(true_dupes)
     false_positives = found_dupes.difference(true_dupes)
 
-    print('found duplicate')
+    print("found duplicate")
     print(len(found_dupes))
 
-    print('precision')
+    print("precision")
     print(1 - len(false_positives) / float(len(found_dupes)))
 
-    print('recall')
+    print("recall")
     print(len(true_positives) / float(len(true_dupes)))
 
 
-if __name__ == '__main__':
+if __name__ == "__main__":
 
     optp = optparse.OptionParser()
-    optp.add_option('-v', '--verbose', dest='verbose', action='count',
-                    help='Increase verbosity (specify multiple times for more)'
-                    )
+    optp.add_option(
+        "-v",
+        "--verbose",
+        dest="verbose",
+        action="count",
+        help="Increase verbosity (specify multiple times for more)",
+    )
     (opts, args) = optp.parse_args()
     log_level = logging.WARNING
     if opts.verbose:
@@ -53,20 +56,21 @@ def evaluateDuplicates(found_dupes, true_dupes):
             log_level = logging.DEBUG
     logging.getLogger().setLevel(log_level)
 
-    settings_file = 'canonical_data_matching_learned_settings'
+    settings_file = "canonical_data_matching_learned_settings"
 
-    data_1, header = canonicalImport('tests/datasets/restaurant-1.csv')
-    data_2, _ = canonicalImport('tests/datasets/restaurant-2.csv')
+    data_1, header = canonicalImport("tests/datasets/restaurant-1.csv")
+    data_2, _ = canonicalImport("tests/datasets/restaurant-2.csv")
 
-    training_pairs = dedupe.training_data_link(data_1, data_2, 'unique_id', 5000)
+    training_pairs = dedupe.training_data_link(data_1, data_2, "unique_id", 5000)
 
     all_data = data_1.copy()
     all_data.update(data_2)
 
     duplicates_s = set()
-    for _, pair in itertools.groupby(sorted(all_data.items(),
-                                            key=lambda x: x[1]['unique_id']),
-                                     key=lambda x: x[1]['unique_id']):
+    for _, pair in itertools.groupby(
+        sorted(all_data.items(), key=lambda x: x[1]["unique_id"]),
+        key=lambda x: x[1]["unique_id"],
+    ):
         pair = list(pair)
         if len(pair) == 2:
             a, b = pair
@@ -74,46 +78,47 @@ def evaluateDuplicates(found_dupes, true_dupes):
 
     t0 = time.time()
 
-    print('number of known duplicate pairs', len(duplicates_s))
+    print("number of known duplicate pairs", len(duplicates_s))
 
     if os.path.exists(settings_file):
-        with open(settings_file, 'rb') as f:
+        with open(settings_file, "rb") as f:
             deduper = dedupe.StaticRecordLink(f)
     else:
-        fields = [{'field': 'name', 'type': 'String'},
-                  {'field': 'address', 'type': 'String'},
-                  {'field': 'cuisine', 'type': 'String'},
-                  {'field': 'city', 'type': 'String'}
-                  ]
+        fields = [
+            {"field": "name", "type": "String"},
+            {"field": "address", "type": "String"},
+            {"field": "cuisine", "type": "String"},
+            {"field": "city", "type": "String"},
+        ]
 
         deduper = dedupe.RecordLink(fields)
         deduper.prepare_training(data_1, data_2, sample_size=10000)
         deduper.mark_pairs(training_pairs)
         deduper.train()
 
-        with open(settings_file, 'wb') as f:
+        with open(settings_file, "wb") as f:
             deduper.write_settings(f)
 
     # print candidates
-    print('clustering...')
+    print("clustering...")
     clustered_dupes = deduper.join(data_1, data_2, threshold=0.5)
 
-    print('Evaluate Clustering')
-    confirm_dupes = set(frozenset(pair)
-                        for pair, score in clustered_dupes)
+    print("Evaluate Clustering")
+    confirm_dupes = set(frozenset(pair) for pair, score in clustered_dupes)
 
     evaluateDuplicates(confirm_dupes, duplicates_s)
 
-    print('ran in ', time.time() - t0, 'seconds')
+    print("ran in ", time.time() - t0, "seconds")
 
     # print candidates
-    print('clustering...')
-    clustered_dupes = deduper.join(data_1, data_2, threshold=0.5, constraint='many-to-one')
+    print("clustering...")
+    clustered_dupes = deduper.join(
+        data_1, data_2, threshold=0.5, constraint="many-to-one"
+    )
 
-    print('Evaluate Clustering')
-    confirm_dupes = set(frozenset(pair)
-                        for pair, score in clustered_dupes)
+    print("Evaluate Clustering")
+    confirm_dupes = set(frozenset(pair) for pair, score in clustered_dupes)
 
     evaluateDuplicates(confirm_dupes, duplicates_s)
 
-    print('ran in ', time.time() - t0, 'seconds')
+    print("ran in ", time.time() - t0, "seconds")
diff --git a/tests/duplicateCluster_memory_case.py b/tests/duplicateCluster_memory_case.py
index d5d1b496b..6bbe99d4f 100644
--- a/tests/duplicateCluster_memory_case.py
+++ b/tests/duplicateCluster_memory_case.py
@@ -24,8 +24,7 @@
 def candidates_gen():
     candidate_set = set([])
     for _ in range(10**5):
-        block = [((random.randint(0, 1000), 'a'),
-                  (random.randint(0, 1000), 'b'))]
+        block = [((random.randint(0, 1000), "a"), (random.randint(0, 1000), "b"))]
         for candidate in block:
             pair_ids = (candidate[0][0], candidate[1][0])
             if pair_ids not in candidate_set:
diff --git a/tests/exampleIO.py b/tests/exampleIO.py
index d6ed956fb..2d8848f4e 100644
--- a/tests/exampleIO.py
+++ b/tests/exampleIO.py
@@ -3,8 +3,8 @@
 
 
 def preProcess(column):
-    column = re.sub('  +', ' ', column)
-    column = re.sub('\n', ' ', column)
+    column = re.sub("  +", " ", column)
+    column = re.sub("\n", " ", column)
     column = column.strip().strip('"').strip("'").lower()
     if not column:
         column = None
@@ -38,5 +38,5 @@ def print_csv(input_file, output_file, header, clustered_dupes):
         for id in orig_data:
             if id not in set(dupe_id_list):
                 row = orig_data[id]
-                row.insert(0, 'x')
+                row.insert(0, "x")
                 writer.writerow(row)
diff --git a/tests/test_api.py b/tests/test_api.py
index 1054394d8..52ad62caa 100644
--- a/tests/test_api.py
+++ b/tests/test_api.py
@@ -10,54 +10,73 @@ def icfi(x):
     return list(itertools.chain.from_iterable(x))
 
 
-DATA_SAMPLE = [({'age': '27', 'name': 'Kyle'},
-                {'age': '50', 'name': 'Bob'}),
-               ({'age': '27', 'name': 'Kyle'},
-                {'age': '35', 'name': 'William'}),
-               ({'age': '10', 'name': 'Sue'},
-                {'age': '35', 'name': 'William'}),
-               ({'age': '27', 'name': 'Kyle'},
-                {'age': '20', 'name': 'Jimmy'}),
-               ({'age': '75', 'name': 'Charlie'},
-                {'age': '21', 'name': 'Jimbo'})]
-
-data_dict = OrderedDict(((0, {'name': 'Bob', 'age': '51'}),
-                         (1, {'name': 'Linda', 'age': '50'}),
-                         (2, {'name': 'Gene', 'age': '12'}),
-                         (3, {'name': 'Tina', 'age': '15'}),
-                         (4, {'name': 'Bob B.', 'age': '51'}),
-                         (5, {'name': 'bob belcher', 'age': '51'}),
-                         (6, {'name': 'linda ', 'age': '50'})))
-
-data_dict_2 = OrderedDict(((7, {'name': 'BOB', 'age': '51'}),
-                           (8, {'name': 'LINDA', 'age': '50'}),
-                           (9, {'name': 'GENE', 'age': '12'}),
-                           (10, {'name': 'TINA', 'age': '15'}),
-                           (11, {'name': 'BOB B.', 'age': '51'}),
-                           (12, {'name': 'BOB BELCHER', 'age': '51'}),
-                           (13, {'name': 'LINDA ', 'age': '50'})))
+DATA_SAMPLE = [
+    ({"age": "27", "name": "Kyle"}, {"age": "50", "name": "Bob"}),
+    ({"age": "27", "name": "Kyle"}, {"age": "35", "name": "William"}),
+    ({"age": "10", "name": "Sue"}, {"age": "35", "name": "William"}),
+    ({"age": "27", "name": "Kyle"}, {"age": "20", "name": "Jimmy"}),
+    ({"age": "75", "name": "Charlie"}, {"age": "21", "name": "Jimbo"}),
+]
+
+data_dict = OrderedDict(
+    (
+        (0, {"name": "Bob", "age": "51"}),
+        (1, {"name": "Linda", "age": "50"}),
+        (2, {"name": "Gene", "age": "12"}),
+        (3, {"name": "Tina", "age": "15"}),
+        (4, {"name": "Bob B.", "age": "51"}),
+        (5, {"name": "bob belcher", "age": "51"}),
+        (6, {"name": "linda ", "age": "50"}),
+    )
+)
+
+data_dict_2 = OrderedDict(
+    (
+        (7, {"name": "BOB", "age": "51"}),
+        (8, {"name": "LINDA", "age": "50"}),
+        (9, {"name": "GENE", "age": "12"}),
+        (10, {"name": "TINA", "age": "15"}),
+        (11, {"name": "BOB B.", "age": "51"}),
+        (12, {"name": "BOB BELCHER", "age": "51"}),
+        (13, {"name": "LINDA ", "age": "50"}),
+    )
+)
 
 
 class ActiveMatch(unittest.TestCase):
     def setUp(self):
-        self.field_definition = [{'field': 'name', 'type': 'String'},
-                                 {'field': 'age', 'type': 'String'}]
+        self.field_definition = [
+            {"field": "name", "type": "String"},
+            {"field": "age", "type": "String"},
+        ]
 
     def test_initialize_fields(self):
         self.assertRaises(TypeError, dedupe.api.ActiveMatching)
 
         with self.assertRaises(ValueError):
-            dedupe.api.ActiveMatching([],)
+            dedupe.api.ActiveMatching(
+                [],
+            )
 
         with self.assertRaises(ValueError):
-            dedupe.api.ActiveMatching([{'field': 'name', 'type': 'Custom', 'comparator': lambda x: 1}],)
+            dedupe.api.ActiveMatching(
+                [{"field": "name", "type": "Custom", "comparator": lambda x: 1}],
+            )
 
         with self.assertRaises(ValueError):
-            dedupe.api.ActiveMatching([{'field': 'name', 'type': 'Custom', 'comparator': lambda x: 1},
-                                       {'field': 'age', 'type': 'Custom', 'comparator': lambda x: 1}],)
-
-        dedupe.api.ActiveMatching([{'field': 'name', 'type': 'Custom', 'comparator': lambda x: 1},
-                                   {'field': 'age', 'type': 'String'}],)
+            dedupe.api.ActiveMatching(
+                [
+                    {"field": "name", "type": "Custom", "comparator": lambda x: 1},
+                    {"field": "age", "type": "Custom", "comparator": lambda x: 1},
+                ],
+            )
+
+        dedupe.api.ActiveMatching(
+            [
+                {"field": "name", "type": "Custom", "comparator": lambda x: 1},
+                {"field": "age", "type": "String"},
+            ],
+        )
 
     def test_check_record(self):
         matcher = dedupe.api.ActiveMatching(self.field_definition)
@@ -67,15 +86,17 @@ def test_check_record(self):
         self.assertRaises(ValueError, matcher._checkRecordPair, (1, 2, 3))
         self.assertRaises(ValueError, matcher._checkRecordPair, ({}, {}))
 
-        matcher._checkRecordPair(({'name': 'Frank', 'age': '72'},
-                                  {'name': 'Bob', 'age': '27'}))
+        matcher._checkRecordPair(
+            ({"name": "Frank", "age": "72"}, {"name": "Bob", "age": "27"})
+        )
 
     def test_markPair(self):
         from collections import OrderedDict
-        good_training_pairs = OrderedDict((('match', DATA_SAMPLE[3:5]),
-                                           ('distinct', DATA_SAMPLE[0:3])))
-        bad_training_pairs = {'non_dupes': DATA_SAMPLE[0:3],
-                              'match': DATA_SAMPLE[3:5]}
+
+        good_training_pairs = OrderedDict(
+            (("match", DATA_SAMPLE[3:5]), ("distinct", DATA_SAMPLE[0:3]))
+        )
+        bad_training_pairs = {"non_dupes": DATA_SAMPLE[0:3], "match": DATA_SAMPLE[3:5]}
 
         matcher = dedupe.api.ActiveMatching(self.field_definition)
 
@@ -85,10 +106,9 @@ def test_markPair(self):
 
         with warnings.catch_warnings(record=True) as w:
             warnings.simplefilter("always")
-            matcher.mark_pairs({'match': [], 'distinct': []})
+            matcher.mark_pairs({"match": [], "distinct": []})
             assert len(w) == 1
-            assert str(
-                w[-1].message) == "Didn't return any labeled record pairs"
+            assert str(w[-1].message) == "Didn't return any labeled record pairs"
 
 
 if __name__ == "__main__":
diff --git a/tests/test_blocking.py b/tests/test_blocking.py
index 0032f33ec..5de465cf7 100644
--- a/tests/test_blocking.py
+++ b/tests/test_blocking.py
@@ -8,31 +8,30 @@
 class BlockingTest(unittest.TestCase):
     def setUp(self):
 
-        field_definition = [{'field': 'name', 'type': 'String'}]
+        field_definition = [{"field": "name", "type": "String"}]
         self.data_model = dedupe.Dedupe(field_definition).data_model
         self.training_pairs = {
-            'match': [({"name": "Bob", "age": "50"},
-                       {"name": "Bob", "age": "75"}),
-                      ({"name": "Meredith", "age": "40"},
-                       {"name": "Sue", "age": "10"})],
-            'distinct': [({"name": "Jimmy", "age": "20"},
-                          {"name": "Jimbo", "age": "21"}),
-                         ({"name": "Willy", "age": "35"},
-                          {"name": "William", "age": "35"}),
-                         ({"name": "William", "age": "36"},
-                          {"name": "William", "age": "35"})]
+            "match": [
+                ({"name": "Bob", "age": "50"}, {"name": "Bob", "age": "75"}),
+                ({"name": "Meredith", "age": "40"}, {"name": "Sue", "age": "10"}),
+            ],
+            "distinct": [
+                ({"name": "Jimmy", "age": "20"}, {"name": "Jimbo", "age": "21"}),
+                ({"name": "Willy", "age": "35"}, {"name": "William", "age": "35"}),
+                ({"name": "William", "age": "36"}, {"name": "William", "age": "35"}),
+            ],
         }
 
-        self.training = self.training_pairs['match'] + \
-            self.training_pairs['distinct']
+        self.training = self.training_pairs["match"] + self.training_pairs["distinct"]
         self.training_records = []
         for pair in self.training:
             for record in pair:
                 if record not in self.training_records:
                     self.training_records.append(record)
 
-        self.simple = lambda x: set([str(k) for k in x
-                                     if "CompoundPredicate" not in str(k)])
+        self.simple = lambda x: set(
+            [str(k) for k in x if "CompoundPredicate" not in str(k)]
+        )
 
 
 class TfidfTest(unittest.TestCase):
@@ -53,23 +52,19 @@ def setUp(self):
     def test_unconstrained_inverted_index(self):
 
         blocker = dedupe.blocking.Fingerprinter(
-            [dedupe.predicates.TfidfTextSearchPredicate(0.0, "name")])
+            [dedupe.predicates.TfidfTextSearchPredicate(0.0, "name")]
+        )
 
-        blocker.index(set(record["name"]
-                          for record
-                          in viewvalues(self.data_d)),
-                      "name")
+        blocker.index(set(record["name"] for record in viewvalues(self.data_d)), "name")
 
         blocks = defaultdict(set)
 
         for block_key, record_id in blocker(self.data_d.items()):
             blocks[block_key].add(record_id)
 
-        blocks = set([frozenset(block) for block in blocks.values()
-                      if len(block) > 1])
+        blocks = set([frozenset(block) for block in blocks.values() if len(block) > 1])
 
-        assert blocks ==\
-            set([frozenset([120, 125]), frozenset([130, 135])])
+        assert blocks == set([frozenset([120, 125]), frozenset([130, 135])])
 
 
 class TfIndexUnindex(unittest.TestCase):
@@ -88,17 +83,20 @@ def setUp(self):
         }
 
         self.blocker = dedupe.blocking.Fingerprinter(
-            [dedupe.predicates.TfidfTextSearchPredicate(0.0, "name")])
-
-        self.records_1 = dict((record_id, record)
-                              for record_id, record
-                              in viewitems(data_d)
-                              if record["dataset"] == 0)
-
-        self.fields_2 = dict((record_id, record["name"])
-                             for record_id, record
-                             in viewitems(data_d)
-                             if record["dataset"] == 1)
+            [dedupe.predicates.TfidfTextSearchPredicate(0.0, "name")]
+        )
+
+        self.records_1 = dict(
+            (record_id, record)
+            for record_id, record in viewitems(data_d)
+            if record["dataset"] == 0
+        )
+
+        self.fields_2 = dict(
+            (record_id, record["name"])
+            for record_id, record in viewitems(data_d)
+            if record["dataset"] == 1
+        )
 
     def test_index(self):
         self.blocker.index(set(self.fields_2.values()), "name")
diff --git a/tests/test_canonical.py b/tests/test_canonical.py
index 15abaf80b..28fbe21e0 100644
--- a/tests/test_canonical.py
+++ b/tests/test_canonical.py
@@ -3,53 +3,55 @@
 
 
 class CanonicalizationTest(unittest.TestCase):
-
     def test_get_centroid(self):
         from affinegap import normalizedAffineGapDistance as comparator
 
-        attributeList = ['mary crane center',
-                         'mary crane center north',
-                         'mary crane league - mary crane - west',
-                         'mary crane league mary crane center (east)',
-                         'mary crane league mary crane center (north)',
-                         'mary crane league mary crane center (west)',
-                         'mary crane league - mary crane - east',
-                         'mary crane family and day care center',
-                         'mary crane west', 'mary crane center east',
-                         'mary crane league mary crane center (east)',
-                         'mary crane league mary crane center (north)',
-                         'mary crane league mary crane center (west)',
-                         'mary crane league',
-                         'mary crane',
-                         'mary crane east 0-3',
-                         'mary crane north',
-                         'mary crane north 0-3',
-                         'mary crane league - mary crane - west',
-                         'mary crane league - mary crane - north',
-                         'mary crane league - mary crane - east',
-                         'mary crane league - mary crane - west',
-                         'mary crane league - mary crane - north',
-                         'mary crane league - mary crane - east']
+        attributeList = [
+            "mary crane center",
+            "mary crane center north",
+            "mary crane league - mary crane - west",
+            "mary crane league mary crane center (east)",
+            "mary crane league mary crane center (north)",
+            "mary crane league mary crane center (west)",
+            "mary crane league - mary crane - east",
+            "mary crane family and day care center",
+            "mary crane west",
+            "mary crane center east",
+            "mary crane league mary crane center (east)",
+            "mary crane league mary crane center (north)",
+            "mary crane league mary crane center (west)",
+            "mary crane league",
+            "mary crane",
+            "mary crane east 0-3",
+            "mary crane north",
+            "mary crane north 0-3",
+            "mary crane league - mary crane - west",
+            "mary crane league - mary crane - north",
+            "mary crane league - mary crane - east",
+            "mary crane league - mary crane - west",
+            "mary crane league - mary crane - north",
+            "mary crane league - mary crane - east",
+        ]
 
         centroid = dedupe.canonical.getCentroid(attributeList, comparator)
-        assert centroid == 'mary crane'
+        assert centroid == "mary crane"
 
     def test_get_canonical_rep(self):
-        record_list = [{"name": "mary crane",
-                        "address": "123 main st", "zip": "12345"},
-                       {"name": "mary crane east",
-                        "address": "123 main street", "zip": ""},
-                       {"name": "mary crane west",
-                        "address": "123 man st", "zip": ""}]
+        record_list = [
+            {"name": "mary crane", "address": "123 main st", "zip": "12345"},
+            {"name": "mary crane east", "address": "123 main street", "zip": ""},
+            {"name": "mary crane west", "address": "123 man st", "zip": ""},
+        ]
 
         rep = dedupe.canonical.getCanonicalRep(record_list)
-        assert rep == {'name': 'mary crane',
-                       'address': '123 main street', 'zip': "12345"}
+        assert rep == {
+            "name": "mary crane",
+            "address": "123 main street",
+            "zip": "12345",
+        }
 
         rep = dedupe.canonical.getCanonicalRep(record_list[0:2])
-        assert rep == {"name": "mary crane",
-                       "address": "123 main st", "zip": "12345"}
+        assert rep == {"name": "mary crane", "address": "123 main st", "zip": "12345"}
 
         rep = dedupe.canonical.getCanonicalRep(record_list[0:1])
-        assert rep == {"name": "mary crane",
-                       "address": "123 main st", "zip": "12345"}
+        assert rep == {"name": "mary crane", "address": "123 main st", "zip": "12345"}
diff --git a/tests/test_core.py b/tests/test_core.py
index fc3e51ec3..4399414bc 100644
--- a/tests/test_core.py
+++ b/tests/test_core.py
@@ -10,133 +10,146 @@ class ScoreDuplicates(unittest.TestCase):
     def setUp(self):
         random.seed(123)
 
-        long_string = 'asa;sasdfjasdio;fio;asdnfasdvnvao;asduifvnavjasdfasdfasfasasdfasdfasdfasdfasdfsdfasgnuavpidcvaspdivnaspdivninasduinguipghauipsdfnvaspfighapsdifnasdifnasdpighuignpaguinpgiasidfjasdfjsdofgiongag'  # noqa: E501
-
-        self.records = iter([((long_string, {'name': 'Margret', 'age': '32'}),
-                              ('2', {'name': 'Marga', 'age': '33'})),
-                             (('2', {'name': 'Marga', 'age': '33'}),
-                              ('3', {'name': 'Maria', 'age': '19'})),
-                             (('4', {'name': 'Maria', 'age': '19'}),
-                              ('5', {'name': 'Monica', 'age': '39'})),
-                             (('6', {'name': 'Monica', 'age': '39'}),
-                              ('7', {'name': 'Mira', 'age': '47'})),
-                             (('8', {'name': 'Mira', 'age': '47'}),
-                              ('9', {'name': 'Mona', 'age': '9'})),
-                             ])
-
-        deduper = dedupe.Dedupe([{'field': "name", 'type': 'String'}])
+        long_string = "asa;sasdfjasdio;fio;asdnfasdvnvao;asduifvnavjasdfasdfasfasasdfasdfasdfasdfasdfsdfasgnuavpidcvaspdivnaspdivninasduinguipghauipsdfnvaspfighapsdifnasdifnasdpighuignpaguinpgiasidfjasdfjsdofgiongag"  # noqa: E501
+
+        self.records = iter(
+            [
+                (
+                    (long_string, {"name": "Margret", "age": "32"}),
+                    ("2", {"name": "Marga", "age": "33"}),
+                ),
+                (
+                    ("2", {"name": "Marga", "age": "33"}),
+                    ("3", {"name": "Maria", "age": "19"}),
+                ),
+                (
+                    ("4", {"name": "Maria", "age": "19"}),
+                    ("5", {"name": "Monica", "age": "39"}),
+                ),
+                (
+                    ("6", {"name": "Monica", "age": "39"}),
+                    ("7", {"name": "Mira", "age": "47"}),
+                ),
+                (
+                    ("8", {"name": "Mira", "age": "47"}),
+                    ("9", {"name": "Mona", "age": "9"}),
+                ),
+            ]
+        )
+
+        deduper = dedupe.Dedupe([{"field": "name", "type": "String"}])
         self.data_model = deduper.data_model
         self.classifier = deduper.classifier
         self.classifier.weights = [-1.0302742719650269]
         self.classifier.bias = 4.76
 
-        score_dtype = [('pairs', '<U192', 2), ('score', 'f4')]
+        score_dtype = [("pairs", "<U192", 2), ("score", "f4")]
 
-        self.desired_scored_pairs = numpy.array([((long_string, '2'), 0.96),
-                                                 (['2', '3'], 0.96),
-                                                 (['4', '5'], 0.78),
-                                                 (['6', '7'], 0.72),
-                                                 (['8', '9'], 0.84)],
-                                                dtype=score_dtype)
+        self.desired_scored_pairs = numpy.array(
+            [
+                ((long_string, "2"), 0.96),
+                (["2", "3"], 0.96),
+                (["4", "5"], 0.78),
+                (["6", "7"], 0.72),
+                (["8", "9"], 0.84),
+            ],
+            dtype=score_dtype,
+        )
 
     def test_score_duplicates(self):
-        scores = dedupe.core.scoreDuplicates(self.records,
-                                             self.data_model,
-                                             self.classifier,
-                                             2)
+        scores = dedupe.core.scoreDuplicates(
+            self.records, self.data_model, self.classifier, 2
+        )
 
-        numpy.testing.assert_equal(scores['pairs'],
-                                   self.desired_scored_pairs['pairs'])
+        numpy.testing.assert_equal(scores["pairs"], self.desired_scored_pairs["pairs"])
 
-        numpy.testing.assert_allclose(scores['score'],
-                                      self.desired_scored_pairs['score'], 2)
+        numpy.testing.assert_allclose(
+            scores["score"], self.desired_scored_pairs["score"], 2
+        )
 
     def test_score_duplicates_with_zeros(self):
         self.classifier.weights = [-1000]
         self.classifier.bias = 1000
-        self.records = iter([(('1', {'name': 'ABCD'}),
-                              ('2', {'name': 'EFGH'})),
-                             (('3', {'name': 'IJKL'}),
-                              ('4', {'name': 'IJKL'}))
-                             ])
-        scores = dedupe.core.scoreDuplicates(self.records,
-                                             self.data_model,
-                                             self.classifier,
-                                             2)
+        self.records = iter(
+            [
+                (("1", {"name": "ABCD"}), ("2", {"name": "EFGH"})),
+                (("3", {"name": "IJKL"}), ("4", {"name": "IJKL"})),
+            ]
+        )
+        scores = dedupe.core.scoreDuplicates(
+            self.records, self.data_model, self.classifier, 2
+        )
 
-        score_dtype = [('pairs', '<U1', 2), ('score', 'f4')]
+        score_dtype = [("pairs", "<U1", 2), ("score", "f4")]
 
-        self.desired_scored_pairs = numpy.array([(['1', '2'], 0),
-                                                 (['3', '4'], 1)],
-                                                dtype=score_dtype)
+        self.desired_scored_pairs = numpy.array(
+            [(["1", "2"], 0), (["3", "4"], 1)], dtype=score_dtype
+        )
 
-        numpy.testing.assert_equal(scores['pairs'],
-                                   self.desired_scored_pairs['pairs'])
+        numpy.testing.assert_equal(scores["pairs"], self.desired_scored_pairs["pairs"])
 
-        numpy.testing.assert_allclose(scores['score'],
-                                      self.desired_scored_pairs['score'], 2)
+        numpy.testing.assert_allclose(
+            scores["score"], self.desired_scored_pairs["score"], 2
+        )
 
 
 class FieldDistances(unittest.TestCase):
-
     def test_exact_comparator(self):
-        deduper = dedupe.Dedupe([{'field': 'name',
-                                  'type': 'Exact'}
-                                 ])
+        deduper = dedupe.Dedupe([{"field": "name", "type": "Exact"}])
 
-        record_pairs = (({'name': 'Shmoo'}, {'name': 'Shmee'}),
-                        ({'name': 'Shmoo'}, {'name': 'Shmoo'}))
+        record_pairs = (
+            ({"name": "Shmoo"}, {"name": "Shmee"}),
+            ({"name": "Shmoo"}, {"name": "Shmoo"}),
+        )
 
-        numpy.testing.assert_array_almost_equal(deduper.data_model.distances(record_pairs),
-                                                numpy.array([[0.0],
-                                                             [1.0]]),
-                                                3)
+        numpy.testing.assert_array_almost_equal(
+            deduper.data_model.distances(record_pairs), numpy.array([[0.0], [1.0]]), 3
+        )
 
     def test_comparator(self):
-        deduper = dedupe.Dedupe([{'field': 'type',
-                                  'type': 'Categorical',
-
-                                  'categories': ['a', 'b', 'c']}])
+        deduper = dedupe.Dedupe(
+            [{"field": "type", "type": "Categorical", "categories": ["a", "b", "c"]}]
+        )
 
-        record_pairs = (({'type': 'a'},
-                         {'type': 'b'}),
-                        ({'type': 'a'},
-                         {'type': 'c'}))
+        record_pairs = (({"type": "a"}, {"type": "b"}), ({"type": "a"}, {"type": "c"}))
 
-        numpy.testing.assert_array_almost_equal(deduper.data_model.distances(record_pairs),
-                                                numpy.array([[0, 0, 1, 0, 0],
-                                                             [0, 0, 0, 1, 0]]),
-                                                3)
+        numpy.testing.assert_array_almost_equal(
+            deduper.data_model.distances(record_pairs),
+            numpy.array([[0, 0, 1, 0, 0], [0, 0, 0, 1, 0]]),
+            3,
+        )
 
     def test_comparator_interaction(self):
-        deduper = dedupe.Dedupe([{'field': 'type',
-                                  'variable name': 'type',
-                                  'type': 'Categorical',
-                                  'categories': ['a', 'b']},
-                                 {'type': 'Interaction',
-                                  'interaction variables': ['type', 'name']},
-                                 {'field': 'name',
-                                  'variable name': 'name',
-                                  'type': 'Exact'}])
-
-        record_pairs = (({'name': 'steven', 'type': 'a'},
-                         {'name': 'steven', 'type': 'b'}),
-                        ({'name': 'steven', 'type': 'b'},
-                         {'name': 'steven', 'type': 'b'}))
-
-        numpy.testing.assert_array_almost_equal(deduper.data_model.distances(record_pairs),
-                                                numpy.array([[0, 1, 1, 0, 1],
-                                                             [1, 0, 1, 1, 0]]), 3)
+        deduper = dedupe.Dedupe(
+            [
+                {
+                    "field": "type",
+                    "variable name": "type",
+                    "type": "Categorical",
+                    "categories": ["a", "b"],
+                },
+                {"type": "Interaction", "interaction variables": ["type", "name"]},
+                {"field": "name", "variable name": "name", "type": "Exact"},
+            ]
+        )
+
+        record_pairs = (
+            ({"name": "steven", "type": "a"}, {"name": "steven", "type": "b"}),
+            ({"name": "steven", "type": "b"}, {"name": "steven", "type": "b"}),
+        )
+
+        numpy.testing.assert_array_almost_equal(
+            deduper.data_model.distances(record_pairs),
+            numpy.array([[0, 1, 1, 0, 1], [1, 0, 1, 1, 0]]),
+            3,
+        )
 
 
 class Unique(unittest.TestCase):
-
     def test_unique(self):
-        target = ([{1: 1, 2: 2}, {3: 3, 4: 4}],
-                  [{3: 3, 4: 4}, {1: 1, 2: 2}])
+        target = ([{1: 1, 2: 2}, {3: 3, 4: 4}], [{3: 3, 4: 4}, {1: 1, 2: 2}])
 
-        assert dedupe.core.unique(
-            [{1: 1, 2: 2}, {3: 3, 4: 4}, {1: 1, 2: 2}]) in target
+        assert dedupe.core.unique([{1: 1, 2: 2}, {3: 3, 4: 4}, {1: 1, 2: 2}]) in target
 
 
 if __name__ == "__main__":
diff --git a/tests/test_dedupe.py b/tests/test_dedupe.py
index 7618dc592..366c69c3d 100644
--- a/tests/test_dedupe.py
+++ b/tests/test_dedupe.py
@@ -7,143 +7,160 @@
 
 import dedupe
 
-DATA = {100: {"name": "Bob", "age": "50"},
-        105: {"name": "Charlie", "age": "75"},
-        110: {"name": "Meredith", "age": "40"},
-        115: {"name": "Sue", "age": "10"},
-        120: {"name": "Jimmy", "age": "20"},
-        125: {"name": "Jimbo", "age": "21"},
-        130: {"name": "Willy", "age": "35"},
-        135: {"name": "William", "age": "35"},
-        140: {"name": "Martha", "age": "19"},
-        145: {"name": "Kyle", "age": "27"}
-        }
-
-DATA_SAMPLE = (({'age': '27', 'name': 'Kyle'},
-                {'age': '50', 'name': 'Bob'}),
-               ({'age': '27', 'name': 'Kyle'},
-                {'age': '35', 'name': 'William'}),
-               ({'age': '10', 'name': 'Sue'},
-                {'age': '35', 'name': 'William'}),
-               ({'age': '27', 'name': 'Kyle'},
-                {'age': '20', 'name': 'Jimmy'}),
-               ({'age': '75', 'name': 'Charlie'},
-                {'age': '21', 'name': 'Jimbo'}))
+DATA = {
+    100: {"name": "Bob", "age": "50"},
+    105: {"name": "Charlie", "age": "75"},
+    110: {"name": "Meredith", "age": "40"},
+    115: {"name": "Sue", "age": "10"},
+    120: {"name": "Jimmy", "age": "20"},
+    125: {"name": "Jimbo", "age": "21"},
+    130: {"name": "Willy", "age": "35"},
+    135: {"name": "William", "age": "35"},
+    140: {"name": "Martha", "age": "19"},
+    145: {"name": "Kyle", "age": "27"},
+}
+
+DATA_SAMPLE = (
+    ({"age": "27", "name": "Kyle"}, {"age": "50", "name": "Bob"}),
+    ({"age": "27", "name": "Kyle"}, {"age": "35", "name": "William"}),
+    ({"age": "10", "name": "Sue"}, {"age": "35", "name": "William"}),
+    ({"age": "27", "name": "Kyle"}, {"age": "20", "name": "Jimmy"}),
+    ({"age": "75", "name": "Charlie"}, {"age": "21", "name": "Jimbo"}),
+)
 
 
 class DataModelTest(unittest.TestCase):
-
     def test_data_model(self):
         DataModel = dedupe.datamodel.DataModel
 
         self.assertRaises(TypeError, DataModel)
 
-        data_model = DataModel([{'field': 'a',
-                                 'variable name': 'a',
-                                 'type': 'String'},
-                                {'field': 'b',
-                                 'variable name': 'b',
-                                 'type': 'String'},
-                                {'type': 'Interaction',
-                                 'interaction variables': ['a', 'b']}])
+        data_model = DataModel(
+            [
+                {"field": "a", "variable name": "a", "type": "String"},
+                {"field": "b", "variable name": "b", "type": "String"},
+                {"type": "Interaction", "interaction variables": ["a", "b"]},
+            ]
+        )
 
         assert data_model._interaction_indices == [[0, 1]]
 
-        data_model = DataModel([{'field': 'a',
-                                 'variable name': 'a',
-                                 'type': 'String',
-                                 'has missing': True},
-                                {'field': 'b',
-                                 'variable name': 'b',
-                                 'type': 'String'},
-                                {'type': 'Interaction',
-                                 'interaction variables': ['a', 'b']}])
+        data_model = DataModel(
+            [
+                {
+                    "field": "a",
+                    "variable name": "a",
+                    "type": "String",
+                    "has missing": True,
+                },
+                {"field": "b", "variable name": "b", "type": "String"},
+                {"type": "Interaction", "interaction variables": ["a", "b"]},
+            ]
+        )
 
         assert data_model._missing_field_indices == [0, 2]
 
-        data_model = DataModel([{'field': 'a',
-                                 'variable name': 'a',
-                                 'type': 'String',
-                                 'has missing': False},
-                                {'field': 'b',
-                                 'variable name': 'b',
-                                 'type': 'String'},
-                                {'type': 'Interaction',
-                                 'interaction variables': ['a', 'b']}])
+        data_model = DataModel(
+            [
+                {
+                    "field": "a",
+                    "variable name": "a",
+                    "type": "String",
+                    "has missing": False,
+                },
+                {"field": "b", "variable name": "b", "type": "String"},
+                {"type": "Interaction", "interaction variables": ["a", "b"]},
+            ]
+        )
 
         assert data_model._missing_field_indices == []
 
 
 class ConnectedComponentsTest(unittest.TestCase):
     def test_components(self):
-        G = numpy.array([((1, 2), .1),
-                         ((2, 3), .2),
-                         ((4, 5), .2),
-                         ((4, 6), .2),
-                         ((7, 9), .2),
-                         ((8, 9), .2),
-                         ((10, 11), .2),
-                         ((12, 13), .2),
-                         ((12, 14), .5),
-                         ((11, 12), .2)],
-                        dtype=[('pairs', 'i4', 2), ('score', 'f4')])
+        G = numpy.array(
+            [
+                ((1, 2), 0.1),
+                ((2, 3), 0.2),
+                ((4, 5), 0.2),
+                ((4, 6), 0.2),
+                ((7, 9), 0.2),
+                ((8, 9), 0.2),
+                ((10, 11), 0.2),
+                ((12, 13), 0.2),
+                ((12, 14), 0.5),
+                ((11, 12), 0.2),
+            ],
+            dtype=[("pairs", "i4", 2), ("score", "f4")],
+        )
         components = dedupe.clustering.connected_components
-        G_components = {frozenset(tuple(edge) for edge, _ in component)
-                        for component in components(G, 30000)}
-        assert G_components == {frozenset(((1, 2), (2, 3))),
-                                frozenset(((4, 5), (4, 6))),
-                                frozenset(
-                                    ((12, 13), (12, 14), (10, 11), (11, 12))),
-                                frozenset(((7, 9), (8, 9)))}
+        G_components = {
+            frozenset(tuple(edge) for edge, _ in component)
+            for component in components(G, 30000)
+        }
+        assert G_components == {
+            frozenset(((1, 2), (2, 3))),
+            frozenset(((4, 5), (4, 6))),
+            frozenset(((12, 13), (12, 14), (10, 11), (11, 12))),
+            frozenset(((7, 9), (8, 9))),
+        }
 
 
 class ClusteringTest(unittest.TestCase):
     def setUp(self):
         # Fully connected star network
-        self.dupes = numpy.array([((1, 2), .86),
-                                  ((1, 3), .72),
-                                  ((1, 4), .2),
-                                  ((1, 5), .6),
-                                  ((2, 3), .86),
-                                  ((2, 4), .2),
-                                  ((2, 5), .72),
-                                  ((3, 4), .3),
-                                  ((3, 5), .5),
-                                  ((4, 5), .72),
-                                  ((10, 11), .9)],
-                                 dtype=[('pairs', 'i4', 2),
-                                        ('score', 'f4')])
+        self.dupes = numpy.array(
+            [
+                ((1, 2), 0.86),
+                ((1, 3), 0.72),
+                ((1, 4), 0.2),
+                ((1, 5), 0.6),
+                ((2, 3), 0.86),
+                ((2, 4), 0.2),
+                ((2, 5), 0.72),
+                ((3, 4), 0.3),
+                ((3, 5), 0.5),
+                ((4, 5), 0.72),
+                ((10, 11), 0.9),
+            ],
+            dtype=[("pairs", "i4", 2), ("score", "f4")],
+        )
 
         # Dupes with Ids as String
-        self.str_dupes = numpy.array([(('1', '2'), .86),
-                                      (('1', '3'), .72),
-                                      (('1', '4'), .2),
-                                      (('1', '5'), .6),
-                                      (('2', '3'), .86),
-                                      (('2', '4'), .2),
-                                      (('2', '5'), .72),
-                                      (('3', '4'), .3),
-                                      (('3', '5'), .5),
-                                      (('4', '5'), .72)],
-                                     dtype=[('pairs', 'S4', 2),
-                                            ('score', 'f4')])
-
-        self.bipartite_dupes = (((1, 5), .1),
-                                ((1, 6), .72),
-                                ((1, 7), .2),
-                                ((1, 8), .6),
-                                ((2, 5), .2),
-                                ((2, 6), .2),
-                                ((2, 7), .72),
-                                ((2, 8), .3),
-                                ((3, 5), .24),
-                                ((3, 6), .72),
-                                ((3, 7), .24),
-                                ((3, 8), .65),
-                                ((4, 5), .63),
-                                ((4, 6), .96),
-                                ((4, 7), .23),
-                                ((5, 8), .24))
+        self.str_dupes = numpy.array(
+            [
+                (("1", "2"), 0.86),
+                (("1", "3"), 0.72),
+                (("1", "4"), 0.2),
+                (("1", "5"), 0.6),
+                (("2", "3"), 0.86),
+                (("2", "4"), 0.2),
+                (("2", "5"), 0.72),
+                (("3", "4"), 0.3),
+                (("3", "5"), 0.5),
+                (("4", "5"), 0.72),
+            ],
+            dtype=[("pairs", "S4", 2), ("score", "f4")],
+        )
+
+        self.bipartite_dupes = (
+            ((1, 5), 0.1),
+            ((1, 6), 0.72),
+            ((1, 7), 0.2),
+            ((1, 8), 0.6),
+            ((2, 5), 0.2),
+            ((2, 6), 0.2),
+            ((2, 7), 0.72),
+            ((2, 8), 0.3),
+            ((3, 5), 0.24),
+            ((3, 6), 0.72),
+            ((3, 7), 0.24),
+            ((3, 8), 0.65),
+            ((4, 5), 0.63),
+            ((4, 6), 0.96),
+            ((4, 7), 0.23),
+            ((5, 8), 0.24),
+        )
 
     def clusterEquals(self, x, y):
         if [] == x == y:
@@ -162,127 +179,138 @@ def clusterEquals(self, x, y):
 
     def test_hierarchical(self):
         hierarchical = dedupe.clustering.cluster
-        assert self.clusterEquals(list(hierarchical(self.dupes, 1)),
-                                  [])
-
-        assert self.clusterEquals(list(hierarchical(self.dupes, 0.5)),
-                                  [((1, 2, 3),
-                                    (0.778,
-                                     0.860,
-                                     0.778)),
-                                   ((4, 5),
-                                    (0.720,
-                                     0.720)),
-                                   ((10, 11),
-                                    (0.899,
-                                     0.899))])
+        assert self.clusterEquals(list(hierarchical(self.dupes, 1)), [])
+
+        assert self.clusterEquals(
+            list(hierarchical(self.dupes, 0.5)),
+            [
+                ((1, 2, 3), (0.778, 0.860, 0.778)),
+                ((4, 5), (0.720, 0.720)),
+                ((10, 11), (0.899, 0.899)),
+            ],
+        )
 
         print(hierarchical(self.dupes, 0.0))
-        assert self.clusterEquals(list(hierarchical(self.dupes, 0)),
-                                  [((1, 2, 3, 4, 5),
-                                    (0.526,
-                                     0.564,
-                                     0.542,
-                                     0.320,
-                                     0.623)),
-                                   ((10, 11),
-                                    (0.899,
-                                     0.899))])
+        assert self.clusterEquals(
+            list(hierarchical(self.dupes, 0)),
+            [
+                ((1, 2, 3, 4, 5), (0.526, 0.564, 0.542, 0.320, 0.623)),
+                ((10, 11), (0.899, 0.899)),
+            ],
+        )
 
         assert list(hierarchical(self.str_dupes, 1)) == []
-        assert list(zip(*hierarchical(self.str_dupes, 0.5)))[0] == ((b'1', b'2', b'3'),
-                                                                    (b'4', b'5'))
-        assert list(zip(*hierarchical(self.str_dupes, 0)))[0] == ((b'1', b'2', b'3', b'4', b'5'),)
+        assert list(zip(*hierarchical(self.str_dupes, 0.5)))[0] == (
+            (b"1", b"2", b"3"),
+            (b"4", b"5"),
+        )
+        assert list(zip(*hierarchical(self.str_dupes, 0)))[0] == (
+            (b"1", b"2", b"3", b"4", b"5"),
+        )
 
     def test_greedy_matching(self):
         greedyMatch = dedupe.clustering.greedyMatching
 
-        bipartite_dupes = numpy.array(list(self.bipartite_dupes),
-                                      dtype=[('ids', int, 2),
-                                             ('score', float)])
+        bipartite_dupes = numpy.array(
+            list(self.bipartite_dupes), dtype=[("ids", int, 2), ("score", float)]
+        )
 
-        assert list(greedyMatch(bipartite_dupes)) == [((4, 6), 0.96),
-                                                      ((2, 7), 0.72),
-                                                      ((3, 8), 0.65),
-                                                      ((1, 5), 0.1)]
+        assert list(greedyMatch(bipartite_dupes)) == [
+            ((4, 6), 0.96),
+            ((2, 7), 0.72),
+            ((3, 8), 0.65),
+            ((1, 5), 0.1),
+        ]
 
     def test_gazette_matching(self):
 
         gazetteMatch = dedupe.clustering.gazetteMatching
-        blocked_dupes = itertools.groupby(self.bipartite_dupes,
-                                          key=lambda x: x[0][0])
+        blocked_dupes = itertools.groupby(self.bipartite_dupes, key=lambda x: x[0][0])
 
         def to_numpy(x):
-            return numpy.array(x, dtype=[('ids', int, 2),
-                                         ('score', float)])
+            return numpy.array(x, dtype=[("ids", int, 2), ("score", float)])
 
         blocked_dupes = [to_numpy(list(block)) for _, block in blocked_dupes]
 
-        target = [(((1, 6), 0.72), ((1, 8), 0.6)),
-                  (((2, 7), 0.72), ((2, 8), 0.3)),
-                  (((3, 6), 0.72), ((3, 8), 0.65)),
-                  (((4, 6), 0.96), ((4, 5), 0.63)),
-                  (((5, 8), 0.24),)]
+        target = [
+            (((1, 6), 0.72), ((1, 8), 0.6)),
+            (((2, 7), 0.72), ((2, 8), 0.3)),
+            (((3, 6), 0.72), ((3, 8), 0.65)),
+            (((4, 6), 0.96), ((4, 5), 0.63)),
+            (((5, 8), 0.24),),
+        ]
 
-        assert [tuple((tuple(pair), score) for pair, score in each.tolist())
-                for each in gazetteMatch(blocked_dupes, n_matches=2)] == target
+        assert [
+            tuple((tuple(pair), score) for pair, score in each.tolist())
+            for each in gazetteMatch(blocked_dupes, n_matches=2)
+        ] == target
 
 
 class PredicatesTest(unittest.TestCase):
     def test_predicates_correctness(self):
-        field = '123 16th st'
-        assert dedupe.predicates.sortedAcronym(field) == ('11s',)
-        assert dedupe.predicates.wholeFieldPredicate(field) == ('123 16th st',)
-        assert dedupe.predicates.firstTokenPredicate(field) == ('123',)
-        assert dedupe.predicates.firstTokenPredicate('') == ()
-        assert dedupe.predicates.firstTokenPredicate('123/') == ('123',)
-        assert dedupe.predicates.firstTwoTokensPredicate(field) == ('123 16th',)
-        assert dedupe.predicates.firstTwoTokensPredicate('oneword') == ()
-        assert dedupe.predicates.firstTwoTokensPredicate('') == ()
-        assert dedupe.predicates.firstTwoTokensPredicate('123 456/') == ('123 456',)
-        assert dedupe.predicates.tokenFieldPredicate(' ') == set([])
-        assert dedupe.predicates.tokenFieldPredicate(
-            field) == set(['123', '16th', 'st'])
-        assert dedupe.predicates.commonIntegerPredicate(
-            field) == set(['123', '16'])
-        assert dedupe.predicates.commonIntegerPredicate('foo') == set([])
-        assert dedupe.predicates.firstIntegerPredicate('foo') == ()
-        assert dedupe.predicates.firstIntegerPredicate('1foo') == ('1',)
-        assert dedupe.predicates.firstIntegerPredicate('f1oo') == ()
-        assert dedupe.predicates.sameThreeCharStartPredicate(field) == ('123',)
-        assert dedupe.predicates.sameThreeCharStartPredicate('12') == ('12', )
-        assert dedupe.predicates.commonFourGram('12') == set([])
-        assert dedupe.predicates.sameFiveCharStartPredicate(
-            field) == ('12316',)
-        assert dedupe.predicates.sameSevenCharStartPredicate(
-            field) == ('12316th',)
-        assert dedupe.predicates.nearIntegersPredicate(
-            field) == set(['15', '17', '16', '122', '123', '124'])
+        field = "123 16th st"
+        assert dedupe.predicates.sortedAcronym(field) == ("11s",)
+        assert dedupe.predicates.wholeFieldPredicate(field) == ("123 16th st",)
+        assert dedupe.predicates.firstTokenPredicate(field) == ("123",)
+        assert dedupe.predicates.firstTokenPredicate("") == ()
+        assert dedupe.predicates.firstTokenPredicate("123/") == ("123",)
+        assert dedupe.predicates.firstTwoTokensPredicate(field) == ("123 16th",)
+        assert dedupe.predicates.firstTwoTokensPredicate("oneword") == ()
+        assert dedupe.predicates.firstTwoTokensPredicate("") == ()
+        assert dedupe.predicates.firstTwoTokensPredicate("123 456/") == ("123 456",)
+        assert dedupe.predicates.tokenFieldPredicate(" ") == set([])
+        assert dedupe.predicates.tokenFieldPredicate(field) == set(
+            ["123", "16th", "st"]
+        )
+        assert dedupe.predicates.commonIntegerPredicate(field) == set(["123", "16"])
+        assert dedupe.predicates.commonIntegerPredicate("foo") == set([])
+        assert dedupe.predicates.firstIntegerPredicate("foo") == ()
+        assert dedupe.predicates.firstIntegerPredicate("1foo") == ("1",)
+        assert dedupe.predicates.firstIntegerPredicate("f1oo") == ()
+        assert dedupe.predicates.sameThreeCharStartPredicate(field) == ("123",)
+        assert dedupe.predicates.sameThreeCharStartPredicate("12") == ("12",)
+        assert dedupe.predicates.commonFourGram("12") == set([])
+        assert dedupe.predicates.sameFiveCharStartPredicate(field) == ("12316",)
+        assert dedupe.predicates.sameSevenCharStartPredicate(field) == ("12316th",)
+        assert dedupe.predicates.nearIntegersPredicate(field) == set(
+            ["15", "17", "16", "122", "123", "124"]
+        )
         assert dedupe.predicates.commonFourGram(field) == set(
-            ['1231', '2316', '316t', '16th', '6ths', 'thst'])
+            ["1231", "2316", "316t", "16th", "6ths", "thst"]
+        )
         assert dedupe.predicates.commonSixGram(field) == set(
-            ['12316t', '2316th', '316ths', '16thst'])
-        assert dedupe.predicates.initials(field, 12) == ('123 16th st',)
-        assert dedupe.predicates.initials(field, 7) == ('123 16t',)
-        assert dedupe.predicates.ngrams(
-            field, 3) == ['123', '23 ', '3 1', ' 16', '16t', '6th', 'th ', 'h s', ' st']
-        assert dedupe.predicates.commonTwoElementsPredicate(
-            (1, 2, 3)) == set(('1 2', '2 3'))
+            ["12316t", "2316th", "316ths", "16thst"]
+        )
+        assert dedupe.predicates.initials(field, 12) == ("123 16th st",)
+        assert dedupe.predicates.initials(field, 7) == ("123 16t",)
+        assert dedupe.predicates.ngrams(field, 3) == [
+            "123",
+            "23 ",
+            "3 1",
+            " 16",
+            "16t",
+            "6th",
+            "th ",
+            "h s",
+            " st",
+        ]
+        assert dedupe.predicates.commonTwoElementsPredicate((1, 2, 3)) == set(
+            ("1 2", "2 3")
+        )
         assert dedupe.predicates.commonTwoElementsPredicate((1,)) == set([])
-        assert dedupe.predicates.commonThreeElementsPredicate(
-            (1, 2, 3)) == set(('1 2 3',))
+        assert dedupe.predicates.commonThreeElementsPredicate((1, 2, 3)) == set(
+            ("1 2 3",)
+        )
         assert dedupe.predicates.commonThreeElementsPredicate((1,)) == set([])
 
-        assert dedupe.predicates.fingerprint(
-            'time sandwich') == (u'sandwichtime',)
-        assert dedupe.predicates.oneGramFingerprint(
-            'sandwich time') == (u'acdehimnstw',)
-        assert dedupe.predicates.twoGramFingerprint(
-            'sandwich time') == (u'anchdwhticimmendsatiwi',)
-        assert dedupe.predicates.twoGramFingerprint('1') == ()
-        assert dedupe.predicates.commonTwoTokens(
-            'foo bar') == set([u'foo bar'])
-        assert dedupe.predicates.commonTwoTokens('foo') == set([])
+        assert dedupe.predicates.fingerprint("time sandwich") == ("sandwichtime",)
+        assert dedupe.predicates.oneGramFingerprint("sandwich time") == ("acdehimnstw",)
+        assert dedupe.predicates.twoGramFingerprint("sandwich time") == (
+            "anchdwhticimmendsatiwi",
+        )
+        assert dedupe.predicates.twoGramFingerprint("1") == ()
+        assert dedupe.predicates.commonTwoTokens("foo bar") == set(["foo bar"])
+        assert dedupe.predicates.commonTwoTokens("foo") == set([])
 
 
 if __name__ == "__main__":
diff --git a/tests/test_labeler.py b/tests/test_labeler.py
index ab5c5d0cb..f18106565 100644
--- a/tests/test_labeler.py
+++ b/tests/test_labeler.py
@@ -3,18 +3,19 @@
 import random
 import pytest
 
-SAMPLE = [({"name": "Bob", "age": "50"}, {"name": "Charlie", "age": "75"}),
-          ({"name": "Meredith", "age": "40"}, {"name": "Sue", "age": "10"}),
-          ({"name": "Willy", "age": "35"}, {"name": "William", "age": "35"}),
-          ({"name": "Jimmy", "age": "20"}, {"name": "Jimbo", "age": "21"})]
+SAMPLE = [
+    ({"name": "Bob", "age": "50"}, {"name": "Charlie", "age": "75"}),
+    ({"name": "Meredith", "age": "40"}, {"name": "Sue", "age": "10"}),
+    ({"name": "Willy", "age": "35"}, {"name": "William", "age": "35"}),
+    ({"name": "Jimmy", "age": "20"}, {"name": "Jimbo", "age": "21"}),
+]
 
 
 class ActiveLearningTest(unittest.TestCase):
     def setUp(self):
-        self.data_model = dedupe.datamodel.DataModel([{'field': 'name',
-                                                       'type': 'String'},
-                                                      {'field': 'age',
-                                                       'type': 'String'}])
+        self.data_model = dedupe.datamodel.DataModel(
+            [{"field": "name", "type": "String"}, {"field": "age", "type": "String"}]
+        )
 
     def test_AL(self):
         random.seed(1111111111110)
@@ -24,20 +25,20 @@ def test_AL(self):
         assert len(active_learner) == original_N
         pair = active_learner.pop()
         print(pair)
-        assert pair == ({"name": "Willy", "age": "35"},
-                        {"name": "William", "age": "35"})
+        assert pair == (
+            {"name": "Willy", "age": "35"},
+            {"name": "William", "age": "35"},
+        )
 
         assert len(active_learner) == original_N - 1
 
         pair = active_learner.pop()
         print(pair)
-        assert pair == ({"name": "Jimmy", "age": "20"},
-                        {"name": "Jimbo", "age": "21"})
+        assert pair == ({"name": "Jimmy", "age": "20"}, {"name": "Jimbo", "age": "21"})
         assert len(active_learner) == original_N - 2
 
         pair = active_learner.pop()
-        assert pair == ({"name": "Meredith", "age": "40"},
-                        {"name": "Sue", "age": "10"})
+        assert pair == ({"name": "Meredith", "age": "40"}, {"name": "Sue", "age": "10"})
 
         assert len(active_learner) == original_N - 3
 
diff --git a/tests/test_predicates.py b/tests/test_predicates.py
index 714ac312d..91e385d56 100644
--- a/tests/test_predicates.py
+++ b/tests/test_predicates.py
@@ -5,26 +5,24 @@
 
 class TestPuncStrip(unittest.TestCase):
     def test_sevenchar(self):
-        s1 = predicates.StringPredicate(predicates.sameSevenCharStartPredicate,
-                                        'foo')
-        assert s1({'foo': u'fo,18v*1vaad80'}) == s1({'foo': u'fo18v1vaad80'})
+        s1 = predicates.StringPredicate(predicates.sameSevenCharStartPredicate, "foo")
+        assert s1({"foo": "fo,18v*1vaad80"}) == s1({"foo": "fo18v1vaad80"})
 
     def test_set(self):
-        s1 = predicates.SimplePredicate(predicates.wholeSetPredicate,
-                                        'foo')
-        colors = set(['red', 'blue', 'green'])
-        assert s1({'foo': colors}) == (str(colors),)
+        s1 = predicates.SimplePredicate(predicates.wholeSetPredicate, "foo")
+        colors = set(["red", "blue", "green"])
+        assert s1({"foo": colors}) == (str(colors),)
 
 
 class TestMetaphone(unittest.TestCase):
     def test_metaphone_token(self):
-        block_val = predicates.metaphoneToken('9301 S. State St. ')
-        assert block_val == set([u'STT', u'S', u'ST'])
+        block_val = predicates.metaphoneToken("9301 S. State St. ")
+        assert block_val == set(["STT", "S", "ST"])
 
 
 class TestWholeSet(unittest.TestCase):
     def setUp(self):
-        self.s1 = set(['red', 'blue', 'green'])
+        self.s1 = set(["red", "blue", "green"])
 
     def test_full_set(self):
         block_val = predicates.wholeSetPredicate(self.s1)
@@ -33,11 +31,11 @@ def test_full_set(self):
 
 class TestSetElement(unittest.TestCase):
     def setUp(self):
-        self.s1 = set(['red', 'blue', 'green'])
+        self.s1 = set(["red", "blue", "green"])
 
     def test_long_set(self):
         block_val = predicates.commonSetElementPredicate(self.s1)
-        self.assertEqual(set(block_val), set(('blue', 'green', 'red')))
+        self.assertEqual(set(block_val), set(("blue", "green", "red")))
 
     def test_empty_set(self):
         block_val = predicates.commonSetElementPredicate(set())
@@ -45,13 +43,13 @@ def test_empty_set(self):
 
     def test_first_last(self):
         block_val = predicates.lastSetElementPredicate(self.s1)
-        assert block_val == ('red',)
+        assert block_val == ("red",)
         block_val = predicates.firstSetElementPredicate(self.s1)
-        assert block_val == ('blue',)
+        assert block_val == ("blue",)
 
     def test_magnitude(self):
         block_val = predicates.magnitudeOfCardinality(self.s1)
-        assert block_val == (u'0', )
+        assert block_val == ("0",)
 
         block_val = predicates.magnitudeOfCardinality(())
         assert block_val == ()
@@ -63,64 +61,61 @@ def setUp(self):
 
     def test_precise_latlong(self):
         block_val = predicates.latLongGridPredicate(self.latlong1)
-        assert block_val == (u'[42.5, -5.0]',)
+        assert block_val == ("[42.5, -5.0]",)
         block_val = predicates.latLongGridPredicate((0, 0))
         assert block_val == ()
 
 
 class TestNumericPredicates(unittest.TestCase):
     def test_order_of_magnitude(self):
-        assert predicates.orderOfMagnitude(10) == (u'1',)
-        assert predicates.orderOfMagnitude(9) == (u'1',)
-        assert predicates.orderOfMagnitude(2) == (u'0',)
+        assert predicates.orderOfMagnitude(10) == ("1",)
+        assert predicates.orderOfMagnitude(9) == ("1",)
+        assert predicates.orderOfMagnitude(2) == ("0",)
         assert predicates.orderOfMagnitude(-2) == ()
 
     def test_round_to_1(self):
-        assert predicates.roundTo1(22315) == (u'20000',)
-        assert predicates.roundTo1(-22315) == (u'-20000',)
+        assert predicates.roundTo1(22315) == ("20000",)
+        assert predicates.roundTo1(-22315) == ("-20000",)
 
 
 class TestCompoundPredicate(unittest.TestCase):
     def test_escapes_colon(self):
-        '''
+        """
         Regression test for issue #836
-        '''
+        """
         predicate_1 = predicates.SimplePredicate(
-            predicates.commonSetElementPredicate, 'col_1')
+            predicates.commonSetElementPredicate, "col_1"
+        )
         predicate_2 = predicates.SimplePredicate(
-            predicates.commonSetElementPredicate, 'col_2')
-        record = {
-            'col_1': ['foo:', 'foo'],
-            'col_2': [':bar', 'bar']
-        }
-
-        block_val = predicates.CompoundPredicate([
-            predicate_1,
-            predicate_2
-        ])(record)
+            predicates.commonSetElementPredicate, "col_2"
+        )
+        record = {"col_1": ["foo:", "foo"], "col_2": [":bar", "bar"]}
+
+        block_val = predicates.CompoundPredicate([predicate_1, predicate_2])(record)
         assert len(set(block_val)) == 4
-        assert block_val == ['foo\\::\\:bar', 'foo\\::bar', 'foo:\\:bar', 'foo:bar']
+        assert block_val == ["foo\\::\\:bar", "foo\\::bar", "foo:\\:bar", "foo:bar"]
 
     def test_escapes_escaped_colon(self):
-        '''
+        """
         Regression test for issue #836
-        '''
+        """
         predicate_1 = predicates.SimplePredicate(
-            predicates.commonSetElementPredicate, 'col_1')
+            predicates.commonSetElementPredicate, "col_1"
+        )
         predicate_2 = predicates.SimplePredicate(
-            predicates.commonSetElementPredicate, 'col_2')
-        record = {
-            'col_1': ['foo\\:', 'foo'],
-            'col_2': ['\\:bar', 'bar']
-        }
-
-        block_val = predicates.CompoundPredicate([
-            predicate_1,
-            predicate_2
-        ])(record)
+            predicates.commonSetElementPredicate, "col_2"
+        )
+        record = {"col_1": ["foo\\:", "foo"], "col_2": ["\\:bar", "bar"]}
+
+        block_val = predicates.CompoundPredicate([predicate_1, predicate_2])(record)
         assert len(set(block_val)) == 4
-        assert block_val == ['foo\\\\::\\\\:bar', 'foo\\\\::bar', 'foo:\\\\:bar', 'foo:bar']
+        assert block_val == [
+            "foo\\\\::\\\\:bar",
+            "foo\\\\::bar",
+            "foo:\\\\:bar",
+            "foo:bar",
+        ]
 
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     unittest.main()
diff --git a/tests/test_serializer.py b/tests/test_serializer.py
index 9c4f9f793..d422db43b 100644
--- a/tests/test_serializer.py
+++ b/tests/test_serializer.py
@@ -11,41 +11,49 @@
 
 class SerializerTest(unittest.TestCase):
     def test_writeTraining(self):
-        if sys.version < '3':
+        if sys.version < "3":
             from StringIO import StringIO
+
             output = StringIO()
-            encoded_file = codecs.EncodedFile(output,
-                                              data_encoding='utf8',
-                                              file_encoding='ascii')
+            encoded_file = codecs.EncodedFile(
+                output, data_encoding="utf8", file_encoding="ascii"
+            )
         else:
             from io import StringIO
-            encoded_file = StringIO()
 
-        training_pairs = {u"distinct": [[{u'bar': frozenset([u'barë']),
-                                          'baz': (1, 2),
-                                          'bang': (1, 2),
-                                          u'foo': u'baz'},
-                                         {u'foo': u'baz'}]],
-                          u"match": []}
+            encoded_file = StringIO()
 
-        json.dump(training_pairs,
-                  encoded_file,
-                  cls=dedupe.serializer.TupleEncoder)
+        training_pairs = {
+            "distinct": [
+                [
+                    {
+                        "bar": frozenset(["barë"]),
+                        "baz": (1, 2),
+                        "bang": (1, 2),
+                        "foo": "baz",
+                    },
+                    {"foo": "baz"},
+                ]
+            ],
+            "match": [],
+        }
+
+        json.dump(training_pairs, encoded_file, cls=dedupe.serializer.TupleEncoder)
 
         encoded_file.seek(0)
 
-        loaded_training_pairs = json.load(encoded_file,
-                                          object_hook=dedupe.serializer._from_json)
+        loaded_training_pairs = json.load(
+            encoded_file, object_hook=dedupe.serializer._from_json
+        )
 
-        assert loaded_training_pairs["distinct"][0][0] ==\
-            dict(training_pairs["distinct"][0][0])
+        assert loaded_training_pairs["distinct"][0][0] == dict(
+            training_pairs["distinct"][0][0]
+        )
 
-        assert isinstance(loaded_training_pairs["distinct"][0][0]["bar"],
-                          frozenset)
-        assert isinstance(loaded_training_pairs["distinct"][0][0]["baz"],
-                          tuple)
+        assert isinstance(loaded_training_pairs["distinct"][0][0]["bar"], frozenset)
+        assert isinstance(loaded_training_pairs["distinct"][0][0]["baz"], tuple)
 
-        deduper = dedupe.Dedupe([{'field': 'foo', 'type': 'String'}])
+        deduper = dedupe.Dedupe([{"field": "foo", "type": "String"}])
         deduper.classifier.cv = False
 
         encoded_file.seek(0)
diff --git a/tests/test_tfidf.py b/tests/test_tfidf.py
index 89bbf4b7a..9d7b7fefa 100644
--- a/tests/test_tfidf.py
+++ b/tests/test_tfidf.py
@@ -7,24 +7,24 @@ def setUp(self):
         self.index = dedupe.tfidf.TfIdfIndex()
 
     def test_keywords(self):
-        self.index.index(('AND', 'OR', 'EOF', 'NOT'))
+        self.index.index(("AND", "OR", "EOF", "NOT"))
         self.index._index.initSearch()
-        assert self.index.search(('AND', 'OR', 'EOF', 'NOT'))[0] == 1
+        assert self.index.search(("AND", "OR", "EOF", "NOT"))[0] == 1
 
     def test_keywords_title(self):
-        self.index.index(('And', 'Or', 'Eof', 'Not'))
+        self.index.index(("And", "Or", "Eof", "Not"))
         self.index._index.initSearch()
-        assert self.index.search(('And', 'Or', 'Eof', 'Not'))[0] == 1
+        assert self.index.search(("And", "Or", "Eof", "Not"))[0] == 1
 
     def test_empty_search(self):
         self.index._index.initSearch()
         assert self.index.search(()) == []
 
     def test_wildcards(self):
-        self.index.index((r'f\o',))
-        self.index.index(('f*',))
+        self.index.index((r"f\o",))
+        self.index.index(("f*",))
         self.index._index.initSearch()
-        assert len(self.index.search(('f*',))) == 1
+        assert len(self.index.search(("f*",))) == 1
 
 
 if __name__ == "__main__":
diff --git a/tests/test_training.py b/tests/test_training.py
index bcccd997d..24761abf5 100644
--- a/tests/test_training.py
+++ b/tests/test_training.py
@@ -6,52 +6,60 @@
 class TrainingTest(unittest.TestCase):
     def setUp(self):
 
-        field_definition = [{'field': 'name', 'type': 'String'}]
+        field_definition = [{"field": "name", "type": "String"}]
         self.data_model = dedupe.Dedupe(field_definition).data_model
         self.training_pairs = {
-            'match': [({"name": "Bob", "age": "50"},
-                       {"name": "Bob", "age": "75"}),
-                      ({"name": "Meredith", "age": "40"},
-                       {"name": "Sue", "age": "10"})],
-            'distinct': [({"name": "Jimmy", "age": "20"},
-                          {"name": "Jimbo", "age": "21"}),
-                         ({"name": "Willy", "age": "35"},
-                          {"name": "William", "age": "35"}),
-                         ({"name": "William", "age": "36"},
-                          {"name": "William", "age": "35"})]
+            "match": [
+                ({"name": "Bob", "age": "50"}, {"name": "Bob", "age": "75"}),
+                ({"name": "Meredith", "age": "40"}, {"name": "Sue", "age": "10"}),
+            ],
+            "distinct": [
+                ({"name": "Jimmy", "age": "20"}, {"name": "Jimbo", "age": "21"}),
+                ({"name": "Willy", "age": "35"}, {"name": "William", "age": "35"}),
+                ({"name": "William", "age": "36"}, {"name": "William", "age": "35"}),
+            ],
         }
 
-        self.training = self.training_pairs['match'] + \
-            self.training_pairs['distinct']
+        self.training = self.training_pairs["match"] + self.training_pairs["distinct"]
         self.training_records = []
         for pair in self.training:
             for record in pair:
                 if record not in self.training_records:
                     self.training_records.append(record)
 
-        self.simple = lambda x: set([str(k) for k in x
-                                     if "CompoundPredicate" not in str(k)])
+        self.simple = lambda x: set(
+            [str(k) for k in x if "CompoundPredicate" not in str(k)]
+        )
 
         self.block_learner = training.BlockLearner
-        self.block_learner.blocker = dedupe.blocking.Fingerprinter(self.data_model.predicates())
-        self.block_learner.blocker.index_all({i: x for i, x in enumerate(self.training_records)})
+        self.block_learner.blocker = dedupe.blocking.Fingerprinter(
+            self.data_model.predicates()
+        )
+        self.block_learner.blocker.index_all(
+            {i: x for i, x in enumerate(self.training_records)}
+        )
 
     def test_dedupe_coverage(self):
         coverage = self.block_learner.cover(self.block_learner, self.training)
         assert self.simple(coverage.keys()).issuperset(
-            set(["SimplePredicate: (tokenFieldPredicate, name)",
-                 "SimplePredicate: (commonSixGram, name)",
-                 "TfidfTextCanopyPredicate: (0.4, name)",
-                 "SimplePredicate: (sortedAcronym, name)",
-                 "SimplePredicate: (sameThreeCharStartPredicate, name)",
-                 "TfidfTextCanopyPredicate: (0.2, name)",
-                 "SimplePredicate: (sameFiveCharStartPredicate, name)",
-                 "TfidfTextCanopyPredicate: (0.6, name)",
-                 "SimplePredicate: (wholeFieldPredicate, name)",
-                 "TfidfTextCanopyPredicate: (0.8, name)",
-                 "SimplePredicate: (commonFourGram, name)",
-                 "SimplePredicate: (firstTokenPredicate, name)",
-                 "SimplePredicate: (sameSevenCharStartPredicate, name)"]))
+            set(
+                [
+                    "SimplePredicate: (tokenFieldPredicate, name)",
+                    "SimplePredicate: (commonSixGram, name)",
+                    "TfidfTextCanopyPredicate: (0.4, name)",
+                    "SimplePredicate: (sortedAcronym, name)",
+                    "SimplePredicate: (sameThreeCharStartPredicate, name)",
+                    "TfidfTextCanopyPredicate: (0.2, name)",
+                    "SimplePredicate: (sameFiveCharStartPredicate, name)",
+                    "TfidfTextCanopyPredicate: (0.6, name)",
+                    "SimplePredicate: (wholeFieldPredicate, name)",
+                    "TfidfTextCanopyPredicate: (0.8, name)",
+                    "SimplePredicate: (commonFourGram, name)",
+                    "SimplePredicate: (firstTokenPredicate, name)",
+                    "SimplePredicate: (sameSevenCharStartPredicate, name)",
+                ]
+            )
+        )
 
     def test_uncovered_by(self):
         before = {1: {1, 2, 3}, 2: {1, 2}, 3: {3}}
@@ -67,8 +75,7 @@ def test_covered_pairs(self):
         p1 = lambda x, target=None: (1,)  # noqa: E 731
 
         self.block_learner.blocker.predicates = (p1,)
-        cover = self.block_learner.cover(self.block_learner,
-                                         [('a', 'b')] * 2)
+        cover = self.block_learner.cover(self.block_learner, [("a", "b")] * 2)
 
         assert cover[p1] == {0, 1}