Skip to content

sanitize_attributes_default_value

xsdata.codegen.handlers.sanitize_attributes_default_value

SanitizeAttributesDefaultValue

Bases: RelativeHandlerInterface

Sanitize attributes default values.

Cases
  1. Ignore enumerations.
  2. List fields can not have a default value
  3. Optional choice/sequence fields can not have a default value
  4. xsi:type fields are ignored, mark them as optional
  5. Convert string literal default value for enum fields.
Source code in xsdata/codegen/handlers/sanitize_attributes_default_value.py
  8
  9
 10
 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
class SanitizeAttributesDefaultValue(RelativeHandlerInterface):
    """Sanitize attributes default values.

    Cases:
        1. Ignore enumerations.
        2. List fields can not have a default value
        3. Optional choice/sequence fields can not have a default value
        4. xsi:type fields are ignored, mark them as optional
        5. Convert string literal default value for enum fields.
    """

    __slots__ = ()

    def process(self, target: Class):
        """Process entrypoint for classes.

        Inspect all attrs and attr choices.

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

            for choice in attr.choices:
                self.process_attribute(target, choice)

    def process_attribute(self, target: Class, attr: Attr):
        """Process entrypoint for attrs.

        Cases:
            - Reset min_occurs
            - Reset default value
            - Validate default value against types
            - Set empty string as default value for string text nodes.

        Args:
            target: The target class instance
            attr: The attr instance
        """
        if self.should_reset_required(attr):
            attr.restrictions.min_occurs = 0

        if self.should_reset_default(attr):
            attr.fixed = False
            attr.default = None

        if attr.default is not None:
            self.process_types(target, attr)
        elif attr.xml_type is None and str in attr.native_types:
            # String text nodes get an empty string as default!
            attr.default = ""

    def process_types(self, target: Class, attr: Attr):
        """Reset attr types if default value doesn't pass validation.

        Args:
            target: The target class instance
            attr: The attr instance
        """
        if self.is_valid_external_value(target, attr):
            return

        if self.is_valid_native_value(target, attr):
            return

        logger.warning(
            "Failed to match %s.%s default value `%s` to one of %s",
            target.name,
            attr.local_name,
            attr.default,
            [tp.qname for tp in attr.types],
        )

        self.reset_attribute_types(attr)

    def is_valid_external_value(self, target: Class, attr: Attr) -> bool:
        """Validate user defined types.

        Only enumerations and complex content inner types are supported.

        Args:
            target: The target class instance
            attr: The attr instance
        .
        """
        for tp in attr.user_types:
            source = self.find_inner_type(target, tp)
            if self.is_valid_inner_type(source, attr, tp):
                return True

            if self.is_valid_enum_type(source, attr):
                return True

        return False

    def find_inner_type(self, target: Class, attr_type: AttrType) -> Class:
        """Find the inner class for the given attr type.

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

        Returns:
            The inner class instance.
        """
        if attr_type.forward:
            return self.container.find_inner(target, attr_type.qname)

        return self.container.first(attr_type.qname)

    @classmethod
    def is_valid_inner_type(
        cls,
        source: Class,
        attr: Attr,
        attr_type: AttrType,
    ) -> bool:
        """Return whether the inner class can inherit the attr default value.

        If it does, then swap the default/fixed values.

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

        Returns:
            The bool result.
        """
        if attr_type.forward:
            for src_attr in source.attrs:
                if src_attr.xml_type is None:
                    src_attr.default = attr.default
                    src_attr.fixed = attr.fixed
                    attr.default = None
                    attr.fixed = False
                    return True
        return False

    @classmethod
    def is_valid_enum_type(cls, source: Class, attr: Attr) -> bool:
        """Return whether the default value matches an enum member.

        If it does convert the string literal default value to
        enumeration members placeholders. The placeholder will be
        converted to proper references from the generator filters,
        because we don't know yet the enumeration class name.


        Placeholder examples:
            Single -> @enum@qname::member_name
            Multiple -> @enum@qname::first_member@second_member

        Args:
            source: The inner source class instance
            attr: The attr instance

        Returns:
            The bool result.
        """
        assert attr.default is not None

        value_members = {x.default: x.name for x in source.attrs}
        name = value_members.get(attr.default)
        if name:
            attr.default = f"@enum@{source.qname}::{name}"
            return True

        names = [
            value_members[token]
            for token in attr.default.split()
            if token in value_members
        ]
        if names:
            attr.default = f"@enum@{source.qname}::{'@'.join(names)}"
            return True

        return False

    @classmethod
    def is_valid_native_value(cls, target: Class, attr: Attr) -> bool:
        """Return whether the default value can be converted successfully.

        The test process for enumerations and fixed value fields are
        strict, meaning the textual representation also needs to match
        the original.

        Args:
            target: The target class instance
            attr: The attr instance

        Returns:
            The bool result.
        """
        assert attr.default is not None

        types = converter.sort_types(attr.native_types)
        if not types:
            return False

        tokens = attr.default.split() if attr.restrictions.tokens else [attr.default]

        if len(tokens) == 1 and attr.is_enumeration and attr.restrictions.tokens:
            attr.restrictions.tokens = False

        # Enumerations are also fixed!!!
        strict = attr.fixed

        return all(
            converter.test(
                token,
                types,
                strict=strict,
                ns_map=target.ns_map,
                format=attr.restrictions.format,
            )
            for token in tokens
        )

    @classmethod
    def should_reset_required(cls, attr: Attr) -> bool:
        """Return whether the min_occurs needs to be reset.

        Condition:
            - Attr not derived from xs:attribute
            - It has no default value
            - It's derived from xs:anyType
            - It has max_occurs==1

        Args:
            attr: The attr instance

        Returns:
            The bool result.
        """
        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:
        """Return whether the default value needs to be reset.

        Cases:
            - Attr is xsi:type (ignorable)
            - Attr has max_occurs > 1
            - Attr is not derived from xs:attribute and has min_occurs=0

        Args:
            attr: The attr instance

        Returns:
            The bool result.
        """
        return attr.default is not None and (
            attr.is_xsi_type
            or attr.is_list
            or (not attr.is_attribute and attr.is_optional)
        )

    @classmethod
    def reset_attribute_types(cls, attr: Attr):
        """Reset the attribute type to string.

        Args:
            attr: The attr instance
        """
        attr.types.clear()
        attr.types.append(AttrType(qname=str(DataType.STRING), native=True))
        attr.restrictions.format = None

process(target)

Process entrypoint for classes.

Inspect all attrs and attr choices.

Parameters:

Name Type Description Default
target Class

The target class instance.

required
Source code in xsdata/codegen/handlers/sanitize_attributes_default_value.py
21
22
23
24
25
26
27
28
29
30
31
32
33
def process(self, target: Class):
    """Process entrypoint for classes.

    Inspect all attrs and attr choices.

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

        for choice in attr.choices:
            self.process_attribute(target, choice)

