Skip to content

# resolver

## `xsdata.codegen.resolver`

### `DependenciesResolver`

The dependencies resolver class.

Calculate what classes need to be imported per package, with aliases support.

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`registry` | `dict[str, str]` | The full class qname-module map. | required

Attributes:

Name | Type | Description
--- | --- | ---
`aliases` | `dict[str, str]` | The generated aliases dictionary
`imports` | `list[Import]` | The list of generated imports
`class_list` | `list[str]` | The topo-sorted list of class qnames
`class_map` | `dict[str, Class]` | A qname-class map

Source code in `xsdata/codegen/resolver.py`

     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

    class DependenciesResolver:
        """The dependencies resolver class.

        Calculate what classes need to be imported
        per package, with aliases support.

        Args:
            registry: The full class qname-module map.

        Attributes:
            aliases: The generated aliases dictionary
            imports: The list of generated imports
            class_list: The topo-sorted list of class qnames
            class_map: A qname-class map

        """

        __slots__ = "aliases", "class_list", "class_map", "imports", "registry"

        def __init__(self, registry: dict[str, str]):
            """Initialize the resolver."""
            self.registry = registry
            self.aliases: dict[str, str] = {}
            self.imports: list[Import] = []
            self.class_list: list[str] = []
            self.class_map: dict[str, Class] = {}

        def process(self, classes: list[Class]) -> None:
            """Resolve the dependencies for the given class list.

            Reset previously resolved imports and aliases.

            Args:
                classes: A list of classes that belong to the same target module
            """
            self.imports.clear()
            self.aliases.clear()
            self.class_map = self.create_class_map(classes)
            self.class_list = self.create_class_list(classes)
            self.resolve_imports()

        def sorted_imports(self) -> list[Import]:
            """Return a new sorted by name list of import instances."""
            return sorted(self.imports, key=lambda x: x.name)

        def sorted_classes(self) -> list[Class]:
            """Apply aliases and return the sorted the generated class list."""
            result = []
            for name in self.class_list:
                obj = self.class_map.get(name)
                if obj is not None:
                    self.apply_aliases(obj)
                    result.append(obj)
            return result

        def apply_aliases(self, target: Class) -> None:
            """Apply import aliases to the target class.

            Update attr and extension types to point to the
            new class aliases. Process inner classes too!

            Args:
                target: The target class instance to process
            """
            for attr in target.attrs:
                for attr_type in attr.types:
                    attr_type.alias = self.aliases.get(attr_type.qname)

                for choice in attr.choices:
                    for choice_type in choice.types:
                        choice_type.alias = self.aliases.get(choice_type.qname)

            for ext in target.extensions:
                ext.type.alias = self.aliases.get(ext.type.qname)

            collections.apply(target.inner, self.apply_aliases)

        def resolve_imports(self) -> None:
            """Build the list of class imports and set aliases if necessary."""
            self.imports = [
                Import(qname=qname, source=self.get_class_module(qname))
                for qname in self.import_classes()
            ]
            protected = {obj.slug for obj in self.class_map.values()}
            self.resolve_conflicts(self.imports, protected)
            self.set_aliases()

        def set_aliases(self) -> None:
            """Store generated aliases."""
            self.aliases = {imp.qname: imp.alias for imp in self.imports if imp.alias}

        @classmethod
        def resolve_conflicts(cls, imports: list[Import], protected: set):
            """Find naming conflicts between imports and generate aliases.

            Example:
                from foo.bar import MyType as BarMyType
                from bar.foo import MyType as FooMyType

            Args:
                imports: The list of class import instances
                protected: The set of protected class names from the module
            """
            for slug, group in collections.group_by(imports, key=get_slug).items():
                if len(group) == 1:
                    if slug in protected:
                        imp = group[0]
                        module = imp.source.split(".")[-1]
                        imp.alias = f"{module}:{imp.name}"
                    continue

                for index, cur in enumerate(group):
                    cmp = group[index + 1] if index == 0 else group[index - 1]
                    parts = re.split("[_.]", cur.source)
                    diff = set(parts) - set(re.split("[_.]", cmp.source))

                    add = "_".join(part for part in parts if part in diff)
                    cur.alias = f"{add}:{cur.name}"

        def get_class_module(self, qname: str) -> str:
            """Return the module for the given qualified class name.

            Args:
                qname: The namespace qualified name of the class

            Raises:
                CodeGenerationError: if name doesn't exist.
            """
            if qname not in self.registry:
                raise CodegenError("Failed to resolve dependency", qname=qname)
            return self.registry[qname]

        def import_classes(self) -> list[str]:
            """Return a list of class qnames that need to be imported."""
            return [qname for qname in self.class_list if qname not in self.class_map]

        @staticmethod
        def create_class_list(classes: list[Class]) -> list[str]:
            """Use topology sort to return a flat list for all the dependencies."""
            return toposort_flatten({obj.qname: set(obj.dependencies()) for obj in classes})

        @staticmethod
        def create_class_map(classes: list[Class]) -> dict[str, Class]:
            """Index the list of classes by their qualified names.

            Raises:
                CodeGenerationError: If two classes have the same qname.

            Returns:
                A qname-class map.
            """
            result: dict[str, Class] = {}
            for obj in classes:
                if obj.qname in result:
                    raise CodegenError("Duplicate class during resolve", qname=obj.qname)
                result[obj.qname] = obj

            return result

