Skip to content

Commit 991bfa1

Browse files
committed
Propagate parent hierarchies to extended cubes and fix alias refs
Collect extends relationships during parse and use them to propagate parent cube hierarchies to child cubes after inheritance resolution. Previously, inherited dimensions from parent cubes had no parent links if the child didn't redeclare the hierarchy. Build alias map when processing view includes with {name, alias} and apply it to dependent references (parent on dimensions, drill_fields on metrics) so aliased members maintain valid internal references.
1 parent c818d2c commit 991bfa1

1 file changed

Lines changed: 46 additions & 10 deletions

File tree

sidemantic/adapters/cube.py

Lines changed: 46 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -71,38 +71,50 @@ def parse(self, source: str | Path) -> SemanticGraph:
7171
source_path = Path(source)
7272
pending_views: list[dict] = []
7373
pending_hierarchies: dict[str, list[dict]] = {}
74+
pending_extends: dict[str, str] = {} # child_name -> parent_name
7475

7576
if source_path.is_dir():
7677
# Parse all YAML files in directory
7778
for yaml_file in source_path.rglob("*.yml"):
78-
self._parse_file(yaml_file, graph, pending_views, pending_hierarchies)
79+
self._parse_file(yaml_file, graph, pending_views, pending_hierarchies, pending_extends)
7980
for yaml_file in source_path.rglob("*.yaml"):
80-
self._parse_file(yaml_file, graph, pending_views, pending_hierarchies)
81+
self._parse_file(yaml_file, graph, pending_views, pending_hierarchies, pending_extends)
8182
else:
8283
# Parse single file
83-
self._parse_file(source_path, graph, pending_views, pending_hierarchies)
84+
self._parse_file(source_path, graph, pending_views, pending_hierarchies, pending_extends)
8485

8586
# Resolve extends (inheritance) after all cubes are parsed
8687
from sidemantic.core.inheritance import resolve_model_inheritance
8788

8889
if any(m.extends for m in graph.models.values()):
8990
graph.models = resolve_model_inheritance(graph.models)
9091

91-
# Apply hierarchies after inheritance so inherited dimensions are available
92-
for model_name, hierarchy_defs in pending_hierarchies.items():
93-
model = graph.models.get(model_name)
94-
if not model:
95-
continue
96-
for h_def in hierarchy_defs:
92+
# Apply hierarchies after inheritance so inherited dimensions are available.
93+
# Also propagate parent hierarchies to child cubes via extends_map.
94+
def _apply_hierarchies(model: Model, h_defs: list[dict]) -> None:
95+
for h_def in h_defs:
9796
levels = h_def.get("levels", [])
9897
for i in range(1, len(levels)):
9998
child_name = levels[i]
10099
parent_name = levels[i - 1]
101100
if "." not in parent_name and "." not in child_name:
102101
child_dim = model.get_dimension(child_name)
103-
if child_dim:
102+
if child_dim and not child_dim.parent:
104103
child_dim.parent = parent_name
105104

105+
# Apply explicit hierarchies
106+
for model_name, hierarchy_defs in pending_hierarchies.items():
107+
model = graph.models.get(model_name)
108+
if model:
109+
_apply_hierarchies(model, hierarchy_defs)
110+
111+
# Propagate parent hierarchies to child cubes that inherited dimensions
112+
for child_name, parent_name in pending_extends.items():
113+
if parent_name in pending_hierarchies:
114+
child_model = graph.models.get(child_name)
115+
if child_model:
116+
_apply_hierarchies(child_model, pending_hierarchies[parent_name])
117+
106118
# Parse views after all cubes are loaded and inheritance resolved
107119
for view_def in pending_views:
108120
model = self._parse_view(view_def, graph)
@@ -117,6 +129,7 @@ def _parse_file(
117129
graph: SemanticGraph,
118130
pending_views: list[dict],
119131
pending_hierarchies: dict[str, list[dict]],
132+
pending_extends: dict[str, str],
120133
) -> None:
121134
"""Parse a single Cube YAML file.
122135
@@ -125,6 +138,7 @@ def _parse_file(
125138
graph: Semantic graph to add models to
126139
pending_views: List to accumulate view definitions for deferred parsing
127140
pending_hierarchies: Dict to accumulate hierarchy definitions per cube for deferred application
141+
pending_extends: Dict to track extends relationships (child -> parent)
128142
"""
129143
with open(file_path) as f:
130144
data = yaml.safe_load(f)
@@ -143,6 +157,10 @@ def _parse_file(
143157
h_defs = cube_def.get("hierarchies")
144158
if h_defs:
145159
pending_hierarchies[model.name] = h_defs
160+
# Track extends for hierarchy propagation
161+
ext = cube_def.get("extends")
162+
if ext:
163+
pending_extends[model.name] = ext
146164

147165
# Collect views for deferred parsing (need all cubes loaded first)
148166
for view_def in data.get("views") or []:
@@ -670,6 +688,9 @@ def _parse_view(self, view_def: dict, graph: SemanticGraph) -> Model | None:
670688
cube_alias = cube_spec.get("alias")
671689
prefix_str = f"{cube_alias or target_name}_" if prefix else ""
672690

691+
# Build alias map for renaming dependent references
692+
alias_map: dict[str, str] = {}
693+
673694
if includes == "*":
674695
dims = [d for d in target.dimensions if d.name not in excludes]
675696
mets = [m for m in target.metrics if m.name not in excludes]
@@ -686,6 +707,8 @@ def _parse_view(self, view_def: dict, graph: SemanticGraph) -> Model | None:
686707
elif isinstance(inc, dict):
687708
orig = inc.get("name", "")
688709
alias = inc.get("alias", orig)
710+
if alias != orig:
711+
alias_map[orig] = alias
689712
d = target.get_dimension(orig)
690713
if d:
691714
dims.append(d.model_copy(update={"name": alias}))
@@ -695,6 +718,19 @@ def _parse_view(self, view_def: dict, graph: SemanticGraph) -> Model | None:
695718
else:
696719
continue
697720

721+
# Apply alias map to dependent references (parent, drill_fields)
722+
if alias_map:
723+
dims = [
724+
d.model_copy(update={"parent": alias_map[d.parent]}) if d.parent and d.parent in alias_map else d
725+
for d in dims
726+
]
727+
mets = [
728+
m.model_copy(update={"drill_fields": [alias_map.get(f, f) for f in m.drill_fields]})
729+
if m.drill_fields and any(f in alias_map for f in m.drill_fields)
730+
else m
731+
for m in mets
732+
]
733+
698734
if prefix_str:
699735
# Prefix names and update dependent references (parent, drill_fields)
700736
prefixed_dims = []

0 commit comments

Comments
 (0)