Skip to content

process_attributes_types

xsdata.codegen.handlers.process_attributes_types

ProcessAttributeTypes

Bases: RelativeHandlerInterface

Minimize class attrs complexity by filtering and flattening types.

Parameters:

Name Type Description Default
container ContainerInterface

The container instance

required

Attributes:

Name Type Description
dependencies Dict

Class qname dependencies mapping

Source code in xsdata/codegen/handlers/process_attributes_types.py
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
class ProcessAttributeTypes(RelativeHandlerInterface):
    """Minimize class attrs complexity by filtering and flattening types.

    Args:
        container: The container instance

    Attributes:
        dependencies: Class qname dependencies mapping
    """

    __slots__ = "dependencies"

    def __init__(self, container: ContainerInterface):
        super().__init__(container)
        self.dependencies: Dict = {}

    def process(self, target: Class):
        """Process the given class attrs and their types.

        Cascades class restrictions to class attrs.

        Args:
            target: The target class instance
        """
        for attr in list(target.attrs):
            self.process_types(target, attr)
            self.cascade_properties(target, attr)

    def process_types(self, target: Class, attr: Attr):
        """Process every attr type and filter out duplicates.

        Args:
            target: The target class instance
            attr: The attr instance
        """
        if self.container.config.output.ignore_patterns:
            attr.restrictions.pattern = None

        for attr_type in list(attr.types):
            self.process_type(target, attr, attr_type)

        attr.types = ClassUtils.filter_types(attr.types)

    @classmethod
    def cascade_properties(cls, target: Class, attr: Attr):
        """Cascade class properties to the attr if it's a text node.

        Properties:
            - Default value
            - Fixed flag
            - Nillable flag

        Args:
            target: The target class instance
            attr: The attr instance
        """
        if attr.xml_type is None:
            if target.default is not None and attr.default is None:
                attr.default = target.default
                attr.fixed = target.fixed

            if target.nillable:
                attr.restrictions.nillable = True

    def process_type(self, target: Class, attr: Attr, attr_type: AttrType):
        """Process attr type.

        Cases:
            - Attr type is a native xsd type
            - Attr type is a forward reference (inner class)
            - Attr type is a complex user defined type

        Args:
            target: The target class instance
            attr: The attr instance
            attr_type: The attr type instance
        """
        if attr_type.native:
            self.process_native_type(attr, attr_type)
        elif attr_type.forward:
            self.process_inner_type(target, attr, attr_type)
        else:
            self.process_dependency_type(target, attr, attr_type)

    @classmethod
    def process_native_type(cls, attr: Attr, attr_type: AttrType):
        """Process native xsd types.

        Cascade the datatype restrictions to the attr and also
        resets the type to a simple xsd:string if there is a pattern
        restriction.

        Args:
            attr: The attr instance
            attr_type: The attr type instance
        """
        datatype = attr_type.datatype

        assert datatype is not None

        cls.update_restrictions(attr, datatype)

        if attr.restrictions.pattern:
            cls.reset_attribute_type(attr_type)

    def find_dependency(self, attr_type: AttrType, tag: str) -> Optional[Class]:
        """Find the source type from the attr type and tag.

        Avoid conflicts by selecting any matching type by qname and preferably:
            1. Match the candidate object tag
            2. Match element again complexType
            3. Match non element and complexType
            4. Anything

        Args:
            attr_type: The attr type instance
            tag: The xml tag name, e.g. Element, Attribute, ComplexType

        Returns:
            The source class or None if no match is found
        """
        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 x: True,
        )

        for condition in conditions:
            result = self.container.find(attr_type.qname, condition=condition)
            if result:
                return result

        return None

    def process_inner_type(self, target: Class, attr: Attr, attr_type: AttrType):
        """Process an attr type that depends on a simple inner type.

        Skip If the source class is not simple type, or it's a circular reference.

        Args:
            target: The target class instance
            attr: The attr instance
            attr_type: The attr type instance
        """
        if attr_type.circular:
            attr_type.reference = target.ref
            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
        ):
            self.copy_attribute_properties(inner, target, attr, attr_type)
            target.inner.remove(inner)
        else:
            attr_type.reference = inner.ref

    def process_dependency_type(self, target: Class, attr: Attr, attr_type: AttrType):
        """Process an attr type that depends on any global type.

        Strategies:
            1. Reset absent types with a warning
            3. Copy format restriction from an enumeration
            2. Copy attribute properties from a simple type
            4. Set circular flag for the rest

        Args:
            target: The target class instance
            attr: The attr instance
            attr_type: The attr type instance
        """
        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)
        elif source.is_enumeration:
            attr.restrictions.min_length = None
            attr.restrictions.max_length = None
            attr.restrictions.format = collections.first(
                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.
            ClassUtils.remove_attribute(target, attr)
        else:
            if source.nillable:
                attr.restrictions.nillable = True

            attr_type.reference = id(source)
            self.detect_lazy_namespace(source, target, attr)

    @classmethod
    def copy_attribute_properties(
        cls,
        source: Class,
        target: Class,
        attr: Attr,
        attr_type: AttrType,
    ):
        """Replace the attr type with the types of the first attr in the source class.

        Ignore enumerations and gracefully handle dump types with no
        attrs.

        Args:
            source: The source class instance
            target: The target class instance
            attr: The attr instance
            attr_type: The attr type instance

        Raises:
            AnalyzerValueError: if the source class has more than one attributes
        """
        source_attr = source.attrs[0]
        index = attr.types.index(attr_type)
        attr.types.pop(index)

        for source_attr_type in source_attr.types:
            clone_type = source_attr_type.clone()
            attr.types.insert(index, clone_type)
            index += 1

            ClassUtils.copy_inner_class(source, target, clone_type)

        restrictions = source_attr.restrictions.clone()
        restrictions.merge(attr.restrictions)

        # Maintain occurrences no matter what!
        restrictions.min_occurs = attr.restrictions.min_occurs
        restrictions.max_occurs = attr.restrictions.max_occurs

        if source.nillable:
            restrictions.nillable = True

        attr.restrictions = restrictions
        attr.help = attr.help or source_attr.help
        attr.fixed = attr.fixed or source_attr.fixed
        attr.default = attr.default or source_attr.default

    @classmethod
    def reset_attribute_type(cls, attr_type: AttrType, use_str: bool = True):
        """Reset the attribute type to string or any simple type.

        The method will also unset the circular/forward flags, as native
        types only depend on python builtin types.

        Args:
            attr_type: The attr type instance to reset
            use_str: Whether to use xs:string or xs:anySimpleType
        """
        attr_type.qname = str(DataType.STRING if use_str else DataType.ANY_SIMPLE_TYPE)
        attr_type.native = True
        attr_type.circular = False
        attr_type.forward = False

    @classmethod
    def update_restrictions(cls, attr: Attr, datatype: DataType):
        """Helper method to copy the native datatype restriction to the attr.

        Sets:
            - The format restriction, e.g. hexBinary, base64Binary
            - The tokens flag for xs:NMTOKENS and xs:IDREFS

        Args:
            attr: The attr to update
            datatype: The datatype to extract the restrictions.
        """
        attr.restrictions.format = datatype.format

        if datatype in (DataType.NMTOKENS, DataType.IDREFS):
            attr.restrictions.tokens = True

    @classmethod
    def detect_lazy_namespace(cls, source: Class, target: Class, attr: Attr):
        """Set the attr namespace if the current is marked as lazy.

        Cases:
            WSDL message part type can be an element, complex or
            simple type, we can't do the detection during the initial
            mapping to class objects.

        Args:
            source: The source class instance
            target: The target class instance
            attr: The target class attr instance
        """
        if attr.namespace == "##lazy":
            logger.warning(
                "Overriding field type namespace %s:%s (%s)",
                target.name,
                attr.name,
                source.namespace,
            )

            if not source.namespace:
                attr.namespace = "" if target.namespace else None
            else:
                attr.namespace = source.namespace

process(target)

Process the given class attrs and their types.

Cascades class restrictions to class attrs.

Parameters:

Name Type Description Default
target Class

The target class instance

required
Source code in xsdata/codegen/handlers/process_attributes_types.py
27
28
29
30
31
32
33
34
35
36
37
def process(self, target: Class):
    """Process the given class attrs and their types.

    Cascades class restrictions to class attrs.

    Args:
        target: The target class instance
    """
    for attr in list(target.attrs):
        self.process_types(target, attr)
        self.cascade_properties(target, attr)

process_types(target, attr)

Process every attr type and filter out duplicates.

Parameters:

Name Type Description Default
target Class

The target class instance

required
attr Attr

The attr instance

required
Source code in xsdata/codegen/handlers/process_attributes_types.py
39
40
41
42
43
44
45
46
47
48
49
50
51
52
def process_types(self, target: Class, attr: Attr):
    """Process every attr type and filter out duplicates.

    Args:
        target: The target class instance
        attr: The attr instance
    """
    if self.container.config.output.ignore_patterns:
        attr.restrictions.pattern = None

    for attr_type in list(attr.types):
        self.process_type(target, attr, attr_type)

    attr.types = ClassUtils.filter_types(attr.types)

cascade_properties(target, attr) classmethod

Cascade class properties to the attr if it's a text node.

Properties
  • Default value
  • Fixed flag
  • Nillable flag

Parameters:

Name Type Description Default
target Class

The target class instance

required
attr Attr

The attr instance

required
Source code in xsdata/codegen/handlers/process_attributes_types.py
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
@classmethod
def cascade_properties(cls, target: Class, attr: Attr):
    """Cascade class properties to the attr if it's a text node.

    Properties:
        - Default value
        - Fixed flag
        - Nillable flag

    Args:
        target: The target class instance
        attr: The attr instance
    """
    if attr.xml_type is None:
        if target.default is not None and attr.default is None:
            attr.default = target.default
            attr.fixed = target.fixed

        if target.nillable:
            attr.restrictions.nillable = True

process_type(target, attr, attr_type)

Process attr type.

Cases
  • Attr type is a native xsd type
  • Attr type is a forward reference (inner class)
  • Attr type is a complex user defined type

Parameters:

Name Type Description Default
target Class

The target class instance

required
attr Attr

The attr instance

required
attr_type AttrType

The attr type instance

required
Source code in xsdata/codegen/handlers/process_attributes_types.py
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
def process_type(self, target: Class, attr: Attr, attr_type: AttrType):
    """Process attr type.

    Cases:
        - Attr type is a native xsd type
        - Attr type is a forward reference (inner class)
        - Attr type is a complex user defined type

    Args:
        target: The target class instance
        attr: The attr instance
        attr_type: The attr type instance
    """
    if attr_type.native:
        self.process_native_type(attr, attr_type)
    elif attr_type.forward:
        self.process_inner_type(target, attr, attr_type)
    else:
        self.process_dependency_type(target, attr, attr_type)

process_native_type(attr, attr_type) classmethod

Process native xsd types.

Cascade the datatype restrictions to the attr and also resets the type to a simple xsd:string if there is a pattern restriction.

Parameters:

Name Type Description Default
attr Attr

The attr instance

required
attr_type AttrType

The attr type instance

required
Source code in xsdata/codegen/handlers/process_attributes_types.py
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
@classmethod
def process_native_type(cls, attr: Attr, attr_type: AttrType):
    """Process native xsd types.

    Cascade the datatype restrictions to the attr and also
    resets the type to a simple xsd:string if there is a pattern
    restriction.

    Args:
        attr: The attr instance
        attr_type: The attr type instance
    """
    datatype = attr_type.datatype

    assert datatype is not None

    cls.update_restrictions(attr, datatype)

    if attr.restrictions.pattern:
        cls.reset_attribute_type(attr_type)

find_dependency(attr_type, tag)

Find the source type from the attr type and tag.

Avoid conflicts by selecting any matching type by qname and preferably
  1. Match the candidate object tag
  2. Match element again complexType
  3. Match non element and complexType
  4. Anything

Parameters:

Name Type Description Default
attr_type AttrType

The attr type instance

required
tag str

The xml tag name, e.g. Element, Attribute, ComplexType

required

Returns:

Type Description
Optional[Class]

The source class or None if no match is found

Source code in xsdata/codegen/handlers/process_attributes_types.py
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
def find_dependency(self, attr_type: AttrType, tag: str) -> Optional[Class]:
    """Find the source type from the attr type and tag.

    Avoid conflicts by selecting any matching type by qname and preferably:
        1. Match the candidate object tag
        2. Match element again complexType
        3. Match non element and complexType
        4. Anything

    Args:
        attr_type: The attr type instance
        tag: The xml tag name, e.g. Element, Attribute, ComplexType

    Returns:
        The source class or None if no match is found
    """
    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 x: True,
    )

    for condition in conditions:
        result = self.container.find(attr_type.qname, condition=condition)
        if result:
            return result

    return None

