Repository: tefra/xsdata
Pull request #951 (2024-02-19 14:20:30)

Revert "Avoid flattening elements"
Reverts tefra/xsdata#945

I want to release a patch for 24.1 let's take out the new feature


Patch:
diff --git a/docs/codegen/config.md b/docs/codegen/config.md
index 4e9490f0..b913ed76 100644
--- a/docs/codegen/config.md
+++ b/docs/codegen/config.md
@@ -140,6 +140,21 @@ The style of docstrings to create.
 **CLI Option:**
 `-ds, --docstring-style [reStructuredText|NumPy|Google|Accessible|Blank]`
 
+### FilterStrategy
+
+When the class analyzer runs, a lot of types, base classes are flattened, leaving a lot
+of orphan classes. The filter strategy can be used to remove unused classes.
+
+| Style             | Description                                                                 |
+| ----------------- | --------------------------------------------------------------------------- |
+| `all`             | Generate all simple and complex types.                                      |
+| `allGlobals`      | Generate global elements and complex types and all referenced simple types. |
+| `referredGlobals` | Generate global elements and all referenced simple and complex types.       |
+
+**Default Value:** `allGlobals`
+
+**CLI Option:** `-fs, --filter-strategy [all|allGlobals|referredGlobals]`
+
 ### RelativeImports
 
 Generate relative instead of absolute imports.
diff --git a/xsdata/codegen/handlers/filter_classes.py b/xsdata/codegen/handlers/filter_classes.py
index 7dcc7a59..4219cd1e 100644
--- a/xsdata/codegen/handlers/filter_classes.py
+++ b/xsdata/codegen/handlers/filter_classes.py
@@ -3,6 +3,7 @@ from typing import List
 from xsdata.codegen.mixins import ContainerHandlerInterface
 from xsdata.codegen.models import Class
 from xsdata.logger import logger
+from xsdata.models.config import ClassFilterStrategy
 
 
 class FilterClasses(ContainerHandlerInterface):
@@ -14,16 +15,29 @@ class FilterClasses(ContainerHandlerInterface):
         """Main entrypoint to filter the class container.
 
         In order for a class to be considered global it has
-        to be derived from an element, complexType, a binding
-        operation or message
+        to be a non-abstract element, a complex type without
+        simple content or a wsdl binding element.
 
-        If no global types exist, all classes will be generated.
+        Strategies:
+            - Filter all global classes and the referenced simple types.
+            - Filter global classes with references to other global classes.
+            - Filter all classes
         """
-        classes = self.filter_all_globals()
+        classes = []
+        filter_strategy = self.container.config.output.filter_strategy
+        if filter_strategy == ClassFilterStrategy.ALL_GLOBALS:
+            classes = self.filter_all_globals()
+        elif filter_strategy == ClassFilterStrategy.REFERRED_GLOBALS:
+            classes = self.filter_referred_globals()
+
         if classes:
             self.container.set(classes)
-        else:
-            logger.warning("No global types exist, will generate all types.")
+        elif filter_strategy != ClassFilterStrategy.ALL:
+            logger.warning(
+                "The filter strategy '%s' returned no classes,"
+                " will generate all types.",
+                filter_strategy.value,
+            )
 
     def filter_all_globals(self) -> List[Class]:
         """Filter all globals and any referenced types.
@@ -36,8 +50,27 @@ class FilterClasses(ContainerHandlerInterface):
         """
         occurs = set()
         for obj in self.container:
-            if obj.is_complex_type:
+            if obj.is_global_type:
                 occurs.add(obj.ref)
                 occurs.update(obj.references)
 
         return [obj for obj in self.container if obj.ref in occurs]
+
+    def filter_referred_globals(self) -> List[Class]:
+        """Filter globals with any references.
+
+        This filter is trying to remove unused global
+        types.
+
+        Returns:
+            The list of classes for generation.
+        """
+        occurs = set()
+        for obj in self.container:
+            if obj.is_global_type:
+                references = list(obj.references)
+                occurs.update(references)
+                if references:
+                    occurs.add(obj.ref)
+
+        return [obj for obj in self.container if obj.ref in occurs]
diff --git a/xsdata/codegen/handlers/flatten_class_extensions.py b/xsdata/codegen/handlers/flatten_class_extensions.py
index 325c4920..329f79a1 100644
--- a/xsdata/codegen/handlers/flatten_class_extensions.py
+++ b/xsdata/codegen/handlers/flatten_class_extensions.py
@@ -73,7 +73,7 @@ class FlattenClassExtensions(RelativeHandlerInterface):
             target.extensions.remove(extension)
         elif target.is_enumeration:
             self.process_enum_extension(source, target, extension)
