You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@avro.apache.org by ko...@apache.org on 2021/07/13 01:56:39 UTC

[avro] branch master updated: AVRO-2720: Enhance AvroTypeException message to include field name (#1287)

This is an automated email from the ASF dual-hosted git repository.

kojiromike pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/avro.git


The following commit(s) were added to refs/heads/master by this push:
     new f468930  AVRO-2720: Enhance AvroTypeException message to include field name (#1287)
f468930 is described below

commit f468930a1e119517491ed4e64b1bbd2906446b49
Author: Subhash Bhushan <su...@gmail.com>
AuthorDate: Mon Jul 12 18:56:31 2021 -0700

    AVRO-2720: Enhance AvroTypeException message to include field name (#1287)
    
    * AVRO-2720: Enhance AvroTypeException message to include field name
    
    The exception message now includes the field name on which the type exception
    was raised.
    
    Closes: AVRO-2720
    
    * Increase Pypy 3.6 minimum speed to 5 secs
    * Increase benchmark timings for both READ and WRITE
    * Remove unnecessary sys import
    * Change the global warnings filter to catch IgnoredLogicalType: With this change, the ResourceWarning would just get messaged, and not raised as an error, but the IgnoredLogicalType would get raised and then caught, and we can test for it.
---
 lang/py/avro/errors.py           |  5 +++--
 lang/py/avro/io.py               |  2 +-
 lang/py/avro/test/test_bench.py  |  4 ++--
 lang/py/avro/test/test_io.py     | 21 +++++++++++++++++++--
 lang/py/avro/test/test_schema.py | 26 ++++++++++++++------------
 5 files changed, 39 insertions(+), 19 deletions(-)

diff --git a/lang/py/avro/errors.py b/lang/py/avro/errors.py
index 1723b6c..5ff3603 100644
--- a/lang/py/avro/errors.py
+++ b/lang/py/avro/errors.py
@@ -53,10 +53,11 @@ class AvroTypeException(AvroException):
 
     def __init__(self, *args):
         try:
-            expected_schema, datum = args[:2]
+            expected_schema, name, datum = args[:3]
         except (IndexError, ValueError):
             return super().__init__(*args)
-        return super().__init__(f"The datum {datum} of the type {type(datum)} is not an example of the schema {_safe_pretty(expected_schema)}")
+        pretty_expected = json.dumps(json.loads(str(expected_schema)), indent=2)
+        return super().__init__(f'The datum "{datum}" provided for "{name}" is not an example of the schema {pretty_expected}')
 
 
 class InvalidDefaultException(AvroTypeException):
diff --git a/lang/py/avro/io.py b/lang/py/avro/io.py
index 161f739..d8b0f94 100644
--- a/lang/py/avro/io.py
+++ b/lang/py/avro/io.py
@@ -144,7 +144,7 @@ def validate(expected_schema: avro.schema.Schema, datum: object, raise_on_error:
 
         if valid_node is None:
             if raise_on_error:
-                raise avro.errors.AvroTypeException(current_node.schema, current_node.datum)
+                raise avro.errors.AvroTypeException(current_node.schema, current_node.name, current_node.datum)
             return False  # preserve the prior validation behavior of returning false when there are problems.
         # if there are children of this node to append, do so.
         for child_node in _iterate_node(valid_node):
diff --git a/lang/py/avro/test/test_bench.py b/lang/py/avro/test/test_bench.py
index e9edd08..eec667f 100644
--- a/lang/py/avro/test/test_bench.py
+++ b/lang/py/avro/test/test_bench.py
@@ -51,8 +51,8 @@ SCHEMA: avro.schema.RecordSchema = avro.schema.parse(
 READER = avro.io.DatumReader(SCHEMA)
 WRITER = avro.io.DatumWriter(SCHEMA)
 NUMBER_OF_TESTS = 10000
-MAX_WRITE_SECONDS = 3 if platform.python_implementation() == "PyPy" else 1
-MAX_READ_SECONDS = 3 if platform.python_implementation() == "PyPy" else 1
+MAX_WRITE_SECONDS = 5 if platform.python_implementation() == "PyPy" else 3
+MAX_READ_SECONDS = 5 if platform.python_implementation() == "PyPy" else 3
 
 
 class TestBench(unittest.TestCase):
diff --git a/lang/py/avro/test/test_io.py b/lang/py/avro/test/test_io.py
index 3ef5c2b..a07d122 100644
--- a/lang/py/avro/test/test_io.py
+++ b/lang/py/avro/test/test_io.py
@@ -538,7 +538,7 @@ class TestMisc(unittest.TestCase):
         datum_read = read_datum(writer, writers_schema, readers_schema)
         self.assertEqual(datum_to_read, datum_read)
 
-    def test_type_exception(self) -> None:
+    def test_type_exception_int(self) -> None:
         writers_schema = avro.schema.parse(
             json.dumps(
                 {
@@ -552,7 +552,24 @@ class TestMisc(unittest.TestCase):
             )
         )
         datum_to_write = {"E": 5, "F": "Bad"}
-        self.assertRaises(avro.errors.AvroTypeException, write_datum, datum_to_write, writers_schema)
+        with self.assertRaises(avro.errors.AvroTypeException) as exc:
+            write_datum(datum_to_write, writers_schema)
+        assert str(exc.exception) == 'The datum "Bad" provided for "F" is not an example of the schema "int"'
+
+    def test_type_exception_long(self) -> None:
+        writers_schema = avro.schema.parse(json.dumps({"type": "record", "name": "Test", "fields": [{"name": "foo", "type": "long"}]}))
+        datum_to_write = {"foo": 5.0}
+
+        with self.assertRaises(avro.errors.AvroTypeException) as exc:
+            write_datum(datum_to_write, writers_schema)
+        assert str(exc.exception) == 'The datum "5.0" provided for "foo" is not an example of the schema "long"'
+
+    def test_type_exception_record(self) -> None:
+        writers_schema = avro.schema.parse(json.dumps({"type": "record", "name": "Test", "fields": [{"name": "foo", "type": "long"}]}))
+        datum_to_write = ("foo", 5.0)
+
+        with self.assertRaisesRegex(avro.errors.AvroTypeException, r"The datum \".*\" provided for \".*\" is not an example of the schema [\s\S]*"):
+            write_datum(datum_to_write, writers_schema)
 
 
 def load_tests(loader: unittest.TestLoader, default_tests: None, pattern: None) -> unittest.TestSuite:
diff --git a/lang/py/avro/test/test_schema.py b/lang/py/avro/test/test_schema.py
index 1a8e96a..4506744 100644
--- a/lang/py/avro/test/test_schema.py
+++ b/lang/py/avro/test/test_schema.py
@@ -713,19 +713,21 @@ class SchemaParseTestCase(unittest.TestCase):
         # Never hide repeated warnings when running this test case.
         warnings.simplefilter("always")
 
-    def parse_valid(self):
+    def parse_valid(self) -> None:
         """Parsing a valid schema should not error, but may contain warnings."""
-        with warnings.catch_warnings(record=True) as actual_warnings:
-            try:
-                self.test_schema.parse()
-            except (avro.errors.AvroException, avro.errors.SchemaParseException):  # pragma: no coverage
-                self.fail(f"Valid schema failed to parse: {self.test_schema!s}")
-            actual_messages = [str(wmsg.message) for wmsg in actual_warnings]
-            if self.test_schema.warnings:
-                expected_messages = [str(w) for w in self.test_schema.warnings]
-                self.assertEqual(actual_messages, expected_messages)
-            else:
-                self.assertEqual(actual_messages, [])
+        test_warnings = self.test_schema.warnings or []
+        try:
+            warnings.filterwarnings(action="error", category=avro.errors.IgnoredLogicalType)
+            self.test_schema.parse()
+        except (avro.errors.IgnoredLogicalType) as e:
+            self.assertIn(type(e), (type(w) for w in test_warnings))
+            self.assertIn(str(e), (str(w) for w in test_warnings))
+        except (avro.errors.AvroException, avro.errors.SchemaParseException):  # pragma: no coverage
+            self.fail(f"Valid schema failed to parse: {self.test_schema!s}")
+        else:
+            self.assertEqual([], test_warnings)
+        finally:
+            warnings.filterwarnings(action="default", category=avro.errors.IgnoredLogicalType)
 
     def parse_invalid(self):
         """Parsing an invalid schema should error."""