Skip to content

# validate_attributes_overrides

## `xsdata.codegen.handlers.validate_attributes_overrides`

### `ValidateAttributesOverrides`

Bases: `RelativeHandlerInterface`

Validate override and restricted attributes.

Source code in `xsdata/codegen/handlers/validate_attributes_overrides.py`

     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

    class ValidateAttributesOverrides(RelativeHandlerInterface):
        """Validate override and restricted attributes."""

        __slots__ = ()

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

            - Validate override attrs
            - Add restricted attrs

            Args:
                target: The target class instance
            """
            base_attrs_map = self.base_attrs_map(target)
            # We need the original class attrs before validation, in order to
            # prohibit the rest of the parent attrs later...
            explicit_attrs = {
                attr.slug for attr in target.attrs if attr.can_be_restricted()
            }
            self.validate_attrs(target, base_attrs_map)
            if target.is_restricted:
                self.prohibit_parent_attrs(target, explicit_attrs, base_attrs_map)

        @classmethod
        def prohibit_parent_attrs(
            cls,
            target: Class,
            explicit_attrs: set[str],
            base_attrs_map: dict[str, list[Attr]],
        ):
            """Prepend prohibited parent attrs to the target class.

            Prepend the parent prohibited attrs and reset their
            types and default values in order to avoid conflicts
            later.

            Args:
                target: The target class instance
                explicit_attrs: The list of explicit attrs in the class
                base_attrs_map: A mapping of qualified names to lists of parent attrs

            """
            for slug, attrs in reversed(base_attrs_map.items()):
                attr = attrs[0]
                if attr.can_be_restricted() and slug not in explicit_attrs:
                    attr_restricted = attr.clone()
                    attr_restricted.restrictions.max_occurs = 0
                    attr_restricted.default = None
                    attr_restricted.types.clear()
                    target.attrs.insert(0, attr_restricted)

        @classmethod
        def validate_attrs(cls, target: Class, base_attrs_map: dict[str, list[Attr]]):
            """Validate overriding attrs.

            Cases:
                - Overriding attr, either remove it or update parent attr
                - Duplicate names, resolve conflicts
                - Remove prohibited attrs.

            Args:
                target: The target class instance
                base_attrs_map: A mapping of qualified names to lists of parent attrs
            """
            for attr in target.attrs.copy():
                base_attrs = base_attrs_map.get(attr.slug)

                if base_attrs:
                    base_attr = base_attrs[0]
                    if cls.overrides(attr, base_attr):
                        cls.validate_override(target, attr, base_attr)
                    else:
                        cls.resolve_conflict(attr, base_attr)
                elif attr.is_prohibited:
                    cls.remove_attribute(target, attr)

        @classmethod
        def overrides(cls, a: Attr, b: Attr) -> bool:
            """Override attrs must belong to the same xml type and namespace.

            Args:
                a: The first attr
                b: The second attr

            Returns:
                The bool result.
            """
            return a.xml_type == b.xml_type and a.namespace == b.namespace

        def base_attrs_map(self, target: Class) -> dict[str, list[Attr]]:
            """Create a mapping of qualified names to lists of parent attrs.

            Args:
                target: The target class instance

            Returns:
                A mapping of qualified names to lists of parent attrs.
            """
            base_attrs = self.base_attrs(target)
            return collections.group_by(base_attrs, key=get_slug)

        @classmethod
        def validate_override(cls, target: Class, child_attr: Attr, parent_attr: Attr):
            """Validate the override will not break mypy type checking.

            - Ignore wildcard attrs.
            - If child is a list and parent isn't convert parent to list
            - If restrictions are the same we can safely remove override attr

            Args:
                target: The target class instance
                child_attr: The child attr
                parent_attr: The parent attr
            """
            if parent_attr.is_any_type and not child_attr.is_any_type:
                return

            if (
                child_attr.is_list
                and not parent_attr.is_list
                and not parent_attr.is_prohibited
            ) or (
                not child_attr.is_list
                and not child_attr.is_prohibited
                and parent_attr.is_list
            ):
                # Hack much??? idk but Optional[str] can't override List[str]
                msg = "Converting {} field `{}::{}` to a list to match {} class `{}`"
                assert parent_attr.parent is not None

                if child_attr.is_list:
                    parent_attr.restrictions.max_occurs = sys.maxsize
                    log_message = msg.format(
                        "parent",
                        parent_attr.parent,
                        parent_attr.name,
                        "child",
                        target.qname,
                    )
                else:
                    child_attr.restrictions.max_occurs = parent_attr.restrictions.max_occurs
                    log_message = msg.format(
                        "child",
                        target.name,
                        child_attr.name,
                        "parent",
                        parent_attr.parent,
                    )
                logger.warning(log_message)

            if (
                child_attr.default == parent_attr.default
                and _bool_eq(child_attr.fixed, parent_attr.fixed)
                and _bool_eq(child_attr.mixed, parent_attr.mixed)
                and _bool_eq(child_attr.is_tokens, parent_attr.is_tokens)
                and _bool_eq(child_attr.is_nillable, parent_attr.is_nillable)
                and _bool_eq(child_attr.is_prohibited, parent_attr.is_prohibited)
                and _bool_eq(child_attr.is_optional, parent_attr.is_optional)
            ):
                cls.remove_attribute(target, child_attr)

        @classmethod
        def remove_attribute(cls, target: Class, attr: Attr):
            """Safely remove attr.

            The search is done with the reference id for safety,
            of removing attrs with same name. If the attr has
            a forward reference, the inner class will also be removed
            if it's unused!

            Args:
                target: The target class instance
                attr: The attr to remove

            """
            ClassUtils.remove_attribute(target, attr)
            ClassUtils.clean_inner_classes(target)

        @classmethod
        def resolve_conflict(cls, child_attr: Attr, parent_attr: Attr):
            """Rename the child or parent attr.

            Args:
                child_attr: The child attr instance
                parent_attr: The  parent attr instance
            """
            ClassUtils.rename_attribute_by_preference(child_attr, parent_attr)

#### `process(target)`

Process entrypoint for classes.

- Validate override attrs
- Add restricted attrs

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`target` | `Class` | The target class instance | required

Source code in `xsdata/codegen/handlers/validate_attributes_overrides.py`

    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32

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

        - Validate override attrs
        - Add restricted attrs

        Args:
            target: The target class instance
        """
        base_attrs_map = self.base_attrs_map(target)
        # We need the original class attrs before validation, in order to
        # prohibit the rest of the parent attrs later...
        explicit_attrs = {
            attr.slug for attr in target.attrs if attr.can_be_restricted()
        }
        self.validate_attrs(target, base_attrs_map)
        if target.is_restricted:
            self.prohibit_parent_attrs(target, explicit_attrs, base_attrs_map)