-        elif not source.is_complex_type or source.is_enumeration:
+        elif not source.is_complex or source.is_enumeration:
             self.process_simple_extension(source, target, extension)
         else:
             self.process_complex_extension(source, target, extension)
@@ -100,7 +100,7 @@ class FlattenClassExtensions(RelativeHandlerInterface):
         """
         if source.is_enumeration:
             self.merge_enumerations(source, target)
-        elif not source.is_complex_type:
+        elif source.is_simple_type:
             self.merge_enumeration_types(source, target)
         elif len(target.attrs) == 1:
             self.set_default_value(source, target)
@@ -278,7 +278,7 @@ class FlattenClassExtensions(RelativeHandlerInterface):
             target: The target class instance
         """
         if not source.extensions and (
-            not source.is_complex_type
+            source.is_simple_type
             or target.has_suffix_attr
             or (source.has_suffix_attr and target.attrs)
         ):
diff --git a/xsdata/codegen/handlers/process_attributes_types.py b/xsdata/codegen/handlers/process_attributes_types.py
index fa157e79..2b145b6b 100644
--- a/xsdata/codegen/handlers/process_attributes_types.py
+++ b/xsdata/codegen/handlers/process_attributes_types.py
@@ -132,7 +132,7 @@ class ProcessAttributeTypes(RelativeHandlerInterface):
         conditions = (
             lambda obj: obj.tag == tag,
             lambda obj: tag == Tag.ELEMENT and obj.tag == Tag.COMPLEX_TYPE,
-            lambda obj: not obj.is_complex_type,
+            lambda obj: not obj.is_complex,
             lambda x: True,
         )
 
@@ -157,13 +157,7 @@ class ProcessAttributeTypes(RelativeHandlerInterface):
             return
 
         inner = self.container.find_inner(target, attr_type.qname)
-
-        # If the inner class behaves as a simple type flatten it.
-        if (
-            len(inner.attrs) == 1
-            and not inner.attrs[0].xml_type
-            and not inner.extensions
-        ):
+        if inner.is_simple_type:
             self.copy_attribute_properties(inner, target, attr, attr_type)
             target.inner.remove(inner)
 
@@ -172,8 +166,8 @@ class ProcessAttributeTypes(RelativeHandlerInterface):
 
         Strategies:
             1. Reset absent types with a warning
-            3. Copy format restriction from an enumeration
             2. Copy attribute properties from a simple type
+            3. Copy format restriction from an enumeration
             4. Set circular flag for the rest
 
         Args:
@@ -184,7 +178,10 @@ class ProcessAttributeTypes(RelativeHandlerInterface):
         source = self.find_dependency(attr_type, attr.tag)
         if not source:
             logger.warning("Reset absent type: %s", attr_type.name)
-            self.reset_attribute_type(attr_type, True)
+            use_str = not source or not source.is_complex
+            self.reset_attribute_type(attr_type, use_str)
+        elif source.is_simple_type:
+            self.copy_attribute_properties(source, target, attr, attr_type)
         elif source.is_enumeration:
             attr.restrictions.min_length = None
             attr.restrictions.max_length = None
@@ -192,8 +189,6 @@ class ProcessAttributeTypes(RelativeHandlerInterface):
                 x.restrictions.format for x in source.attrs if x.restrictions.format
             )
             attr_type.reference = id(source)
-        elif not source.is_complex_type:
-            self.copy_attribute_properties(source, target, attr, attr_type)
         elif source.is_element and source.abstract:
             # Substitution groups with abstract elements are used like
             # placeholders and shouldn't be added as standalone fields.
diff --git a/xsdata/codegen/models.py b/xsdata/codegen/models.py
index 0c01ae8b..65364b41 100644
--- a/xsdata/codegen/models.py
+++ b/xsdata/codegen/models.py
@@ -19,13 +19,8 @@ xml_type_map = {
     Tag.ELEMENT: XmlType.ELEMENT,
 }
 
