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
    282
    283
    284

    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) -> None:
            """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) -> None:
            """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:
                # Text nodes get an empty default value
                if str in attr.native_types:
                    attr.default = ""
                elif bytes in attr.native_types:
                    attr.default = ""  # Will be converted to b"" during codegen

        def process_types(self, target: Class, attr: Attr) -> None:
            """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) -> None:
        """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
    60
    61
    62

    def process_attribute(self, target: Class, attr: Attr) -> None:
        """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:
            # Text nodes get an empty default value
            if str in attr.native_types:
                attr.default = ""
            elif bytes in attr.native_types:
                attr.default = ""  # Will be converted to b"" during codegen

#### `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`

    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85

    def process_types(self, target: Class, attr: Attr) -> None:
        """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`

     87
     88
     89
     90
     91
     92
     93
     94
     95
     96
     97
     98
     99
    100
    101
    102
    103
    104
    105

    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`

    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120

    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`

    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

    @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`

    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

    @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`

    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

    @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`

    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252

    @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`

    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273

    @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`

    275
    276
    277
    278
    279
    280
    281
    282
    283
    284

    @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