Repository: cwacek/python-jsonschema-objects

Issue:
Properties with multiple types are not parsed correctly
http://json-schema.org/latest/json-schema-validation.html#anchor79

Example property

        "claimed_by": {
            "id": "claimed",
            "type": ["string", "null"],
            "description": "Robots Only. The human agent that has claimed this robot.",
            "required": false
        },

Traceback (most recent call last):
  File "/home/idanforth/fetch/src/sandbox/fetchcore/test/unit/test_scheduler.py", line 58, in setUp
    agent_ns = agent_builder.build_classes()
  File "/usr/local/lib/python2.7/dist-packages/python_jsonschema_objects/__init__.py", line 83, in build_classes
    builder.construct(nm, self.schema)
  File "/usr/local/lib/python2.7/dist-packages/python_jsonschema_objects/classbuilder.py", line 288, in construct
    ret = self._construct(uri, *args, **kw)
  File "/usr/local/lib/python2.7/dist-packages/python_jsonschema_objects/classbuilder.py", line 362, in _construct
    parent)
  File "/usr/local/lib/python2.7/dist-packages/python_jsonschema_objects/classbuilder.py", line 498, in _build_object
    typ = self.construct(uri, detail)
  File "/usr/local/lib/python2.7/dist-packages/python_jsonschema_objects/classbuilder.py", line 288, in construct
    ret = self._construct(uri, *args, **kw)
  File "/usr/local/lib/python2.7/dist-packages/python_jsonschema_objects/classbuilder.py", line 380, in _construct
    "no type and no reference".format(clsdata))
NotImplementedError: Unable to parse schema object '{'raw_name': u'claimed_by', u'required': False, u'type': [u'string'
, u'null'], u'id': u'claimed', u'description': u'Robots Only. The human agent that has claimed this robot.'}' with no t
ype and no reference


Discussion:

Patch:
diff --git a/python_jsonschema_objects/classbuilder.py b/python_jsonschema_objects/classbuilder.py
index c01fdcc..3a5ca41 100644
--- a/python_jsonschema_objects/classbuilder.py
+++ b/python_jsonschema_objects/classbuilder.py
@@ -145,7 +145,7 @@ class ProtocolBase(collections.MutableMapping):
         else:  # We got nothing
             raise validators.ValidationError(
                 "Unable to instantiate any valid types: \n"
-                "\n".join("{0}: {1}".format(k, e) for k, e in validation_errors)
+                "".join("{0}: {1}\n".format(k, e) for k, e in validation_errors)
             )
 
         return obj
@@ -314,7 +314,7 @@ class TypeProxy(object):
         else:  # We got nothing
             raise validators.ValidationError(
                 "Unable to instantiate any valid types: \n"
-                "\n".join("{0}: {1}".format(k, e) for k, e in validation_errors)
+                "".join("{0}: {1}\n".format(k, e) for k, e in validation_errors)
             )
 
 
@@ -474,6 +474,18 @@ class ClassBuilder(object):
                 **clsdata_copy)
             return self.resolved[uri]
 
+        elif isinstance(clsdata.get('type'), list):
+            types = []
+            for i, item_detail in enumerate(clsdata['type']):
+                subdata = {k: v for k, v in six.iteritems(clsdata) if k != 'type'}
+                subdata['type'] = item_detail
+                types.append(self._build_literal(
+                    uri + "_%s" % i,
+                    subdata))
+
+            self.resolved[uri] = TypeProxy(types)
+            return self.resolved[uri]
+
         elif (clsdata.get('type', None) == 'object' or
               clsdata.get('properties', None) is not None or
               clsdata.get('additionalProperties', False)):
@@ -770,6 +782,10 @@ def make_property(prop, info, desc=""):
                 val = info['type'](**util.coerce_for_expansion(val))
 
             val.validate()
+
+        elif isinstance(info['type'], TypeProxy):
+            val = info['type'](val)
+
         elif info['type'] is None:
             # This is the null value
             if val is not None:


Test patch:
diff --git a/test/test_regression_17.py b/test/test_regression_17.py
new file mode 100644
index 0000000..c13e942
--- /dev/null
+++ b/test/test_regression_17.py
@@ -0,0 +1,33 @@
+import pytest
+
+import python_jsonschema_objects as pjo
+
+
+@pytest.fixture
+def test_class():
+    schema =  {
+        'title': 'Example',
+        'properties': {
+            "claimed_by": {
+                "id": "claimed",
+                "type": ["string", "integer", "null"],
+                "description": "Robots Only. The human agent that has claimed this robot.",
+                "required": False
+                },
+            }
+        }
+
+    builder = pjo.ObjectBuilder(schema)
+    ns = builder.build_classes()
+    return ns
+
+
+@pytest.mark.parametrize('value', [
+    "Hi", 4, None])
+def test_properties_can_have_multiple_types(test_class, value):
+    test_class.Example(claimed_by=value)
+
+@pytest.mark.parametrize('value', [2.4])
+def test_multiply_typed_properties_still_validate(test_class, value):
+    with pytest.raises(pjo.ValidationError):
+        test_class.Example(claimed_by=value)