process_inner_type(target, attr, attr_type)

Process an attr type that depends on a simple inner type.

Skip If the source class is not simple type, or it's a circular reference.

Parameters:

Name Type Description Default
target Class

The target class instance

required
attr Attr

The attr instance

required
attr_type AttrType

The attr type instance

required
Source code in xsdata/codegen/handlers/process_attributes_types.py
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
def process_inner_type(self, target: Class, attr: Attr, attr_type: AttrType):
    """Process an attr type that depends on a simple inner type.

    Skip If the source class is not simple type, or it's a circular reference.

    Args:
        target: The target class instance
        attr: The attr instance
        attr_type: The attr type instance
    """
    if attr_type.circular:
        attr_type.reference = target.ref
        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
    ):
        self.copy_attribute_properties(inner, target, attr, attr_type)
        target.inner.remove(inner)
    else:
        attr_type.reference = inner.ref

process_dependency_type(target, attr, attr_type)

Process an attr type that depends on any global type.

Strategies
  1. Reset absent types with a warning
  2. Copy format restriction from an enumeration
  3. Copy attribute properties from a simple type
  4. Set circular flag for the rest

Parameters:

Name Type Description Default
target Class

The target class instance

required
attr Attr

The attr instance

required
attr_type AttrType

The attr type instance