process_attribute(target, attr)

Process entrypoint for attrs.

Cases
  • Reset min_occurs
  • Reset default value
  • Validate default value against types
  • Set empty string as default value for string text nodes.

Parameters:

Name Type Description Default
target Class

The target class instance

required
attr Attr

The attr instance

required
Source code in xsdata/codegen/handlers/sanitize_attributes_default_value.py
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
def process_attribute(self, target: Class, attr: Attr):
    """Process entrypoint for attrs.

    Cases:
        - Reset min_occurs
        - Reset default value
        - Validate default value against types
        - Set empty string as default value for string text nodes.

    Args:
        target: The target class instance
        attr: The attr instance
    """
    if self.should_reset_required(attr):
        attr.restrictions.min_occurs = 0

    if self.should_reset_default(attr):
        attr.fixed = False
        attr.default = None

    if attr.default is not None:
        self.process_types(target, attr)
    elif attr.xml_type is None and str in attr.native_types:
        # String text nodes get an empty string as default!
        attr.default = ""

process_types(target, attr)

Reset attr types if default value doesn't pass validation.

Parameters:

Name Type Description Default
target Class

The target class instance

required
attr Attr

The attr instance

required
Source code in xsdata/codegen/handlers/sanitize_attributes_default_value.py
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
def process_types(self, target: Class, attr: Attr):
    """Reset attr types if default value doesn't pass validation.

    Args:
        target: The target class instance
        attr: The attr instance
    """
    if self.is_valid_external_value(target, attr):
        return

    if self.is_valid_native_value(target, attr):
        return

    logger.warning(
        "Failed to match %s.%s default value `%s` to one of %s",
        target.name,
        attr.local_name,
        attr.default,
        [tp.qname for tp in attr.types],
    )

    self.reset_attribute_types(attr)

