-
Notifications
You must be signed in to change notification settings - Fork 15
/
Copy pathstructure.py
160 lines (142 loc) · 6.29 KB
/
structure.py
1
2
3
4
5
6
7
8
9
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
from typing import Dict, Iterable, List, Set, Tuple
from . import Index, ModelsStructureType
from .utils import ListEx, PositionsDict
from ..dynamic_typing import BaseType, DOptional, ModelMeta, ModelPtr
def compose_models(models_map: Dict[str, ModelMeta]) -> ModelsStructureType:
"""
Generate nested sorted models structure for internal usage.
:return: List of root models data, Map(child model -> root model) for absolute ref generation
"""
root_models = ListEx()
root_nested_ix = 0
structure_hash_table: Dict[Index, dict] = {
key: {
"model": model,
"nested": ListEx(),
"roots": list(extract_root(model)), # Indexes of root level models
} for key, model in models_map.items()
}
# TODO: Test path_injections
path_injections: Dict[ModelMeta, ModelMeta] = {}
for key, model in models_map.items():
pointers = list(filter_pointers(model))
has_root_pointers = len(pointers) != len(model.pointers)
if not pointers:
# Root level model
if not has_root_pointers:
raise Exception(f'Model {model.name} has no pointers')
root_models.append(structure_hash_table[key])
else:
parents = {ptr.parent.index for ptr in pointers}
struct = structure_hash_table[key]
# Model is using by other models
if has_root_pointers or len(parents) > 1 and len(struct["roots"]) > 1:
# Model is using by different root models
try:
root_models.insert_before(
struct,
*(structure_hash_table[parent_key] for parent_key in struct["roots"])
)
except ValueError:
root_models.insert(root_nested_ix, struct)
root_nested_ix += 1
elif len(parents) > 1 and len(struct["roots"]) == 1:
# Model is using by single root model
parent = structure_hash_table[struct["roots"][0]]
parent["nested"].insert(0, struct)
path_injections[struct["model"]] = parent["model"]
else:
# Model is using by only one model
parent = structure_hash_table[next(iter(parents))]
struct = structure_hash_table[key]
parent["nested"].append(struct)
return root_models, path_injections
def compose_models_flat(models_map: Dict[Index, ModelMeta]) -> ModelsStructureType:
"""
Generate flat sorted (by nesting level, ASC) models structure for internal usage.
:param models_map: Mapping (model index -> model meta instance).
:return: List of root models data, Map(child model -> root model) for absolute ref generation
"""
root_models = ListEx()
positions: PositionsDict[Index, int] = PositionsDict()
top_level_models: Set[Index] = set()
structure_hash_table: Dict[Index, dict] = {
key: {
"model": model,
"nested": ListEx(),
"roots": list(extract_root(model)), # Indexes of root level models
} for key, model in models_map.items()
}
for key, model in models_map.items():
pointers = list(filter_pointers(model))
has_root_pointers = len(pointers) != len(model.pointers)
if not pointers:
# Root level model
if not has_root_pointers:
raise Exception(f'Model {model.name} has no pointers')
root_models.insert(positions["root"], structure_hash_table[key])
top_level_models.add(key)
positions.update_position("root", PositionsDict.INC)
else:
parents = {ptr.parent.index for ptr in pointers}
struct = structure_hash_table[key]
# Model is using by other models
if has_root_pointers or len(parents) > 1 and len(struct["roots"]) >= 1:
# Model is using by different root models
if parents & top_level_models:
parents.add("root")
parents_positions = {positions[parent_key] for parent_key in parents
if parent_key in positions}
parents_joined = "#".join(sorted(parents))
if parents_joined in positions:
parents_positions.add(positions[parents_joined])
pos = max(parents_positions) if parents_positions else len(root_models)
positions.update_position(parents_joined, pos + 1)
else:
# Model is using by only one model
parent = next(iter(parents))
pos = positions.get(parent, len(root_models))
positions.update_position(parent, pos + 1)
positions.update_position(key, pos + 1)
root_models.insert(pos, struct)
return root_models, {}
def filter_pointers(model: ModelMeta) -> Iterable[ModelPtr]:
"""
Return iterator over pointers with not None parent
"""
return (ptr for ptr in model.pointers if ptr.parent)
def extract_root(model: ModelMeta) -> Set[Index]:
"""
Return set of indexes of root models that are use given `model` directly or through another nested model.
"""
seen: Set[Index] = set()
nodes: List[ModelPtr] = list(filter_pointers(model))
roots: Set[Index] = set()
while nodes:
node = nodes.pop()
seen.add(node.type.index)
filtered = list(filter_pointers(node.parent))
nodes.extend(ptr for ptr in filtered if ptr.type.index not in seen)
if not filtered:
roots.add(node.parent.index)
return roots
def sort_fields(model_meta: ModelMeta, unicode_fix=False) -> Tuple[List[str], List[str]]:
"""
Split fields into required and optional groups
:return: two list of fields names: required fields, optional fields
"""
fields = model_meta.type
required = []
required_2 = []
optional = []
for key, meta in fields.items():
if isinstance(meta, DOptional):
optional.append(key)
elif unicode_fix and isinstance(meta, BaseType) and any(
isinstance(node, ModelMeta)
for node in meta.iter_child()
):
required_2.append(key)
else:
required.append(key)
return required + required_2, optional