Repository: tefra/xsdata

Issue:
restriction base not used in generation
I am currently using analysis on mro basis in order to figure out how objects relate to eachother.

I notice that "restriction base" is not affecting the schema generation. Below are some examples. Can the restriction base used for inheritance. Or am I missing the point of the restriction in the first place?

https://github.com/NeTEx-CEN/NeTEx/issues/407#issuecomment-1398257962

Discussion:

Patch:
diff --git a/xsdata/codegen/handlers/create_compound_fields.py b/xsdata/codegen/handlers/create_compound_fields.py
index b690e88d..d7cfcb93 100644
--- a/xsdata/codegen/handlers/create_compound_fields.py
+++ b/xsdata/codegen/handlers/create_compound_fields.py
@@ -1,6 +1,8 @@
+from collections import Counter
 from collections import defaultdict
 from typing import Dict
 from typing import List
+from typing import Set
 
 from xsdata.codegen.mixins import ContainerInterface
 from xsdata.codegen.mixins import RelativeHandlerInterface
@@ -8,9 +10,9 @@ from xsdata.codegen.models import Attr
 from xsdata.codegen.models import AttrType
 from xsdata.codegen.models import Class
 from xsdata.codegen.models import get_restriction_choice
-from xsdata.codegen.models import get_slug
 from xsdata.codegen.models import Restrictions
 from xsdata.codegen.utils import ClassUtils
+from xsdata.formats.dataclass.models.elements import XmlType
 from xsdata.models.enums import DataType
 from xsdata.models.enums import Tag
 from xsdata.utils.collections import group_by
@@ -77,9 +79,6 @@ class CreateCompoundFields(RelativeHandlerInterface):
         )
 
     def choose_name(self, target: Class, names: List[str]) -> str:
-        reserved = set(map(get_slug, self.base_attrs(target)))
-        reserved.update(map(get_slug, target.attrs))
-
         if (
             self.config.force_default_name
             or len(names) > 3
@@ -89,8 +88,21 @@ class CreateCompoundFields(RelativeHandlerInterface):
         else:
             name = "_Or_".join(names)
 
+        reserved = self.build_reserved_names(target, names)
         return ClassUtils.unique_name(name, reserved)
 
+    def build_reserved_names(self, target: Class, names: List[str]) -> Set[str]:
+        names_counter = Counter(names)
+        all_attrs = self.base_attrs(target)
+        all_attrs.extend(target.attrs)
+
+        return {
+            attr.slug
+            for attr in all_attrs
+            if attr.xml_type != XmlType.ELEMENTS
+            or Counter([x.local_name for x in attr.choices]) != names_counter
+        }
+
     @classmethod
     def build_attr_choice(cls, attr: Attr) -> Attr:
         """
diff --git a/xsdata/codegen/handlers/flatten_class_extensions.py b/xsdata/codegen/handlers/flatten_class_extensions.py
index 7f7774d0..b8aad125 100644
--- a/xsdata/codegen/handlers/flatten_class_extensions.py
+++ b/xsdata/codegen/handlers/flatten_class_extensions.py
@@ -213,24 +213,12 @@ class FlattenClassExtensions(RelativeHandlerInterface):
             source.is_simple_type
             or target.has_suffix_attr
             or (source.has_suffix_attr and target.attrs)
-            or not cls.validate_type_overrides(source, target)
             or not cls.validate_sequence_order(source, target)
         ):
             return True
 
         return False
 
-    @classmethod
-    def validate_type_overrides(cls, source: Class, target: Class) -> bool:
-        """Validate every override is using a subset of the parent attr
-        types."""
-        for attr in target.attrs:
-            src_attr = ClassUtils.find_attr(source, attr.name)
-            if src_attr and any(tp not in src_attr.types for tp in attr.types):
-                return False
-
-        return True
-
     @classmethod
     def validate_sequence_order(cls, source: Class, target: Class) -> bool:
         """
diff --git a/xsdata/codegen/handlers/sanitize_attributes_default_value.py b/xsdata/codegen/handlers/sanitize_attributes_default_value.py
index 77b66293..872112e9 100644
--- a/xsdata/codegen/handlers/sanitize_attributes_default_value.py
+++ b/xsdata/codegen/handlers/sanitize_attributes_default_value.py
@@ -171,10 +171,14 @@ class SanitizeAttributesDefaultValue(RelativeHandlerInterface):
         """
         Return whether the min occurrences for the attr needs to be reset.
 
-        Cases:
-            1. xs:any(Simple)Type, with no default value that's not a list already!
+        @Todo figure out if wildcards are supposed to be optional!
         """
-        return attr.default is None and object in attr.native_types and not attr.is_list
+        return (
+            not attr.is_attribute
+            and attr.default is None
+            and object in attr.native_types
+            and not attr.is_list
+        )
 
     @classmethod
     def should_reset_default(cls, attr: Attr) -> bool:
diff --git a/xsdata/codegen/handlers/validate_attributes_overrides.py b/xsdata/codegen/handlers/validate_attributes_overrides.py
index 944ffcbd..51774e46 100644
--- a/xsdata/codegen/handlers/validate_attributes_overrides.py
+++ b/xsdata/codegen/handlers/validate_attributes_overrides.py
@@ -30,17 +30,24 @@ class ValidateAttributesOverrides(RelativeHandlerInterface):
 
             if base_attrs:
                 base_attr = base_attrs[0]
-                if attr.tag == base_attr.tag:
+                if self.overrides(attr, base_attr):
                     self.validate_override(target, attr, base_attr)
                 else:
                     self.resolve_conflict(attr, base_attr)
 
+    @classmethod
+    def overrides(cls, a: Attr, b: Attr) -> bool:
+        return a.xml_type == b.xml_type and a.namespace == b.namespace
+
     def base_attrs_map(self, target: Class) -> Dict[str, List[Attr]]:
         base_attrs = self.base_attrs(target)
         return collections.group_by(base_attrs, key=get_slug)
 
     @classmethod
     def validate_override(cls, target: Class, attr: Attr, source_attr: Attr):
+        if source_attr.is_any_type and not attr.is_any_type:
+            return
+
         if attr.is_list and not source_attr.is_list:
             # Hack much??? idk but Optional[str] can't override List[str]
             source_attr.restrictions.max_occurs = sys.maxsize
diff --git a/xsdata/codegen/models.py b/xsdata/codegen/models.py
index f995cb86..d1eadd6b 100644
--- a/xsdata/codegen/models.py
+++ b/xsdata/codegen/models.py
@@ -336,16 +336,14 @@ class Attr:
         xs:any."""
         return self.tag in (Tag.ANY_ATTRIBUTE, Tag.ANY)
 
+    @property
+    def is_any_type(self) -> bool:
+        return any(tp is object for tp in self.get_native_types())
+
     @property
     def native_types(self) -> List[Type]:
         """Return a list of all builtin data types."""
-        result = set()
-        for tp in self.types:
-            datatype = tp.datatype
-            if datatype:
-                result.add(datatype.type)
-
-        return list(result)
+        return list(set(self.get_native_types()))
 
     @property
     def user_types(self) -> Iterator[AttrType]:
@@ -371,6 +369,12 @@ class Attr:
             restrictions=self.restrictions.clone(),
         )
 
+    def get_native_types(self) -> Iterator[Type]:
+        for tp in self.types:
+            datatype = tp.datatype
+            if datatype:
+                yield datatype.type
+
 
 @dataclass(unsafe_hash=True)
 class Extension:
diff --git a/xsdata/models/xsd.py b/xsdata/models/xsd.py
index 53005027..2499e419 100644
--- a/xsdata/models/xsd.py
+++ b/xsdata/models/xsd.py
@@ -329,6 +329,11 @@ class Attribute(AnnotationBase):
         elif self.ref:
             yield self.ref
 
+    @property
+    def default_type(self) -> str:
+        datatype = DataType.STRING if self.fixed else DataType.ANY_SIMPLE_TYPE
+        return datatype.prefixed(self.xs_prefix)
+
     def get_restrictions(self) -> Dict[str, Anything]:
         if self.use == UseType.REQUIRED:
             restrictions = {"min_occurs": 1, "max_occurs": 1}


Test patch:
diff --git a/tests/codegen/handlers/test_create_compound_fields.py b/tests/codegen/handlers/test_create_compound_fields.py
index 1f52cdfb..6a1c9198 100644
--- a/tests/codegen/handlers/test_create_compound_fields.py
+++ b/tests/codegen/handlers/test_create_compound_fields.py
@@ -6,6 +6,7 @@ from xsdata.codegen.models import Class
 from xsdata.codegen.models import Restrictions
 from xsdata.models.config import GeneratorConfig
 from xsdata.models.enums import DataType
+from xsdata.models.enums import Tag
 from xsdata.utils.testing import AttrFactory
 from xsdata.utils.testing import AttrTypeFactory
 from xsdata.utils.testing import ClassFactory
@@ -123,12 +124,6 @@ class CreateCompoundFieldsTests(FactoryTestCase):
         actual = self.processor.choose_name(target, ["a", "b", "c", "d"])
         self.assertEqual("choice_1", actual)
 
-        base = ClassFactory.create()
-        base.attrs.append(AttrFactory.create(name="Choice!"))
-        target.extensions.append(ExtensionFactory.reference(base.qname))
-        self.container.extend((target, base))
-
-        target.attrs.clear()
         actual = self.processor.choose_name(target, ["a", "b", "c", "d"])
         self.assertEqual("choice_1", actual)
 
@@ -140,6 +135,39 @@ class CreateCompoundFieldsTests(FactoryTestCase):
         actual = self.processor.choose_name(target, ["a", "b", "c"])
         self.assertEqual("ThisOrThat", actual)
 
+    def test_build_reserved_names(self):
+        base = ClassFactory.create(
+            attrs=[
+                AttrFactory.create("standalone"),
+                AttrFactory.create(
+                    name="first",
+                    tag=Tag.CHOICE,
+                    choices=[
+                        AttrFactory.create(name="a"),
+                        AttrFactory.create(name="b"),
+                        AttrFactory.create(name="c"),
+                    ],
+                ),
+                AttrFactory.create(
+                    name="second",
+                    tag=Tag.CHOICE,
+                    choices=[
+                        AttrFactory.create(name="b"),
+                        AttrFactory.create(name="c"),
+                    ],
+                ),
+            ]
+        )
+
+        target = ClassFactory.create()
+        target.extensions.append(ExtensionFactory.reference(qname=base.qname))
+        self.processor.container.extend([base, target])
+
+        actual = self.processor.build_reserved_names(target, names=["b", "c"])
+        expected = {"standalone", "first"}
+
+        self.assertEqual(expected, actual)
+
     def test_build_attr_choice(self):
         attr = AttrFactory.create(
             name="a", namespace="xsdata", default="123", help="help", fixed=True
diff --git a/tests/codegen/handlers/test_flatten_class_extensions.py b/tests/codegen/handlers/test_flatten_class_extensions.py
index 24746b75..903c05aa 100644
--- a/tests/codegen/handlers/test_flatten_class_extensions.py
+++ b/tests/codegen/handlers/test_flatten_class_extensions.py
@@ -401,22 +401,6 @@ class FlattenClassExtensionsTests(FactoryTestCase):
         target.attrs = [target.attrs[1], target.attrs[0], target.attrs[2]]
         self.assertTrue(self.processor.should_flatten_extension(source, target))
 
-        # Types violation
-        target = source.clone()
-        target.attrs[1].types = [
-            AttrTypeFactory.native(DataType.INT),
-            AttrTypeFactory.native(DataType.FLOAT),
-        ]
-
-        source.attrs[1].types = [
-            AttrTypeFactory.native(DataType.INT),
-            AttrTypeFactory.native(DataType.FLOAT),
-            AttrTypeFactory.native(DataType.DECIMAL),
-        ]
-        self.assertFalse(self.processor.should_flatten_extension(source, target))
-        target.attrs[1].types.append(AttrTypeFactory.native(DataType.QNAME))
-        self.assertTrue(self.processor.should_flatten_extension(source, target))
-
     def test_replace_attributes_type(self):
         extension = ExtensionFactory.create()
         target = ClassFactory.elements(2)
diff --git a/tests/codegen/handlers/test_validate_attributes_overrides.py b/tests/codegen/handlers/test_validate_attributes_overrides.py
index 77fa78a0..c8373798 100644
--- a/tests/codegen/handlers/test_validate_attributes_overrides.py
+++ b/tests/codegen/handlers/test_validate_attributes_overrides.py
@@ -5,6 +5,7 @@ from xsdata.codegen.container import ClassContainer
 from xsdata.codegen.handlers import ValidateAttributesOverrides
 from xsdata.codegen.models import Status
 from xsdata.models.config import GeneratorConfig
+from xsdata.models.enums import DataType
 from xsdata.models.enums import Tag
 from xsdata.utils.testing import AttrFactory
 from xsdata.utils.testing import ClassFactory
@@ -50,6 +51,18 @@ class ValidateAttributesOverridesTests(FactoryTestCase):
             class_a.attrs[1], class_c.attrs[1]
         )
 
+    def test_overrides(self):
+        a = AttrFactory.create(tag=Tag.SIMPLE_TYPE)
+        b = a.clone()
+
+        self.assertTrue(self.processor.overrides(a, b))
+
+        b.tag = Tag.EXTENSION
+        self.assertTrue(self.processor.overrides(a, b))
+
+        b.namespace = "foo"
+        self.assertFalse(self.processor.overrides(a, b))
+
     def test_validate_override(self):
         attr_a = AttrFactory.create()
         attr_b = attr_a.clone()
@@ -91,6 +104,7 @@ class ValidateAttributesOverridesTests(FactoryTestCase):
         self.processor.validate_override(target, attr_a, attr_b)
         self.assertEqual(0, len(target.attrs))
 
+        # Source is list, parent is not
         target.attrs.append(attr_a)
         attr_a.restrictions.min_occurs = None
         attr_a.restrictions.max_occurs = 10
@@ -99,6 +113,14 @@ class ValidateAttributesOverridesTests(FactoryTestCase):
         self.processor.validate_override(target, attr_a, attr_b)
         self.assertEqual(sys.maxsize, attr_b.restrictions.max_occurs)
 
+        # 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])
+
     def test_resolve_conflicts(self):
         a = AttrFactory.create(name="foo", tag=Tag.ATTRIBUTE)
         b = a.clone()
diff --git a/tests/codegen/models/test_attr.py b/tests/codegen/models/test_attr.py
index 3b60adc5..6e20eb24 100644
--- a/tests/codegen/models/test_attr.py
+++ b/tests/codegen/models/test_attr.py
@@ -95,6 +95,18 @@ class AttrTests(FactoryTestCase):
         self.assertFalse(AttrFactory.create(tag=Tag.ATTRIBUTE).is_nameless)
         self.assertTrue(AttrFactory.create(tag=Tag.ANY).is_nameless)
 
+    def test_property_is_any_type(self):
+        attr = AttrFactory.create(
+            types=[
+                AttrTypeFactory.create(qname="foo"),
+                AttrTypeFactory.native(DataType.FLOAT),
+            ]
+        )
+        self.assertFalse(attr.is_any_type)
+
+        attr.types.append(AttrTypeFactory.native(DataType.ANY_SIMPLE_TYPE))
+        self.assertTrue(attr.is_any_type)
+
     def test_property_native_types(self):
         attr = AttrFactory.create(
             types=[
diff --git a/tests/models/xsd/test_attribute.py b/tests/models/xsd/test_attribute.py
index 93827851..9cbf1d50 100644
--- a/tests/models/xsd/test_attribute.py
+++ b/tests/models/xsd/test_attribute.py
@@ -67,3 +67,14 @@ class AttributeTests(TestCase):
         obj.type = "foo"
         obj.simple_type = None
         self.assertEqual(["foo"], list(obj.bases))
+
+    def test_property_default_type(self):
+        obj = Attribute()
+        self.assertEqual("anySimpleType", obj.default_type)
+
+        obj = Attribute()
+        obj.ns_map["foo"] = Namespace.XS.uri
+        self.assertEqual("foo:anySimpleType", obj.default_type)
+
+        obj.fixed = "aa"
+        self.assertEqual("foo:string", obj.default_type)