-GLOBAL_TYPES = (
-    Tag.ELEMENT,
-    Tag.COMPLEX_TYPE,
-    Tag.BINDING_OPERATION,
-    Tag.BINDING_MESSAGE,
-    Tag.MESSAGE,
-)
+SIMPLE_TYPES = (Tag.EXTENSION, Tag.LIST, Tag.SIMPLE_TYPE, Tag.UNION)
+GLOBAL_TYPES = (Tag.ELEMENT, Tag.BINDING_OPERATION, Tag.BINDING_MESSAGE, Tag.MESSAGE)
 
 
 @dataclass
@@ -543,6 +538,11 @@ class Class:
         """Return whether at least one of attrs has help content."""
         return any(attr.help and attr.help.strip() for attr in self.attrs)
 
+    @property
+    def is_complex(self) -> bool:
+        """Return whether class represents a xs:element/complex type."""
+        return self.tag in (Tag.ELEMENT, Tag.COMPLEX_TYPE)
+
     @property
     def is_element(self) -> bool:
         """Return whether this class represents a xml element."""
@@ -554,12 +554,14 @@ class Class:
         return len(self.attrs) > 0 and all(attr.is_enumeration for attr in self.attrs)
 
     @property
-    def is_complex_type(self) -> bool:
+    def is_global_type(self) -> bool:
         """Return whether this class represents a root/global class.
 
         Global classes are the only classes that get generated by default.
         """
-        return self.tag in GLOBAL_TYPES
+        return (not self.abstract and self.tag in GLOBAL_TYPES) or (
+            self.tag == Tag.COMPLEX_TYPE and not self.is_simple_type
+        )
 
     @property
     def is_group(self) -> bool:
@@ -588,6 +590,21 @@ class Class:
         """Return whether this instance is derived from a wsdl:operation."""
         return self.tag == Tag.BINDING_OPERATION
 
+    @property
+    def is_simple_type(self) -> bool:
+        """Return whether the class represents a simple type.
+
+        Simple Types:
+            - xs:simpleType/extension/list/union
+            - have only one attr
+            - have no extensions.
+        """
+        return (
+            len(self.attrs) == 1
+            and self.attrs[0].tag in SIMPLE_TYPES
+            and not self.extensions
+        )
+
     @property
     def references(self) -> Iterator[int]:
         """Yield all class object reference numbers."""
diff --git a/xsdata/models/config.py b/xsdata/models/config.py
index 85c875c1..41d0e6e0 100644
--- a/xsdata/models/config.py
+++ b/xsdata/models/config.py
@@ -105,6 +105,21 @@ class DocstringStyle(Enum):
     BLANK = "Blank"
 
 