#### `__init__(registry)`

Initialize the resolver.

Source code in `xsdata/codegen/resolver.py`

    32
    33
    34
    35
    36
    37
    38

    def __init__(self, registry: dict[str, str]):
        """Initialize the resolver."""
        self.registry = registry
        self.aliases: dict[str, str] = {}
        self.imports: list[Import] = []
        self.class_list: list[str] = []
        self.class_map: dict[str, Class] = {}

#### `process(classes)`

Resolve the dependencies for the given class list.

Reset previously resolved imports and aliases.

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`classes` | `list[Class]` | A list of classes that belong to the same target module | required

Source code in `xsdata/codegen/resolver.py`

    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52

    def process(self, classes: list[Class]) -> None:
        """Resolve the dependencies for the given class list.

        Reset previously resolved imports and aliases.

        Args:
            classes: A list of classes that belong to the same target module
        """
        self.imports.clear()
        self.aliases.clear()
        self.class_map = self.create_class_map(classes)
        self.class_list = self.create_class_list(classes)
        self.resolve_imports()

#### `sorted_imports()`

Return a new sorted by name list of import instances.

Source code in `xsdata/codegen/resolver.py`

    54
    55
    56

    def sorted_imports(self) -> list[Import]:
        """Return a new sorted by name list of import instances."""
        return sorted(self.imports, key=lambda x: x.name)

#### `sorted_classes()`

Apply aliases and return the sorted the generated class list.

Source code in `xsdata/codegen/resolver.py`

    58
    59
    60
    61
    62
    63
    64
    65
    66

    def sorted_classes(self) -> list[Class]:
        """Apply aliases and return the sorted the generated class list."""
        result = []
        for name in self.class_list:
            obj = self.class_map.get(name)
            if obj is not None:
                self.apply_aliases(obj)
                result.append(obj)
        return result

#### `apply_aliases(target)`

Apply import aliases to the target class.

Update attr and extension types to point to the new class aliases. Process inner classes too!

Parameters:

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

Source code in `xsdata/codegen/resolver.py`

    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88

    def apply_aliases(self, target: Class) -> None:
        """Apply import aliases to the target class.

        Update attr and extension types to point to the
        new class aliases. Process inner classes too!

        Args:
            target: The target class instance to process
        """
        for attr in target.attrs:
            for attr_type in attr.types:
                attr_type.alias = self.aliases.get(attr_type.qname)

            for choice in attr.choices:
                for choice_type in choice.types:
                    choice_type.alias = self.aliases.get(choice_type.qname)

        for ext in target.extensions:
            ext.type.alias = self.aliases.get(ext.type.qname)

        collections.apply(target.inner, self.apply_aliases)

#### `resolve_imports()`

Build the list of class imports and set aliases if necessary.