#### `prohibit_parent_attrs(target, explicit_attrs, base_attrs_map)` `classmethod`

Prepend prohibited parent attrs to the target class.

Prepend the parent prohibited attrs and reset their types and default values in order to avoid conflicts later.

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`target` | `Class` | The target class instance | required
`explicit_attrs` | `set[str]` | The list of explicit attrs in the class | required
`base_attrs_map` | `dict[str, list[Attr]]` | A mapping of qualified names to lists of parent attrs | required

Source code in `xsdata/codegen/handlers/validate_attributes_overrides.py`

    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

    @classmethod
    def prohibit_parent_attrs(
        cls,
        target: Class,
        explicit_attrs: set[str],
        base_attrs_map: dict[str, list[Attr]],
    ):
        """Prepend prohibited parent attrs to the target class.

        Prepend the parent prohibited attrs and reset their
        types and default values in order to avoid conflicts
        later.

        Args:
            target: The target class instance
            explicit_attrs: The list of explicit attrs in the class
            base_attrs_map: A mapping of qualified names to lists of parent attrs

        """
        for slug, attrs in reversed(base_attrs_map.items()):
            attr = attrs[0]
            if attr.can_be_restricted() and slug not in explicit_attrs:
                attr_restricted = attr.clone()
                attr_restricted.restrictions.max_occurs = 0
                attr_restricted.default = None
                attr_restricted.types.clear()
                target.attrs.insert(0, attr_restricted)

#### `validate_attrs(target, base_attrs_map)` `classmethod`

Validate overriding attrs.

Cases

- Overriding attr, either remove it or update parent attr
- Duplicate names, resolve conflicts
- Remove prohibited attrs.

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`target` | `Class` | The target class instance | required
`base_attrs_map` | `dict[str, list[Attr]]` | A mapping of qualified names to lists of parent attrs | required

Source code in `xsdata/codegen/handlers/validate_attributes_overrides.py`

    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

    @classmethod
    def validate_attrs(cls, target: Class, base_attrs_map: dict[str, list[Attr]]):
        """Validate overriding attrs.

        Cases:
            - Overriding attr, either remove it or update parent attr
            - Duplicate names, resolve conflicts
            - Remove prohibited attrs.

        Args:
            target: The target class instance
            base_attrs_map: A mapping of qualified names to lists of parent attrs
        """
        for attr in target.attrs.copy():
            base_attrs = base_attrs_map.get(attr.slug)

            if base_attrs:
                base_attr = base_attrs[0]
                if cls.overrides(attr, base_attr):
                    cls.validate_override(target, attr, base_attr)
                else:
                    cls.resolve_conflict(attr, base_attr)
            elif attr.is_prohibited:
                cls.remove_attribute(target, attr)

#### `overrides(a, b)` `classmethod`

Override attrs must belong to the same xml type and namespace.

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`a` | `Attr` | The first attr | required
`b` | `Attr` | The second attr | required

Returns:

Type | Description
--- | ---
`bool` | The bool result.

