Repository: tefra/xsdata

Issue:
Issue while generating bindings
While generating bindings with the following command:

 xsdata generate -r xsd --package generali.service-models

I receive the following error

xsdata.exceptions.CodeGenerationError: Enumeration class with a complex extension.

JAXB (Java API) manages the xsd , and I submitted the same to w3c org validator. Any way to get more details about the error? 

Discussion:

Patch:
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 648057e8..28173956 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -45,7 +45,7 @@ repos:
     rev: v1.20.0
     hooks:
       - id: setup-cfg-fmt
-        args: ["--max-py-version=3.10"]
+        args: ["--max-py-version=3.11"]
   - repo: https://github.com/PyCQA/doc8
     rev: 0.10.1
     hooks:
diff --git a/setup.cfg b/setup.cfg
index cbaa8d30..c6f2ba87 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -21,6 +21,7 @@ classifiers =
     Programming Language :: Python :: 3.8
     Programming Language :: Python :: 3.9
     Programming Language :: Python :: 3.10
+    Programming Language :: Python :: 3.11
     Programming Language :: Python :: Implementation :: CPython
     Programming Language :: Python :: Implementation :: PyPy
     Topic :: Software Development :: Code Generators
diff --git a/xsdata/codegen/handlers/attribute_name_conflict.py b/xsdata/codegen/handlers/attribute_name_conflict.py
index 05a48428..1c1c11e2 100644
--- a/xsdata/codegen/handlers/attribute_name_conflict.py
+++ b/xsdata/codegen/handlers/attribute_name_conflict.py
@@ -1,8 +1,13 @@
 from xsdata.codegen.mixins import HandlerInterface
+from xsdata.codegen.models import Attr
 from xsdata.codegen.models import Class
-from xsdata.codegen.models import get_slug
 from xsdata.codegen.utils import ClassUtils
 from xsdata.utils.collections import group_by
+from xsdata.utils.constants import DEFAULT_ATTR_NAME
+
+
+def attr_group_name(x: Attr) -> str:
+    return x.slug or DEFAULT_ATTR_NAME
 
 
 class AttributeNameConflictHandler(HandlerInterface):
@@ -13,7 +18,7 @@ class AttributeNameConflictHandler(HandlerInterface):
     def process(self, target: Class):
         """Sanitize duplicate attribute names that might exist by applying
         rename strategies."""
-        grouped = group_by(target.attrs, key=get_slug)
+        grouped = group_by(target.attrs, key=attr_group_name)
         for items in grouped.values():
             total = len(items)
             if total == 2 and not items[0].is_enumeration:
diff --git a/xsdata/codegen/handlers/class_extension.py b/xsdata/codegen/handlers/class_extension.py
index 2aa3604d..345c0702 100644
--- a/xsdata/codegen/handlers/class_extension.py
+++ b/xsdata/codegen/handlers/class_extension.py
@@ -6,7 +6,6 @@ from xsdata.codegen.models import AttrType
 from xsdata.codegen.models import Class
 from xsdata.codegen.models import Extension
 from xsdata.codegen.utils import ClassUtils
-from xsdata.exceptions import CodeGenerationError
 from xsdata.logger import logger
 from xsdata.models.enums import DataType
 from xsdata.models.enums import NamespaceType
@@ -66,25 +65,26 @@ class ClassExtensionHandler(RelativeHandlerInterface):
         """
         Process enumeration class extension.
 
-        Extension cases:
-            1. Enumeration: copy all attr properties
-            2. Simple type: copy value attr properties recursively
-            3. Complex type:
-                3.1 Target has one enumeration member, convert to complex type
-                3.2 Invalid schema.
+        Cases:
+            1. Source is an enumeration: merge them
+            2. Source is a simple type: copy all source attr types
+            3. Source is a complex type
+                3.1 Target has a single member: Restrict default value
+                3.2 Target has multiple members: unsupported reset enumeration
         """
-        if ext:
-            target.extensions.remove(ext)
-
         if source.is_enumeration:
             self.merge_enumerations(source, target)