is_valid_external_value(target, attr)

Validate user defined types.

Only enumerations and complex content inner types are supported.

Parameters:

Name Type Description Default
target Class

The target class instance

required
attr Attr

The attr instance

required

.

Source code in xsdata/codegen/handlers/sanitize_attributes_default_value.py
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
def is_valid_external_value(self, target: Class, attr: Attr) -> bool:
    """Validate user defined types.

    Only enumerations and complex content inner types are supported.

    Args:
        target: The target class instance
        attr: The attr instance
    .
    """
    for tp in attr.user_types:
        source = self.find_inner_type(target, tp)
        if self.is_valid_inner_type(source, attr, tp):
            return True

        if self.is_valid_enum_type(source, attr):
            return True

    return False

find_inner_type(target, attr_type)

Find the inner class for the given attr type.

Parameters:

Name Type Description Default
target Class

The target class instance

required
attr_type AttrType

The attr type instance

required

Returns:

Type Description
Class

The inner class instance.

Source code in xsdata/codegen/handlers/sanitize_attributes_default_value.py
104
105
106
107
108
109
110
111
112
113
114
115
116
117
def find_inner_type(self, target: Class, attr_type: AttrType) -> Class:
    """Find the inner class for the given attr type.

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

    Returns:
        The inner class instance.
    """
    if attr_type.forward:
        return self.container.find_inner(target, attr_type.qname)

    return self.container.first(attr_type.qname)

is_valid_inner_type(source, attr, attr_type) classmethod

Return whether the inner class can inherit the attr default value.

If it does, then swap the default/fixed values.

Parameters:

Name Type Description Default
source Class

The inner source class instance

required
attr Attr

The attr instance

required
attr_type AttrType

The attr type instance

required

Returns:

Type Description
bool

The bool result.

Source code in xsdata/codegen/handlers/sanitize_attributes_default_value.py
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
@classmethod
def is_valid_inner_type(
    cls,
    source: Class,
    attr: Attr,
    attr_type: AttrType,
) -> bool:
    """Return whether the inner class can inherit the attr default value.

    If it does, then swap the default/fixed values.

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

    Returns:
        The bool result.
    """
    if attr_type.forward:
        for src_attr in source.attrs:
            if src_attr.xml_type is None:
                src_attr.default = attr.default
                src_attr.fixed = attr.fixed
                attr.default = None
                attr.fixed = False
                return True
    return False

is_valid_enum_type(source, attr) classmethod

Return whether the default value matches an enum member.

If it does convert the string literal default value to enumeration members placeholders. The placeholder will be converted to proper references from the generator filters, because we don't know yet the enumeration class name.

Placeholder examples

Single -> @enum@qname::member_name Multiple -> @enum@qname::first_member@second_member

Parameters:

Name Type Description Default
source Class

The inner source class instance

required
attr Attr

The attr instance

required

Returns:

Type Description
bool

The bool result.

Source code in xsdata/codegen/handlers/sanitize_attributes_default_value.py
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
@classmethod
def is_valid_enum_type(cls, source: Class, attr: Attr) -> bool:
    """Return whether the default value matches an enum member.

    If it does convert the string literal default value to
    enumeration members placeholders. The placeholder will be
    converted to proper references from the generator filters,
    because we don't know yet the enumeration class name.


    Placeholder examples:
        Single -> @enum@qname::member_name
        Multiple -> @enum@qname::first_member@second_member

    Args:
        source: The inner source class instance
        attr: The attr instance

    Returns:
        The bool result.
    """
    assert attr.default is not None

    value_members = {x.default: x.name for x in source.attrs}
    name = value_members.get(attr.default)
    if name:
        attr.default = f"@enum@{source.qname}::{name}"
        return True

    names = [
        value_members[token]
        for token in attr.default.split()
        if token in value_members
    ]
    if names:
        attr.default = f"@enum@{source.qname}::{'@'.join(names)}"
        return True

    return False

