Repository: altair-viz/altair
Pull request #746 (2018-04-11 15:42:46)

MAINT: fix schemapi to work with old jsonschema versions


Patch:
diff --git a/altair/utils/schemapi.py b/altair/utils/schemapi.py
index d7bfb630..d9c7ade7 100644
--- a/altair/utils/schemapi.py
+++ b/altair/utils/schemapi.py
@@ -4,6 +4,7 @@
 # tools/generate_schema_wrapper.py. Do not modify directly.
 import collections
 import contextlib
+import inspect
 import json
 
 import jsonschema
@@ -42,14 +43,30 @@ def debug_mode(arg):
 class SchemaValidationError(jsonschema.ValidationError):
     """A wrapper for jsonschema.ValidationError with friendlier traceback"""
     def __init__(self, obj, err):
-        super(SchemaValidationError, self).__init__(**err._contents())
+        super(SchemaValidationError, self).__init__(**self._get_contents(err))
         self.obj = obj
 
+    @staticmethod
+    def _get_contents(err):
+        """Get a dictionary with the contents of a ValidationError"""
+        try:
+            # works in jsonschema 2.3 or later
+            contents = err._contents()
+        except:
+            try:
+                # works in Python >=3.4
+                spec = inspect.getfullargspec(err.__init__)
+            except AttributeError:
+                # works in Python <3.4
+                spec = inspect.getargspec(err.__init__)
+            contents = {key: getattr(err, key) for key in spec.args[1:]}
+        return contents
+
     def __unicode__(self):
         cls = self.obj.__class__
         schema_path = ['{0}.{1}'.format(cls.__module__, cls.__name__)]
         schema_path.extend(self.schema_path)
-        schema_path = ' ->'.join(val for val in schema_path[:-1]
+        schema_path = '->'.join(val for val in schema_path[:-1]
                                 if val not in ('properties',
                                                'additionalProperties',
                                                'patternProperties'))
diff --git a/tools/schemapi/schemapi.py b/tools/schemapi/schemapi.py
index e064c737..d9d2c581 100644
--- a/tools/schemapi/schemapi.py
+++ b/tools/schemapi/schemapi.py
@@ -1,5 +1,6 @@
 import collections
 import contextlib
+import inspect
 import json
 
 import jsonschema
@@ -38,14 +39,30 @@ def debug_mode(arg):
 class SchemaValidationError(jsonschema.ValidationError):
     """A wrapper for jsonschema.ValidationError with friendlier traceback"""
     def __init__(self, obj, err):
-        super(SchemaValidationError, self).__init__(**err._contents())
+        super(SchemaValidationError, self).__init__(**self._get_contents(err))
         self.obj = obj
 
+    @staticmethod
+    def _get_contents(err):
+        """Get a dictionary with the contents of a ValidationError"""
+        try:
+            # works in jsonschema 2.3 or later
+            contents = err._contents()
+        except:
+            try:
+                # works in Python >=3.4
+                spec = inspect.getfullargspec(err.__init__)
+            except AttributeError:
+                # works in Python <3.4
+                spec = inspect.getargspec(err.__init__)
+            contents = {key: getattr(err, key) for key in spec.args[1:]}
+        return contents
+
     def __unicode__(self):
         cls = self.obj.__class__
         schema_path = ['{0}.{1}'.format(cls.__module__, cls.__name__)]
         schema_path.extend(self.schema_path)
-        schema_path = ' ->'.join(val for val in schema_path[:-1]
+        schema_path = '->'.join(val for val in schema_path[:-1]
                                 if val not in ('properties',
                                                'additionalProperties',
                                                'patternProperties'))


Test patch:
diff --git a/altair/utils/tests/test_schemapi.py b/altair/utils/tests/test_schemapi.py
index 8d03c112..37d59609 100644
--- a/altair/utils/tests/test_schemapi.py
+++ b/altair/utils/tests/test_schemapi.py
@@ -5,7 +5,8 @@
 import jsonschema
 import pytest
 
-from ..schemapi import UndefinedType, SchemaBase, Undefined, _FromDict
+from ..schemapi import (UndefinedType, SchemaBase, Undefined, _FromDict,
+                        SchemaValidationError)
 
 # Make tests inherit from _TestSchema, so that when we test from_dict it won't
 # try to use SchemaBase objects defined elsewhere as wrappers.
@@ -280,3 +281,19 @@ def test_hash_schema(use_json):
         hsh2 = _FromDict.hash_schema(cls._schema, use_json=use_json)
         assert hsh1 == hsh2
         assert hash(hsh1) == hash(hsh2)
+
+
+def test_schema_validation_error():
+    try:
+        MySchema(a={'foo': 4})
+        the_err = None
+    except jsonschema.ValidationError as err:
+        the_err = err
+
+    assert isinstance(the_err, SchemaValidationError)
+    message = str(the_err)
+
+    assert message.startswith('Invalid specification')
+    assert 'test_schemapi.MySchema->a' in message
+    assert "validating {0!r}".format(the_err.validator) in message
+    assert the_err.message in message
diff --git a/tools/schemapi/tests/test_schemapi.py b/tools/schemapi/tests/test_schemapi.py
index ed2bb6f5..4eb329b8 100644
--- a/tools/schemapi/tests/test_schemapi.py
+++ b/tools/schemapi/tests/test_schemapi.py
@@ -1,7 +1,8 @@
 import jsonschema
 import pytest
 
-from ..schemapi import UndefinedType, SchemaBase, Undefined, _FromDict
+from ..schemapi import (UndefinedType, SchemaBase, Undefined, _FromDict,
+                        SchemaValidationError)
 
 # Make tests inherit from _TestSchema, so that when we test from_dict it won't
 # try to use SchemaBase objects defined elsewhere as wrappers.
@@ -276,3 +277,19 @@ def test_hash_schema(use_json):
         hsh2 = _FromDict.hash_schema(cls._schema, use_json=use_json)
         assert hsh1 == hsh2
         assert hash(hsh1) == hash(hsh2)
+
+
+def test_schema_validation_error():
+    try:
+        MySchema(a={'foo': 4})
+        the_err = None
+    except jsonschema.ValidationError as err:
+        the_err = err
+
+    assert isinstance(the_err, SchemaValidationError)
+    message = str(the_err)
+
+    assert message.startswith('Invalid specification')
+    assert 'test_schemapi.MySchema->a' in message
+    assert "validating {0!r}".format(the_err.validator) in message
+    assert the_err.message in message