Repository: tefra/xsdata
Pull request #516 (2021-06-05 14:12:28)

Relax attribute override validations completely


Patch:
diff --git a/xsdata/codegen/handlers/attribute_overrides.py b/xsdata/codegen/handlers/attribute_overrides.py
index 82ad8fc0..30a61243 100644
--- a/xsdata/codegen/handlers/attribute_overrides.py
+++ b/xsdata/codegen/handlers/attribute_overrides.py
@@ -58,7 +58,7 @@ class AttributeOverridesHandler(RelativeHandlerInterface):
             attr.default == source_attr.default
             and attr.fixed == source_attr.fixed
             and attr.mixed == source_attr.mixed
-            and attr.restrictions == source_attr.restrictions
+            and attr.restrictions.is_compatible(source_attr.restrictions)
         ):
             ClassUtils.remove_attribute(target, attr)
 
diff --git a/xsdata/codegen/models.py b/xsdata/codegen/models.py
index 81a1f501..e8b26234 100644
--- a/xsdata/codegen/models.py
+++ b/xsdata/codegen/models.py
@@ -61,8 +61,8 @@ class Restrictions:
 
     required: Optional[bool] = field(default=None)
     prohibited: Optional[bool] = field(default=None)
-    min_occurs: Optional[int] = field(default=None, compare=False)
-    max_occurs: Optional[int] = field(default=None, compare=False)
+    min_occurs: Optional[int] = field(default=None)
+    max_occurs: Optional[int] = field(default=None)
     min_exclusive: Optional[str] = field(default=None)
     min_inclusive: Optional[str] = field(default=None)
     min_length: Optional[int] = field(default=None)
@@ -76,10 +76,10 @@ class Restrictions:
     pattern: Optional[str] = field(default=None)
     explicit_timezone: Optional[str] = field(default=None)
     nillable: Optional[bool] = field(default=None)
-    sequential: Optional[bool] = field(default=None, compare=False)
+    sequential: Optional[bool] = field(default=None)
     tokens: Optional[bool] = field(default=None)
     format: Optional[str] = field(default=None)
-    choice: Optional[str] = field(default=None, compare=False)
+    choice: Optional[str] = field(default=None)
 
     @property
     def is_list(self) -> bool:
@@ -95,6 +95,16 @@ class Restrictions:
     def is_prohibited(self) -> bool:
         return self.prohibited or self.max_occurs == 0
 
+    def is_compatible(self, other: "Restrictions") -> bool:
+        def bool_eq(a: Optional[bool], b: Optional[bool]) -> bool:
+            return bool(a) is bool(b)
+
+        return (
+            bool_eq(self.required, other.required)
+            and bool_eq(self.nillable, other.nillable)
+            and bool_eq(self.tokens, other.tokens)
+        )
+
     def merge(self, source: "Restrictions"):
         """Update properties from another instance."""
 


Test patch:
diff --git a/tests/codegen/handlers/test_attribute_overrides.py b/tests/codegen/handlers/test_attribute_overrides.py
index 444f1658..3847150a 100644
--- a/tests/codegen/handlers/test_attribute_overrides.py
+++ b/tests/codegen/handlers/test_attribute_overrides.py
@@ -80,20 +80,12 @@ class AttributeOverridesHandlerTests(FactoryTestCase):
 
         # restrictions except choice, min/max occurs don't match
         attr_b.fixed = attr_a.fixed
-        attr_a.restrictions.max_length = 10
+        attr_a.restrictions.required = not attr_b.restrictions.required
         self.processor.validate_override(target, attr_a, attr_b)
         self.assertEqual(1, len(target.attrs))
 
         # Restrictions are compatible again
-        attr_b.restrictions.max_length = attr_a.restrictions.max_length
-
-        attr_a.restrictions.min_occurs = 1
-        attr_a.restrictions.max_occurs = 2
-        attr_a.restrictions.choice = 3
-
-        attr_b.restrictions.min_occurs = 4
-        attr_b.restrictions.max_occurs = 5
-        attr_b.restrictions.choice = 6
+        attr_a.restrictions.required = attr_b.restrictions.required
         self.processor.validate_override(target, attr_a, attr_b)
         self.assertEqual(0, len(target.attrs))
 
diff --git a/tests/codegen/models/test_restrictions.py b/tests/codegen/models/test_restrictions.py
index 00cbe2da..b67613a2 100644
--- a/tests/codegen/models/test_restrictions.py
+++ b/tests/codegen/models/test_restrictions.py
@@ -122,17 +122,23 @@ class RestrictionsTests(TestCase):
         self.assertEqual(clone, restrictions)
         self.assertIsNot(clone, restrictions)
 
-    def test_compare(self):
+    def test_is_compatible(self):
         clone = self.restrictions.clone()
-        self.assertEqual(clone, self.restrictions)
+        self.assertTrue(self.restrictions.is_compatible(clone))
 
-        clone.min_occurs = 54
-        clone.max_occurs = 56
-        self.assertEqual(clone, self.restrictions)
+        clone.max_length = 10
+        self.assertTrue(self.restrictions.is_compatible(clone))
 
-        clone.choice = 145
-        clone.sequential = True
-        self.assertEqual(clone, self.restrictions)
+        clone.required = not self.restrictions.required
+        clone.nillable = not self.restrictions.nillable
+        clone.tokens = not self.restrictions.tokens
+        self.assertFalse(self.restrictions.is_compatible(clone))
 
-        clone.max_length = 11
-        self.assertNotEqual(clone, self.restrictions)
+        clone.required = not clone.required
+        self.assertFalse(self.restrictions.is_compatible(clone))
+
+        clone.nillable = not clone.nillable
+        self.assertFalse(self.restrictions.is_compatible(clone))
+
+        clone.tokens = not clone.tokens
+        self.assertTrue(self.restrictions.is_compatible(clone))