Repository: isi-vista/immutablecollections

Issue:
Provide ImmutableSet factory methods which ban duplicate additions
This can sometimes be very helpful for debugging.

Comments:

Patch:
diff --git a/immutablecollections/__init__.py b/immutablecollections/__init__.py
index a3c516f..af96c53 100644
--- a/immutablecollections/__init__.py
+++ b/immutablecollections/__init__.py
@@ -27,7 +27,11 @@ from attr.exceptions import FrozenInstanceError
 
 from immutablecollections.immutablecollection import ImmutableCollection
 from immutablecollections._immutabledict import immutabledict, ImmutableDict
-from immutablecollections._immutableset import immutableset, ImmutableSet
+from immutablecollections._immutableset import (
+    immutableset,
+    immutableset_from_unique_elements,
+    ImmutableSet,
+)
 from immutablecollections._immutablemultidict import (
     ImmutableSetMultiDict,
     ImmutableListMultiDict,
diff --git a/immutablecollections/_immutableset.py b/immutablecollections/_immutableset.py
index 73920ae..593b362 100644
--- a/immutablecollections/_immutableset.py
+++ b/immutablecollections/_immutableset.py
@@ -34,7 +34,10 @@ ViewTypes = (KeysView, ValuesView, ItemsView)  # pylint:disable=invalid-name
 
 
 def immutableset(
-    iterable: Optional[Iterable[T]] = None, *, disable_order_check: bool = False
+    iterable: Optional[Iterable[T]] = None,
+    *,
+    disable_order_check: bool = False,
+    forbid_duplicate_elements: bool = False,
 ) -> "ImmutableSet[T]":
     """
     Create an immutable set with the given contents.
@@ -48,6 +51,9 @@ def immutableset(
     could be removed in the future and should not be relied upon.  This check may be disabled
     by setting *disable_order_check* to ``True``.
 
+    If *forbid_duplicate_elements* is ``True`` and one item occurs twice in *iterable*, then
+    a ``ValueError`` will be thrown.
+
     If *iterable* is already an ``ImmutableSet``, *iterable* itself will be returned.
     """
     # immutableset() should return an empty set
@@ -92,6 +98,10 @@ def immutableset(
         if value not in containment_set:
             containment_set.add(value)
             iteration_order.append(value)
+        elif forbid_duplicate_elements:
+            raise ValueError(
+                "Input collection has duplicate items and forbid_duplicate_elements=False"
+            )
 
     if iteration_order:
         if len(iteration_order) == 1:
@@ -102,6 +112,18 @@ def immutableset(
         return _EMPTY
 
 
+def immutableset_from_unique_elements(
+    iterable: Optional[Iterable[T]] = None, *, disable_order_check: bool = False
+):
+    """
+    Create an immutableset from *iterable*. If one item occurs twice in *iterable*, then
+    a ``ValueError`` will be thrown. More information in ``immutablecollections.immutableset``.
+    """
+    return immutableset(
+        iterable, disable_order_check=disable_order_check, forbid_duplicate_elements=True
+    )
+
+
 # typing.AbstractSet matches collections.abc.Set
 class ImmutableSet(  # pylint: disable=duplicate-bases
     Generic[T],


Test patch:
diff --git a/tests/test_immutableset.py b/tests/test_immutableset.py
index 2cc00a7..ead7932 100644
--- a/tests/test_immutableset.py
+++ b/tests/test_immutableset.py
@@ -3,7 +3,11 @@ from collections.abc import Set
 from unittest import TestCase
 from unittest.mock import patch
 
-from immutablecollections import ImmutableSet, immutableset
+from immutablecollections import (
+    ImmutableSet,
+    immutableset,
+    immutableset_from_unique_elements,
+)
 
 
 class TestImmutableSet(TestCase):
@@ -280,3 +284,13 @@ class TestImmutableSet(TestCase):
         # frozensets on LHS
         self.assertEqual({5, 6}, frozenset([4, 5, 6]) - immutableset([2, 3, 4]))
         self.assertEqual({"a"}, frozenset(["a", "b"]) - immutableset(["b", "c"]))
+
+    def test_forbid_duplicate_elements(self):
+        with self.assertRaises(ValueError):
+            immutableset([4, 6, 7, 9, 7], forbid_duplicate_elements=True)
+        immutableset([3, 7, 5, 9], forbid_duplicate_elements=True)
+
+    def test_immutableset_from_unique_elements(self):
+        with self.assertRaises(ValueError):
+            immutableset_from_unique_elements([4, 6, 7, 9, 7])
+        immutableset_from_unique_elements([3, 7, 5, 9])