Repository: onnx/onnx

Issue:
`helper.make_attribute` should print attribute names in error messages
https://github.com/onnx/onnx/blob/9a17d4d64c210b86107e152d98539fc93aa8f329/onnx/helper.py#L911

prints the enum as ints: `TypeError: Inferred attribute type 2 mismatched with specified type 1`, which is not very readable. We should print the enum names instead.


Discussion:
Going to take a look at this.

Resolving patch:
diff --git a/onnx/helper.py b/onnx/helper.py
--- a/onnx/helper.py
+++ b/onnx/helper.py
@@ -925,7 +925,7 @@ def make_attribute(  # pylint: disable=too-many-statements
 
     if attr_type is not None and attr.type != attr_type:
         raise TypeError(
-            f"Inferred attribute type {attr.type} mismatched with specified type {attr_type}"
+            f"Inferred attribute type '{_attr_type_to_str(attr.type)}'({attr.type}) mismatched with specified type '{_attr_type_to_str(attr_type)}'({attr_type})"
         )
     return attr
 
@@ -1538,3 +1538,18 @@ def get_all_tensor_dtypes() -> KeysView[int]:
     :return: all tensor types from TensorProto
     """
     return mapping.TENSOR_TYPE_MAP.keys()
+
+
+_ATTRIBUTE_TYPE_TO_STR = {k: v for v, k in AttributeProto.AttributeType.items()}
+
+
+def _attr_type_to_str(attr_type: int) -> str:
+    """
+    Convert AttributeProto type to string.
+
+    :param attr_type: AttributeProto type.
+    :return: String representing the supplied attr_type.
+    """
+    if attr_type in AttributeProto.AttributeType.values():
+        return _ATTRIBUTE_TYPE_TO_STR[attr_type]
+    return AttributeProto.AttributeType.keys()[0]


Test patch:
diff --git a/onnx/test/helper_test.py b/onnx/test/helper_test.py
--- a/onnx/test/helper_test.py
+++ b/onnx/test/helper_test.py
@@ -10,6 +10,7 @@
 from typing import Any, List, Tuple
 
 import numpy as np
+import parameterized
 import pytest
 
 from onnx import (
@@ -218,6 +219,10 @@ def test_attr_empty_list(self) -> None:
         self.assertEqual(len(attr.strings), 0)
         self.assertRaises(ValueError, helper.make_attribute, "empty", [])
 
+    def test_attr_mismatch(self) -> None:
+        with self.assertRaisesRegex(TypeError, "Inferred attribute type 'FLOAT'"):
+            helper.make_attribute("test", 6.4, attr_type=AttributeProto.STRING)
+
     def test_is_attr_legal(self) -> None:
         # no name, no field
         attr = AttributeProto()
@@ -883,6 +888,34 @@ def test_tensor_dtype_to_field_bfloat16(self) -> None:
         )
 
 
+class TestAttrTypeToStr(unittest.TestCase):
+    @parameterized.parameterized.expand(
+        [
+            (AttributeProto.AttributeType.FLOAT, "FLOAT"),  # type: ignore[attr-defined]
+            (AttributeProto.AttributeType.INT, "INT"),  # type: ignore[attr-defined]
+            (AttributeProto.AttributeType.STRING, "STRING"),  # type: ignore[attr-defined]
+            (AttributeProto.AttributeType.TENSOR, "TENSOR"),  # type: ignore[attr-defined]
+            (AttributeProto.AttributeType.GRAPH, "GRAPH"),  # type: ignore[attr-defined]
+            (AttributeProto.AttributeType.SPARSE_TENSOR, "SPARSE_TENSOR"),  # type: ignore[attr-defined]
+            (AttributeProto.AttributeType.TYPE_PROTO, "TYPE_PROTO"),  # type: ignore[attr-defined]
+            (AttributeProto.AttributeType.FLOATS, "FLOATS"),  # type: ignore[attr-defined]
+            (AttributeProto.AttributeType.INTS, "INTS"),  # type: ignore[attr-defined]
+            (AttributeProto.AttributeType.STRINGS, "STRINGS"),  # type: ignore[attr-defined]
+            (AttributeProto.AttributeType.TENSORS, "TENSORS"),  # type: ignore[attr-defined]
+            (AttributeProto.AttributeType.GRAPHS, "GRAPHS"),  # type: ignore[attr-defined]
+            (AttributeProto.AttributeType.SPARSE_TENSORS, "SPARSE_TENSORS"),  # type: ignore[attr-defined]
+            (AttributeProto.AttributeType.TYPE_PROTOS, "TYPE_PROTOS"),  # type: ignore[attr-defined]
+        ]
+    )
+    def test_attr_type_to_str(self, attr_type, expected_str):
+        result = helper._attr_type_to_str(attr_type)  # pylint: disable=protected-access
+        self.assertEqual(result, expected_str)
+
+    def test_attr_type_to_str_undefined(self):
+        result = helper._attr_type_to_str(9999)  # pylint: disable=protected-access
+        self.assertEqual(result, "UNDEFINED")
+
+
 if __name__ == "__main__":
     unittest.main()
     pytest.main([__file__])