+class ClassFilterStrategy(Enum):
+    """Class filter strategy enumeration.
+
+    Attributes:
+        ALL: all: Generate all types, discouraged!!!
+        ALL_GLOBALS: allGlobals: Generate all global types
+        REFERRED_GLOBALS: referredGlobals: Generate all global types
+            with at least one reference.
+    """
+
+    ALL = "all"
+    ALL_GLOBALS = "allGlobals"
+    REFERRED_GLOBALS = "referredGlobals"
+
+
 class ObjectType(Enum):
     """Object type enumeration.
 
@@ -215,6 +230,7 @@ class GeneratorOutput:
         format: Output format
         structure_style: Output structure style
         docstring_style: Docstring style
+        filter_strategy: Class filter strategy
         relative_imports: Use relative imports
         compound_fields: Use compound fields for repeatable elements
         max_line_length: Adjust the maximum line length
@@ -233,6 +249,9 @@ class GeneratorOutput:
         default=StructureStyle.FILENAMES, name="Structure"
     )
     docstring_style: DocstringStyle = element(default=DocstringStyle.RST)
+    filter_strategy: ClassFilterStrategy = element(
+        default=ClassFilterStrategy.ALL_GLOBALS
+    )
     relative_imports: bool = element(default=False)
     compound_fields: CompoundFields = element(default_factory=CompoundFields)
     max_line_length: int = attribute(default=79)


Test patch:
diff --git a/tests/codegen/handlers/test_filter_classes.py b/tests/codegen/handlers/test_filter_classes.py
index 6e5d87ca..237a0806 100644
--- a/tests/codegen/handlers/test_filter_classes.py
+++ b/tests/codegen/handlers/test_filter_classes.py
@@ -2,9 +2,9 @@ from unittest import mock
 
 from xsdata.codegen.container import ClassContainer
 from xsdata.codegen.handlers import FilterClasses
-from xsdata.models.config import GeneratorConfig
+from xsdata.models.config import ClassFilterStrategy, GeneratorConfig
 from xsdata.models.enums import Tag
-from xsdata.utils.testing import ClassFactory, FactoryTestCase
+from xsdata.utils.testing import AttrFactory, ClassFactory, FactoryTestCase
 
 
 class FilterClassesTests(FactoryTestCase):
@@ -27,18 +27,44 @@ class FilterClassesTests(FactoryTestCase):
 
         element = ClassFactory.create(tag=Tag.ELEMENT, abstract=True)
 
-        expected = [complex_type, enum_1, element]
+        expected = [complex_type, enum_1]
         self.container.extend([complex_type, enum_1, simple_type, enum_2, element])
         self.handler.run()
         self.assertEqual(expected, list(self.container))
 
+    def test_filter_referred_globals(self):
+        self.container.config.output.filter_strategy = (
+            ClassFilterStrategy.REFERRED_GLOBALS
+        )
+
+        element_1 = ClassFactory.create(tag=Tag.ELEMENT, attrs=AttrFactory.list(2))
+        element_2 = ClassFactory.create(tag=Tag.ELEMENT, attrs=AttrFactory.list(2))
+        enum_1 = ClassFactory.enumeration(2)
+        element_2.attrs[0].types[0].reference = enum_1.ref
+
+        expected = [element_2, enum_1]
+        self.container.extend([element_1, element_2, enum_1])
+        self.handler.run()
+        self.assertEqual(expected, list(self.container))
+
+    def test_filter_all(self):
+        simple_type = ClassFactory.simple_type()
+        enumeration = ClassFactory.enumeration(2)
+        complex_type = ClassFactory.elements(2)
+        self.container.extend([simple_type, enumeration, complex_type])
+        self.container.config.output.filter_strategy = ClassFilterStrategy.ALL
+
+        self.handler.run()
+        self.assertEqual(3, len(list(self.container)))
+
     @mock.patch("xsdata.codegen.handlers.filter_classes.logger.warning")
-    def test_run_with_no_global_types(self, mock_warning):
+    def test_run_with_strategy_not_all_with_no_classes(self, mock_warning):
         classes = [ClassFactory.enumeration(2), ClassFactory.simple_type()]
         self.container.extend(classes)
         self.handler.run()
         self.assertEqual(classes, list(self.container))
 
         mock_warning.assert_called_once_with(
-            "No global types exist, will generate all types.",
+            "The filter strategy '%s' returned no classes, will generate all types.",
+            "allGlobals",
         )
diff --git a/tests/codegen/handlers/test_flatten_class_extensions.py b/tests/codegen/handlers/test_flatten_class_extensions.py
index ac215442..0dfb1827 100644
--- a/tests/codegen/handlers/test_flatten_class_extensions.py
+++ b/tests/codegen/handlers/test_flatten_class_extensions.py
@@ -385,8 +385,8 @@ class FlattenClassExtensionsTests(FactoryTestCase):
         self.assertTrue(callback(source, target, extension))
 
     def test_should_flatten_extension(self):
-        source = ClassFactory.create(tag=Tag.COMPLEX_TYPE)
-        target = ClassFactory.create(tag=Tag.ELEMENT)
+        source = ClassFactory.create()
+        target = ClassFactory.create()
 
         self.assertFalse(self.processor.should_flatten_extension(source, target))
 
@@ -407,7 +407,7 @@ class FlattenClassExtensionsTests(FactoryTestCase):
         self.assertTrue(self.processor.should_flatten_extension(source, target))
 
         # Source is a simple type
-        source = ClassFactory.simple_type()
+        source = ClassFactory.create(attrs=[AttrFactory.create(tag=Tag.SIMPLE_TYPE)])
         target = ClassFactory.elements(1)
         self.assertTrue(self.processor.should_flatten_extension(source, target))
 
diff --git a/tests/codegen/models/test_class.py b/tests/codegen/models/test_class.py
index 537181d3..2fb2f43f 100644
--- a/tests/codegen/models/test_class.py
+++ b/tests/codegen/models/test_class.py
@@ -1,5 +1,6 @@
 import sys
 
+from xsdata.codegen.models import SIMPLE_TYPES
 from xsdata.exceptions import CodeGenerationError
 from xsdata.models.enums import DataType, Namespace, Tag
 from xsdata.utils.namespaces import build_qname
@@ -101,6 +102,16 @@ class ClassTests(FactoryTestCase):
         obj.attrs[1].index = sys.maxsize
         self.assertTrue(obj.has_suffix_attr)
 
+    def test_property_is_complex(self):
+        obj = ClassFactory.create(tag=Tag.ELEMENT)
+        self.assertTrue(obj.is_complex)
+
+        obj = ClassFactory.create(tag=Tag.COMPLEX_TYPE)
+        self.assertTrue(obj.is_complex)
+
+        obj = ClassFactory.create(tag=Tag.SIMPLE_TYPE)
+        self.assertFalse(obj.is_complex)
+
     def test_property_is_element(self):
         obj = ClassFactory.create(tag=Tag.ELEMENT)
         self.assertTrue(obj.is_element)
@@ -118,6 +129,21 @@ class ClassTests(FactoryTestCase):
         obj.attrs.clear()
         self.assertFalse(obj.is_enumeration)
 
+    def test_property_is_global_type(self):
+        obj = ClassFactory.create(abstract=True, tag=Tag.ELEMENT)
+        self.assertFalse(obj.is_global_type)
+
+        obj = ClassFactory.create(tag=Tag.ELEMENT)
+        self.assertTrue(obj.is_global_type)
+
+        obj = ClassFactory.create(tag=Tag.COMPLEX_TYPE)
+        obj.extensions.append(ExtensionFactory.create())
+        self.assertTrue(obj.is_global_type)
+
+        obj.extensions.clear()
+        obj.attrs.append(AttrFactory.create(tag=Tag.EXTENSION))
+        self.assertFalse(obj.is_global_type)
+
     def test_property_is_restricted(self):
         obj = ClassFactory.create()
         ext = ExtensionFactory.create(tag=Tag.EXTENSION)
@@ -128,6 +154,21 @@ class ClassTests(FactoryTestCase):
         ext.tag = Tag.RESTRICTION
         self.assertTrue(obj.is_restricted)
 
+    def test_property_is_simple_type(self):
+        obj = ClassFactory.elements(2)
+
+        self.assertFalse(obj.is_simple_type)
+
+        obj.attrs.pop()
+        self.assertFalse(obj.is_simple_type)
+
+        for tag in SIMPLE_TYPES:
+            obj.attrs[0].tag = tag
+            self.assertTrue(obj.is_simple_type)
+
+        obj.extensions.append(ExtensionFactory.create())
+        self.assertFalse(obj.is_simple_type)
+
     def test_property_is_group(self):
         self.assertTrue(ClassFactory.create(tag=Tag.GROUP).is_group)
         self.assertTrue(ClassFactory.create(tag=Tag.ATTRIBUTE_GROUP).is_group)
diff --git a/tests/codegen/test_container.py b/tests/codegen/test_container.py
index b86ace5e..71193fac 100644
--- a/tests/codegen/test_container.py
+++ b/tests/codegen/test_container.py
@@ -145,7 +145,7 @@ class ClassContainerTests(FactoryTestCase):
         container = ClassContainer(config=GeneratorConfig())
         container.extend([complex_type, enum_1, enum_2, simple_type, element])
 
-        expected = [complex_type, enum_1, element]
+        expected = [complex_type, enum_1]
         container.filter_classes()
         self.assertEqual(expected, list(container))
 
diff --git a/tests/fixtures/dtd/models/__init__.py b/tests/fixtures/dtd/models/__init__.py
index 2d6702c8..c8bc4b42 100644
--- a/tests/fixtures/dtd/models/__init__.py
+++ b/tests/fixtures/dtd/models/__init__.py
@@ -1,23 +1,13 @@
 from tests.fixtures.dtd.models.complete_example import (
     Blog,
-    Body,
-    Origin,
     Post,
     PostStatus,
-    Source,
-    Tag,
     Tags,
-    Title,
 )
 
 __all__ = [
     "Blog",
-    "Body",
-    "Origin",
     "Post",
     "PostStatus",
-    "Source",
-    "Tag",
     "Tags",
-    "Title",
 ]
diff --git a/tests/fixtures/dtd/models/complete_example.py b/tests/fixtures/dtd/models/complete_example.py
index edeeba95..47fadb86 100644
--- a/tests/fixtures/dtd/models/complete_example.py
+++ b/tests/fixtures/dtd/models/complete_example.py
@@ -3,64 +3,14 @@ from enum import Enum
 from typing import List, Optional
 
 
-@dataclass
-class Body:
-    value: str = field(
-        default="",
-        metadata={
-            "required": True,
-        },
-    )
-
-
-@dataclass
-class Origin:
-    value: str = field(
-        default="",
-        metadata={
-            "required": True,
-        },
-    )
-
-
 class PostStatus(Enum):
     DRAFT = "draft"
     PUBLISHED = "published"
 
 
-@dataclass
-class Source:
-    value: str = field(
-        default="",
-        metadata={
-            "required": True,
-        },
-    )
-
-
-@dataclass
-class Tag:
-    value: str = field(
-        default="",
-        metadata={
-            "required": True,
-        },
-    )
-
-
-@dataclass
-class Title:
-    value: str = field(
-        default="",
-        metadata={
-            "required": True,
-        },
-    )
-
-
 @dataclass
 class Tags:
-    tag: List[Tag] = field(
+    tag: List[str] = field(
         default_factory=list,
         metadata={
             "name": "Tag",
@@ -100,21 +50,21 @@ class Post:
             "required": True,
         },
     )
-    origin: List[Origin] = field(
+    origin: List[str] = field(
         default_factory=list,
         metadata={
             "name": "Origin",
             "type": "Element",
         },
     )
-    source: List[Source] = field(
+    source: List[str] = field(
         default_factory=list,
         metadata={
             "name": "Source",
             "type": "Element",
         },
     )
-    title: Optional[Title] = field(
+    title: Optional[str] = field(
         default=None,
         metadata={
             "name": "Title",
@@ -122,7 +72,7 @@ class Post:
             "required": True,
         },
     )
-    body: Optional[Body] = field(
+    body: Optional[str] = field(
         default=None,
         metadata={
             "name": "Body",
diff --git a/tests/fixtures/primer/order.py b/tests/fixtures/primer/order.py
index c200987c..1658a28b 100644
--- a/tests/fixtures/primer/order.py
+++ b/tests/fixtures/primer/order.py
@@ -4,73 +4,6 @@ from typing import List, Optional
 from xsdata.models.datatype import XmlDate
 
 
-@dataclass
-class Usaddress:
-    class Meta:
-        name = "USAddress"
-
-    name: Optional[str] = field(
-        default=None,
-        metadata={
-            "type": "Element",
-            "namespace": "",
-            "required": True,
-        },
-    )
-    street: Optional[str] = field(
-        default=None,
-        metadata={
-            "type": "Element",
-            "namespace": "",
-            "required": True,
-        },
-    )
-    city: Optional[str] = field(
-        default=None,
-        metadata={
-            "type": "Element",
-            "namespace": "",
-            "required": True,
-        },
-    )
-    state: Optional[str] = field(
-        default=None,
-        metadata={
-            "type": "Element",
-            "namespace": "",
-            "required": True,
-        },
-    )
-    zip: Optional[Decimal] = field(
-        default=None,
-        metadata={
-            "type": "Element",
-            "namespace": "",
-            "required": True,
-        },
-    )
-    country: str = field(
-        init=False,
-        default="US",
-        metadata={
-            "type": "Attribute",
-        },
-    )
-
-
-@dataclass
-class Comment:
-    class Meta:
-        name = "comment"
-
-    value: str = field(
-        default="",
-        metadata={
-            "required": True,
-        },
-    )
-
-
 @dataclass
 class Items:
     item: List["Items.Item"] = field(
@@ -123,7 +56,7 @@ class Items:
                 "required": True,
             },
         )
-        comment: Optional[Comment] = field(
+        comment: Optional[str] = field(
             default=None,
             metadata={
                 "type": "Element",
@@ -148,6 +81,73 @@ class Items:
         )
 
 
+@dataclass
+class Usaddress:
+    class Meta:
+        name = "USAddress"
+
+    name: Optional[str] = field(
+        default=None,
+        metadata={
+            "type": "Element",
+            "namespace": "",
+            "required": True,
+        },
+    )
+    street: Optional[str] = field(
+        default=None,
+        metadata={
+            "type": "Element",
+            "namespace": "",
+            "required": True,
+        },
+    )
+    city: Optional[str] = field(
+        default=None,
+        metadata={
+            "type": "Element",
+            "namespace": "",
+            "required": True,
+        },
+    )
+    state: Optional[str] = field(
+        default=None,
+        metadata={
+            "type": "Element",
+            "namespace": "",
+            "required": True,
+        },
+    )
+    zip: Optional[Decimal] = field(
+        default=None,
+        metadata={
+            "type": "Element",
+            "namespace": "",
+            "required": True,
+        },
+    )
+    country: str = field(
+        init=False,
+        default="US",
+        metadata={
+            "type": "Attribute",
+        },
+    )
+
+
+@dataclass
+class Comment:
+    class Meta:
+        name = "comment"
+
+    value: str = field(
+        default="",
+        metadata={
+            "required": True,
+        },
+    )
+
+
 @dataclass
 class PurchaseOrderType:
     """Purchase order schema for Example.com.
