Repository: alecthomas/voluptuous

Issue:
Description argument not documented
The `description` argument for the `Marker` class (or any subclass) is not documented anywhere, so it's unclear what's allowed and how it affects the schema.

Comments:

Patch:
diff --git a/voluptuous/schema_builder.py b/voluptuous/schema_builder.py
index 6ad8758..f7427f8 100644
--- a/voluptuous/schema_builder.py
+++ b/voluptuous/schema_builder.py
@@ -1020,7 +1020,11 @@ class VirtualPathComponent(str):
 
 
 class Marker(object):
-    """Mark nodes for special treatment."""
+    """Mark nodes for special treatment.
+
+    `description` is an optional field, unused by Voluptuous itself, but can be used
+    introspected by any external tool, for example to generate schema documentation.
+    """
 
     def __init__(
         self,
diff --git a/voluptuous/validators.py b/voluptuous/validators.py
index a372afe..9951738 100644
--- a/voluptuous/validators.py
+++ b/voluptuous/validators.py
@@ -821,9 +821,15 @@ class In(object):
         except TypeError:
             check = True
         if check:
-            raise InInvalid(
-                self.msg or 'value must be one of {}'.format(sorted(self.container))
-            )
+            try:
+                raise InInvalid(
+                    self.msg or f'value must be one of {sorted(self.container)}'
+                )
+            except TypeError:
+                raise InInvalid(
+                    self.msg
+                    or f'value must be one of {sorted(self.container, key=str)}'
+                )
         return v
 
     def __repr__(self):
@@ -845,9 +851,15 @@ class NotIn(object):
         except TypeError:
             check = True
         if check:
-            raise NotInInvalid(
-                self.msg or 'value must not be one of {}'.format(sorted(self.container))
-            )
+            try:
+                raise NotInInvalid(
+                    self.msg or f'value must not be one of {sorted(self.container)}'
+                )
+            except TypeError:
+                raise NotInInvalid(
+                    self.msg
+                    or f'value must not be one of {sorted(self.container, key=str)}'
+                )
         return v
 
     def __repr__(self):


Test patch:
diff --git a/voluptuous/tests/tests.py b/voluptuous/tests/tests.py
index 05b7e8e..7286b31 100644
--- a/voluptuous/tests/tests.py
+++ b/voluptuous/tests/tests.py
@@ -79,19 +79,54 @@ def test_in():
     assert isinstance(ctx.value.errors[0], InInvalid)
 
 
+def test_in_unsortable_container():
+    """Verify that In works with unsortable container."""
+    schema = Schema({"type": In((int, str, float))})
+    schema({"type": float})
+    with pytest.raises(
+        MultipleInvalid,
+        match=(
+            r"value must be one of \[<class 'float'>, <class 'int'>, <class 'str'>\] for dictionary value "
+            r"@ data\['type'\]"
+        ),
+    ) as ctx:
+        schema({"type": 42})
+    assert len(ctx.value.errors) == 1
+    assert isinstance(ctx.value.errors[0], InInvalid)
+
+
 def test_not_in():
     """Verify that NotIn works."""
     schema = Schema({"color": NotIn(frozenset(["red", "blue", "yellow"]))})
     schema({"color": "orange"})
     with pytest.raises(
         MultipleInvalid,
-        match=r"value must not be one of \['blue', 'red', 'yellow'\] for dictionary value @ data\['color'\]",
+        match=(
+            r"value must not be one of \['blue', 'red', 'yellow'\] for dictionary "
+            r"value @ data\['color'\]"
+        ),
     ) as ctx:
         schema({"color": "blue"})
     assert len(ctx.value.errors) == 1
     assert isinstance(ctx.value.errors[0], NotInInvalid)
 
 
+def test_not_in_unsortable_container():
+    """Verify that NotIn works with unsortable container."""
+    schema = Schema({"type": NotIn((int, str, float))})
+    schema({"type": 42})
+    with pytest.raises(
+        MultipleInvalid,
+        match=(
+            r"value must not be one of \[<class 'float'>, <class 'int'>, "
+            r"<class 'str'>\] for dictionary value @ data\['type'\]"
+        ),
+    ) as ctx:
+        schema({"type": str})
+    assert len(ctx.value.errors) == 1
+    assert isinstance(ctx.value.errors[0], NotInInvalid)
+
+
 def test_contains():
     """Verify contains validation method."""
     schema = Schema({'color': Contains('red')})