Repository: alecthomas/voluptuous

Issue:
RFC: Add description attribute to Required/Optional
So I'm not sure if this should be even a thing in voluptuous, but since more users of this lib might be interested I thought I'll bring it up.

We use voluptuous in [Home Assistant](https://github.com/home-assistant/home-assistant) and it's awesome. For some of the things that we have voluptuous validate, we would like to generate a schema of what is allowed.

To this end I started an experiment a while ago called [voluptuous form](https://github.com/balloob/voluptuous_form). I'm looking into reviving this effort but there is one missing piece of the puzzle: description of the fields.

Would description be something that would make sense to add to Voluptuous?

Potential syntax:

```python
import voluptuous as vol

SCHEMA = vol.Schema({
  vol.Required('name', description='The name of the user.'): str,
  vol.Required('age', description='The age of the user.'): int,
})
```

Discussion:

Patch:
diff --git a/voluptuous/schema_builder.py b/voluptuous/schema_builder.py
index 429ab32..2c430a5 100644
--- a/voluptuous/schema_builder.py
+++ b/voluptuous/schema_builder.py
@@ -875,10 +875,11 @@ class VirtualPathComponent(str):
 class Marker(object):
     """Mark nodes for special treatment."""
 
-    def __init__(self, schema_, msg=None):
+    def __init__(self, schema_, msg=None, description=None):
         self.schema = schema_
         self._schema = Schema(schema_)
         self.msg = msg
+        self.description = description
 
     def __call__(self, v):
         try:
@@ -930,8 +931,9 @@ class Optional(Marker):
     {'key2': 'value'}
     """
 
-    def __init__(self, schema, msg=None, default=UNDEFINED):
-        super(Optional, self).__init__(schema, msg=msg)
+    def __init__(self, schema, msg=None, default=UNDEFINED, description=None):
+        super(Optional, self).__init__(schema, msg=msg,
+                                       description=description)
         self.default = default_factory(default)
 
 
@@ -971,8 +973,9 @@ class Exclusive(Optional):
     ...             'social': {'social_network': 'barfoo', 'token': 'tEMp'}})
     """
 
-    def __init__(self, schema, group_of_exclusion, msg=None):
-        super(Exclusive, self).__init__(schema, msg=msg)
+    def __init__(self, schema, group_of_exclusion, msg=None, description=None):
+        super(Exclusive, self).__init__(schema, msg=msg,
+                                        description=description)
         self.group_of_exclusion = group_of_exclusion
 
 
@@ -1038,8 +1041,9 @@ class Required(Marker):
     {'key': []}
     """
 
-    def __init__(self, schema, msg=None, default=UNDEFINED):
-        super(Required, self).__init__(schema, msg=msg)
+    def __init__(self, schema, msg=None, default=UNDEFINED, description=None):
+        super(Required, self).__init__(schema, msg=msg,
+                                       description=description)
         self.default = default_factory(default)
 
 


Test patch:
diff --git a/voluptuous/tests/tests.py b/voluptuous/tests/tests.py
index 8b82f98..48cbfd0 100644
--- a/voluptuous/tests/tests.py
+++ b/voluptuous/tests/tests.py
@@ -6,7 +6,7 @@ import sys
 from nose.tools import assert_equal, assert_raises, assert_true
 
 from voluptuous import (
-    Schema, Required, Optional, Extra, Invalid, In, Remove, Literal,
+    Schema, Required, Exclusive, Optional, Extra, Invalid, In, Remove, Literal,
     Url, MultipleInvalid, LiteralInvalid, TypeInvalid, NotIn, Match, Email,
     Replace, Range, Coerce, All, Any, Length, FqdnUrl, ALLOW_EXTRA, PREVENT_EXTRA,
     validate, ExactSequence, Equal, Unordered, Number, Maybe, Datetime, Date,
@@ -915,3 +915,17 @@ def test_PathExists():
     schema = Schema(PathExists())
     assert_raises(MultipleInvalid, schema, 3)
     schema(os.path.abspath(__file__))
+
+
+def test_description():
+    marker = Marker(Schema(str), description='Hello')
+    assert marker.description == 'Hello'
+
+    optional = Optional('key', description='Hello')
+    assert optional.description == 'Hello'
+
+    exclusive = Exclusive('alpha', 'angles', description='Hello')
+    assert exclusive.description == 'Hello'
+
+    required = Required('key', description='Hello')
+    assert required.description == 'Hello'