Repository: tefra/xsdata
Pull request #998 (2024-03-25 06:55:27)

fit: Resolve prohibited attrs override issues
## 📒 Description

- We supported converting attrs to list when parent was a list but not the child attr, we also need to support the opposite
- In both cases we need to protect against updating prohibited attrs


## 🔗 What I've Done

> Write a description of the steps taken to resolve the issue

## 💬 Comments

> A place to write any comments to the reviewer.

## 🛫 Checklist

- [ ] Updated docs
- [ ] Added unit-tests
- [ ] [Sample tests](https://github.com/tefra/xsdata-samples) pass
- [ ] [W3C tests](https://github.com/tefra/xsdata-w3c-tests) pass



Patch:
diff --git a/xsdata/codegen/handlers/validate_attributes_overrides.py b/xsdata/codegen/handlers/validate_attributes_overrides.py
index dd675bda..24c48646 100644
--- a/xsdata/codegen/handlers/validate_attributes_overrides.py
+++ b/xsdata/codegen/handlers/validate_attributes_overrides.py
@@ -127,27 +127,45 @@ class ValidateAttributesOverrides(RelativeHandlerInterface):
         if parent_attr.is_any_type and not child_attr.is_any_type:
             return
 
-        if child_attr.is_list and not parent_attr.is_list:
+        if (
+            child_attr.is_list
+            and not parent_attr.is_list
+            and not parent_attr.is_prohibited
+        ) or (
+            not child_attr.is_list
+            and not child_attr.is_prohibited
+            and parent_attr.is_list
+        ):
             # Hack much??? idk but Optional[str] can't override List[str]
-            parent_attr.restrictions.max_occurs = sys.maxsize
+            msg = "Converting {} field `{}::{}` to a list to match {} class `{}`"
             assert parent_attr.parent is not None
-            logger.warning(
-                "Converting parent field `%s::%s` to a list to match child class `%s`",
-                parent_attr.parent,
-                parent_attr.name,
-                target.name,
-            )
+
+            if child_attr.is_list:
+                parent_attr.restrictions.max_occurs = sys.maxsize
+                log_message = msg.format(
+                    "parent",
+                    parent_attr.parent,
+                    parent_attr.name,
+                    "child",
+                    target.qname,
+                )
+            else:
+                child_attr.restrictions.max_occurs = parent_attr.restrictions.max_occurs
+                log_message = msg.format(
+                    "child",
+                    target.name,
+                    child_attr.name,
+                    "parent",
+                    parent_attr.parent,
+                )
+            logger.warning(log_message)
 
         if (
             child_attr.default == parent_attr.default
             and _bool_eq(child_attr.fixed, parent_attr.fixed)
             and _bool_eq(child_attr.mixed, parent_attr.mixed)
-            and _bool_eq(
-                child_attr.restrictions.tokens, parent_attr.restrictions.tokens
-            )
-            and _bool_eq(
-                child_attr.restrictions.nillable, parent_attr.restrictions.nillable
-            )
+            and _bool_eq(child_attr.is_tokens, parent_attr.is_tokens)
+            and _bool_eq(child_attr.is_nillable, parent_attr.is_nillable)
             and _bool_eq(child_attr.is_prohibited, parent_attr.is_prohibited)
             and _bool_eq(child_attr.is_optional, parent_attr.is_optional)
         ):
diff --git a/xsdata/codegen/mixins.py b/xsdata/codegen/mixins.py
index 5a242c9f..11dcd5ea 100644
--- a/xsdata/codegen/mixins.py
+++ b/xsdata/codegen/mixins.py
@@ -153,12 +153,12 @@ class RelativeHandlerInterface(HandlerInterface, metaclass=ABCMeta):
 
             assert base is not None
 
-            attrs.extend(self.base_attrs(base))
-
             for attr in base.attrs:
                 attr.parent = base.qname
                 attrs.append(attr)
 
+            attrs.extend(self.base_attrs(base))
+
         return attrs
 
     @abc.abstractmethod


Test patch:
diff --git a/tests/codegen/handlers/test_validate_attributes_overrides.py b/tests/codegen/handlers/test_validate_attributes_overrides.py
index 6dcd4aa1..0c7d96e4 100644
--- a/tests/codegen/handlers/test_validate_attributes_overrides.py
+++ b/tests/codegen/handlers/test_validate_attributes_overrides.py
@@ -170,11 +170,29 @@ class ValidateAttributesOverridesTests(FactoryTestCase):
         self.processor.validate_override(target, attr_a, attr_b)
         self.assertEqual(sys.maxsize, attr_b.restrictions.max_occurs)
 
+        # Parent is list, source is not
+        target.attrs = [attr_a]
+        attr_a.restrictions.min_occurs = 0
+        attr_a.restrictions.max_occurs = 1
+        attr_b.restrictions.min_occurs = 0
+        attr_b.restrictions.max_occurs = 2
+        self.processor.validate_override(target, attr_a, attr_b)
+        self.assertEqual(2, attr_b.restrictions.max_occurs)
+
+        # Source is list, parent is prohibited
+        target.attrs = [attr_a]
+        attr_a.restrictions.min_occurs = None
+        attr_a.restrictions.max_occurs = 10
+        attr_b.restrictions.min_occurs = 0
+        attr_b.restrictions.max_occurs = 0
+        self.processor.validate_override(target, attr_a, attr_b)
+        self.assertEqual(0, attr_b.restrictions.max_occurs)
+        self.assertIn(attr_a, target.attrs)
+
         # Parent is any type, source isn't, skip
         attr_a = AttrFactory.native(DataType.STRING)
         attr_b = AttrFactory.native(DataType.ANY_SIMPLE_TYPE)
         target = ClassFactory.create(attrs=[attr_a])
-
         self.processor.validate_override(target, attr_a.clone(), attr_b)
         self.assertEqual(attr_a, target.attrs[0])