Skip to content

detect_circular_references

xsdata.codegen.handlers.detect_circular_references

DetectCircularReferences

Bases: RelativeHandlerInterface

Accurately detect circular dependencies between classes.

Parameters:

Name Type Description Default
container ContainerInterface

The class container instance

required

Attributes:

Name Type Description
reference_types Dict[int, List[AttrType]]

A map of class refs to dependency types

Source code in xsdata/codegen/handlers/detect_circular_references.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
class DetectCircularReferences(RelativeHandlerInterface):
    """Accurately detect circular dependencies between classes.

    Args:
        container: The class container instance

    Attributes:
        reference_types: A map of class refs to dependency types
    """

    __slots__ = "container", "reference_types"

    def __init__(self, container: ContainerInterface):
        super().__init__(container)
        self.reference_types: Dict[int, List[AttrType]] = {}

    def process(self, target: Class):
        """Go through all the attr types and find circular references.

        Args:
            target: The class to inspect and update
        """
        if not self.reference_types:
            self.build_reference_types()

        for attr in target.attrs:
            self.process_types(attr.types, target.ref)

            for choice in attr.choices:
                self.process_types(choice.types, target.ref)

    def process_types(self, types: List[AttrType], class_reference: int):
        """Go through the types and find circular references.

        Args:
            types: A list attr/choice type instances
            class_reference: The parent attr/choice class reference
        """
        for tp in types:
            if not tp.forward and not tp.native and not tp.circular:
                tp.circular = self.is_circular(tp.reference, class_reference)

    def is_circular(self, start: int, stop: int) -> bool:
        """Detect if the start reference leads to the stop reference.

        The procedure is a dfs search to avoid max recursion errors.

        Args:
            start: The attr type reference
            stop: The parent class reference

        Returns:
            Whether the start reference leads back to the stop reference.
        """
        path = set()
        stack = [start]
        while len(stack) != 0:
            if stop in path:
                return True

            ref = stack.pop()
            path.add(ref)

            for tp in self.reference_types[ref]:
                if not tp.circular and tp.reference not in path:
                    stack.append(tp.reference)

        return stop in path

    def build_reference_types(self):
        """Build the reference types mapping."""

        def generate(target: Class):
            yield target.ref, [tp for tp in target.types() if tp.reference]

            for inner in target.inner:
                yield from generate(inner)

        for item in self.container:
            for ref, types in generate(item):
                self.reference_types[ref] = types

process(target)

Go through all the attr types and find circular references.

Parameters:

Name Type Description Default
target Class

The class to inspect and update

required
Source code in xsdata/codegen/handlers/detect_circular_references.py
26
27
28
29
30
31
32
33
34
35
36
37
38
39
def process(self, target: Class):
    """Go through all the attr types and find circular references.

    Args:
        target: The class to inspect and update
    """
    if not self.reference_types:
        self.build_reference_types()

    for attr in target.attrs:
        self.process_types(attr.types, target.ref)

        for choice in attr.choices:
            self.process_types(choice.types, target.ref)

process_types(types, class_reference)

Go through the types and find circular references.

Parameters:

Name Type Description Default
types List[AttrType]

A list attr/choice type instances

required
class_reference int

The parent attr/choice class reference

required
Source code in xsdata/codegen/handlers/detect_circular_references.py
41
42
43
44
45
46
47
48
49
50
def process_types(self, types: List[AttrType], class_reference: int):
    """Go through the types and find circular references.

    Args:
        types: A list attr/choice type instances
        class_reference: The parent attr/choice class reference
    """
    for tp in types:
        if not tp.forward and not tp.native and not tp.circular:
            tp.circular = self.is_circular(tp.reference, class_reference)

is_circular(start, stop)

Detect if the start reference leads to the stop reference.

The procedure is a dfs search to avoid max recursion errors.

Parameters:

Name Type Description Default
start int

The attr type reference

required
stop int

The parent class reference

required

Returns:

Type Description
bool

Whether the start reference leads back to the stop reference.

Source code in xsdata/codegen/handlers/detect_circular_references.py
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
def is_circular(self, start: int, stop: int) -> bool:
    """Detect if the start reference leads to the stop reference.

    The procedure is a dfs search to avoid max recursion errors.

    Args:
        start: The attr type reference
        stop: The parent class reference

    Returns:
        Whether the start reference leads back to the stop reference.
    """
    path = set()
    stack = [start]
    while len(stack) != 0:
        if stop in path:
            return True

        ref = stack.pop()
        path.add(ref)

        for tp in self.reference_types[ref]:
            if not tp.circular and tp.reference not in path:
                stack.append(tp.reference)

    return stop in path

build_reference_types()

Build the reference types mapping.

Source code in xsdata/codegen/handlers/detect_circular_references.py
79
80
81
82
83
84
85
86
87
88
89
90
def build_reference_types(self):
    """Build the reference types mapping."""

    def generate(target: Class):
        yield target.ref, [tp for tp in target.types() if tp.reference]

        for inner in target.inner:
            yield from generate(inner)

    for item in self.container:
        for ref, types in generate(item):
            self.reference_types[ref] = types