Repository: tefra/xsdata

Issue:
Enum converter Decimal('NaN') != Decimal('NaN')
This interesting behavior is failing the Enum converter 

```python

In [1]: from enum import Enum

In [2]: from decimal import Decimal

In [3]: class Value(Enum):
   ...:     VALUE_9_99 = Decimal('9.99')
   ...:     NAN = Decimal('NaN')
   ...: 


In [6]: Value(Decimal('9.99'))
Out[6]: <Value.VALUE_9_99: Decimal('9.99')>

In [7]: Value(Decimal('NaN'))

ValueError: Decimal('NaN') is not a valid Value


```

```python
In [8]: Decimal('NaN') == Decimal('NaN')
Out[8]: False
```


Discussion:

Patch:
diff --git a/xsdata/formats/converter.py b/xsdata/formats/converter.py
index b5897fe2..e222c88d 100644
--- a/xsdata/formats/converter.py
+++ b/xsdata/formats/converter.py
@@ -230,12 +230,12 @@ class QNameConverter(Converter):
         self, value: QName, ns_map: Optional[Dict] = None, **kwargs: Any
     ) -> str:
         """
-        Convert a QName instance to string either with a namespace prefix if
-        context namespaces are provided or as fully qualified with the
-        namespace uri.
+        Convert a QName instance to string either with a namespace prefix if a
+        prefix-URI namespaces mapping is provided or to a fully qualified name
+        with the namespace.
 
         examples:
-            - QName("http://www.w3.org/2001/XMLSchema", "int") & namespaces -> xs:int
+            - QName("http://www.w3.org/2001/XMLSchema", "int") & ns_map -> xs:int
             - QName("foo, "bar") -> {foo}bar
         """
 
@@ -294,12 +294,12 @@ class LxmlQNameConverter(Converter):
         self, value: etree.QName, ns_map: Optional[Dict] = None, **kwargs: Any
     ) -> str:
         """
-        Convert a QName instance to string either with a namespace prefix if
-        context namespaces are provided or as fully qualified with the
-        namespace uri.
+        Convert a QName instance to string either with a namespace prefix if a
+        prefix-URI namespaces mapping is provided or to a fully qualified name
+        with the namespace.
 
         examples:
-            - QName("http://www.w3.org/2001/XMLSchema", "int") & namespaces -> xs:int
+            - QName("http://www.w3.org/2001/XMLSchema", "int") & ns_map -> xs:int
             - QName("foo, "bar") -> {foo}bar
         """
 
@@ -319,17 +319,33 @@ class EnumConverter(Converter):
 
         # Convert string value to the type of the first enum member first, otherwise
         # more complex types like QName, Decimals will fail.
-        enum_member: Enum = list(data_type)[0]
-        real_value = converter.from_string(value, [type(enum_member.value)], **kwargs)
+        member: Enum = list(data_type)[0]
+        value_type = type(member.value)
+
+        # Suppress warnings
+        with warnings.catch_warnings():
+            warnings.simplefilter("ignore")
+            real_value = converter.from_string(value, [value_type], **kwargs)
+
+        # Raise exception if the real value doesn't match the expected type.
+        if not isinstance(real_value, value_type):
+            raise ConverterError()
+
+        # Attempt #1 use the enum constructor
+        with contextlib.suppress(ValueError):
+            return data_type(real_value)
 
         try:
-            try:
-                return data_type(real_value)
-            except ValueError:
-                # enums may be derived from xs:NMTOKENS or xs:list
-                # try again after removing excess whitespace.
+            # Attempt #2 the enum might be derived from
+            # xs:NMTOKENS or xs:list removing excess whitespace.
+            if isinstance(real_value, str):
                 return data_type(" ".join(value.split()))
-        except ValueError:
+
+            # Attempt #3 some values are never equal try to match
+            # canonical representations.
+            repr_value = repr(real_value)
+            return next(x for x in data_type if repr(x.value) == repr_value)
+        except (ValueError, StopIteration):
             raise ConverterError()
 
     def to_string(self, value: Enum, **kwargs: Any) -> str:


Test patch:
diff --git a/tests/formats/test_converter.py b/tests/formats/test_converter.py
index 11661a63..cfc8fc11 100644
--- a/tests/formats/test_converter.py
+++ b/tests/formats/test_converter.py
@@ -242,10 +242,7 @@ class EnumConverterTests(TestCase):
             with warnings.catch_warnings(record=True) as w:
                 convert("a", data_type=Fixture)
 
-        self.assertEqual(
-            "Failed to convert value `a` to one of [<class 'float'>]",
-            str(w[-1].message),
-        )
+        self.assertEqual(0, len(w))
 
         self.assertEqual(Fixture.two_point_one, convert("2.1", data_type=Fixture))
 
@@ -256,6 +253,16 @@ class EnumConverterTests(TestCase):
         convert = self.converter.from_string
         self.assertEqual(Fixture.a, convert(" a \na a  ", data_type=Fixture))
 
+    def test_from_string_with_value_never_equal_to_anything(self):
+        class Fixture(Enum):
+            a = Decimal("NaN")
+
+        convert = self.converter.from_string
+        self.assertEqual(Fixture.a, convert("NaN", data_type=Fixture))
+
+        with self.assertRaises(ConverterError):
+            convert("1.0", data_type=Fixture)
+
     def test_from_string_raises_exception_on_missing_data_type(self):
         with self.assertRaises(ConverterError) as cm:
             self.converter.from_string("a")