-        elif len(source.attrs) == 1:
+        elif source.is_simple_type:
             self.merge_enumeration_types(source, target)
         elif len(target.attrs) == 1:
-            # We are not an enumeration pal.
-            self.convert_to_complex_type(source, target)
+            self.set_default_value(source, target)
         else:
-            raise CodeGenerationError("Enumeration class with a complex extension.")
+            # We can't subclass and override the value field
+            # the target enumeration, mypy doesn't play nicely.
+            target.attrs.clear()
+
+        if ext and target.is_enumeration:
+            target.extensions.remove(ext)
 
     @classmethod
     def merge_enumerations(cls, source: Class, target: Class):
@@ -110,15 +110,14 @@ class ClassExtensionHandler(RelativeHandlerInterface):
                 self.process_enum_extension(base, target, None)
 
     @classmethod
-    def convert_to_complex_type(cls, source: Class, target: Class):
-        default = target.attrs[0].default
-        target.attrs = [attr.clone() for attr in source.attrs]
-        target.extensions = [ext.clone() for ext in source.extensions]
-
-        for attr in target.attrs:
-            if attr.xml_type is None:
-                attr.default = default
-                attr.fixed = True
+    def set_default_value(cls, source: Class, target: Class):
+        """Restrict the extension source class with the target single
+        enumeration value."""
+        new_attr = ClassUtils.find_value_attr(source).clone()
+        new_attr.types = target.attrs[0].types
+        new_attr.default = target.attrs[0].default
+        new_attr.fixed = True
+        target.attrs = [new_attr]
 
     @classmethod
     def process_simple_extension(cls, source: Class, target: Class, ext: Extension):
diff --git a/xsdata/codegen/utils.py b/xsdata/codegen/utils.py
index 4e0331c2..7bd498f7 100644
--- a/xsdata/codegen/utils.py
+++ b/xsdata/codegen/utils.py
@@ -21,6 +21,19 @@ from xsdata.utils import text
 class ClassUtils:
     """General reusable utils methods that didn't fit anywhere else."""
 