required
Source code in xsdata/codegen/handlers/process_attributes_types.py
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
def process_dependency_type(self, target: Class, attr: Attr, attr_type: AttrType):
    """Process an attr type that depends on any global type.

    Strategies:
        1. Reset absent types with a warning
        3. Copy format restriction from an enumeration
        2. Copy attribute properties from a simple type
        4. Set circular flag for the rest

    Args:
        target: The target class instance
        attr: The attr instance
        attr_type: The attr type instance
    """
    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)
    elif source.is_enumeration:
        attr.restrictions.min_length = None
        attr.restrictions.max_length = None
        attr.restrictions.format = collections.first(
            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.
        ClassUtils.remove_attribute(target, attr)
    else:
        if source.nillable:
            attr.restrictions.nillable = True

        attr_type.reference = id(source)
        self.detect_lazy_namespace(source, target, attr)

copy_attribute_properties(source, target, attr, attr_type) classmethod

Replace the attr type with the types of the first attr in the source class.

Ignore enumerations and gracefully handle dump types with no attrs.

Parameters:

Name Type Description Default
source Class

The source class instance

required
target Class

The target class instance

required
attr Attr

The attr instance

required
attr_type AttrType

The attr type instance

required

Raises:

Type Description
AnalyzerValueError

if the source class has more than one attributes

Source code in xsdata/codegen/handlers/process_attributes_types.py
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
@classmethod
def copy_attribute_properties(
    cls,
    source: Class,
    target: Class,
    attr: Attr,
    attr_type: AttrType,
):
    """Replace the attr type with the types of the first attr in the source class.

    Ignore enumerations and gracefully handle dump types with no
    attrs.

    Args:
        source: The source class instance
        target: The target class instance
        attr: The attr instance
        attr_type: The attr type instance

    Raises:
        AnalyzerValueError: if the source class has more than one attributes
    """
    source_attr = source.attrs[0]
    index = attr.types.index(attr_type)
    attr.types.pop(index)

    for source_attr_type in source_attr.types:
        clone_type = source_attr_type.clone()
        attr.types.insert(index, clone_type)
        index += 1

        ClassUtils.copy_inner_class(source, target, clone_type)

    restrictions = source_attr.restrictions.clone()
    restrictions.merge(attr.restrictions)

    # Maintain occurrences no matter what!
    restrictions.min_occurs = attr.restrictions.min_occurs
    restrictions.max_occurs = attr.restrictions.max_occurs

    if source.nillable:
        restrictions.nillable = True

    attr.restrictions = restrictions
    attr.help = attr.help or source_attr.help
    attr.fixed = attr.fixed or source_attr.fixed
    attr.default = attr.default or source_attr.default

reset_attribute_type(attr_type, use_str=True) classmethod

Reset the attribute type to string or any simple type.

The method will also unset the circular/forward flags, as native types only depend on python builtin types.

Parameters:

Name Type Description Default
attr_type AttrType

The attr type instance to reset

required
use_str bool

Whether to use xs:string or xs:anySimpleType

True
Source code in xsdata/codegen/handlers/process_attributes_types.py
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
@classmethod
def reset_attribute_type(cls, attr_type: AttrType, use_str: bool = True):
    """Reset the attribute type to string or any simple type.

    The method will also unset the circular/forward flags, as native
    types only depend on python builtin types.

    Args:
        attr_type: The attr type instance to reset
        use_str: Whether to use xs:string or xs:anySimpleType
    """
    attr_type.qname = str(DataType.STRING if use_str else DataType.ANY_SIMPLE_TYPE)
    attr_type.native = True
    attr_type.circular = False
    attr_type.forward = False

update_restrictions(attr, datatype) classmethod

Helper method to copy the native datatype restriction to the attr.

Sets
  • The format restriction, e.g. hexBinary, base64Binary
  • The tokens flag for xs:NMTOKENS and xs:IDREFS

Parameters:

Name Type Description Default
attr Attr

The attr to update

required
datatype DataType

The datatype to extract the restrictions.

required
Source code in xsdata/codegen/handlers/process_attributes_types.py
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
@classmethod
def update_restrictions(cls, attr: Attr, datatype: DataType):
    """Helper method to copy the native datatype restriction to the attr.

    Sets:
        - The format restriction, e.g. hexBinary, base64Binary
        - The tokens flag for xs:NMTOKENS and xs:IDREFS

    Args:
        attr: The attr to update
        datatype: The datatype to extract the restrictions.
    """
    attr.restrictions.format = datatype.format

    if datatype in (DataType.NMTOKENS, DataType.IDREFS):
        attr.restrictions.tokens = True

detect_lazy_namespace(source, target, attr) classmethod

Set the attr namespace if the current is marked as lazy.

Cases

WSDL message part type can be an element, complex or simple type, we can't do the detection during the initial mapping to class objects.

Parameters:

Name Type Description Default
source Class

The source class instance

required
target Class

The target class instance

required
attr Attr

The target class attr instance

required
Source code in xsdata/codegen/handlers/process_attributes_types.py
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
@classmethod
def detect_lazy_namespace(cls, source: Class, target: Class, attr: Attr):
    """Set the attr namespace if the current is marked as lazy.

    Cases:
        WSDL message part type can be an element, complex or
        simple type, we can't do the detection during the initial
        mapping to class objects.

    Args:
        source: The source class instance
        target: The target class instance
        attr: The target class attr instance
    """
    if attr.namespace == "##lazy":
        logger.warning(
            "Overriding field type namespace %s:%s (%s)",
            target.name,
            attr.name,
            source.namespace,
        )

        if not source.namespace:
            attr.namespace = "" if target.namespace else None
        else:
            attr.namespace = source.namespace