Source code in `xsdata/codegen/handlers/validate_attributes_overrides.py`

    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99

    @classmethod
    def overrides(cls, a: Attr, b: Attr) -> bool:
        """Override attrs must belong to the same xml type and namespace.

        Args:
            a: The first attr
            b: The second attr

        Returns:
            The bool result.
        """
        return a.xml_type == b.xml_type and a.namespace == b.namespace

#### `base_attrs_map(target)`

Create a mapping of qualified names to lists of parent attrs.

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`target` | `Class` | The target class instance | required

Returns:

Type | Description
--- | ---
`dict[str, list[Attr]]` | A mapping of qualified names to lists of parent attrs.

Source code in `xsdata/codegen/handlers/validate_attributes_overrides.py`

    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111

    def base_attrs_map(self, target: Class) -> dict[str, list[Attr]]:
        """Create a mapping of qualified names to lists of parent attrs.

        Args:
            target: The target class instance

        Returns:
            A mapping of qualified names to lists of parent attrs.
        """
        base_attrs = self.base_attrs(target)
        return collections.group_by(base_attrs, key=get_slug)

#### `validate_override(target, child_attr, parent_attr)` `classmethod`

Validate the override will not break mypy type checking.

- Ignore wildcard attrs.
- If child is a list and parent isn't convert parent to list
- If restrictions are the same we can safely remove override attr

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`target` | `Class` | The target class instance | required
`child_attr` | `Attr` | The child attr | required
`parent_attr` | `Attr` | The parent attr | required

Source code in `xsdata/codegen/handlers/validate_attributes_overrides.py`

    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

    @classmethod
    def validate_override(cls, target: Class, child_attr: Attr, parent_attr: Attr):
        """Validate the override will not break mypy type checking.

        - Ignore wildcard attrs.
        - If child is a list and parent isn't convert parent to list
        - If restrictions are the same we can safely remove override attr

        Args:
            target: The target class instance
            child_attr: The child attr
            parent_attr: The parent attr
        """
        if parent_attr.is_any_type and not child_attr.is_any_type:
            return

        if (
            child_attr.is_list
            and not parent_attr.is_list
            and not parent_attr.is_prohibited
        ) or (
            not child_attr.is_list
            and not child_attr.is_prohibited
            and parent_attr.is_list
        ):
            # Hack much??? idk but Optional[str] can't override List[str]
            msg = "Converting {} field `{}::{}` to a list to match {} class `{}`"
            assert parent_attr.parent is not None

            if child_attr.is_list:
                parent_attr.restrictions.max_occurs = sys.maxsize
                log_message = msg.format(
                    "parent",
                    parent_attr.parent,
                    parent_attr.name,
                    "child",
                    target.qname,
                )
            else:
                child_attr.restrictions.max_occurs = parent_attr.restrictions.max_occurs
                log_message = msg.format(
                    "child",
                    target.name,
                    child_attr.name,
                    "parent",
                    parent_attr.parent,
                )
            logger.warning(log_message)

        if (
            child_attr.default == parent_attr.default
            and _bool_eq(child_attr.fixed, parent_attr.fixed)
            and _bool_eq(child_attr.mixed, parent_attr.mixed)
            and _bool_eq(child_attr.is_tokens, parent_attr.is_tokens)
            and _bool_eq(child_attr.is_nillable, parent_attr.is_nillable)
            and _bool_eq(child_attr.is_prohibited, parent_attr.is_prohibited)
            and _bool_eq(child_attr.is_optional, parent_attr.is_optional)
        ):
            cls.remove_attribute(target, child_attr)

#### `remove_attribute(target, attr)` `classmethod`

Safely remove attr.

The search is done with the reference id for safety, of removing attrs with same name. If the attr has a forward reference, the inner class will also be removed if it's unused!

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`target` | `Class` | The target class instance | required
`attr` | `Attr` | The attr to remove | required

Source code in `xsdata/codegen/handlers/validate_attributes_overrides.py`

    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188

    @classmethod
    def remove_attribute(cls, target: Class, attr: Attr):
        """Safely remove attr.

        The search is done with the reference id for safety,
        of removing attrs with same name. If the attr has
        a forward reference, the inner class will also be removed
        if it's unused!

        Args:
            target: The target class instance
            attr: The attr to remove

        """
        ClassUtils.remove_attribute(target, attr)
        ClassUtils.clean_inner_classes(target)

#### `resolve_conflict(child_attr, parent_attr)` `classmethod`

Rename the child or parent attr.

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`child_attr` | `Attr` | The child attr instance | required
`parent_attr` | `Attr` | The parent attr instance | required

Source code in `xsdata/codegen/handlers/validate_attributes_overrides.py`

    190
    191
    192
    193
    194
    195
    196
    197
    198

    @classmethod
    def resolve_conflict(cls, child_attr: Attr, parent_attr: Attr):
        """Rename the child or parent attr.

        Args:
            child_attr: The child attr instance
            parent_attr: The  parent attr instance
        """
        ClassUtils.rename_attribute_by_preference(child_attr, parent_attr)