+    @classmethod
+    def find_value_attr(cls, target: Class) -> Attr:
+        """
+        Find the text attribute of the class.
+
+        :raise CodeGenerationError: If no text node/attribute exists
+        """
+        for attr in target.attrs:
+            if not attr.xml_type:
+                return attr
+
+        raise CodeGenerationError(f"Class has no value attr {target.qname}")
+
     @classmethod
     def remove_attribute(cls, target: Class, attr: Attr):
         """Safely remove the given attr from the target class by check obj


Test patch:
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 05a630ac..2bcef8b8 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -21,6 +21,7 @@ jobs:
           - {name: Python 3.8, python: '3.8', os: ubuntu}
           - {name: Python 3.9, python: '3.9', os: ubuntu}
           - {name: Python 3.10, python: '3.10', os: ubuntu}
+          - {name: Python 3.11, python: '3.11-dev', os: ubuntu}
           - {name: Windows py37, python: '3.7', os: windows}
           - {name: MacOS py37, python: '3.7', os: macos}
     steps:
@@ -30,7 +31,7 @@ jobs:
         with:
           python-version: ${{ matrix.python }}
       - name: Install libxml2
-        if: startsWith(matrix.python, 'pypy')
+        if: startsWith(matrix.python, 'pypy') || startsWith(matrix.python, '3.11-dev')
         run: |
           sudo apt-get install libxml2-dev libxslt-dev
       - name: Install dependencies
@@ -50,7 +51,7 @@ jobs:
     strategy:
       fail-fast: false
       matrix:
-        python: [3.7, 3.8, 3.9]
+        python: ['3.7', '3.8', '3.9', '3.10', '3.11-dev']
         mode: [xsd, json, xml]
     steps:
       - uses: actions/checkout@v2
@@ -64,6 +65,10 @@ jobs:
         uses: actions/setup-python@v2
         with:
           python-version: ${{ matrix.python }}
+      - name: Install libxml2
+        if: startsWith(matrix.python, '3.11-dev')
+        run: |
+          sudo apt-get install libxml2-dev libxslt-dev
       - name: Install dependencies
         run: |
           python -m pip install --upgrade pip setuptools pytest pytest-xdist xmlschema
diff --git a/tests/codegen/handlers/test_class_extension.py b/tests/codegen/handlers/test_class_extension.py
index 63f22d68..622e2667 100644
--- a/tests/codegen/handlers/test_class_extension.py
+++ b/tests/codegen/handlers/test_class_extension.py
@@ -126,7 +126,7 @@ class ClassExtensionHandlerTests(FactoryTestCase):
             AttrTypeFactory.native(DataType.FLOAT),
             AttrTypeFactory.native(DataType.DECIMAL),
         ]
-        source = ClassFactory.elements(1)
+        source = ClassFactory.simple_type()
         source.attrs[0].types = [
             AttrTypeFactory.native(DataType.INT),
             AttrTypeFactory.create(base.qname),
@@ -162,25 +162,19 @@ class ClassExtensionHandlerTests(FactoryTestCase):
         target.attrs[0].default = "Yes"
         extension = ExtensionFactory.reference(source.qname)
         target.extensions.append(extension)
-        expected = target.clone()
 
         self.processor.container.add(source)
         self.processor.container.add(target)
         self.processor.process_dependency_extension(target, extension)
+        expected = source.attrs[1]
 
-        expected.attrs = [attr.clone() for attr in source.attrs]
-        expected.extensions = [ext.clone() for ext in source.extensions]
-        expected.attrs[1].default = "Yes"
-        expected.attrs[1].fixed = True
-
-        self.assertEqual(expected, target)
-
-        self.assertIsNone(target.attrs[0].default)
-        self.assertFalse(target.attrs[0].fixed)
-        self.assertEqual("Yes", target.attrs[1].default)
-        self.assertTrue(target.attrs[1].fixed)
+        self.assertEqual(1, len(target.attrs))
+        self.assertEqual(expected, target.attrs[0])
+        self.assertTrue(target.attrs[0].fixed)
+        self.assertEqual("Yes", target.attrs[0].default)
+        self.assertEqual(expected.types, target.attrs[0].types)
 
-    def test_process_enum_extension_raises_exception(self):
+    def test_process_enum_extension_with_complex_source_and_multiple_members(self):
         source = ClassFactory.elements(2)
         target = ClassFactory.enumeration(2)
         extension = ExtensionFactory.reference(source.qname)
@@ -188,8 +182,10 @@ class ClassExtensionHandlerTests(FactoryTestCase):
         self.processor.container.add(source)
         self.processor.container.add(target)
 
-        with self.assertRaises(CodeGenerationError):
-            self.processor.process_dependency_extension(target, extension)
+        self.processor.process_dependency_extension(target, extension)
+
+        self.assertEqual(0, len(target.attrs))
+        self.assertIn(extension, target.extensions)
 
     @mock.patch.object(ClassExtensionHandler, "process_complex_extension")
     @mock.patch.object(ClassExtensionHandler, "process_simple_extension")
diff --git a/tests/codegen/test_utils.py b/tests/codegen/test_utils.py
index 05853a5b..9a9a8d69 100644
--- a/tests/codegen/test_utils.py
+++ b/tests/codegen/test_utils.py
@@ -15,8 +15,22 @@ from xsdata.utils.testing import FactoryTestCase
 
 
 class ClassUtilsTests(FactoryTestCase):
-    def test_remove_attribute(self):
+    def test_find_value_attr(self):
+        target = ClassFactory.create()
+        with self.assertRaises(CodeGenerationError) as cm:
+            ClassUtils.find_value_attr(target)
+
+        self.assertEqual("Class has no value attr {xsdata}class_B", str(cm.exception))
 
+        target.attrs.append(AttrFactory.element())
+        with self.assertRaises(CodeGenerationError) as cm:
+            ClassUtils.find_value_attr(target)
+
+        target.attrs.append(AttrFactory.extension())
+        actual = ClassUtils.find_value_attr(target)
+        self.assertEqual(target.attrs[1], actual)
+
+    def test_remove_attribute(self):
         target = ClassFactory.elements(1)
         attr = target.attrs[0]