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
 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
class ValidateAttributesOverrides(RelativeHandlerInterface):
    """Validate override and restricted attributes."""

    __slots__ = ()

    def process(self, target: Class):
        """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 list(target.attrs):
            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
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
def process(self, target: Class):
    """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
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
@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
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
@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 list(target.attrs):
        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
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
@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
102
103
104
105
106
107
108
109
110
111
112
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
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
@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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
@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
191
192
193
194
195
196
197
198
199
@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)