@@ -183,7 +183,7 @@ class PurchaseOrderType:
             "required": True,
         },
     )
-    comment: Optional[Comment] = field(
+    comment: Optional[str] = field(
         default=None,
         metadata={
             "type": "Element",
diff --git a/tests/fixtures/primer/sample.json b/tests/fixtures/primer/sample.json
index b77907a4..c1252a05 100644
--- a/tests/fixtures/primer/sample.json
+++ b/tests/fixtures/primer/sample.json
@@ -15,18 +15,14 @@
     "zip": "95819",
     "country": "US"
   },
-  "comment": {
-    "value": "Hurry, my lawn is going wild!"
-  },
+  "comment": "Hurry, my lawn is going wild!",
   "items": {
     "item": [
       {
         "productName": "Lawnmower",
         "quantity": 1,
         "USPrice": "148.95",
-        "comment": {
-          "value": "Confirm this is electric"
-        },
+        "comment": "Confirm this is electric",
         "shipDate": null,
         "partNum": "872-AA"
       },
diff --git a/tests/models/test_config.py b/tests/models/test_config.py
index 35423072..c8247b11 100644
--- a/tests/models/test_config.py
+++ b/tests/models/test_config.py
@@ -33,6 +33,7 @@ class GeneratorConfigTests(TestCase):
             '    <Format repr="true" eq="true" order="false" unsafeHash="false" frozen="false" slots="false" kwOnly="false">dataclasses</Format>\n'
             "    <Structure>filenames</Structure>\n"
             "    <DocstringStyle>reStructuredText</DocstringStyle>\n"
+            "    <FilterStrategy>allGlobals</FilterStrategy>\n"
             "    <RelativeImports>false</RelativeImports>\n"
             '    <CompoundFields defaultName="choice" useSubstitutionGroups="false" forceDefaultName="false" maxNameParts="3">false</CompoundFields>\n'
             "    <PostponedAnnotations>false</PostponedAnnotations>\n"
@@ -93,6 +94,7 @@ class GeneratorConfigTests(TestCase):
             ' frozen="false" slots="false" kwOnly="false">dataclasses</Format>\n'
             "    <Structure>filenames</Structure>\n"
             "    <DocstringStyle>reStructuredText</DocstringStyle>\n"
+            "    <FilterStrategy>allGlobals</FilterStrategy>\n"
             "    <RelativeImports>false</RelativeImports>\n"
             '    <CompoundFields defaultName="choice" useSubstitutionGroups="false" forceDefaultName="false" maxNameParts="3">false</CompoundFields>\n'
             "    <PostponedAnnotations>false</PostponedAnnotations>\n"