Source code in `xsdata/codegen/resolver.py`

    90
    91
    92
    93
    94
    95
    96
    97
    98

    def resolve_imports(self) -> None:
        """Build the list of class imports and set aliases if necessary."""
        self.imports = [
            Import(qname=qname, source=self.get_class_module(qname))
            for qname in self.import_classes()
        ]
        protected = {obj.slug for obj in self.class_map.values()}
        self.resolve_conflicts(self.imports, protected)
        self.set_aliases()

#### `set_aliases()`

Store generated aliases.

Source code in `xsdata/codegen/resolver.py`

    100
    101
    102

    def set_aliases(self) -> None:
        """Store generated aliases."""
        self.aliases = {imp.qname: imp.alias for imp in self.imports if imp.alias}

#### `resolve_conflicts(imports, protected)` `classmethod`

Find naming conflicts between imports and generate aliases.

Example

from foo.bar import MyType as BarMyType from bar.foo import MyType as FooMyType

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`imports` | `list[Import]` | The list of class import instances | required
`protected` | `set` | The set of protected class names from the module | required

Source code in `xsdata/codegen/resolver.py`

    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

    @classmethod
    def resolve_conflicts(cls, imports: list[Import], protected: set):
        """Find naming conflicts between imports and generate aliases.

        Example:
            from foo.bar import MyType as BarMyType
            from bar.foo import MyType as FooMyType

        Args:
            imports: The list of class import instances
            protected: The set of protected class names from the module
        """
        for slug, group in collections.group_by(imports, key=get_slug).items():
            if len(group) == 1:
                if slug in protected:
                    imp = group[0]
                    module = imp.source.split(".")[-1]
                    imp.alias = f"{module}:{imp.name}"
                continue

            for index, cur in enumerate(group):
                cmp = group[index + 1] if index == 0 else group[index - 1]
                parts = re.split("[_.]", cur.source)
                diff = set(parts) - set(re.split("[_.]", cmp.source))

                add = "_".join(part for part in parts if part in diff)
                cur.alias = f"{add}:{cur.name}"

#### `get_class_module(qname)`

Return the module for the given qualified class name.

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`qname` | `str` | The namespace qualified name of the class | required

Raises:

Type | Description
--- | ---
`CodeGenerationError` | if name doesn't exist.

Source code in `xsdata/codegen/resolver.py`

    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143

    def get_class_module(self, qname: str) -> str:
        """Return the module for the given qualified class name.

        Args:
            qname: The namespace qualified name of the class

        Raises:
            CodeGenerationError: if name doesn't exist.
        """
        if qname not in self.registry:
            raise CodegenError("Failed to resolve dependency", qname=qname)
        return self.registry[qname]

#### `import_classes()`

Return a list of class qnames that need to be imported.

Source code in `xsdata/codegen/resolver.py`

    145
    146
    147

    def import_classes(self) -> list[str]:
        """Return a list of class qnames that need to be imported."""
        return [qname for qname in self.class_list if qname not in self.class_map]

#### `create_class_list(classes)` `staticmethod`

Use topology sort to return a flat list for all the dependencies.

Source code in `xsdata/codegen/resolver.py`

    149
    150
    151
    152

    @staticmethod
    def create_class_list(classes: list[Class]) -> list[str]:
        """Use topology sort to return a flat list for all the dependencies."""
        return toposort_flatten({obj.qname: set(obj.dependencies()) for obj in classes})

#### `create_class_map(classes)` `staticmethod`

Index the list of classes by their qualified names.

Raises:

Type | Description
--- | ---
`CodeGenerationError` | If two classes have the same qname.

Returns:

Type | Description
--- | ---
`dict[str, Class]` | A qname-class map.

Source code in `xsdata/codegen/resolver.py`

    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170

    @staticmethod
    def create_class_map(classes: list[Class]) -> dict[str, Class]:
        """Index the list of classes by their qualified names.

        Raises:
            CodeGenerationError: If two classes have the same qname.

        Returns:
            A qname-class map.
        """
        result: dict[str, Class] = {}
        for obj in classes:
            if obj.qname in result:
                raise CodegenError("Duplicate class during resolve", qname=obj.qname)
            result[obj.qname] = obj

        return result