is_valid_native_value(target, attr) classmethod

Return whether the default value can be converted successfully.

The test process for enumerations and fixed value fields are strict, meaning the textual representation also needs to match the original.

Parameters:

Name Type Description Default
target Class

The target class instance

required
attr Attr

The attr instance

required

Returns:

Type Description
bool

The bool result.

Source code in xsdata/codegen/handlers/sanitize_attributes_default_value.py
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
@classmethod
def is_valid_native_value(cls, target: Class, attr: Attr) -> bool:
    """Return whether the default value can be converted successfully.

    The test process for enumerations and fixed value fields are
    strict, meaning the textual representation also needs to match
    the original.

    Args:
        target: The target class instance
        attr: The attr instance

    Returns:
        The bool result.
    """
    assert attr.default is not None

    types = converter.sort_types(attr.native_types)
    if not types:
        return False

    tokens = attr.default.split() if attr.restrictions.tokens else [attr.default]

    if len(tokens) == 1 and attr.is_enumeration and attr.restrictions.tokens:
        attr.restrictions.tokens = False

    # Enumerations are also fixed!!!
    strict = attr.fixed

    return all(
        converter.test(
            token,
            types,
            strict=strict,
            ns_map=target.ns_map,
            format=attr.restrictions.format,
        )
        for token in tokens
    )

should_reset_required(attr) classmethod

Return whether the min_occurs needs to be reset.

Condition
  • Attr not derived from xs:attribute
  • It has no default value
  • It's derived from xs:anyType
  • It has max_occurs==1

Parameters:

Name Type Description Default
attr Attr

The attr instance

required

Returns:

Type Description
bool

The bool result.

Source code in xsdata/codegen/handlers/sanitize_attributes_default_value.py
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
@classmethod
def should_reset_required(cls, attr: Attr) -> bool:
    """Return whether the min_occurs needs to be reset.

    Condition:
        - Attr not derived from xs:attribute
        - It has no default value
        - It's derived from xs:anyType
        - It has max_occurs==1

    Args:
        attr: The attr instance

    Returns:
        The bool result.
    """
    return (
        not attr.is_attribute
        and attr.default is None
        and object in attr.native_types
        and not attr.is_list
    )

should_reset_default(attr) classmethod

Return whether the default value needs to be reset.

Cases
  • Attr is xsi:type (ignorable)
  • Attr has max_occurs > 1
  • Attr is not derived from xs:attribute and has min_occurs=0

Parameters:

Name Type Description Default
attr Attr

The attr instance

required

Returns:

Type Description
bool

The bool result.

Source code in xsdata/codegen/handlers/sanitize_attributes_default_value.py
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
@classmethod
def should_reset_default(cls, attr: Attr) -> bool:
    """Return whether the default value needs to be reset.

    Cases:
        - Attr is xsi:type (ignorable)
        - Attr has max_occurs > 1
        - Attr is not derived from xs:attribute and has min_occurs=0

    Args:
        attr: The attr instance

    Returns:
        The bool result.
    """
    return attr.default is not None and (
        attr.is_xsi_type
        or attr.is_list
        or (not attr.is_attribute and attr.is_optional)
    )

reset_attribute_types(attr) classmethod

Reset the attribute type to string.

Parameters:

Name Type Description Default
attr Attr

The attr instance

required
Source code in xsdata/codegen/handlers/sanitize_attributes_default_value.py
272
273
274
275
276
277
278
279
280
281
@classmethod
def reset_attribute_types(cls, attr: Attr):
    """Reset the attribute type to string.

    Args:
        attr: The attr instance
    """
    attr.types.clear()
    attr.types.append(AttrType(qname=str(DataType.STRING), native=True))
    attr.restrictions.format = None