diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2830c8011889fb409863ab9658986e7f9d54321c..a9b10366725f526a07c98b34308a9a6977cae6be 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1 +1,3 @@
 # v2.17.20
+
+* added is_subset
diff --git a/docs/coding/transforms.rst b/docs/coding/transforms.rst
index dbb63f8bd37e2e173bf5ec738e9ffa241b895286..702c54c160d7457863a7401468cdb2391a3bf0a3 100644
--- a/docs/coding/transforms.rst
+++ b/docs/coding/transforms.rst
@@ -1,6 +1,8 @@
 Rudimentary data transforms and algorithms
 ==========================================
 
+.. autofunction:: satella.coding.transforms.is_subset
+
 .. autoclass:: satella.coding.transforms.merge_list
 
 .. autofunction:: satella.coding.transforms.hashables_to_int
diff --git a/satella/__init__.py b/satella/__init__.py
index 44f8134504a1b71c4b2bd2032011a26cd10e1f8e..ace31da48c3bcc4fee28b5addc04c8391c121785 100644
--- a/satella/__init__.py
+++ b/satella/__init__.py
@@ -1 +1 @@
-__version__ = '2.17.20a2'
+__version__ = '2.17.20'
diff --git a/satella/coding/transforms/__init__.py b/satella/coding/transforms/__init__.py
index 5dd08e5a0c572ceeac08ec534feea6a1fc90148f..4104511eb284b4f60d58f37215e2ad4126e70f59 100644
--- a/satella/coding/transforms/__init__.py
+++ b/satella/coding/transforms/__init__.py
@@ -11,11 +11,12 @@ from .percentile import percentile
 from .base64 import b64encode
 from .interpol import linear_interpolate
 from .words import hashables_to_int
+from .predicates import is_subset
 
 __all__ = ['stringify', 'split_shuffle_and_join', 'one_tuple', 'none_if_false',
            'merge_series', 'pad_to_multiple_of_length', 'clip', 'hashables_to_int',
            'jsonify', 'intify', 'percentile', 'b64encode', 'linear_interpolate',
-           'merge_list']
+           'merge_list', 'is_subset']
 
 from satella.coding.typing import T, NoArgCallable, Appendable, Number, Predicate
 
diff --git a/satella/coding/transforms/predicates.py b/satella/coding/transforms/predicates.py
new file mode 100644
index 0000000000000000000000000000000000000000..994dbfa2a1a0cc42d3a4a971ceaddacd4dd3fb42
--- /dev/null
+++ b/satella/coding/transforms/predicates.py
@@ -0,0 +1,18 @@
+import typing as tp
+
+
+def is_subset(subset: tp.Dict, superset: tp.Dict) -> bool:
+    """
+    Does superset contain all keys of subset, and are their values equal?
+
+    :param subset: the set that contains all the keys
+    :param superset: the set that is to contain all the keys in subset, and their values
+        have to be equal
+    :return: does the condition hold?
+    """
+    for k, v in subset:
+        if k not in superset:
+            return False
+        if v != superset[k]:
+            return False
+    return True
diff --git a/tests/test_coding/test_transforms.py b/tests/test_coding/test_transforms.py
index cd0dd3c6d0b6d072d925a5c88b32fbc42fffcb53..202767ab17b848dfe72cd08536c35bcb4c3d9829 100644
--- a/tests/test_coding/test_transforms.py
+++ b/tests/test_coding/test_transforms.py
@@ -5,11 +5,18 @@ import base64
 
 from satella.coding.transforms import stringify, split_shuffle_and_join, one_tuple, \
     merge_series, pad_to_multiple_of_length, clip, b64encode, linear_interpolate, \
-    hashables_to_int, none_if_false, merge_list
+    hashables_to_int, none_if_false, merge_list, is_subset
 
 
 class TestTransforms(unittest.TestCase):
 
+    def test_is_subset(self):
+        self.assertTrue(is_subset({}, {}))
+        self.assertTrue(is_subset({}, {1: 2}))
+        self.assertFalse(is_subset({2: 3}, {1: 2}))
+        self.assertFalse(is_subset({1: 3}, {1: 2}))
+        self.assertTrue(is_subset({1: 2}, {1: 2}))
+
     def test_merge_list(self):
         a = [(1, 1), (2, 2), (3, 3)]
         b = [(1, 2), (3, 2), (4, 4)]