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] | |
Source code in xsdata/codegen/resolver.py
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__ = "registry", "aliases", "imports", "class_list", "class_map"
def __init__(self, registry: Dict[str, str]):
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]):
"""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):
"""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):
"""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):
"""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:
ResolverValueError: if name doesn't exist.
"""
if qname not in self.registry:
raise ResolverValueError(f"Unknown dependency: {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:
ResolverValueError: 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 ResolverValueError(f"Duplicate class: `{obj.name}`")
result[obj.qname] = obj
return result
|
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]):
"""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
| 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):
"""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):
"""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
| def set_aliases(self):
"""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:
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:
ResolverValueError: if name doesn't exist.
"""
if qname not in self.registry:
raise ResolverValueError(f"Unknown dependency: {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
| 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
| @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:
Returns:
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:
ResolverValueError: 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 ResolverValueError(f"Duplicate class: `{obj.name}`")
result[obj.qname] = obj
return result
|